GitHub - CodeToCAD/CodeToCAD: Create models and simulations in your favorite modeling software using CodeToCAD! · GitHub
Skip to content

Repository files navigation

CodeToCAD

CodeToCAD accelerates mechanical and electrical CAD design, simulations/FEA, controls software and MCU firmware by giving you one language to define your design — your script is federated to the modeling or design application automatically.

Install

pip install codetocad

Quick start (CLI)

codetocad init cup

This creates a cup/ folder with a cup.py file and opens an interactive menu to create parts and sketches, transform, boolean, shell, constrain and export them. Every action updates generated python part files (for example cup_cylinder.py), so the CLI extends to the full functionality of the CodeToCAD classes.

Run a script:

codetocad path/to/script.py

Quick start (Python)

import codetocad

body = codetocad.cylinder(radius="2cm", height="5cm")
body.shell(thickness="5mm")
body.set_material(codetocad.aluminum_material())
body.export("cup.stl")

Highlights

  • Units: floats are meters/radians; strings such as "2in", "10 deg" or expressions like "2in - 5mm" are parsed and converted.
  • Locations: 6-dof positions/orientations; CubeLocations shortcuts to the 23 topological locations of any shape's bounding cube; the @codetocad.location decorator marks named locations on your part classes.
  • Parts & assemblies: Part2D/Part3D with extrude, shell, fillet, chamfer, hole; Assembly2D/Assembly3D constraints (coincide, parallel, fixed, revolute, prismatic, ...) recorded in ledgers.
  • Primitives: cube, cylinder, sphere, rectangle, circle, text, import_file and material presets.
  • Technical drawings: part.generate_drawing() projects any Part3D — or a whole assembly — into a standard third-angle sheet (front, top, right and isometric views, dimensioned, with a title block), returned as an editable Part2D you export with drawing.export("part.svg").
  • ECAD: led, diode, resistor, capacitor, inductor, voltage_source, current_source components (each a Part3D with pins, a value and a Footprint) wired into a Circuit of Nets — capture schematics with skidl and simulate with SPICE (below).
  • Mixins: sensors (CameraMixin, IMUMixin, MicrophoneMixin) and actuators (DCMotorMixin, BLDCMotorMixin) for custom parts.
  • Fasteners: CommonFasteners enum that can build() a part or apply features (clearance holes) to another part.

Build123D integration

Install the extra (uv sync --extra build123d) and your parts are federated to real OpenCascade solids — booleans, shells, fillets, chamfers, holes and transforms are replayed natively, and geometry queries, analysis and STL/STEP export use the native topology:

from codetocad_integrations.build123d import make_cube

if __name__ == "__main__":
    cube = make_cube("10cm", "10cm", "5cm")
    cube.hole(cube.top_center, radius="4cm", amount="5cm")
    cube.export("my_cube.stl")

Subclass codetocad_integrations.build123d.Part3D and override build_native() to model a custom base shape with the Build123D API; all CodeToCAD operations still apply on top. adapt(part) converts any core CodeToCAD part (including led(), resistor(), fasteners, ...) into a Build123D-federated one.

See codetocad_integrations/build123d/examples/ for the full gallery.

Blender integration

With Blender on your PATH (or CODETOCAD_BLENDER pointing at it), the same designs federate to Blender mesh objects — booleans, shells (solidify), fillets/chamfers (bevel), holes and transforms are replayed with modifiers, and you can export .stl, .obj, .glb, .fbx or a full .blend scene:

from codetocad_integrations.blender import ensure_blender, make_cube

if __name__ == "__main__":
    ensure_blender()  # relaunches this script under `blender --background`
    cube = make_cube("10cm", "10cm", "5cm")
    cube.hole(cube.top_center, radius="4cm", amount="5cm")
    cube.export("my_cube.blend")

Subclass codetocad_integrations.blender.Part3D and override build_native() to model with bpy/bmesh directly. See codetocad_integrations/blender/examples/.

Simulation (PyBullet & MuJoCo)

Model in Build123D or Blender, assemble with joint constraints, and import right into physics simulation — simulate(part) walks the assembly, exports the meshes and generates a URDF (PyBullet) or MJCF (MuJoCo):

from codetocad import Location
from codetocad_integrations.build123d import make_cube, make_cylinder
from codetocad_integrations.pybullet import simulate  # or ...mujoco

mount = make_cube("6cm", "6cm", "4cm", start_location=Location(z="52cm"))
rod = make_cylinder("1cm", "40cm", start_location=Location(z="30cm"))
pivot = Location.from_euler(0, 0, "50cm", x_deg=-90, name="pivot")
mount.revolute(pivot, rod, pivot)  # hinge about the Y axis

sim = simulate(mount, gui=True)
sim.set_joint_value("pivot", 1.0)
sim.run(10.0, realtime=True)

Joint axes come from the constraint Location's orientation, limits from min_limits/max_limits, masses/inertias from part materials and geometry, and codetocad.Lighting/codetocad.Camera describe scene lights and the overview camera. The camera's pose is a Location like everything else (simulate(..., camera=Camera.look_at(eye=(2, -2, 1.5), target=(0, 0, 0.3))), or set them live with sim.set_camera(...) / sim.set_lighting(...)) — the same across PyBullet, MuJoCo and Blender. Free-floating scene_parts (objects the robot can interact with) and a ground_plane complete the scene. See the examples in codetocad_integrations/pybullet/examples/ and codetocad_integrations/mujoco/examples/ (a 6-DOF arm with a parallel-jaw gripper that picks up a cube, pendulum, double pendulum).

FEA (CalculiX)

Analyze the same parts with finite elements — analyze(part) meshes the exported geometry with gmsh, applies fixtures/loads described with Locations, solves with CalculiX via pygccx, and returns displacement and von Mises stress fields with visualization:

from codetocad import steel_material
from codetocad_integrations.build123d import make_cube
from codetocad_integrations.calculix import analyze

beam = make_cube("200mm", "20mm", "10mm")
beam.set_material(steel_material())

fea = analyze(beam)
fea.fix(beam.left_center)                          # clamp the left face
fea.add_force(beam.right_center, force=(0, 0, -100))
results = fea.solve()
print(results.max_displacement, results.max_von_mises)
results.visualize("beam_fea.png")

Materials carry elastic properties (steel_material(), aluminum_material() or set youngs_modulus/poissons_ratio on any MaterialBase). The ccx solver is auto-discovered from CODETOCAD_CCX, the PATH, or ~/.codetocad/ccx/bin/ccx. See codetocad_integrations/calculix/examples/.

Visualization (Open3D)

Display any Part3D — core, Build123D- or Blender-federated — in an Open3D window, or render a screenshot headlessly for docs/CI:

from codetocad_integrations.build123d import make_cube
from codetocad_integrations.open3d import show, render

cube = make_cube("10cm", "10cm", "5cm")
cube.hole(cube.top_center, radius="4cm", amount="5cm")

show(cube)                          # interactive window
render(cube, path="cube.png")       # offscreen screenshot

The part is exported (part.export()) to a temporary mesh and loaded into Open3D, so it works with any backend — Open3D itself isn't a CAD kernel. See codetocad_integrations/open3d/examples/.

ECAD: schematics (skidl) & simulation (SPICE)

Describe a circuit once as a Circuit of components and nets, then federate it to schematic capture and circuit simulation — the same components are Part3Ds (each carries a Footprint), so they drop straight into a board assembly:

from codetocad import Circuit, resistor, voltage_source
from codetocad_integrations.skidl import export_netlist, export_schematic
from codetocad_integrations.spice import simulate

circuit = Circuit("divider")
v1 = circuit.add(voltage_source(dc=9))
r1, r2 = circuit.add(resistor("10k"), resistor("20k"))
circuit.connect(v1["+"], r1[1], name="VIN")
circuit.connect(r1[2], r2[1], name="VOUT")
circuit.connect(r2[2], v1["-"], circuit.gnd)

export_netlist(circuit, "divider.net")     # KiCad netlist (with footprints)
export_schematic(circuit, "divider.svg")   # schematic SVG (netlistsvg)

op = simulate(circuit).operating_point()
print(op.voltage("VOUT"))                   # 6.0

Schematics (skidl). codetocad_integrations.skidl converts a Circuit into a skidl circuit to run its ERC and write KiCad netlists/XML, and renders schematic SVGs with standard analog symbols via netlistsvg. Install with uv sync --extra skidl plus npm install -g netlistsvg. See codetocad_integrations/skidl/examples/.

Simulation (SPICE). codetocad_integrations.spice builds a SPICE netlist from the Circuit (diode/LED models are derived from each component's electrical properties) and runs it with ngspice: operating point, DC sweep, transient and AC analyses come back as numpy vectors with plot()/bode() helpers. Install with uv sync --extra spice plus ngspice (brew install ngspice / apt install ngspice). See codetocad_integrations/spice/examples/.

WebApp control panels

Any Part3D can double as a sensor or actuator via mixins (DCMotorMixin, EncoderMixin, IMUMixin, ...). Bind them to a Microcontroller's pins and a PythonApp/WebApp federates sliders, buttons, gauges and plots to the same JSON-lines wire protocol the firmware speaks:

from codetocad import Microcontroller, MicrocontrollerBoard, SerialCommunication, WebApp
from codetocad.mixins import DCMotorMixin, EncoderMixin

class GearMotor(DCMotorMixin):
    no_load_speed_rpm = 200

motor, encoder = GearMotor(), EncoderMixin()
mcu = Microcontroller("motor-lab", board=MicrocontrollerBoard.ESP32)
mcu.bind_actuator(motor, name="wheel", pwm_pin=5, dir_pin=18)
mcu.bind_sensor(encoder, name="enc", a=32, b=33)
mcu.set_communication(SerialCommunication("/dev/ttyUSB0"))

app = WebApp("motor lab").set_communication(mcu.communication)
app.add_slider("speed (rpm)", target=motor, command="velocity_rpm", maximum=200)
app.add_plot("measured rpm", source=encoder)
app.run()

PythonApp opens a native window instead of a browser page and RerunApp streams telemetry to the Rerun viewer instead — all three take the same Communication instance as the microcontroller so both ends agree, and EmulatedMicrocontroller can stand in for real hardware to drive a physics simulation instead (see Robotics, below). Install with uv sync --extra nicegui (or --extra rerun). See Microcontroller, Sensors/Actuators definition, and communication in the design doc for I2C/SPI/UART buses, wireless transports and signal filtering.

Robotics examples: putting it all together

codetocad_integrations/robotics/ contains examples that combine everything above into one script: MCAD (the assembled parts and joint constraints), ECAD (the microcontroller as an ElectricalComponent with pin bindings), an MCU definition (run by real firmware or, for simulation, an in-process EmulatedMicrocontroller), and a WebApp control panel — all driving the same physics simulation.

codetocad_integrations/robotics/turtlebot/ is a differential-drive TurtleBot3 Burger: real chassis/wheel/caster dimensions, Dynamixel XL430-W250 motor/encoder specs, an ESP32 Microcontroller definition, MuJoCo physics with velocity-controlled wheels, and a WebApp with motor sliders and encoder/pose readouts. Swapping the emulator for a SerialCommunication to a real board is the only change needed to drive physical hardware from the same app.

User-defined parts

Define a part with the API of your choice (for example Build123D):

import build123d
import codetocad

class Box(codetocad.Part3D):
    def build(self):
        length, width, thickness = 80.0, 60.0, 10.0
        with build123d.BuildPart() as ex1:
            build123d.Box(length, width, thickness)

    @codetocad.location
    def example_location(self):
        return codetocad.CubeLocations.top_center.translate(x="2cm", y="2mm")

See CodeToCAD.md for the full design document.

About

Create models and simulations in your favorite modeling software using CodeToCAD!

Topics

Resources

Stars

119 stars

Watchers

5 watching

Forks

Releases

Used by

Contributors

Languages