-
Notifications
You must be signed in to change notification settings - Fork 1
Manufacturing
The manufacturing module sits between the OpenRocket design phase and cadsmith CAD generation. The manufacturing agent (agents/manufacturing.md) owns all manufacturing decisions — which components get printed, fused, purchased, or skipped — and applies DFAM rules to the component tree.
openrocket_component(action="read") → gui/component_tree.json (raw)
↓
manufacturing_annotate_tree → gui/component_tree.json (annotated)
↓ ↑
↓ feedback loop via
↓ openrocket_component
↓ (dimension changes)
↓
cadsmith reads annotated tree → STEP/STL files
The manufacturing agent may send dimension changes back to the OpenRocket agent (e.g., fin thickness from 3mm to 12.7mm) via openrocket_component(action="update"), then regenerate the tree to reflect the updated design.
| Tool | Description |
|---|---|
manufacturing_annotate_tree |
Apply DFAM rules to an existing component_tree.json, annotating each component with manufacturing decisions; writes the annotated tree back to the project directory. Optional out_path overrides the output location. |
The component tree is a three-level hierarchy defined in manufacturing/models.py:
ComponentTree
schema_version: int
source_ork: str
project_root: str
generated_at: str (ISO 8601)
rocket_name: str
stages: list[Stage]
Stage
name: str
components: list[Component]
cg, cp: QuantityField (mm)
stability_cal: float
max_diameter: QuantityField (mm)
Component
type: str # OpenRocket type (e.g. "BodyTube")
name: str
category: ComponentCategory # structural, recovery, hardware, electronics, propulsion
dimensions: Dimensions # discriminated union of dimension models
mass: QuantityField | None
override_mass: QuantityField | None
override_mass_enabled: bool
material: str | None
human_notes: str | None
agent: AgentAnnotation | None # populated by manufacturing_annotate_tree
cost: float | None
step_path: str | None
children: list[Component]
The tree is a living document that evolves through three phases:
-
OpenRocket agent populates it from the
.orkfile. - Manufacturing agent annotates it with DFAM decisions.
- CADSmith agent reads it to generate CAD.
Components are never removed from the tree. Fusion is an annotation, not a deletion.
Each component's agent field holds structured manufacturing decisions:
class AgentAnnotation(BaseModel):
fate: Fate | None # print, fuse, purchase, skip
fused_into: str | None # name of the parent part this fuses into
reason: str | None # human-readable rationale
updated_by: str | None # agent name (e.g. "manufacturing")
updated_at: str | None # ISO 8601 timestamp
model_config = {"extra": "allow"} # allows extra DFAM fieldsThe Fate enum has four values:
| Fate | Meaning |
|---|---|
print |
Standalone printed part |
fuse |
Integrated into another printed part |
purchase |
Bought off the shelf (e.g. parachute, motor) |
skip |
Not a physical part (structural wrapper, absorbed by fusion) |
Extra fields (prefixed dfam_) carry AM-specific parameters like dfam_thickness_mm, dfam_fillet_mm, dfam_shoulder_od_mm, etc. These are allowed by the extra = "allow" model config.
The annotate_dfam() function in manufacturing/dfam.py walks the component tree and applies these rules:
| Component Type | Default Fate | Rule |
|---|---|---|
NoseCone |
print |
Standalone part; integral shoulder added with configurable length (default 30 mm) and OD matched to body tube ID |
BodyTube |
print |
Standalone part; children are annotated relative to this tube |
TrapezoidFinSet / EllipticalFinSet / FreeformFinSet
|
fuse |
Integrated into parent body tube; thickness bumped to minimum 12.7 mm; fillet clamped to min(thickness * 0.25, 3.0 mm)
|
InnerTube (motor mount) |
fuse |
Local wall thickening in parent body tube; overridable to separate
|
CenteringRing |
skip |
Absorbed when motor mount is fused; printed separately when motor mount is separate
|
TubeCoupler |
fuse |
Integral aft shoulder on parent body tube; overridable to separate
|
Parachute, MassComponent, LaunchLug, RailButton
|
purchase |
Non-structural / purchased items |
- Minimum fin thickness: 12.7 mm (overridable via
fin_thickness_mm). - Fillet radius:
min(thickness * 0.25, 3.0 mm)by default. The fillet is further clamped tothickness / 2to avoid OCC kernel failures. This matches the known OCC limitation where root fillets above~thickness/2 * 0.9or3 mmfail.
openrocket_component(action="read")
|
v
gui/component_tree.json (unannotated)
|
v
manufacturing_annotate_tree(fusion_overrides)
|
v
annotate_dfam(tree, fusion_overrides)
- walks stages -> components -> children
- calls type-specific annotators (_annotate_nose_cone, _annotate_fin_set, etc.)
- populates component.agent with AgentAnnotation
|
v
gui/component_tree.json (annotated, written back in place)
The fusion_overrides parameter on manufacturing_annotate_tree accepts a dict with the following keys:
| Key | Type | Default | Effect |
|---|---|---|---|
motor_mount_fate |
"fuse" or "separate"
|
"fuse" |
Whether motor mounts are fused as wall thickening or printed separately |
coupler_fate |
"fuse" or "separate"
|
"fuse" |
Whether couplers become integral shoulders or separate parts |
nose_cone_hollow |
bool |
false |
Whether the nose cone is hollow (with wall thickness) |
nose_cone_wall_mm |
float |
3.0 |
Wall thickness when hollow |
nose_cone_shoulder_length_mm |
float |
30.0 |
Length of the integral nose cone shoulder |
fin_thickness_mm |
float |
12.7 (minimum) |
Override the minimum fin thickness |
fin_fillet_mm |
float |
computed | Override the fillet radius (still clamped to thickness / 2) |
Agent annotations are stored in OpenRocket component comment fields using the == agents == delimiter format:
User's design notes here.
== agents ==
fate: print
fused_into: lower_body_tube
reason: Fins always integrated into parent body tube for AM
dfam_thickness_mm: 12.7
dfam_fillet_mm: 3.0
updated_by: manufacturing
updated_at: 2026-04-10T12:00:00+00:00
This format ensures annotations survive .ork regeneration:
-
parse_comment()splits the comment at the== agents ==delimiter, preserving human notes above and parsing key-value pairs below into anAgentAnnotation. -
serialize_comment()rebuilds the full comment string from(human_notes, AgentAnnotation). - On each
openrocket_component(action="read") call, annotations are re-parsed from the.orkcomments, so manual edits to the.orkfile are respected.