A from-scratch 3D CAD application
- Based on the original KodaCAD
- Built on OCP (Python bindings for OpenCascade's OCCT) -- see below for how this differs from other Python CAD libraries
- Patterned after HP's dynamic modeling lineage (ME-30 → SolidDesigner)
- Part creation workflow: workplane → sketch → extrude/revolve → modify
- Intended to meet the basic requirements in a typical CAD workflow:
- Start a new session or load a previous one (saved as a
.kcadfile: the STEP model plus your workplanes) - Import components in STEP format
- Position them within an assembly structure
- Create simple mounting plates & brackets
- Modify parts directly, even ones you didn't build: remove features (Defeaturing) and push or pull faces (Move Face) with a live preview
- Select, recolor, move and delete whole groups of parts with a box
- Animate a mechanism (Geneva drive, four-bar linkage) with a slider and trace the path of a point
- Save session as
.kcad, or export the 3D model as plain STEP
- Start a new session or load a previous one (saved as a
Designed for users who want to assemble, modify, and create 3D mechanical parts and assemblies using a scriptable, open-source toolchain -- with no commercial dependencies and no Conda environment.
A STEP file arrives with no feature tree. KodaCAD lets you edit it directly, the way the HP/CoCreate lineage did:
- Move Face -- pick a face (or a box of faces), type a distance, and watch a live ghost of the result before you commit. A positive distance adds material (thickens a wall, narrows a slot); a negative one removes it (opens a hole up for a clearance fit). Faces that must move with the one you picked show in yellow, and heavy green or magenta lines mark exactly what changes.
- Defeaturing -- remove a hole, a pocket, a blend or a whole isolated feature, and the surrounding faces heal over the gap.
- They work together. When OpenCascade cannot build an offset, Move Face marks the faces that stand in its way in red and tells you what to remove first; Defeaturing takes them out, and the move goes through.
Try it on a real bracket in the Moving Faces tutorial, and see Defeaturing a Part for the other half.
Most Python CAD libraries (build123d, CadQuery) represent assemblies as copies of shapes organized in a Python object tree. This means modifying one instance of a repeated part only affects that copy.
KodaCAD uses OCCT's XDE document model directly. In XDE, repeated parts are stored once as a prototype shape and placed multiple times as instances (references with location transforms). Modifying the prototype automatically updates all placements -- exactly the behavior you expect from professional CAD software like Creo or CoCreate.
Example: the as1-oc-214.stp test assembly has two L-bracket
assemblies, each containing the same L-bracket part. Applying a fillet
to one L-bracket updates both simultaneously, because they share the
same XDE root label.
- Python programmers who want to work with real STEP assemblies
- Users coming from CoCreate / Creo who want familiar assembly workflow
- Developers who want to understand OCCT's XDE document architecture
- Anyone who needs to load, modify, and save STEP files with full assembly structure preserved
KodaCAD uses uv for dependency management. No Conda required.
-
Install uv if you don't have it
-
Clone this repo
git clone https://github.com/dblanding/kodacad2
cd kodacad2
- Run directly -- uv handles all dependencies automatically
uv run kodacad.py
Dependencies (managed automatically by uv):
ocp-- OCCT Python bindings (replaces PythonOCC)pyside6-- Qt6 Python bindings (replaces PyQt5)build123d-- used for geometry utilities
KodaCAD's native session file is a .kcad file. It is an ordinary
zip archive holding two things side by side, in the spirit of NumPy's
.npz:
mypart.kcad
mypart.stp the 3D model -- a standard STEP file
workplanes.json the workplanes, plain text
The STEP half carries everything a STEP file can: the assembly
structure, part names, colors, shared instances. The JSON half carries
what STEP has no place for: each workplane's position in 3D, its
sketched profile geometry (lines, circles, arcs), its construction
geometry, which workplanes are hidden, and which one was active. The
file is plain data -- nothing in it is executed when it loads -- and
because it is a zip, you can always open it with any archive tool and
pull out the .stp by hand.
File → Save Session
Writes a .kcad file holding the model and your workplanes. The file
is written to a temporary name and swapped into place only when
complete, so a failed save never damages an existing file.
File → Load Session
Opens a .kcad file -- or any plain .stp / .step file with
assembly structure. Replaces the entire 3D document -- whatever was
open before is gone. The loaded file's own content becomes the session,
with no wrapping or nesting involved.
What happens to workplanes depends on the file. A .kcad file brings
its own workplanes and replaces the ones on screen. A plain STEP file
has no workplanes to bring, so it leaves your current workplanes
alone -- handy for loading a part or assembly and sketching on it
with the workplanes you already have set up.
File → Export STEP Writes the 3D model alone, as a plain STEP file, for opening in other CAD applications or sharing with people who don't use KodaCAD. No workplanes are included.
File → Import STEP
Adds a STEP file as a new component under the current /, alongside
whatever's already open, ready to be positioned and dragged into an
assembly. This is the one operation that nests a file's content under
an existing root, so it's the one that needs to guard against a saved
session's own / wrapper getting nested under the current one --
which it does automatically (see below).
- Undo/Redo history. Loading starts you with a clean history.
- Display-only face colors (Color Faces...), which are an illustration aid, not part of the model.
- A workplane's link to the face it was placed on. The workplane keeps its position in space; it just no longer remembers which face it came from.
Undo (Ctrl+Z) and Redo (Ctrl+Y) step through changes to the 3D model -- extrudes, pulls, fillets, shells, positioning, renaming and so on. Workplanes and their sketch geometry are not part of that history. Drawing a line, deleting an element, or repositioning a workplane cannot be undone with Ctrl+Z, and undoing a 3D operation will not rewind your sketches. Workplanes are your construction scaffolding rather than the model itself, and they live outside the document that Undo/Redo manages.
In practice this means: Undo is safe to use freely on the model without
disturbing your sketches, but a workplane edit is final until you make
the opposite edit yourself. Since a workplane's state is only stored
when you save, Save Session before experimenting with sketch work
you might want back -- the .kcad file is your safety net for
workplanes.
Save strips any
redundant / wrapper before writing, so a saved file never contains
one to begin with. Load Session replaces the whole document rather
than nesting anything, so there's nothing for a wrapper to accumulate
into. Import STEP is the one path that adds content under an existing
root, so it explicitly unwraps any /-named root in the file being
imported before adding its children -- composing locations through any
already-nested legacy wrapper levels in one pass, so even an older
file saved before this existed unwraps completely on import.
Note on the tree's own / label: the tree view always shows a /
under the 3D header, whether or not anything is loaded -- that's
just the tree's own organizing scaffolding, not part of any document.
The document's actual root assembly label is also named / by
convention (visible in the tree as its child), which is why a
freshly-built, never-saved session can show what looks like / nested
under / -- two different things that happen to share a name, not an
accumulation bug.
The tree on the left shows the assembly hierarchy:
WP ← workplanes (2D construction geometry)
wp1
3D
/ ← root of 3D assembly
as1 ← loaded assembly
rod-assembly_1
l-bracket-assembly_1
...
button ← newly created part (ready to drag)
- Checkbox — show/hide part or assembly. Parent checkbox propagates to all children.
- Left-click — select item (Ctrl+click / Shift+click select several)
- Right-click — context menu: Item Info, Set Active, Rename, Create New Assembly, Create Empty Part, Create Shared Instance, Copy Part, Set Transparent, Set Opaque, Set Color..., Take Color as Pen, Paint with Pen Color, Color Faces (display only)..., Delete. Set Color, Paint with Pen Color and Delete act on every selected part when you right-click one of several selected rows.
The tree and the viewport select together, in both directions.
- With no operation active, drag a box in the viewport (top to bottom is window mode: parts entirely inside the box; bottom to top is crossing: parts the box touches). Every part picked is highlighted in the tree, and the assemblies that hold them open up. Selecting several rows in the tree (Ctrl/Shift+click) likewise highlights all of them in the viewport.
- Move them: drag any one of the selected rows onto an assembly (create one first with Create New Assembly). All of them move, and keep their place in space. The target must be an assembly; dropping onto a plain part is refused. A new sub-assembly is also the way to hide or show a group with one checkbox.
- Color them: right-click one of the selected rows -> Set Color....
- Delete them: right-click one of the selected rows -> Delete. One confirmation, one undo (Ctrl+Z brings the whole group back). Only the instances are removed. A part's underlying prototype is removed too only when nothing else still uses it, so deleting one instance of a shared pair never damages the other. Workplanes and the top-level root in a selection are left alone.
Matching an existing color by eye in the color picker is slow. Instead, right-click a part -> Take Color as Pen. The status bar (lower right) shows a swatch of the color and its hex value. Then right-click any other part (or one of a group of selected parts) -> Paint with Pen Color. The color is written into the document like Set Color, so it is saved and survives a reload. The pen itself is not saved: it is empty at the start of each session.
Utility -> set Units switches between inches, millimeters, feet and meters (the status bar shows the current choice). Everything is held in millimeters internally; typed distances, measurements and the Move Face distance are converted. Pick feet or meters for large models; the workplane label stays a sensible size at any scale.
Utility -> Animate Mechanism... moves the parts of an assembly with
a slider so you can see how a mechanism works, stop it in any pose
(then project faces onto a workplane to get a snapshot) and Trace
the path of a point. The motion is defined by a short Python file you
choose and can edit; mechanisms/ has a 4-slot Geneva drive and a
pin-jointed four-bar. It is display only: the model, tree and undo
history are untouched, and Done puts every part back. How to write a
mechanism file: docs/MECHANISMS.md.
A crash closes the terminal along with the program, so KodaCAD also
writes crash information to kodacad-crash.log next to
kodacad.py (a start-up line each run; the last block before the next
start-up line is the crash). If you report a crash, send that file.
It is ignored by git and can be deleted at any time.
Full, step-by-step walkthroughs live under tutorials/ --
each one doubles as a regression test, with every step annotated with
what it exercises in KodaCAD's own code.
- The OCC Bottle -- builds the classic OpenCascade tutorial bottle entirely from scratch: sketching, Extrude, Fillet, Mill/Pull, Shell, and Undo/Redo, all on a single part.
- Assembly Structure with
as1-oc-214.stp-- loading a pre-built, multi-part assembly two different ways, the XDE hierarchy viewer, and fillet propagation across a pre-existing shared instance. - Build Assembly
as1from a Kit of Parts -- the reverse of the tutorial above: starting fromas1-oc-214's five loose, un-positioned prototype parts and rebuilding the real assembly from scratch, exercising nearly every Position dialog method along the way. - Building Quaoar's Chassis -- building a wheel-and-axle-in-a-chassis assembly from nothing: sibling-assembly creation, reparenting, shared instances (both a part and an assembly), the Position dialog's full surface, and Undo/Redo across a long, mixed operation history.
- Organizing an Assembly -- organizing a flat file in which all parts are a direct child of the root (often with cryptic names), into a hierarchical structure of functional subassemblies, making it easier to navigate.
- Sketching a Jack-o'-Lantern -- a full workout of the 2D sketch toolbar building a jack-o'-lantern face, then reusing the OCC Bottle's extrude-then-heavily-fillet technique to carve that same face into a pumpkin with a real, multi-profile Pull operation.
- Defeaturing a Part -- demonstrating the ability to remove holes and faces and to extend adjacent faces to heal the gap.
- Lofting a Hollow Part Through a Workplane Set -- building a simple lofted part then working it over: subtracting one lofted part from another, a transverse hole, fillets, and finally defeaturing to take the fillets back off.
- Moving Faces --
pushing and pulling the faces of an imported L-bracket (
L-brkt.stp) with a live preview: thickening a wall, opening up a slot, widening holes (box select and Ctrl+click to unpick), and what to do when OpenCascade cannot build an offset -- the red "suspect" faces, and Defeaturing to fix them.
- Load
as1-oc-214.stp - Set
l-bracket_1as active part - Apply fillet -- both L-brackets update simultaneously
- Rename a bolt -- all shared instances update
- Save session
- Reload -- modifications preserved
- Load
as1-oc-214.stp - Place workplane on top face of right L-bracket
- Create a new part
- RMB click on '/' iin tree; select "Create Empty Part"; name "button"
- Draw 5mm circle on workplane
- Creare/Modify -> Pull; Distance: 3mm
- Button appears under
/alongsideas1 - Drag button to
l-bracket-assembly_2 - Button appears in BOTH L-bracket assemblies (shared instance)
- Save and verify in CAD Assistant -- color and name preserved
kodacad2/
kodacad.py main entry point
mainwindow.py MainWindow, TreeView
docmodel.py XDE document model (core -- read this first)
workplane.py 2D workplane geometry
wp_io.py .kcad session container + workplane save/restore
m2d.py 2D sketch toolbar
koda_viewport.py OCP 3D viewport (AIS_ViewController)
stepanalyzer.py STEP structure analysis utility
face_offset.py Move Face geometry (per-face offset, no GUI)
face_offset_server.py persistent worker that computes the live preview
face_offset_worker.py one-shot worker used by Apply
offset_preview.py Qt client for the preview worker
mechanism_dialog.py Animate Mechanism dialog (slider, Trace)
trace_math.py straight-section finder for Trace (plain Python)
mechanisms/ mechanism files (Geneva drive, four-bar) + STEP models
rpnCalculator.py RPN calculator widget
version.py version string
OCCUtils/ OCC geometry utilities (ported from PythonOCC)
icons/ toolbar icons
step/ sample STEP files
docs/
DEVELOPMENT_LOG.md session-by-session development history
MECHANISMS.md how Animate Mechanism works; writing a mechanism file
TODO.md broken items + future ideas
tutorials/
occ-bottle/ build the OCC bottle from scratch
as1-oc-214/ load and explore a pre-built assembly
build-as1-oc-214/ rebuild that same assembly from loose parts
chassis/ build an assembly from scratch (Quaoar's chassis)
lathe/ organizing and navigating a large assembly
jack/ sketch a jack-o'-lantern face, then carve a pumpkin
defeaturing/ remove holes and features, heal the gap
lofting/ loft through a workplane set, then work it over
move-face/ push/pull faces of an L-bracket, with live preview
pyproject.toml uv project file
docmodel.py is the heart of KodaCAD. It wraps OCCT's XDE
(XCAFDoc_DocumentTool, XCAFDoc_ShapeTool, XCAFDoc_ColorTool) and
provides parse_doc() which builds part_dict and label_dict from
the live document. Everything else in the application reads from these
two dicts.
The XDE label hierarchy:
0:1:1:1 as1 (assembly) <- GetFreeShapes returns this
0:1:1:2 rod-assembly <- prototype shapes at root
0:1:1:3 nut
...
0:1:1:1:1 rod-assembly_1 => 0:1:1:2 <- component (instance)
0:1:1:1:2 l-bracket-assembly_1 => 0:1:1:5
OCP vs PythonOCC: Static methods get _s suffix in OCP.
GetApplication() → GetApplication_s(). See docs/DEVELOPMENT_LOG.md
for the complete list of API changes.
Mouse picking has two separate pipelines -- native OCCT 3D
selection and the 2D sketch engine's own pixel-to-workplane math --
that converge on one callback dispatcher. See
docs/PICKING_ARCHITECTURE.md for
the full picture, including a trap worth knowing about before editing
selection-mode code.
