Skip to content
dblandingPublic

About

A port of KodaCAD 0.2.2 from PythonOCC + PyQt5 + Conda to OCP + PySide6 + uv.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

239 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KodaCAD 2

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 .kcad file: 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
    • Save session as .kcad, or export the 3D model as plain STEP

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.

KodaCAD UI

Dynamic modeling: change the part, not the history

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.

What makes KodaCAD different

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.


Who is it for

  • 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

Installation

KodaCAD uses uv for dependency management. No Conda required.

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

How to use it

Loading a STEP file

Saving and loading

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).

What a session does not save

  • 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 and Redo apply to the 3D model only

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.

Why repeated save/load/import cycles stay clean

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.

Assembly tree

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
  • Right-click — context menu: Item Info, Set Active, Rename, Create New Assembly, Create Shared Instance, Set Transparent, Set Opaque, Delete

Tutorials

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 as1 from a Kit of Parts -- the reverse of the tutorial above: starting from as1-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.

Tested workflows

Load / Modify / Save

  1. Load as1-oc-214.stp
  2. Set l-bracket_1 as active part
  3. Apply fillet -- both L-brackets update simultaneously
  4. Rename a bolt -- all shared instances update
  5. Save session
  6. Reload -- modifications preserved

Create and place a new part

  1. Load as1-oc-214.stp
  2. Place workplane on top face of right L-bracket
  3. 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
  4. Button appears under / alongside as1
  5. Drag button to l-bracket-assembly_2
  6. Button appears in BOTH L-bracket assemblies (shared instance)
  7. Save and verify in CAD Assistant -- color and name preserved

File structure

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
  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
    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

Key concepts for developers

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.

About

A port of KodaCAD 0.2.2 from PythonOCC + PyQt5 + Conda to OCP + PySide6 + uv.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages