Skip to content

Manufacturing

Peter Pak edited this page Apr 15, 2026 · 5 revisions

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.

Pipeline Position

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.

MCP Tools

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.

ComponentTree Model

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:

  1. OpenRocket agent populates it from the .ork file.
  2. Manufacturing agent annotates it with DFAM decisions.
  3. CADSmith agent reads it to generate CAD.

Components are never removed from the tree. Fusion is an annotation, not a deletion.

AgentAnnotation

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 fields

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

DFAM Rules

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

Fin thickness and fillet clamping

  • 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 to thickness / 2 to avoid OCC kernel failures. This matches the known OCC limitation where root fillets above ~thickness/2 * 0.9 or 3 mm fail.

Annotation Flow

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)

Fusion Overrides

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)

Comment Persistence

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 an AgentAnnotation.
  • serialize_comment() rebuilds the full comment string from (human_notes, AgentAnnotation).
  • On each openrocket_component (action="read") call, annotations are re-parsed from the .ork comments, so manual edits to the .ork file are respected.

Clone this wiki locally