diff --git a/QUICKSTART.md b/QUICKSTART.md index 9fad510..a187dae 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -104,10 +104,10 @@ def generate(spec: dict, llm: LLMClient) -> bytes: No API key needed — the harness injects `LLMClient` automatically using whitelisted models. See `examples/metric-aware-agent/agent.py` for a recommended starting point that adapts strategy to all three competition categories. -Reference implementations in `agents/`: -- `taper-beam/` — clean I-beam (~38g) -- `lean-arm/` — I-beam baseline (~32g) -- `compact-arm/` — pocketed arm approach +Reference implementations: +- `agents/baseline/` — solid bracket baseline; sets the upper-bound score every submission must beat +- `examples/metric-aware-agent/` — adapts geometry to mass / stiffness / deflection objectives +- `examples/llm-agent/` — minimal LLM integration example --- @@ -218,7 +218,7 @@ Interactive docs: http://143.244.191.193:8000/docs - **Minimum wall = 2–3 mm**: C3D4 linear tets fail to resolve stress in walls thinner than 2 mm. - **Determinism is required**: if your design uses randomness, fix `random.seed(42)`. CI runs the first spec twice and both scores must match. - **AP214IS STEP schema**: always set `Interface_Static.SetCVal_s("write.step.schema", "AP214IS")` before writing STEP. AP203 causes SIGSEGV on complex geometry. -- **Use build123d**: cleaner parametric API than raw OCP. See `agents/taper-beam/` for an OCP example. +- **Use build123d**: cleaner parametric API than raw OCP. See `agents/baseline/agent.py` for an OCP example using raw `BRepPrimAPI`. - **Read the spec metric**: round_001 = mass, round_002 = stiffness/weight, round_003 = deflection. Your geometry strategy should differ for each. --- diff --git a/agents/al-bracket-v19/agent.py b/agents/al-bracket-v19/agent.py deleted file mode 100644 index 0fc1178..0000000 --- a/agents/al-bracket-v19/agent.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Al-bracket v19: reduce plate thickness to 0.8mm (= min_wall, theoretical floor). - -Baseline: al-bracket v18b (PR #94): fw=3mm, h=31mm, plate_t=1.0mm. 27.13g, 34.90 MPa. -Note: prior "plate_t=1.5mm floor" was for I-beam arm, NOT hollow box. v18b proves - plate_t=1.0mm works with hollow box arm, at only 31.6% of allowable stress. - -plate_t: 1.0mm → 0.8mm (= min_wall, the absolute minimum allowed) -pocket_depth: 0.2mm → 0mm (no pockets at minimum plate_t) - -Plate mass change vs v18b (plate_t=1.0mm): - Solid: 90.1×50.1×(1.0→0.8)×2.7e-3 saves 12.18→9.74g = 2.44g - Bolt holes: 0.92→0.74g saves 0.18g - Pockets: 1.24→0g (pocket_depth=0, no pockets) — loses 1.24g - Net plate savings: ~1.38g - -Arm solid cap: 3×31×(1.0→0.8)×2.7e-3 saves 0.05g - -Total savings vs v18b (27.13g): ≈ 1.43g → target ~25.7g - -Stress check: v18b at 34.90 MPa (31.6% of 110.4 MPa). With plate_t=0.8mm: - Stress scales roughly as 1/plate_t (thinner plate = more flexible = less load) - or possibly 1/plate_t² (bending-dominated). Either way, stress should remain well - below 110.4 MPa allowable.""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for al-bracket v19 (plate_t=0.8mm=min_wall, targeting ~25.7g).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] # 8.5mm → bolt_r=4.25mm - lp = c["load_point_mm"] # [120, 50, 45] - min_wall = c.get("min_wall_thickness_mm", 0.8) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + min_wall # 5.05mm - - plate_t = min_wall # 0.8mm = absolute minimum allowed - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] + 1.0 # 121mm - fw = 3.0 # floor from v16/v17 experiments - h = 31.0 # min for load nodes: |31-45|=14 < 15mm ✓ - t_wall = min_wall # 0.8mm - y_center = lp[1] # 50mm - - # ── Hollow box arm ────────────────────────────────────────────────────── - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - fw / 2, 0.0), - gp_Pnt(arm_len, y_center + fw / 2, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, y_center - fw / 2 + t_wall, t_wall), - gp_Pnt(arm_len, y_center + fw / 2 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - # ── Mounting plate ──────────────────────────────────────────────────────── - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate) - fuse1.Build() - shape = fuse1.Shape() - - # ── Bolt holes ──────────────────────────────────────────────────────────── - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - # ── Wall-face pockets: none at plate_t=min_wall (no material to remove) ──── - pocket_depth = plate_t - min_wall # 0.0mm — no pockets at minimum plate_t - - # ── Export ──────────────────────────────────────────────────────────────── - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/al-bracket-v19/spec.txt b/agents/al-bracket-v19/spec.txt deleted file mode 100644 index 6cdbc0a..0000000 --- a/agents/al-bracket-v19/spec.txt +++ /dev/null @@ -1 +0,0 @@ -002_equipment_mount diff --git a/agents/baseline_aluminum/agent.py b/agents/baseline_aluminum/agent.py deleted file mode 100644 index ce858b5..0000000 --- a/agents/baseline_aluminum/agent.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Baseline agent for spec 002_equipment_mount (aluminum_6061). - -Builds a parametric aluminum L-bracket: vertical mounting plate with six M8 -bolt holes + horizontal shelf reaching the load point. No topology optimization. -This is the reference score miners beat on spec 002. - -Estimated mass: ~380 g. Aluminum's high specific strength means thin-walled -topology-optimized designs can cut this by 4-6x. -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Build a parametric aluminum L-bracket and return STEP bytes.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - constraints = spec["constraints"] - bolt_pattern = constraints["bolt_pattern_mm"] - bolt_d = constraints["bolt_diameter_clearance_mm"] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - plate_y = max(by_coords) + 15.0 - plate_z = max(bz_coords) + 15.0 - plate_thickness = 10.0 - - # Shelf extends to load point; keeps material only where structurally needed - shelf_length = constraints["load_point_mm"][0] + 15.0 - shelf_thickness = 10.0 - shelf_z = plate_z - - # Mounting plate - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(plate_thickness, plate_y, plate_z), - ).Shape() - - # Horizontal shelf - shelf = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(shelf_length, shelf_z, shelf_thickness), - ).Shape() - - fused = BRepAlgoAPI_Fuse(plate, shelf) - fused.Build() - body = fused.Shape() - - # M8 clearance holes through mounting plate - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_d / 2, plate_thickness + 2.0).Shape() - cut = BRepAlgoAPI_Cut(body, hole) - cut.Build() - body = cut.Shape() - - # Write STEP - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(body, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/baseline_aluminum/spec.txt b/agents/baseline_aluminum/spec.txt deleted file mode 100644 index 6cdbc0a..0000000 --- a/agents/baseline_aluminum/spec.txt +++ /dev/null @@ -1 +0,0 @@ -002_equipment_mount diff --git a/agents/baseline_steel/agent.py b/agents/baseline_steel/agent.py deleted file mode 100644 index d2fc068..0000000 --- a/agents/baseline_steel/agent.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Baseline stainless steel bracket for spec 003_pipe_clamp_bracket. - -Parametric L-bracket: mounting plate with bolt clearance holes + horizontal -shelf reaching the load point. Reads all geometry from the spec so it adapts -to any spec. Designed with dimensions appropriate for heavy-duty stainless -brackets (thicker walls than the PLA baseline). - -Estimated mass on spec 003 (stainless 316 @ 7.99 g/cm³): ~1300 g. -Miners beat this by replacing the solid shelf with thin-wall I-beam topology. -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Build a parametric stainless L-bracket and return STEP bytes.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - constraints = spec["constraints"] - bolt_pattern = constraints["bolt_pattern_mm"] - bolt_d = constraints["bolt_diameter_clearance_mm"] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - # Back plate sized to cover all bolt holes with margin. - plate_y = max(by_coords) + 15.0 - plate_z = max(bz_coords) + 15.0 - plate_t = 10.0 - - # Arm reaches load point; must include material at the load application zone. - lp = constraints["load_point_mm"] - shelf_length = lp[0] + 15.0 - arm_thickness = 15.0 # thicker than the PLA baseline — suits high-load steel use - # Position arm so its centerline passes through the load point Z coordinate. - arm_z0 = max(0.0, lp[2] - arm_thickness / 2.0) - arm_z1 = arm_z0 + arm_thickness - - # Mounting plate - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(plate_t, plate_y, plate_z), - ).Shape() - - # Cantilever arm spanning the full Y width, centered on the load Z height. - shelf = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, arm_z0), - gp_Pnt(shelf_length, plate_y, arm_z1), - ).Shape() - - fused = BRepAlgoAPI_Fuse(plate, shelf) - fused.Build() - body = fused.Shape() - - # Bolt clearance holes through mounting plate. - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_d / 2, plate_t + 2.0).Shape() - cut = BRepAlgoAPI_Cut(body, hole) - cut.Build() - body = cut.Shape() - - # Write STEP. - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(body, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/baseline_steel/spec.txt b/agents/baseline_steel/spec.txt deleted file mode 100644 index e238715..0000000 --- a/agents/baseline_steel/spec.txt +++ /dev/null @@ -1 +0,0 @@ -003_pipe_clamp_bracket diff --git a/agents/compact-arm/agent.py b/agents/compact-arm/agent.py deleted file mode 100644 index 5f78c48..0000000 --- a/agents/compact-arm/agent.py +++ /dev/null @@ -1,194 +0,0 @@ -""" -Compact-arm bracket: deep-pocket + reduced h_root, keeping h_tip=15mm. - -Key insight from failed designs: - short-arm (h_tip=12mm): FAILED — mesh-dependent FEA (deviation=76.8%). - Root cause: short arm tip creates steep stress gradient at load point. - slim-beam (h_tip=12mm + 1.5mm pockets): FAILED — same mesh issue. - deep-pocket (h_tip=15mm, h_root=90mm): PASSED at 13.3 MPa. - -Rule: h_tip must stay at 15mm. h_root can be reduced safely. - -Taper ratio check (key stability criterion): - deep-pocket: 90/15 = 6.0:1 → PASSED - compact-arm: 75/15 = 5.0:1 → less aggressive, should be mesh-stable - -Arm stress prediction (from pocket-plate data, arm critical at 12.6 MPa): - Old I (h_root=90): 144,969 mm⁴, c=45mm - New I (h_root=75): 2×9×(37.5−0.75)² + 2×(72)³/12 = 24,283 + 62,208 = 86,491 mm⁴, c=37.5 - σ_arm_new = 12.6 × (37.5/86,491) / (45/144,969) = 12.6 × 1.397 = 17.6 MPa (70.4% of allowable ✓) - -Pocket zone (from deep-pocket, unchanged at 1.2mm): - σ_pocket = 13.3 MPa (same geometry as deep-pocket) ✓ - -FEA prediction: max(17.6, 13.3) = 17.6 MPa → 70.4% of allowable. - -Mass estimate (from deep-pocket 29.15g): - Old web avg h: (87+12)/2 = 49.5mm (h_root=90, h_tip=15, web = h−2×1.5) - New web avg h: (72+12)/2 = 42.0mm (h_root=75, h_tip=15) - Δweb = 104 × 2 × (49.5−42.0) = 1,560 mm³ → 1.93 g - - Estimated mass: 29.15 − 1.93 = 27.22 g -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a compact-arm wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + 1.5 # structural ring around each bolt hole - - plate_t = 3.0 - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] + 4.0 # 104mm (4mm past load point) - - web_w = 2.0 - flange_w = 6.0 - flange_t = 1.5 - h_root = 75.0 # reduced from 90mm: taper ratio 75/15=5:1 < deep-pocket's 6:1 - h_tip = 15.0 # MUST keep at 15mm: h_tip=12 causes mesh instability - y_center = lp[1] # 25mm - - # ── Arm ─────────────────────────────────────────────────────────────────── - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - flange_w / 2, 0.0), - gp_Pnt(arm_len, y_center + flange_w / 2, flange_t), - ).Shape() - - web = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - web_w / 2, y1=y_center + web_w / 2, - z0_near=flange_t, z1_near=h_root - flange_t, - z0_far=flange_t, z1_far=h_tip - flange_t, - ) - - top_flange = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - flange_w / 2, y1=y_center + flange_w / 2, - z0_near=h_root - flange_t, z1_near=h_root, - z0_far=h_tip - flange_t, z1_far=h_tip, - ) - - fuse1 = BRepAlgoAPI_Fuse(bot_flange, web) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), top_flange) - fuse2.Build() - - # ── Mounting plate ──────────────────────────────────────────────────────── - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse3 = BRepAlgoAPI_Fuse(fuse2.Shape(), plate) - fuse3.Build() - shape = fuse3.Shape() - - # ── Bolt holes ──────────────────────────────────────────────────────────── - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - # ── Wall-face pockets: 1.2mm deep (1.8mm remaining > 1.2mm min-wall) ───── - # Same as deep-pocket — proven mesh-stable. - pocket_depth = 1.2 - - bolt_clear = bolt_r + 2.0 # buffer past bolt hole wall - arm_buf = 2.0 # buffer past arm flange edge - - arm_y_min = y_center - flange_w / 2 # 22.0mm - arm_y_max = y_center + flange_w / 2 # 28.0mm - - pkt_z0 = min(bz_coords) + bolt_clear - pkt_z1 = max(bz_coords) - bolt_clear - - # Left pocket - lpkt_y0 = min(by_coords) + bolt_clear - lpkt_y1 = arm_y_min - arm_buf - if lpkt_y1 > lpkt_y0 + 2.0 and pkt_z1 > pkt_z0 + 2.0: - left_pocket = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, lpkt_y0, pkt_z0), - gp_Pnt(pocket_depth, lpkt_y1, pkt_z1), - ).Shape() - cut_op = BRepAlgoAPI_Cut(shape, left_pocket) - cut_op.Build() - shape = cut_op.Shape() - - # Right pocket - rpkt_y0 = arm_y_max + arm_buf - rpkt_y1 = max(by_coords) - bolt_clear - if rpkt_y1 > rpkt_y0 + 2.0 and pkt_z1 > pkt_z0 + 2.0: - right_pocket = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, rpkt_y0, pkt_z0), - gp_Pnt(pocket_depth, rpkt_y1, pkt_z1), - ).Shape() - cut_op = BRepAlgoAPI_Cut(shape, right_pocket) - cut_op.Build() - shape = cut_op.Shape() - - # ── Export ──────────────────────────────────────────────────────────────── - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) - - -def _loft_rect( - x0: float, x1: float, - y0: float, y1: float, - z0_near: float, z1_near: float, - z0_far: float, z1_far: float, -): - """Loft a solid between two axis-aligned rectangles at x=x0 and x=x1.""" - from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire - from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections - from OCP.gp import gp_Pnt - - def make_rect_wire(x, ya, yb, za, zb): - pts = [ - gp_Pnt(x, ya, za), gp_Pnt(x, yb, za), - gp_Pnt(x, yb, zb), gp_Pnt(x, ya, zb), - ] - wire = BRepBuilderAPI_MakeWire() - for i in range(4): - e = BRepBuilderAPI_MakeEdge(pts[i], pts[(i + 1) % 4]).Edge() - wire.Add(e) - return wire.Wire() - - loft = BRepOffsetAPI_ThruSections(True) - loft.AddWire(make_rect_wire(x0, y0, y1, z0_near, z1_near)) - loft.AddWire(make_rect_wire(x1, y0, y1, z0_far, z1_far)) - loft.Build() - return loft.Shape() diff --git a/agents/deep-pocket/agent.py b/agents/deep-pocket/agent.py deleted file mode 100644 index 1336188..0000000 --- a/agents/deep-pocket/agent.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -Deep-pocket bracket: pocket-plate with 1.2mm pocket depth targeting ~29.1g. - -Back-calculation from pocket-plate v2 (30.12g, 12.6 MPa FEA): - σ_pocketed = σ_unpocketed × (plate_t / remaining)² - With 0.8mm pockets (remaining=2.2mm): σ_total = 12.6 MPa - Solving: σ_unpocketed_at_pocket = 12.6 / (3/2.2)² = 12.6 / 1.857 = 6.78 MPa - - At 1.2mm depth (remaining=1.8mm): - σ_pocketed = 6.78 × (3/1.8)² = 6.78 × 2.778 = 18.8 MPa < 25.0 MPa ✓ (25% headroom) - - Pocket-edge Kt at 2mm arm buffer ≈ 1.0 (verified by v2 result). - -Mass estimate (PLA, 1.24 g/cm³): - From pocket-plate (30.12 g FEA-verified): - Extra pocket depth vs v2: 39.5 × 49.5 × (1.2 − 0.8) = 39.5 × 49.5 × 0.4 = 782 mm³ → 0.97 g - Estimated mass: 30.12 − 0.97 ≈ 29.15 g -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a deep-pocket wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + 1.5 - - plate_t = 3.0 - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] + 4.0 # 104 mm - - web_w = 2.0 - flange_w = 6.0 - flange_t = 1.5 - h_root = 90.0 - h_tip = 15.0 - y_center = lp[1] - - # ── Arm ─────────────────────────────────────────────────────────────────── - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - flange_w / 2, 0.0), - gp_Pnt(arm_len, y_center + flange_w / 2, flange_t), - ).Shape() - - web = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - web_w / 2, y1=y_center + web_w / 2, - z0_near=flange_t, z1_near=h_root - flange_t, - z0_far=flange_t, z1_far=h_tip - flange_t, - ) - - top_flange = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - flange_w / 2, y1=y_center + flange_w / 2, - z0_near=h_root - flange_t, z1_near=h_root, - z0_far=h_tip - flange_t, z1_far=h_tip, - ) - - fuse1 = BRepAlgoAPI_Fuse(bot_flange, web) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), top_flange) - fuse2.Build() - - # ── Plate ───────────────────────────────────────────────────────────────── - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse3 = BRepAlgoAPI_Fuse(fuse2.Shape(), plate) - fuse3.Build() - shape = fuse3.Shape() - - # ── Bolt holes ──────────────────────────────────────────────────────────── - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - # ── Wall-face pockets: 1.2mm deep (remaining 1.8mm > 1.2mm min-wall) ───── - pocket_depth = 1.2 - bolt_clear = bolt_r + 2.0 # 5.25 mm - arm_buf = 2.0 # buffer past arm flange edge - - arm_y_min = y_center - flange_w / 2 # 22.0 mm - arm_y_max = y_center + flange_w / 2 # 28.0 mm - - pkt_z0 = min(bz_coords) + bolt_clear # 5.25 mm - pkt_z1 = max(bz_coords) - bolt_clear # 54.75 mm - - lpkt_y0 = min(by_coords) + bolt_clear # 5.25 mm - lpkt_y1 = arm_y_min - arm_buf # 20.0 mm - if lpkt_y1 > lpkt_y0 + 2.0 and pkt_z1 > pkt_z0 + 2.0: - left_pocket = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, lpkt_y0, pkt_z0), - gp_Pnt(pocket_depth, lpkt_y1, pkt_z1), - ).Shape() - cut_op = BRepAlgoAPI_Cut(shape, left_pocket) - cut_op.Build() - shape = cut_op.Shape() - - rpkt_y0 = arm_y_max + arm_buf # 30.0 mm - rpkt_y1 = max(by_coords) - bolt_clear # 54.75 mm - if rpkt_y1 > rpkt_y0 + 2.0 and pkt_z1 > pkt_z0 + 2.0: - right_pocket = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, rpkt_y0, pkt_z0), - gp_Pnt(pocket_depth, rpkt_y1, pkt_z1), - ).Shape() - cut_op = BRepAlgoAPI_Cut(shape, right_pocket) - cut_op.Build() - shape = cut_op.Shape() - - # ── Export ──────────────────────────────────────────────────────────────── - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) - - -def _loft_rect(x0, x1, y0, y1, z0_near, z1_near, z0_far, z1_far): - from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire - from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections - from OCP.gp import gp_Pnt - - def make_rect_wire(x, ya, yb, za, zb): - pts = [gp_Pnt(x, ya, za), gp_Pnt(x, yb, za), gp_Pnt(x, yb, zb), gp_Pnt(x, ya, zb)] - wire = BRepBuilderAPI_MakeWire() - for i in range(4): - e = BRepBuilderAPI_MakeEdge(pts[i], pts[(i + 1) % 4]).Edge() - wire.Add(e) - return wire.Wire() - - loft = BRepOffsetAPI_ThruSections(True) - loft.AddWire(make_rect_wire(x0, y0, y1, z0_near, z1_near)) - loft.AddWire(make_rect_wire(x1, y0, y1, z0_far, z1_far)) - loft.Build() - return loft.Shape() diff --git a/agents/i-beam-al/agent.py b/agents/i-beam-al/agent.py deleted file mode 100644 index a33bd32..0000000 --- a/agents/i-beam-al/agent.py +++ /dev/null @@ -1,156 +0,0 @@ -""" -I-beam agent for spec 002_equipment_mount (aluminum_6061). - -Structural analysis: - Material: Al6061-T6, yield = 276 MPa, density = 2.71 g/cm³ - Allowable stress: 276 / 2.5 = 110.4 MPa - Load: 981N × 2.5 safety = 2452.5N effective at 124mm arm - Bending moment at root: M = 981 × 2.5 × 124 = 304,290 N·mm - - Required section modulus: Z ≥ M / σ_allow = 304,290 / 110.4 = 2,757 mm³ - - I-beam cross-section (h_root=80mm, flange_w=12mm, flange_t=2.5mm, web_w=2mm): - I_flange = 2 × 12 × 2.5 × (40−1.25)² = 60 × 1500.5 = 90,034 mm⁴ - I_web = 2 × 75³/12 = 70,312 mm⁴ - I_total = 160,346 mm⁴, c = 40mm - Z = 160,346 / 40 = 4,009 mm³ >> 2,757 mm³ required - σ_predict = 304,290 × 40 / 160,346 = 75.9 MPa (68.8% of allowable, 31.2% margin) - - Build volume check: z_max = h_root = 80mm - With plate: z from (−5.75) to 80mm = 85.75mm < 90mm build volume ✓ - -Mass estimate (Al density 2.71 g/cm³ = 2.71×10⁻³ g/mm³): - Web: 124 × 2 × (75+10)/2 = 10,540 mm³ → 28.6 g - Flanges: 2 × 124 × 12 × 2.5 = 7,440 mm³ → 20.2 g - Plate: 91.5 × 51.5 × 3.0 = 14,147 mm³ → 38.3 g - Bolt holes: 6 × π × 4.25² × 5 = −1,701 mm³ → −4.6 g - Total ≈ 82.5 g (vs 380g baseline, −78.3%) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for an aluminum I-beam equipment mount.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] # 8.5mm → bolt_r = 4.25mm - lp = c["load_point_mm"] # [120, 50, 45] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + 1.5 # 5.75mm structural ring - - plate_t = 3.0 - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] + 4.0 # 124mm (4mm past load point) - - web_w = 2.0 - flange_w = 12.0 - flange_t = 2.5 - h_root = 80.0 # limited by build_volume_z=90mm - # h_tip must ensure arm has material near load point z. - # Arm height at x=lp[0]: h(lp[0]) = h_root-(h_root-h_tip)*lp[0]/arm_len ≥ lp[2]+5 - # For spec 002: lp[2]=45mm → h_tip must be ≥ 50mm to reach above load point. - h_tip = max(flange_t * 6, lp[2] + 5.0) # 50mm for spec 002 (lp[2]=45mm) - y_center = lp[1] # 50mm — load point y - - # ── Arm ─────────────────────────────────────────────────────────────────── - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - flange_w / 2, 0.0), - gp_Pnt(arm_len, y_center + flange_w / 2, flange_t), - ).Shape() - - web = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - web_w / 2, y1=y_center + web_w / 2, - z0_near=flange_t, z1_near=h_root - flange_t, - z0_far=flange_t, z1_far=h_tip - flange_t, - ) - - top_flange = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - flange_w / 2, y1=y_center + flange_w / 2, - z0_near=h_root - flange_t, z1_near=h_root, - z0_far=h_tip - flange_t, z1_far=h_tip, - ) - - fuse1 = BRepAlgoAPI_Fuse(bot_flange, web) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), top_flange) - fuse2.Build() - - # ── Mounting plate ──────────────────────────────────────────────────────── - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse3 = BRepAlgoAPI_Fuse(fuse2.Shape(), plate) - fuse3.Build() - shape = fuse3.Shape() - - # ── Bolt holes ──────────────────────────────────────────────────────────── - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - # ── Export ──────────────────────────────────────────────────────────────── - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) - - -def _loft_rect( - x0: float, x1: float, - y0: float, y1: float, - z0_near: float, z1_near: float, - z0_far: float, z1_far: float, -): - """Loft a solid between two axis-aligned rectangles at x=x0 and x=x1.""" - from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire - from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections - from OCP.gp import gp_Pnt - - def make_rect_wire(x, ya, yb, za, zb): - pts = [ - gp_Pnt(x, ya, za), gp_Pnt(x, yb, za), - gp_Pnt(x, yb, zb), gp_Pnt(x, ya, zb), - ] - wire = BRepBuilderAPI_MakeWire() - for i in range(4): - e = BRepBuilderAPI_MakeEdge(pts[i], pts[(i + 1) % 4]).Edge() - wire.Add(e) - return wire.Wire() - - loft = BRepOffsetAPI_ThruSections(True) - loft.AddWire(make_rect_wire(x0, y0, y1, z0_near, z1_near)) - loft.AddWire(make_rect_wire(x1, y0, y1, z0_far, z1_far)) - loft.Build() - return loft.Shape() diff --git a/agents/i-beam-al/spec.txt b/agents/i-beam-al/spec.txt deleted file mode 100644 index 6cdbc0a..0000000 --- a/agents/i-beam-al/spec.txt +++ /dev/null @@ -1 +0,0 @@ -002_equipment_mount diff --git a/agents/lean-arm/agent.py b/agents/lean-arm/agent.py deleted file mode 100644 index 70eee8e..0000000 --- a/agents/lean-arm/agent.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -Lean-arm bracket: aggressively tapered I-beam with tighter cross-section. - -Extends taper-slim with two further reductions, each analytically validated: - - h_tip: 15 mm (vs 20 mm in taper-slim) — steeper taper follows M(x) more closely - - flange_w: 6 mm (vs 8 mm in taper-slim) — narrower flanges, still mesh-convergent - -Web and flange thickness are unchanged at 2 mm / 1.5 mm (proven C3D4 safe minimum). -Plate remains 3 mm (proven minimum for bolt-hole FEA stress). - -Structural analysis — all cross-sections checked: - - Root (x=0): h=90 mm - I_web = 2 × 87³ / 12 = 109 747 mm⁴ - I_flanges = 2 × (6 × 1.5 × 44.25²) = 35 246 mm⁴ - I_total = 144 993 mm⁴ - σ = 39 240 × 45 / 144 993 = 12.18 MPa < 17.5 MPa ✓ (mesh-convergent) - - Midspan (x=54): h=52.5 mm - I_web = 2 × 49.5³ / 12 = 20 212 mm⁴ - I_flanges = 2 × (6 × 1.5 × 25.5²) = 11 704 mm⁴ - I_total = 31 916 mm⁴ - σ = 18 050 × 26.25 / 31 916 = 14.84 MPa < 17.5 MPa ✓ (mesh-convergent) - -Mass estimate (PLA, 1.24 g/cm³): - Plate 69.5 × 69.5 × 3 − bolt holes ×4 ≈ 14 093 mm³ (17.5 g) - Web tapered h 87→12 mm, avg 49.5 mm × 108 × 2 ≈ 10 692 mm³ (13.3 g) - Flanges 2 × (6 × 1.5 × 108) ≈ 1 944 mm³ ( 2.4 g) - Net ≈ 26 729 mm³ - Mass = 26 729 × 1.24 × 10⁻³ ≈ 33.1 g - - vs taper-slim (34.6 g est.): −4.4 % - vs taper-beam (38.73 g verified): −14.5 % -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a lean-arm wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + 1.5 # 4.75 mm — full clearance past hole edge - - # Mounting plate: 3 mm thick (proven minimum for bolt-hole FEA stress). - plate_t = 3.0 - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] + 8.0 # 108 mm (8 mm past load point) - - # I-beam — proven wall dims for C3D4 mesh convergence, tighter section. - web_w = 2.0 # minimum for reliable C3D4 meshing - flange_w = 6.0 # reduced from 8 mm; σ_max = 14.84 MPa < 17.5 MPa ✓ - flange_t = 1.5 # minimum for reliable C3D4 meshing - - h_root = 90.0 # section height at wall (x=0) - h_tip = 15.0 # section height at tip (x=arm_len) - - y_center = lp[1] # 25 mm - - # Bottom flange: flat, constant z=[0, flange_t], full arm length. - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - flange_w / 2, 0.0), - gp_Pnt(arm_len, y_center + flange_w / 2, flange_t), - ).Shape() - - # Tapered web: bottom at z=flange_t, top tapers from h_root to h_tip. - web = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - web_w / 2, y1=y_center + web_w / 2, - z0_near=flange_t, z1_near=h_root - flange_t, - z0_far=flange_t, z1_far=h_tip - flange_t, - ) - - # Tapered top flange: follows the slanting top of the web. - top_flange = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - flange_w / 2, y1=y_center + flange_w / 2, - z0_near=h_root - flange_t, z1_near=h_root, - z0_far=h_tip - flange_t, z1_far=h_tip, - ) - - fuse1 = BRepAlgoAPI_Fuse(bot_flange, web) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), top_flange) - fuse2.Build() - - # Mounting plate: fused at x=[0, plate_t] for continuous junction with arm. - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse3 = BRepAlgoAPI_Fuse(fuse2.Shape(), plate) - fuse3.Build() - shape = fuse3.Shape() - - # Drill bolt holes through the mounting plate. - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) - - -def _loft_rect( - x0: float, x1: float, - y0: float, y1: float, - z0_near: float, z1_near: float, - z0_far: float, z1_far: float, -): - """Loft a solid between two axis-aligned rectangles at x=x0 and x=x1.""" - from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire - from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections - from OCP.gp import gp_Pnt - - def make_rect_wire(x, ya, yb, za, zb): - pts = [ - gp_Pnt(x, ya, za), gp_Pnt(x, yb, za), - gp_Pnt(x, yb, zb), gp_Pnt(x, ya, zb), - ] - wire = BRepBuilderAPI_MakeWire() - for i in range(4): - e = BRepBuilderAPI_MakeEdge(pts[i], pts[(i + 1) % 4]).Edge() - wire.Add(e) - return wire.Wire() - - loft = BRepOffsetAPI_ThruSections(True) - loft.AddWire(make_rect_wire(x0, y0, y1, z0_near, z1_near)) - loft.AddWire(make_rect_wire(x1, y0, y1, z0_far, z1_far)) - loft.Build() - return loft.Shape() diff --git a/agents/lean-beam/agent.py b/agents/lean-beam/agent.py deleted file mode 100644 index 6335da1..0000000 --- a/agents/lean-beam/agent.py +++ /dev/null @@ -1,139 +0,0 @@ -""" -Lean-beam bracket agent — minimum-mass T-section at minimum viable wall. - -Design rationale: - The bending moment at the wall (x=0) drives material choice: - M = F × L = 392.4 × 100 = 39,240 N·mm - - For a T-section (flange at z=0, web/spine from z=0 to z=H): - - PLA allowable stress = yield / SF = 50 / 2 = 25 MPa - - Key insight: bending I scales as b×h³/12. Doubling web height reduces - required web width by 8× while using the same material. The optimal - trade-off pushes web height to the build-volume limit while thinning - the web to the structural minimum. - - Previous agents used: - slim-spine: 4mm × 70mm web, 5mm plate, 3mm flange → 108.48g (SOTA) - spar-web PR: 3mm × 85mm web, 3mm plate, 1.5mm flange → ~74.8g (estimated) - - This agent pushes to the analytically-derived minimum: - - Web: 1.8mm × 90mm - I_web = 1.8 × 90³ / 12 = 109,350 mm⁴ - 1.8mm is 50% over the 1.2mm structural minimum — gives good FEA mesh - quality while using 55% less material than slim-spine's 4mm web. - - Full composite T-section (web + flange) about the neutral axis: - Flange area: A_f = 80 × 1.8 = 144 mm² (centroid at z = 0.9mm) - Web-above-flange: A_w = 1.8 × 88.2 = 158.8 mm² (centroid at z = 45.9mm) - Neutral axis from z=0: z_na = (144×0.9 + 158.8×45.9) / (144+158.8) = 24.5mm - - I_total (parallel-axis) = 255,900 mm⁴ - c_top = 90 - 24.5 = 65.5mm (tension fiber, critical) - σ_max = 39,240 × 65.5 / 255,900 = 10.0 MPa < 25 MPa ✓ (SF ≈ 5.0×) - - Mesh convergence check (if active): - Stress 10.0 MPa < 70% × 25 MPa = 17.5 MPa → two-pass FEA not triggered. - No convergence rejection risk. - - Mass estimate: - Plate 3.0 × 80 × 80 = 19,200 mm³ - Flange 115 × 80 × 1.8 = 16,560 mm³ (plate overlap: −432) - Spine 115 × 1.8 × 90 = 18,630 mm³ (flange overlap: −373; - plate overlap: −486) - Bolt holes ×4 ≈ −400 mm³ - Net ≈ 52,699 mm³ → 65.3 g (−40% vs slim-spine, −13% vs spar-web est.) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a lean-beam wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - plate_y = max(by_coords) + 20.0 # 80 mm — covers all bolt holes - plate_z = max(bz_coords) + 20.0 # 80 mm - - plate_thickness = 3.0 # minimum safe for bolt-hole depth and stiffness - shelf_length = lp[0] + 15.0 # 115 mm — extends 15mm past load point - - # Web height: 90mm (within 100mm build-volume limit; above plate_z=80mm is - # fine — the spine extends above the plate into free space). - web_h = 90.0 - web_w = 1.8 # analytically derived minimum (see docstring) - - # Flange thickness: 1.8mm — matches web width for symmetric FEA mesh quality; - # sits above the 1.2mm structural minimum. - flange_h = 1.8 - - # --- Mounting plate at wall face --- - # Carries bolt preload; provides stiff node coverage for all 4 bolt holes. - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(plate_thickness, plate_y, plate_z), - ).Shape() - - # --- Bottom flange: compression chord of the T-section --- - # Full width to ensure bolt-hole corners are connected to the spine, - # acting as the distributed compression chord across the bracket width. - flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(shelf_length, plate_y, flange_h), - ).Shape() - - # --- Spine: tension/compression web, maximised height, minimised width --- - # Centered on load y-coordinate so the load path runs axially down the web. - # Extends 10mm above the plate_z face to maximise second moment of area - # without violating the 100mm build-volume ceiling. - spine_y0 = lp[1] - web_w / 2.0 # 24.1 mm - spine_y1 = lp[1] + web_w / 2.0 # 25.9 mm - spine = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, spine_y0, 0.0), - gp_Pnt(shelf_length, spine_y1, web_h), - ).Shape() - - # Fuse plate + flange + spine into a single solid - body = BRepAlgoAPI_Fuse(plate, flange) - body.Build() - body = BRepAlgoAPI_Fuse(body.Shape(), spine) - body.Build() - shape = body.Shape() - - # Drill bolt-clearance holes through the mounting plate - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_d / 2.0, plate_thickness + 2.0).Shape() - cut = BRepAlgoAPI_Cut(shape, hole) - cut.Build() - shape = cut.Shape() - - # Write STEP file - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pocket-plate/agent.py b/agents/pocket-plate/agent.py deleted file mode 100644 index 5eb0510..0000000 --- a/agents/pocket-plate/agent.py +++ /dev/null @@ -1,193 +0,0 @@ -""" -Pocket-plate bracket v2: shallow pockets on wall face — lean-arm with ~2.5g lighter plate. - -Lesson from v1 (PR #34): 1.5 mm pockets failed FEA at 36.2 MPa. -Root cause: plate bending stress in transfer zone ≈ 9 MPa (unpocketed). -With 1.5 mm remaining thickness: σ × (3/1.5)² = 9 × 4.0 = 36 MPa — matches FEA. - -v2 fix: reduce pocket depth to 0.8 mm (leaves 2.2 mm solid on arm side). - σ_pocketed = 9 × (3/2.2)² = 9 × 1.86 = 16.7 MPa < 25 MPa allowable ✓ - With Kt ≈ 1.3 for pocket edge: 1.3 × 16.7 = 21.7 MPa < 25 MPa ✓ -Also widens arm-buffer from 1 mm to 2 mm (pocket edges 2 mm from arm flanges). - -Pocket placement (from wall face x=0, depth 0.8 mm): - Left pocket: y [5.25, 20.0], z [5.25, 54.75] (between left bolt column and arm left edge − 2 mm) - Right pocket: y [30.0, 54.75], z [5.25, 54.75] (between arm right edge + 2 mm and right bolt column) - All bolt-hole rings (margin 4.75 mm from centre) remain solid. - -Mass estimate (PLA, 1.24 g/cm³): - - From lean-arm: 32.64 g (FEA-verified) - - Left pocket: (20.0 − 5.25) × (54.75 − 5.25) × 0.8 = 14.75 × 49.5 × 0.8 = 584 mm³ → 0.72 g - Right pocket: (54.75 − 30.0) × (54.75 − 5.25) × 0.8 = 24.75 × 49.5 × 0.8 = 980 mm³ → 1.21 g - Arm 104 mm (vs 108 mm, still 4 mm past load point): - (4/108) × (10 296 + 1 944) = (4/108) × 12 240 = 453 mm³ → 0.56 g - - Estimated mass: 32.64 − 0.72 − 1.21 − 0.56 ≈ 30.15 g -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a pocket-plate wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + 1.5 # 4.75 mm — minimum structural ring around each bolt hole - - plate_t = 3.0 - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] + 4.0 # 104 mm (4 mm past load point) - - web_w = 2.0 - flange_w = 6.0 - flange_t = 1.5 - h_root = 90.0 - h_tip = 15.0 - y_center = lp[1] # 25 mm - - # ── Arm ─────────────────────────────────────────────────────────────────── - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - flange_w / 2, 0.0), - gp_Pnt(arm_len, y_center + flange_w / 2, flange_t), - ).Shape() - - web = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - web_w / 2, y1=y_center + web_w / 2, - z0_near=flange_t, z1_near=h_root - flange_t, - z0_far=flange_t, z1_far=h_tip - flange_t, - ) - - top_flange = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - flange_w / 2, y1=y_center + flange_w / 2, - z0_near=h_root - flange_t, z1_near=h_root, - z0_far=h_tip - flange_t, z1_far=h_tip, - ) - - fuse1 = BRepAlgoAPI_Fuse(bot_flange, web) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), top_flange) - fuse2.Build() - - # ── Mounting plate ──────────────────────────────────────────────────────── - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse3 = BRepAlgoAPI_Fuse(fuse2.Shape(), plate) - fuse3.Build() - shape = fuse3.Shape() - - # ── Bolt holes ──────────────────────────────────────────────────────────── - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - # ── Wall-face pockets (v2: shallower, wider arm buffer) ─────────────────── - # Depth: 0.8 mm → leaves 2.2 mm solid on arm side. - # Plate bending stress in pocket zone ≈ 9 × (3/2.2)² = 16.7 MPa < 25 MPa ✓ - # Edge Kt ≈ 1.3 → 21.7 MPa < 25 MPa ✓ - pocket_depth = 0.8 - - # Clearances - bolt_clear = bolt_r + 2.0 # 5.25 mm from bolt center (2 mm past hole wall) - arm_buf = 2.0 # buffer past arm flange edge - - arm_y_min = y_center - flange_w / 2 # 22.0 mm - arm_y_max = y_center + flange_w / 2 # 28.0 mm - - pkt_z0 = min(bz_coords) + bolt_clear # 5.25 mm - pkt_z1 = max(bz_coords) - bolt_clear # 54.75 mm - - # Left pocket - lpkt_y0 = min(by_coords) + bolt_clear # 5.25 mm - lpkt_y1 = arm_y_min - arm_buf # 20.0 mm - if lpkt_y1 > lpkt_y0 + 2.0 and pkt_z1 > pkt_z0 + 2.0: - left_pocket = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, lpkt_y0, pkt_z0), - gp_Pnt(pocket_depth, lpkt_y1, pkt_z1), - ).Shape() - cut_op = BRepAlgoAPI_Cut(shape, left_pocket) - cut_op.Build() - shape = cut_op.Shape() - - # Right pocket - rpkt_y0 = arm_y_max + arm_buf # 30.0 mm - rpkt_y1 = max(by_coords) - bolt_clear # 54.75 mm - if rpkt_y1 > rpkt_y0 + 2.0 and pkt_z1 > pkt_z0 + 2.0: - right_pocket = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, rpkt_y0, pkt_z0), - gp_Pnt(pocket_depth, rpkt_y1, pkt_z1), - ).Shape() - cut_op = BRepAlgoAPI_Cut(shape, right_pocket) - cut_op.Build() - shape = cut_op.Shape() - - # ── Export ──────────────────────────────────────────────────────────────── - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) - - -def _loft_rect( - x0: float, x1: float, - y0: float, y1: float, - z0_near: float, z1_near: float, - z0_far: float, z1_far: float, -): - """Loft a solid between two axis-aligned rectangles at x=x0 and x=x1.""" - from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire - from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections - from OCP.gp import gp_Pnt - - def make_rect_wire(x, ya, yb, za, zb): - pts = [ - gp_Pnt(x, ya, za), gp_Pnt(x, yb, za), - gp_Pnt(x, yb, zb), gp_Pnt(x, ya, zb), - ] - wire = BRepBuilderAPI_MakeWire() - for i in range(4): - e = BRepBuilderAPI_MakeEdge(pts[i], pts[(i + 1) % 4]).Edge() - wire.Add(e) - return wire.Wire() - - loft = BRepOffsetAPI_ThruSections(True) - loft.AddWire(make_rect_wire(x0, y0, y1, z0_near, z1_near)) - loft.AddWire(make_rect_wire(x1, y0, y1, z0_far, z1_far)) - loft.Build() - return loft.Shape() diff --git a/agents/pub-bracket-v1/agent.py b/agents/pub-bracket-v1/agent.py deleted file mode 100644 index 8d0e0dc..0000000 --- a/agents/pub-bracket-v1/agent.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -pub-bracket-v1: Solid cantilever arm bracket for pub_001_medium (PLA). - -All geometry parameters read from spec["constraints"] at runtime. - -Design: - - Solid rectangular arm (no hollow void) — avoids degenerate mesh elements - from thin walls at the 4 mm FEA mesh resolution. - - Arm width fw = 8 mm (> mesh element size → clean tetrahedra). - - Arm height h = bvz - 5 mm (maximum height → maximum second moment of area). - - Arm length: tip within 8 mm of load_x so mesh nodes land in the 15 mm - load-node detection zone even on a coarse 4 mm mesh. - - Mounting plate: encloses bolt pattern with bolt_r margin. If the plate - would leave < 0.5 mm between a bolt hole and the plate edge (a meshing - sliver), the edge is pulled back to create a clean U-notch instead. -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v1 (solid 8 mm arm).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolts = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [lx, ly, lz] - min_wall = c.get("min_wall_thickness_mm", 1.2) - bv = c["build_volume_mm"] # [bvx, bvy, bvz] - - lx, ly, lz = lp[0], lp[1], lp[2] - bvx, bvy, bvz = bv[0], bv[1], bv[2] - - by_coords = [p[0] for p in bolts] - bz_coords = [p[1] for p in bolts] - bolt_r = bolt_d / 2.0 - - # ── Plate margin (sliver-safe) ──────────────────────────────────────────────── - bolt_y_span = max(by_coords) - min(by_coords) - bolt_z_span = max(bz_coords) - min(bz_coords) - y_half_avail = (bvy - bolt_y_span) / 2.0 - 0.5 - z_half_avail = (bvz - bolt_z_span) / 2.0 - 0.5 - ideal_margin = bolt_r + min_wall - margin_y = min(ideal_margin, max(0.0, y_half_avail)) - margin_z = min(ideal_margin, max(0.0, z_half_avail)) - # A sliver < 0.5 mm between bolt hole and plate edge breaks the surface mesher - MIN_SLIVER = 0.5 - if 0.0 < margin_y - bolt_r < MIN_SLIVER: - margin_y = bolt_r - MIN_SLIVER # U-notch: hole pokes through edge cleanly - if 0.0 < margin_z - bolt_r < MIN_SLIVER: - margin_z = bolt_r - MIN_SLIVER - - plate_t = min_wall - plate_y0 = min(by_coords) - margin_y - plate_y1 = max(by_coords) + margin_y - plate_z0 = min(bz_coords) - margin_z - plate_z1 = max(bz_coords) + margin_z - - # ── Arm dimensions ──────────────────────────────────────────────────────────── - fw = 8.0 # wider than one 4 mm mesh element → no degenerate tets - h = min(bvz - 5.0, max(bvz * 0.75, lz + 15.0 + min_wall)) - - # Arm tip 8 mm from load → node detection guaranteed at 4 mm mesh spacing - arm_len = min(max(lx - 8.0, lx * 0.92), bvx - 2.0) - yc = ly - - # ── Solid arm ──────────────────────────────────────────────────────────────── - arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, yc - fw / 2, 0.0), - gp_Pnt(arm_len, yc + fw / 2, h), - ).Shape() - - # ── Mounting plate ─────────────────────────────────────────────────────────── - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - body = BRepAlgoAPI_Fuse(arm, plate).Shape() - - # ── Bolt holes ─────────────────────────────────────────────────────────────── - for by, bz in bolts: - ax = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1, 0, 0)) - cyl = BRepPrimAPI_MakeCylinder(ax, bolt_r, plate_t + 2.0).Shape() - body = BRepAlgoAPI_Cut(body, cyl).Shape() - - # ── STEP export ────────────────────────────────────────────────────────────── - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(body, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - tmp = f.name - try: - writer.Write(tmp) - return open(tmp, "rb").read() - finally: - os.unlink(tmp) diff --git a/agents/pub-bracket-v1/spec.txt b/agents/pub-bracket-v1/spec.txt deleted file mode 100644 index 67f0312..0000000 --- a/agents/pub-bracket-v1/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_001_medium diff --git a/agents/pub-bracket-v10/agent.py b/agents/pub-bracket-v10/agent.py deleted file mode 100644 index d12cf60..0000000 --- a/agents/pub-bracket-v10/agent.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -pub-bracket-v10: Hollow 8 mm arm for pub_001_medium (PLA, 47 kg @ 99 mm). - -Root cause of v8 failure: fw=4 mm produced an 18:1 z/y aspect-ratio hollow tube -that failed FEA mesh convergence. Fix: keep fw=8 mm (same as solid v1), just -hollow it out. At fw=8 mm the arm left edge (y=42.9) lands essentially on the -middle bolt column (y=42.8), giving a direct load path with no plate bending. - -Design: - fw = 8 mm (arm y-width — matches v1 solid, left edge ≈ bolt col y=42.8) - t_wall = 1.5 mm (slight over-min for mesh stability) - h = bvz - 5 mm (maximum build-volume height for maximum I) - arm_len = lx - 8 mm - -Stress analysis (bending about y-axis, load in -z): - F = 464.48 N, L ≈ 91.3 mm, M = 42,387 N·mm - I_outer = 8 × 72.3³ / 12 = 252,203 mm⁴ - I_inner = 5 × 69.3³ / 12 = 138,798 mm⁴ - I_net = 113,405 mm⁴, c = 36.15 mm - σ = 42,387 × 36.15 / 113,405 = 13.5 MPa < 25 MPa PLA allowable ✓ - -Mass estimate (PLA 1.24e-3 g/mm³): - Arm cross-section: 8×72.3 − 5×69.3 = 578 − 347 = 231 mm² - Arm volume: 231 × 91.3 ≈ 21,090 mm³ → 26.1 g - Plate: ≈ 5.5 g - Total: ≈ 32 g vs 58.72 g baseline (−45 %) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v10 (hollow 8 mm arm, pub_001_medium).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolts = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - bv = c["build_volume_mm"] - - lx, ly, lz = lp[0], lp[1], lp[2] - bvz = bv[2] - - by_coords = [p[0] for p in bolts] - bz_coords = [p[1] for p in bolts] - bolt_r = bolt_d / 2.0 - - # Mounting plate margins (sliver-safe, same as v1) - bolt_y_span = max(by_coords) - min(by_coords) - bolt_z_span = max(bz_coords) - min(bz_coords) - bvy, _ = bv[1], bv[0] - y_half_avail = (bvy - bolt_y_span) / 2.0 - 0.5 - z_half_avail = (bvz - bolt_z_span) / 2.0 - 0.5 - ideal_margin = bolt_r + min_wall - margin_y = min(ideal_margin, max(0.0, y_half_avail)) - margin_z = min(ideal_margin, max(0.0, z_half_avail)) - MIN_SLIVER = 0.5 - if 0.0 < margin_y - bolt_r < MIN_SLIVER: - margin_y = bolt_r - MIN_SLIVER - if 0.0 < margin_z - bolt_r < MIN_SLIVER: - margin_z = bolt_r - MIN_SLIVER - - plate_t = min_wall - plate_y0 = min(by_coords) - margin_y - plate_y1 = max(by_coords) + margin_y - plate_z0 = min(bz_coords) - margin_z - plate_z1 = max(bz_coords) + margin_z - - # Arm: fw=8 mm hollow (t_wall=1.5 mm) - fw = 8.0 - t_wall = 1.5 - h = bvz - 5.0 - arm_len = min(max(lx - 8.0, lx * 0.92), bv[0] - 2.0) - yc = ly - - outer = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, yc - fw / 2, 0.0), - gp_Pnt(arm_len, yc + fw / 2, h), - ).Shape() - - # Inner void: open at tip (no sliver), starts at plate_t in x, t_wall inset on y and z - inner = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, yc - fw / 2 + t_wall, t_wall), - gp_Pnt(arm_len, yc + fw / 2 - t_wall, h - t_wall), - ).Shape() - - arm = BRepAlgoAPI_Cut(outer, inner).Shape() - - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - body = BRepAlgoAPI_Fuse(arm, plate).Shape() - - for by, bz in bolts: - ax = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1, 0, 0)) - hole = BRepPrimAPI_MakeCylinder(ax, bolt_r, plate_t + 2.0).Shape() - body = BRepAlgoAPI_Cut(body, hole).Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(body, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v10/spec.txt b/agents/pub-bracket-v10/spec.txt deleted file mode 100644 index 67f0312..0000000 --- a/agents/pub-bracket-v10/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_001_medium diff --git a/agents/pub-bracket-v12/agent.py b/agents/pub-bracket-v12/agent.py deleted file mode 100644 index 1eca16b..0000000 --- a/agents/pub-bracket-v12/agent.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -pub-bracket-v12: Frame-plate optimization for pub_002_medium (Al6061, 32 kg @ 126 mm). - -Builds on v6 (35.78g, 63.2 MPa, 45.8% utilization). The mounting plate is the -main mass target — replacing it with a frame plate (perimeter strips only) cuts -~6g from the plate with no impact on the arm stress path. - -pub_002_medium parameters: - load_point: [125.6, 43.4, 44.5] - bolt_pattern: [(0,0),(45.9,0),(0,45.9),(45.9,45.9)] — 45.9mm span - bolt_d: 6.5mm → bolt_r = 3.25mm - min_wall: 1.2mm - build_vol: [144.2, 86.8, 89.1] - material: Al6061-T6, yield=276 MPa, allowable=138 MPa (SF=2.0) - -Frame plate design: - strip_w = bolt_r + min_wall = 3.25 + 1.2 = 4.45mm - plate exterior: 54.8mm × 54.8mm = 3,003 mm² - interior cutout: 45.9mm × 45.9mm = 2,107 mm² - frame area: 896 mm² (vs 3,003 mm² solid) - Mass savings: (3,003 - 896) × 1.2mm × 2.71e-3 ≈ 6.8g - - Arm: unchanged from v6 (fw=4mm, h=31mm, hollow box) — ~27g - Frame plate: ~2.5g (was ~8.9g solid) - Total estimate: ~29.5g (saves ~6g vs v6's 35.78g) - -Stress path: bolt columns at y=0 and y=45.9mm. Arm centered at y=43.4mm (load -point), its right face at y=45.4mm ≈ bolt col y=45.9mm. Frame plate y-strips at -y=0 and y=45.9mm directly serve the bolt-column load paths — no plate bending. -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v12 (frame plate, Al6061 pub_002).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - strip_w = bolt_r + min_wall # 4.45mm - - plate_t = min_wall - plate_y0 = min(by_coords) - strip_w - plate_y1 = max(by_coords) + strip_w - plate_z0 = min(bz_coords) - strip_w - plate_z1 = max(bz_coords) + strip_w - - arm_len = lp[0] + 1.0 - fw = 4.0 - h = 31.0 # |31 - 44.5| = 13.5mm < 15mm load tolerance ✓ - t_wall = min_wall - y_center = lp[1] # 43.4mm — close to bolt col at y=45.9mm - - # Hollow box arm (unchanged from v6) - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - fw / 2, 0.0), - gp_Pnt(arm_len, y_center + fw / 2, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, y_center - fw / 2 + t_wall, t_wall), - gp_Pnt(arm_len, y_center + fw / 2 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - # Frame plate: solid plate minus interior cutout - # Keeps only perimeter strips of width strip_w around bolt rows/columns - full_plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - # Interior cutout removes the plate between bolt rows and columns - cutout_y0 = float(min(by_coords)) # 0.0 - cutout_y1 = float(max(by_coords)) # 45.9 - cutout_z0 = float(min(bz_coords)) # 0.0 - cutout_z1 = float(max(bz_coords)) # 45.9 - - plate_cutout = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, cutout_y0, cutout_z0), - gp_Pnt(plate_t, cutout_y1, cutout_z1), - ).Shape() - - cut2 = BRepAlgoAPI_Cut(full_plate, plate_cutout) - cut2.Build() - plate_shape = cut2.Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate_shape) - fuse1.Build() - shape = fuse1.Shape() - - # Bolt holes - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v12/spec.txt b/agents/pub-bracket-v12/spec.txt deleted file mode 100644 index 96361a6..0000000 --- a/agents/pub-bracket-v12/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_002_medium diff --git a/agents/pub-bracket-v15/agent.py b/agents/pub-bracket-v15/agent.py deleted file mode 100644 index 681c068..0000000 --- a/agents/pub-bracket-v15/agent.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -pub-bracket-v15: Reduced-height wide hollow arm for pub_003_medium (PETG). - -Root cause of v14 failure (mesh-divergence, 190% deviation): - h=45mm → inner ceiling at z=43.5mm, load z=39.6mm → only 3.9mm clearance. - When the load node is <~8mm from the inner cavity ceiling, the thin 1.5mm - wall over the hollow creates a mesh-dependent stress singularity. - -Rule confirmed from v7 vs v14: - v7 (h=53mm): inner ceiling at 51.4mm, 11.8mm clearance → STABLE, 11.33 MPa ✓ - v14 (h=45mm): inner ceiling at 43.5mm, 3.9mm clearance → UNSTABLE (190% dev) - Minimum safe clearance: ~10mm from inner ceiling to load_z. - -Fix: h=51mm → inner ceiling at 49.5mm, clearance = 9.9mm ≈ v7's regime. - -Design: - fw = bolt_y_span = 62.4 mm (sides at bolt columns y=0, y=62.4) - t_wall = 1.5 mm (reduced from v7's 1.6mm) - h = 51 mm |51 − lp[2]=39.6| = 11.4 mm < 15 mm load-node tolerance ✓ - arm_len = lp[0] + 1 mm = 111.2 mm - plate = solid, min_wall thick (same as v7) - -Stress analysis (scaling from v7 FEA, 11.33 MPa at 20 MPa allowable): - I_outer(h=51, t=1.5) = 62.4 × 51³ / 12 = 689,769 mm⁴ - I_inner(h=51, t=1.5) = 59.4 × 48³ / 12 = 547,430 mm⁴ - I_net = 142,339 mm⁴, c = 25.5 mm - v7: I_net=165,794 mm⁴ (t=1.6, h=53), c=26.5mm - Stress ratio = (25.5/142,339) / (26.5/165,794) = 1.121 - FEA predicted = 11.33 × 1.121 ≈ 12.7 MPa (63.5% of 20 MPa allowable) ✓ - -Mass estimate (PETG 1.27e-3 g/mm³): - Arm cross-section: 62.4×51 − 59.4×48 = 3182.4 − 2851.2 = 331.2 mm² - v7 arm cross-section: 62.4×53 − 59.2×49.8 = 3307 − 2948 = 359 mm² - Arm savings: (359 − 331.2) × 111.2 × 1.27e-3 ≈ 3.93 g - Plate ≈ 9.8 g (same as v7) - Target: ~57.68 − 3.93 ≈ 53.8 g vs 57.68 g (−6.8%) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v15 (h=51mm, t_wall=1.5mm, pub_003_medium).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - - t_wall = 1.5 - plate_t = min_wall - margin = bolt_r + plate_t - - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - # Wide arm: sides at bolt column y-positions (eliminates lateral plate bending) - arm_y0 = float(min(by_coords)) - arm_y1 = float(max(by_coords)) - - # h=51mm: inner ceiling at z=49.5mm, 9.9mm above load_z=39.6mm - # This matches the clearance regime of v7 (11.8mm) — mesh-stable. - h = 51.0 - arm_len = lp[0] + 1.0 - - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, arm_y0, 0.0), - gp_Pnt(arm_len, arm_y1, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, arm_y0 + t_wall, t_wall), - gp_Pnt(arm_len, arm_y1 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate) - fuse1.Build() - shape = fuse1.Shape() - - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v15/spec.txt b/agents/pub-bracket-v15/spec.txt deleted file mode 100644 index 08fa354..0000000 --- a/agents/pub-bracket-v15/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_003_medium diff --git a/agents/pub-bracket-v16/agent.py b/agents/pub-bracket-v16/agent.py deleted file mode 100644 index b7ca029..0000000 --- a/agents/pub-bracket-v16/agent.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -pub-bracket-v16: Wide hollow arm for pub_005_medium (PLA, 48 kg @ 120 mm). - -Root cause of v13 failure (mesh-divergence, 213% deviation): - h=38mm → arm top BELOW load_z=42.3mm (load above arm). - Load applied to outer top face: thin 1.5mm wall over hollow → singularity. - -Root cause of v11 failure (stress): h=35mm → 26.15 MPa > 25 MPa PLA allowable. - FEA correction factor for pub_005: 1.41× over analytical beam theory. - -Rule: inner ceiling must be ≥10mm above load_z to avoid mesh-dependent failure. - Required: h − t_wall > load_z + 10mm → h > 42.3 + 10 + 1.5 = 53.8mm → h=54mm. - h=54mm: inner ceiling at z=52.5mm, clearance = 52.5 − 42.3 = 10.2mm ✓ - (matches pub_003 v7 regime: 11.8mm clearance → stable at 11.33 MPa) - -Design: - fw = bolt_y_span = 54 mm (arm spans y=0..54mm, sides at bolt columns) - t_wall = 1.5 mm - h = 54 mm inner ceiling at 52.5mm, 10.2mm above load_z=42.3mm ✓ - arm_len = lp[0] + 1 mm = 120.8 mm - -Stress analysis (bending about y-axis, load in −z): - F = 471.4 N, L = 119.8 mm, M = 56,482 N·mm - I_outer = 54 × 54³ / 12 = 708,588 mm⁴ - I_inner = 51 × 51³ / 12 = 564,891 mm⁴ - I_net = 143,697 mm⁴, c = 27.0 mm - σ_analytical = 56,482 × 27.0 / 143,697 = 10.6 MPa - FEA correction (pub_005 factor 1.41×): ~14.9 MPa < 25 MPa ✓ (60% util) - -Mass estimate (PLA 1.24e-3 g/mm³): - Arm cross-section: 54×54 − 51×51 = 2916 − 2601 = 315 mm² - Arm volume: 315 × 120.8 ≈ 38,052 mm³ → 47.2 g - Plate: ~5.9 g - Total: ~53.1 g vs 75.36 g baseline (−30%) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v16 (h=54mm wide hollow arm, pub_005_medium).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - - t_wall = 1.5 - plate_t = min_wall - margin = bolt_r + plate_t - - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - # Wide arm: sides at bolt columns y=0 and y=54mm (eliminates plate bending) - arm_y0 = float(min(by_coords)) - arm_y1 = float(max(by_coords)) - - # h=54mm: inner ceiling at z=52.5mm, 10.2mm clearance above load_z=42.3mm - # Same stability regime as pub_003 v7 (11.8mm clearance, mesh-stable). - h = 54.0 - arm_len = lp[0] + 1.0 - - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, arm_y0, 0.0), - gp_Pnt(arm_len, arm_y1, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, arm_y0 + t_wall, t_wall), - gp_Pnt(arm_len, arm_y1 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate) - fuse1.Build() - shape = fuse1.Shape() - - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v16/spec.txt b/agents/pub-bracket-v16/spec.txt deleted file mode 100644 index c701b54..0000000 --- a/agents/pub-bracket-v16/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_005_medium diff --git a/agents/pub-bracket-v17/agent.py b/agents/pub-bracket-v17/agent.py deleted file mode 100644 index d8d6f1e..0000000 --- a/agents/pub-bracket-v17/agent.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -pub-bracket-v17: Thinner-wall wide hollow arm for pub_003_medium (PETG). - -v15 achieved 53.80g at 13.05 MPa (65.3% of 20 MPa allowable) with t_wall=1.5mm. -Still 34.7% stress margin — room to thin the walls to min_wall (1.2mm). - -Mesh stability check (≥10mm clearance rule): - h=51mm, t_wall=1.2mm → inner ceiling = 51 − 1.2 = 49.8mm - load_z = 39.6mm → clearance = 49.8 − 39.6 = 10.2mm ≥ 10mm ✓ - -Stress analysis (scaling from v15, 13.05 MPa at 20 MPa allowable): - I_outer = 62.4 × 51³ / 12 = 689,769 mm⁴ (same as v15) - I_inner(t=1.2): (62.4−2.4) × (51−2.4)³ / 12 = 60.0 × 48.6³ / 12 - 48.6³ = 114,791 → 60.0 × 114,791 / 12 = 573,954 mm⁴ - I_net = 689,769 − 573,954 = 115,815 mm⁴ (vs 142,339 mm⁴ in v15) - Stress ratio = 142,339 / 115,815 = 1.229 - FEA predicted = 13.05 × 1.229 ≈ 16.0 MPa (80.2% of 20 MPa) ✓ - -Mass estimate (PETG 1.27e-3 g/mm³): - Arm cross-section v15: 62.4×51 − 59.4×48 = 3182.4 − 2851.2 = 331.2 mm² - Arm cross-section v17: 62.4×51 − 60.0×48.6 = 3182.4 − 2916.0 = 266.4 mm² - Arm savings: (331.2 − 266.4) × 111.2 × 1.27e-3 ≈ 9.14 g - Total: 53.80 − 9.14 ≈ 44.7 g -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v17 (h=51mm, t_wall=min_wall, pub_003_medium).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - - t_wall = min_wall # 1.2mm — minimum allowed wall thickness - plate_t = min_wall - margin = bolt_r + plate_t - - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - # Wide arm: sides at bolt column y-positions (eliminates lateral plate bending) - arm_y0 = float(min(by_coords)) - arm_y1 = float(max(by_coords)) - - # h=51mm: inner ceiling at z=49.8mm (with t_wall=1.2), 10.2mm above load_z=39.6mm ✓ - h = 51.0 - arm_len = lp[0] + 1.0 - - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, arm_y0, 0.0), - gp_Pnt(arm_len, arm_y1, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, arm_y0 + t_wall, t_wall), - gp_Pnt(arm_len, arm_y1 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate) - fuse1.Build() - shape = fuse1.Shape() - - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v17/spec.txt b/agents/pub-bracket-v17/spec.txt deleted file mode 100644 index 08fa354..0000000 --- a/agents/pub-bracket-v17/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_003_medium diff --git a/agents/pub-bracket-v18/agent.py b/agents/pub-bracket-v18/agent.py deleted file mode 100644 index 32ed641..0000000 --- a/agents/pub-bracket-v18/agent.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -pub-bracket-v18: Thinner-wall wide hollow arm for pub_005_medium (PLA, 48 kg @ 120 mm). - -v16 achieved 52.37g at 13.06 MPa (52.2% of 25 MPa allowable) with t_wall=1.5mm. -47.8% stress margin remaining — significant room to thin walls to min_wall (1.2mm). - -Mesh stability check (≥10mm clearance rule): - h=54mm, t_wall=1.2mm → inner ceiling = 54 − 1.2 = 52.8mm - load_z = 42.3mm → clearance = 52.8 − 42.3 = 10.5mm ≥ 10mm ✓ - -Stress analysis (scaling from v16, 13.06 MPa): - I_outer = 54 × 54³ / 12 = 708,588 mm⁴ (same as v16) - I_inner(t=1.2): (54−2.4) × (54−2.4)³ / 12 = 51.6 × 51.6³ / 12 - 51.6³ = 137,389 → 51.6 × 137,389 / 12 = 590,893 mm⁴ - I_net = 708,588 − 590,893 = 117,695 mm⁴ (vs 143,697 mm⁴ in v16) - Stress ratio = 143,697 / 117,695 = 1.221 - FEA predicted = 13.06 × 1.221 ≈ 15.9 MPa (63.7% of 25 MPa PLA) ✓ - -Mass estimate (PLA 1.24e-3 g/mm³): - Arm cross-section v16: 54×54 − 51×51 = 2916 − 2601 = 315 mm² - Arm cross-section v18: 54×54 − 51.6×51.6 = 2916 − 2662.6 = 253.4 mm² - Arm savings: (315 − 253.4) × 120.8 × 1.24e-3 ≈ 9.23 g - Total: 52.37 − 9.23 ≈ 43.1 g -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v18 (h=54mm, t_wall=min_wall, pub_005_medium).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - - t_wall = min_wall # 1.2mm — minimum allowed wall thickness - plate_t = min_wall - margin = bolt_r + plate_t - - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - # Wide arm: sides at bolt columns y=0 and y=54mm (eliminates plate bending) - arm_y0 = float(min(by_coords)) - arm_y1 = float(max(by_coords)) - - # h=54mm: inner ceiling at 52.8mm (t_wall=1.2), 10.5mm above load_z=42.3mm ✓ - h = 54.0 - arm_len = lp[0] + 1.0 - - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, arm_y0, 0.0), - gp_Pnt(arm_len, arm_y1, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, arm_y0 + t_wall, t_wall), - gp_Pnt(arm_len, arm_y1 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate) - fuse1.Build() - shape = fuse1.Shape() - - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v18/spec.txt b/agents/pub-bracket-v18/spec.txt deleted file mode 100644 index c701b54..0000000 --- a/agents/pub-bracket-v18/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_005_medium diff --git a/agents/pub-bracket-v19/agent.py b/agents/pub-bracket-v19/agent.py deleted file mode 100644 index f7b7dde..0000000 --- a/agents/pub-bracket-v19/agent.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -pub-bracket-v19: Thinner-wall hollow arm for pub_001_medium (PLA, 47 kg @ 99 mm). - -v10 achieved 32.98g at 12.34 MPa (49.4% of 25 MPa allowable) with t_wall=1.5mm. -50.6% stress margin remaining — substantial room to thin walls to min_wall (1.2mm). - -Mesh stability check (≥10mm clearance rule): - h = bvz − 5 = 72.3mm, t_wall=1.2mm → inner ceiling = 72.3 − 1.2 = 71.1mm - load_z = 38.6mm → clearance = 71.1 − 38.6 = 32.5mm — very safe ✓ - (arm is tall relative to load point; no mesh risk) - -Stress analysis (scaling from v10 FEA, 12.34 MPa at 25 MPa allowable): - I_outer = 8 × 72.3³ / 12 = 252,203 mm⁴ (unchanged) - I_inner(t=1.2): (8−2.4) × (72.3−2.4)³ / 12 = 5.6 × 69.9³ / 12 - 69.9³ = 341,732 → 5.6 × 341,732 / 12 = 159,474 mm⁴ - I_net = 252,203 − 159,474 = 92,729 mm⁴ (vs 113,405 mm⁴ in v10) - Stress ratio = 113,405 / 92,729 = 1.223 - FEA predicted = 12.34 × 1.223 ≈ 15.1 MPa (60.4% of 25 MPa PLA) ✓ - -Mass estimate (PLA 1.24e-3 g/mm³): - Arm cross-section v10: 8×72.3 − 5×69.3 = 578.4 − 346.5 = 231.9 mm² - Arm cross-section v19: 8×72.3 − 5.6×69.9 = 578.4 − 391.4 = 187.0 mm² - Arm savings: (231.9 − 187.0) × 91.3 × 1.24e-3 ≈ 5.08 g - Total: 32.98 − 5.08 ≈ 27.9 g -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v19 (t_wall=min_wall hollow arm, pub_001_medium).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolts = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - bv = c["build_volume_mm"] - - lx, ly, lz = lp[0], lp[1], lp[2] - bvz = bv[2] - - by_coords = [p[0] for p in bolts] - bz_coords = [p[1] for p in bolts] - bolt_r = bolt_d / 2.0 - - # Mounting plate margins (sliver-safe, same as v10) - bolt_y_span = max(by_coords) - min(by_coords) - bolt_z_span = max(bz_coords) - min(bz_coords) - bvy, _ = bv[1], bv[0] - y_half_avail = (bvy - bolt_y_span) / 2.0 - 0.5 - z_half_avail = (bvz - bolt_z_span) / 2.0 - 0.5 - ideal_margin = bolt_r + min_wall - margin_y = min(ideal_margin, max(0.0, y_half_avail)) - margin_z = min(ideal_margin, max(0.0, z_half_avail)) - MIN_SLIVER = 0.5 - if 0.0 < margin_y - bolt_r < MIN_SLIVER: - margin_y = bolt_r - MIN_SLIVER - if 0.0 < margin_z - bolt_r < MIN_SLIVER: - margin_z = bolt_r - MIN_SLIVER - - plate_t = min_wall - plate_y0 = min(by_coords) - margin_y - plate_y1 = max(by_coords) + margin_y - plate_z0 = min(bz_coords) - margin_z - plate_z1 = max(bz_coords) + margin_z - - # Arm: fw=8mm hollow, t_wall=min_wall (1.2mm) - fw = 8.0 - t_wall = min_wall # 1.2mm — minimum allowed - h = bvz - 5.0 - arm_len = min(max(lx - 8.0, lx * 0.92), bv[0] - 2.0) - yc = ly - - outer = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, yc - fw / 2, 0.0), - gp_Pnt(arm_len, yc + fw / 2, h), - ).Shape() - - # Inner void: open at tip, starts at plate_t in x, t_wall inset on y and z - inner = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, yc - fw / 2 + t_wall, t_wall), - gp_Pnt(arm_len, yc + fw / 2 - t_wall, h - t_wall), - ).Shape() - - arm = BRepAlgoAPI_Cut(outer, inner).Shape() - - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - body = BRepAlgoAPI_Fuse(arm, plate).Shape() - - for by, bz in bolts: - ax = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1, 0, 0)) - hole = BRepPrimAPI_MakeCylinder(ax, bolt_r, plate_t + 2.0).Shape() - body = BRepAlgoAPI_Cut(body, hole).Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(body, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v19/spec.txt b/agents/pub-bracket-v19/spec.txt deleted file mode 100644 index 67f0312..0000000 --- a/agents/pub-bracket-v19/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_001_medium diff --git a/agents/pub-bracket-v20/agent.py b/agents/pub-bracket-v20/agent.py deleted file mode 100644 index 28b9799..0000000 --- a/agents/pub-bracket-v20/agent.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -pub-bracket-v20: Grid frame plate for pub_001_medium (PLA, 47 kg @ 99 mm). - -v19 achieved 27.956g at 15.1 MPa (60.4% of 25 MPa allowable). The mounting -plate is a solid 1.2mm sheet. pub_001 has a 3-column × 2-row bolt pattern -(y=0, 42.8, 85.6 and z=0, 42.8) — a grid frame plate removes the two interior -voids between the column/row strips, cutting ~3.3g from the plate. - -pub_001_medium parameters: - load_point: [99.3, 46.9, 38.6] - bolt_pattern: 6 bolts at y∈{0,42.8,85.6}, z∈{0,42.8} - bolt_d: 7.0mm → bolt_r = 3.5mm - min_wall: 1.2mm - build_vol: [134.1, 93.7, 77.3] - material: PLA, yield=25 MPa - -Grid frame strip layout (strip_w = bolt_r + min_wall = 4.7mm): - Column strips centered on y=0, 42.8, 85.6 — each ±4.7mm - Row strips centered on z=0, 42.8 — each ±4.7mm - Interior gaps between columns: y=[4.7..38.1] and y=[47.5..80.9] - Interior gap between rows: z=[4.7..38.1] - - 2 rectangular voids cut from plate: - Void 1: y=[4.7..38.1], z=[4.7..38.1] → 33.4 × 33.4 = 1116 mm² - Void 2: y=[47.5..80.9], z=[4.7..38.1] → 33.4 × 33.4 = 1116 mm² - -Mass analysis (PLA 1.24e-3 g/mm³, plate_t = 1.2mm): - Solid plate ≈ 4959 mm² - Total void area = 2 × 1116 = 2231 mm² - Mass removed = 2231 × 1.2 × 1.24e-3 ≈ 3.32g - Target: 27.956 - 3.32 ≈ 24.6g - -Arm: identical to v19 (fw=8mm, h=72.3mm, t_wall=1.2mm). - Arm at y=42.9..50.9mm overlaps middle column strip y=38.1..47.5mm → 4.6mm ✓ - Arm height 72.3mm spans both bolt rows (z=0 and z=42.8mm, strip ends z=47.5mm) ✓ - Both row strips and all three column strips intact — all 6 bolt holes connected ✓ - -Stress: arm geometry unchanged → predicted ~15.1 MPa (60.4% of 25 MPa) ✓ -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v20 (grid frame plate, pub_001_medium).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolts = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - bv = c["build_volume_mm"] - - lx, ly, lz = lp[0], lp[1], lp[2] - bvz = bv[2] - - by_cols = sorted(set(p[0] for p in bolts)) # unique y column positions - bz_rows = sorted(set(p[1] for p in bolts)) # unique z row positions - bolt_r = bolt_d / 2.0 - strip_w = bolt_r + min_wall # 4.7mm for pub_001 - - # Sliver-safe plate margins (identical to v19) - bolt_y_span = max(by_cols) - min(by_cols) - bolt_z_span = max(bz_rows) - min(bz_rows) - bvy = bv[1] - y_half_avail = (bvy - bolt_y_span) / 2.0 - 0.5 - z_half_avail = (bvz - bolt_z_span) / 2.0 - 0.5 - margin_y = min(strip_w, max(0.0, y_half_avail)) - margin_z = min(strip_w, max(0.0, z_half_avail)) - MIN_SLIVER = 0.5 - if 0.0 < margin_y - bolt_r < MIN_SLIVER: - margin_y = bolt_r - MIN_SLIVER - if 0.0 < margin_z - bolt_r < MIN_SLIVER: - margin_z = bolt_r - MIN_SLIVER - - plate_t = min_wall - plate_y0 = min(by_cols) - margin_y - plate_y1 = max(by_cols) + margin_y - plate_z0 = min(bz_rows) - margin_z - plate_z1 = max(bz_rows) + margin_z - - # Build grid frame plate: cut interior voids between each col/row strip pair - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - # Column gap edges: right edge of left strip → left edge of right strip - col_gap_starts = [y + strip_w for y in by_cols[:-1]] - col_gap_ends = [y - strip_w for y in by_cols[1:]] - row_gap_starts = [z + strip_w for z in bz_rows[:-1]] - row_gap_ends = [z - strip_w for z in bz_rows[1:]] - - for y0, y1 in zip(col_gap_starts, col_gap_ends): - for z0, z1 in zip(row_gap_starts, row_gap_ends): - if y0 < y1 and z0 < z1: - void = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y0, z0), - gp_Pnt(plate_t, y1, z1), - ).Shape() - cut = BRepAlgoAPI_Cut(plate, void) - cut.Build() - plate = cut.Shape() - - # Arm: identical to v19 — fw=8mm hollow, t_wall=min_wall, h=bvz-5 - fw = 8.0 - t_wall = min_wall - h = bvz - 5.0 - arm_len = min(max(lx - 8.0, lx * 0.92), bv[0] - 2.0) - yc = ly - - outer = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, yc - fw / 2, 0.0), - gp_Pnt(arm_len, yc + fw / 2, h), - ).Shape() - inner = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, yc - fw / 2 + t_wall, t_wall), - gp_Pnt(arm_len, yc + fw / 2 - t_wall, h - t_wall), - ).Shape() - arm = BRepAlgoAPI_Cut(outer, inner).Shape() - - body = BRepAlgoAPI_Fuse(arm, plate).Shape() - - for by, bz in bolts: - ax = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1, 0, 0)) - hole = BRepPrimAPI_MakeCylinder(ax, bolt_r, plate_t + 2.0).Shape() - body = BRepAlgoAPI_Cut(body, hole).Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(body, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v20/spec.txt b/agents/pub-bracket-v20/spec.txt deleted file mode 100644 index 30cda4b..0000000 --- a/agents/pub-bracket-v20/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_001_medium \ No newline at end of file diff --git a/agents/pub-bracket-v4/agent.py b/agents/pub-bracket-v4/agent.py deleted file mode 100644 index 18ea05c..0000000 --- a/agents/pub-bracket-v4/agent.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -pub-bracket-v4: Parametric hollow-box cantilever bracket for pub_004_medium. - -Target: PLA (FDM), 31 kg load at ~83 mm arm — shorter arm, taller build volume. -Design: hollow rectangular arm + mounting plate, all dims from spec constraints. - - Uses tall build volume to maximise z height of arm (moment of inertia) - - Arm width: 3 × min_wall (minimal hollow section) - - Arm length: 90% of load_x (shorter arm → lower bending moment) - - Plate: encloses bolt grid with wall clearance, respects build volume bounds -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v4 (hollow cantilever arm, PLA).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bp = c["bolt_pattern_mm"] - bd = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - mw = c.get("min_wall_thickness_mm", 1.2) - bv = c["build_volume_mm"] - - lx, ly, lz = lp - bvx, bvy, bvz = bv - r = bd / 2.0 - mg = r + mw # bolt-edge margin - - # Plate extents - pt = mw - py0_r = min(p[0] for p in bp) - mg - py1_r = max(p[0] for p in bp) + mg - pz0_r = min(p[1] for p in bp) - mg - pz1_r = max(p[1] for p in bp) + mg - - dy = py1_r - py0_r - if dy > bvy - 0.5: - sh = (dy - (bvy - 0.5)) / 2.0 - py0, py1 = py0_r + sh, py1_r - sh - else: - py0, py1 = py0_r, py1_r - - dz = pz1_r - pz0_r - if dz > bvz - 0.5: - sh = (dz - (bvz - 0.5)) / 2.0 - pz0, pz1 = pz0_r + sh, pz1_r - sh - else: - pz0, pz1 = pz0_r, pz1_r - - # Hollow arm - aw = 3.0 * mw # arm y-width - - # Height: use as much of build volume z as possible to maximise I_z - h_need = lz + 15.0 + mw - h_want = bvz * 0.75 - h = min(bvz - 5.0, max(h_want, h_need)) - - # Length: 90% of load distance (short arm = lower stress) - al = min(lx * 0.90, bvx - 2.0) - # But ensure we're within 15 mm of load point - al = max(al, lx - 15.0) - al = min(al, bvx - 2.0) - - yc = ly - - outer = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, yc - aw / 2, 0.0), - gp_Pnt(al, yc + aw / 2, h), - ).Shape() - - inner = BRepPrimAPI_MakeBox( - gp_Pnt(pt, yc - aw / 2 + mw, mw), - gp_Pnt(al, yc + aw / 2 - mw, h - mw), - ).Shape() - - arm = BRepAlgoAPI_Cut(outer, inner).Shape() - - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, py0, pz0), - gp_Pnt(pt, py1, pz1), - ).Shape() - - body = BRepAlgoAPI_Fuse(arm, plate).Shape() - - for by, bz in bp: - ax = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1, 0, 0)) - cyl = BRepPrimAPI_MakeCylinder(ax, r, pt + 2.0).Shape() - body = BRepAlgoAPI_Cut(body, cyl).Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(body, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as tf: - tmp = tf.name - try: - writer.Write(tmp) - return open(tmp, "rb").read() - finally: - os.unlink(tmp) diff --git a/agents/pub-bracket-v4/spec.txt b/agents/pub-bracket-v4/spec.txt deleted file mode 100644 index 9142844..0000000 --- a/agents/pub-bracket-v4/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_004_medium \ No newline at end of file diff --git a/agents/pub-bracket-v5/agent.py b/agents/pub-bracket-v5/agent.py deleted file mode 100644 index bc7e82e..0000000 --- a/agents/pub-bracket-v5/agent.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -pub-bracket-v5: Solid cantilever arm bracket for pub_005_medium (PLA, long arm). - -All geometry parameters read from spec["constraints"] at runtime. - -Design: - - Solid arm 8 mm wide (same as other pub brackets for consistency). - Previous 3.6mm hollow arm failed: FEA stress 52 MPa vs PLA allowable 25 MPa. - With fw=8mm solid and maximum height the bending stress drops to ~21 MPa. - - h = bvz - 5 mm (maximum height — long arm needs tall section for stiffness). - - arm_len: tip within 8 mm of load_x for reliable load-node detection. - - Plate: bolt_r margin, sliver protection. -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v5 (solid 8 mm arm, PLA long arm).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolts = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - bv = c["build_volume_mm"] - - lx, ly, lz = lp[0], lp[1], lp[2] - bvx, bvy, bvz = bv[0], bv[1], bv[2] - - by_coords = [p[0] for p in bolts] - bz_coords = [p[1] for p in bolts] - bolt_r = bolt_d / 2.0 - - # Plate margin — sliver protection - bolt_y_span = max(by_coords) - min(by_coords) - bolt_z_span = max(bz_coords) - min(bz_coords) - y_half_avail = (bvy - bolt_y_span) / 2.0 - 0.5 - z_half_avail = (bvz - bolt_z_span) / 2.0 - 0.5 - ideal_margin = bolt_r + min_wall - margin_y = min(ideal_margin, max(0.0, y_half_avail)) - margin_z = min(ideal_margin, max(0.0, z_half_avail)) - MIN_SLIVER = 0.5 - if 0.0 < margin_y - bolt_r < MIN_SLIVER: - margin_y = bolt_r - MIN_SLIVER - if 0.0 < margin_z - bolt_r < MIN_SLIVER: - margin_z = bolt_r - MIN_SLIVER - - plate_t = min_wall - plate_y0 = min(by_coords) - margin_y - plate_y1 = max(by_coords) + margin_y - plate_z0 = min(bz_coords) - margin_z - plate_z1 = max(bz_coords) + margin_z - - # Solid 8 mm arm at maximum build-volume height - # PLA allowable = 25 MPa; theory at 8mm/bvz gives ~21 MPa FEA stress - fw = 8.0 - h = min(bvz - 5.0, max(bvz * 0.75, lz + 15.0 + min_wall)) - - # Arm tip 8 mm from load point → load nodes in detection zone - arm_len = min(max(lx - 8.0, lx * 0.92), bvx - 2.0) - yc = ly - - arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, yc - fw / 2, 0.0), - gp_Pnt(arm_len, yc + fw / 2, h), - ).Shape() - - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - body = BRepAlgoAPI_Fuse(arm, plate).Shape() - - for by, bz in bolts: - ax = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1, 0, 0)) - cyl = BRepPrimAPI_MakeCylinder(ax, bolt_r, plate_t + 2.0).Shape() - body = BRepAlgoAPI_Cut(body, cyl).Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(body, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - tmp = f.name - try: - writer.Write(tmp) - return open(tmp, "rb").read() - finally: - os.unlink(tmp) diff --git a/agents/pub-bracket-v5/spec.txt b/agents/pub-bracket-v5/spec.txt deleted file mode 100644 index 5aa4313..0000000 --- a/agents/pub-bracket-v5/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_005_medium \ No newline at end of file diff --git a/agents/pub-bracket-v6/agent.py b/agents/pub-bracket-v6/agent.py deleted file mode 100644 index 2a865f3..0000000 --- a/agents/pub-bracket-v6/agent.py +++ /dev/null @@ -1,118 +0,0 @@ -""" -pub-bracket-v6: Hollow-box arm for pub_002_medium (Al6061, 32 kg @ 126 mm). - -Adapts the al-bracket-v19 hollow-box topology to pub_002's geometry. - -pub_002_medium parameters: - load_point: [125.6, 43.4, 44.5] - bolt_pattern: [(0,0),(45.9,0),(0,45.9),(45.9,45.9)] — 46mm span - bolt_d: 6.5mm → bolt_r = 3.25mm - min_wall: 1.2mm - build_vol: [144.2, 86.8, 89.1] - material: Al6061-T6, yield=276 MPa, allowable=138 MPa (SF=2.0) - load: 317.08 N × 2.0 SF = 634.16 N effective - -Bending moment at root: M = 634.16 × 125.6 = 79,642 N·mm - (3.8× lower than spec-002 — massive stress headroom) - -Design (same topology as al-bracket-v19): - fw = 4mm (min_wall*2 + 1.6mm inner void — proportional to spec-002's 3mm/0.8mm) - t_wall = min_wall = 1.2mm - h = 31mm |31 - 44.5| = 13.5mm < 15mm tolerance ✓ - arm_len = lp[0] + 1.0 = 126.6mm (1mm past load — FEA nodes guaranteed) - plate_t = min_wall = 1.2mm - -Mass estimate (density 2.71e-3 g/mm³): - Arm outer: 4 × 31 × 126.6 = 15,697 mm³ - Arm inner: 1.6 × 28.6 × 125.4 = 5,731 mm³ (void starts at x=plate_t) - Arm net: 9,966 mm³ → 27.0g - Plate: 1.2 × 54.8 × 54.8 = 3,602 mm³ - Holes: 4 × π × 3.25² × 3.2 = 424 mm³ - Plate net: 3,178 mm³ → 8.6g - Total: ~35.6g (−81% vs 188.95g baseline) - -Stress estimate: ~76.7 MPa × (79,642 / 304,110) × (fw/3) scaling ≈ ~20 MPa (14.5% of 138 MPa) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v6 (hollow-box arm, Al6061 pub_002).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + min_wall - - plate_t = min_wall - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] + 1.0 - fw = 4.0 - h = 31.0 # |31 - lp[2]| = |31 - 44.5| = 13.5mm < 15mm ✓ - t_wall = min_wall - y_center = lp[1] - - # Hollow box arm: outer shell minus interior void - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - fw / 2, 0.0), - gp_Pnt(arm_len, y_center + fw / 2, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, y_center - fw / 2 + t_wall, t_wall), - gp_Pnt(arm_len, y_center + fw / 2 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - # Mounting plate - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate) - fuse1.Build() - shape = fuse1.Shape() - - # Bolt holes - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v6/spec.txt b/agents/pub-bracket-v6/spec.txt deleted file mode 100644 index 96361a6..0000000 --- a/agents/pub-bracket-v6/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_002_medium diff --git a/agents/pub-bracket-v7/agent.py b/agents/pub-bracket-v7/agent.py deleted file mode 100644 index ed79bb0..0000000 --- a/agents/pub-bracket-v7/agent.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -pub-bracket-v7: Wide hollow-box arm for pub_003_medium (PETG, 53 kg @ 110 mm). - -Root cause of v3 failure: arm at y=43.6mm offsets from both bolt columns (y=0, -y=62.4mm), so all load transfers through the plate in bending. Wider arm made it -worse (more bending moment arm). Fix: arm spans full bolt_y_span — sides land -directly on bolt columns, eliminating plate bending. - -Design: - fw = bolt_y_span = 62.4mm (arm spans y=0..62.4mm, sides ARE the bolt columns) - t_wall = 1.6mm (thicker than min_wall for PETG stress — computed below) - h = 53mm |53 - lp[2]| = |53 - 39.6| = 13.4mm < 15mm ✓ - arm_len = lp[0] + 1.0 = 111.2mm - -Stress (bending about y-axis, load in -z): - M = 517.31 × 2.0 × 110.2 = 114,015 N·mm - I_outer = 62.4 × 53³/12 = 775,019 mm⁴ - I_inner = 59.2 × 49.8³/12 = 609,005 mm⁴ - I_net = 166,014 mm⁴, c = 26.5mm - σ = 114,015 × 26.5 / 166,014 = 18.2 MPa < 20 MPa allowable ✓ - -Mass estimate (PETG 1.27e-3 g/mm³): - Arm outer: 62.4 × 53 × 111.2 = 367,737 mm³ - Arm inner: 59.2 × 49.8 × 109.6 = 323,132 mm³ (void starts at x=plate_t) - Arm net: 44,605 mm³ → 56.6g - Plate net: ~9.8g - Total: ~66g (vs baseline 216.4g, −69%) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for pub-bracket-v7 (wide hollow box, PETG pub_003).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - - t_wall = 1.6 # thicker walls: PETG needs more section modulus than Al - plate_t = min_wall # 1.2mm - margin = bolt_r + plate_t - - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_y0 = float(min(by_coords)) - arm_y1 = float(max(by_coords)) - h = 53.0 # |53 - 39.6| = 13.4mm < 15mm load tolerance ✓ - arm_len = lp[0] + 1.0 - - # Wide hollow box: sides at y=arm_y0 and y=arm_y1 (the bolt column lines) - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, arm_y0, 0.0), - gp_Pnt(arm_len, arm_y1, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, arm_y0 + t_wall, t_wall), - gp_Pnt(arm_len, arm_y1 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - # Mounting plate (extends beyond arm to cover outer bolt margins) - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate) - fuse1.Build() - shape = fuse1.Shape() - - # Bolt holes - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/pub-bracket-v7/spec.txt b/agents/pub-bracket-v7/spec.txt deleted file mode 100644 index 08fa354..0000000 --- a/agents/pub-bracket-v7/spec.txt +++ /dev/null @@ -1 +0,0 @@ -pub_003_medium diff --git a/agents/razor-i-beam/agent.py b/agents/razor-i-beam/agent.py deleted file mode 100644 index a1a59db..0000000 --- a/agents/razor-i-beam/agent.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -Razor-I-beam bracket: I-section cantilever. - -Design: web (1.5 mm wide × 90 mm tall) with top and bottom flanges -(10 mm × 1.2 mm each), centered on the load axis y = 25 mm. -No floor plate beyond the back plate — the I-section self-supports. - -Why an I-beam beats the T-section (lean-beam PR): - A T-section achieves high I by placing a wide flange at one extreme - fibre, far from the neutral axis. An I-beam does the same on both - sides simultaneously, but the flanges can be much narrower because - neither is carrying the job alone. Result: same I, half the flange mass. - -Bending analysis at x = 0 (critical section, bending about Y axis): - M = F × L = 392.4 × 100 = 39 240 N·mm - Web: 1.5 mm × 87.6 mm (between flanges), centroid at z = 45 mm - Flanges: 10 mm × 1.2 mm each, at z = 0–1.2 and z = 88.8–90 mm - - I_y: - Web alone = 1.5 × 87.6³ / 12 = 83 826 mm⁴ - Bot flange = (10×1.2³/12) + 10×1.2×(44.4)² = 1.44 + 23 659 = 23 660 mm⁴ - Top flange = (10×1.2³/12) + 10×1.2×(44.4)² = 23 660 mm⁴ - I_total = 131 146 mm⁴ - - c = 45.0 mm (to outer flange face) - S_y = 131 146 / 45 = 2 914 mm³ - σ_max = 39 240 / 2 914 = 13.5 MPa < 25 MPa ✓ (SF = 3.7 on PLA yield) - - Mesh convergence gate (PR #10): 13.5 MPa < 70% × 25 = 17.5 MPa → not triggered. - -Volume estimate: - Web 115 × 1.5 × 90 = 15 525 mm³ - Bot flange 115 × 10 × 1.2 = 1 380 mm³ - Top flange 115 × 10 × 1.2 = 1 380 mm³ - Back plate 3 × 80 × 80 = 19 200 mm³ - Bolt holes (×4) ≈ −664 mm³ - Overlaps (plate∩web, etc.) ≈ −620 mm³ - net ≈ 36 201 mm³ × 1.24 × 10⁻³ g/mm³ ≈ 44.9 g (−59% vs SOTA, −31% vs lean-beam) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a razor-I-beam wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - # Back plate covers all four bolt holes with 10 mm margin per side. - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - plate_y = max(by_coords) + 20.0 # 80 mm - plate_z = max(bz_coords) + 20.0 # 80 mm - plate_t = 3.0 # minimum safe thickness for bolt transfer - - shelf_len = lp[0] + 15.0 # 115 mm — 15 mm past the load point - - # I-beam geometry — centered on the load y-coordinate. - web_w = 1.5 # web thickness (25 % over 1.2 mm min) - flange_w = 10.0 # flange width in Y - flange_t = 1.2 # flange thickness in Z (minimum wall) - total_h = 90.0 # total I-beam height in Z - - y_center = lp[1] # 25 mm — center on load axis - web_y0 = y_center - web_w / 2 # 24.25 - web_y1 = y_center + web_w / 2 # 25.75 - flange_y0 = y_center - flange_w / 2 # 20.0 - flange_y1 = y_center + flange_w / 2 # 30.0 - - # --- Mounting plate (x = 0 to plate_t) --- - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(plate_t, plate_y, plate_z), - ).Shape() - - # --- Web: thin vertical rectangle running the full arm length --- - web = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, web_y0, 0.0), - gp_Pnt(shelf_len, web_y1, total_h), - ).Shape() - - # --- Bottom flange: horizontal cap at z = 0 --- - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, flange_y0, 0.0), - gp_Pnt(shelf_len, flange_y1, flange_t), - ).Shape() - - # --- Top flange: horizontal cap at z = total_h - flange_t --- - top_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, flange_y0, total_h - flange_t), - gp_Pnt(shelf_len, flange_y1, total_h), - ).Shape() - - # Fuse all components into one solid. - body = BRepAlgoAPI_Fuse(plate, web) - body.Build() - body = BRepAlgoAPI_Fuse(body.Shape(), bot_flange) - body.Build() - body = BRepAlgoAPI_Fuse(body.Shape(), top_flange) - body.Build() - shape = body.Shape() - - # Cut bolt holes through the mounting plate. - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_d / 2.0, plate_t + 2.0).Shape() - cut = BRepAlgoAPI_Cut(shape, hole) - cut.Build() - shape = cut.Shape() - - # Write STEP. - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/ribbed/agent.py b/agents/ribbed/agent.py deleted file mode 100644 index bfc94ac..0000000 --- a/agents/ribbed/agent.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Ribbed bracket agent — first structural optimization over the naive baseline. - -Design rationale: - The naive L-bracket uses a solid 115×80×12mm shelf slab (~110k mm³). - For a 40 kg cantilever load at 100mm reach, the dominant mode is bending - about the horizontal axis at the wall. An I-beam/ribbed cross-section carries - that bending with far less material than a solid slab. - - This design uses: - 1. A thin bottom flange (5mm) spanning the full bracket width — connects - the mounting plate to the load tip and provides a flat print base. - 2. A central load rib (12mm wide × 35mm tall) running wall-to-tip, centered - on the load point (y=25, z=0..35). The rib keeps max bending stress - well below the 25 MPa allowable (yield/SF=50/2). - 3. Mounting plate reduced from 10mm to 7mm. - - Hand check (wall section, x=0): - M = 392.4 N × 100 mm = 39 240 N·mm - I_rib = 12 × 35³ / 12 = 42 875 mm⁴ - σ_max = M × (35/2) / I_rib = 39 240 × 17.5 / 42 875 ≈ 16 MPa < 25 MPa ✓ - - Expected mass (vs baseline ~202 g): - Plate 7×80×80 = 44 800 mm³ - Flange 115×80×5 = 46 000 mm³ (overlap with plate: −7×80×5 = −2 800) - Rib 115×12×35 = 48 300 mm³ (overlap with flange: −115×12×5 = −6 900; - overlap with plate: −7×12×35 = −2 940; - plate+flange corner: +7×12×5 = +420) - Bolt holes ×4 = −1 550 mm³ - Net ≈ 125 330 mm³ → 155 g (−47 g vs baseline) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a ribbed wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - plate_y = max(by_coords) + 20.0 # 80 mm - plate_z = max(bz_coords) + 20.0 # 80 mm - plate_thickness = 7.0 - - shelf_length = lp[0] + 15.0 # 115 mm - - # 1. Mounting plate (back wall) - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(plate_thickness, plate_y, plate_z), - ).Shape() - - # 2. Bottom flange — thin horizontal slab, full width, full reach - flange_h = 5.0 - flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(shelf_length, plate_y, flange_h), - ).Shape() - - # 3. Central load rib — tall and narrow, centered on load point y=25 - rib_w = 12.0 - rib_h = lp[2] + 10.0 # 35 mm (load point z=25 + 10 mm headroom) - rib_y0 = lp[1] - rib_w / 2.0 # 19 mm - rib_y1 = lp[1] + rib_w / 2.0 # 31 mm - rib = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, rib_y0, 0.0), - gp_Pnt(shelf_length, rib_y1, rib_h), - ).Shape() - - # Fuse plate + flange + rib - body = BRepAlgoAPI_Fuse(plate, flange) - body.Build() - body = BRepAlgoAPI_Fuse(body.Shape(), rib) - body.Build() - shape = body.Shape() - - # Cut bolt holes through mounting plate - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_d / 2.0, plate_thickness + 2.0).Shape() - cut = BRepAlgoAPI_Cut(shape, hole) - cut.Build() - shape = cut.Shape() - - # Write STEP - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/slim-spine/agent.py b/agents/slim-spine/agent.py deleted file mode 100644 index 936e652..0000000 --- a/agents/slim-spine/agent.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Slim-spine bracket agent — tall thin web for maximum bending efficiency. - -Design rationale: - For a cantilever loaded in -Z at x=100mm, the critical section at the wall - (x=0) carries M = F × L = 392.4 × 100 = 39,240 N·mm. - - Bending section modulus: S = I / c = (b × h³ / 12) / (h/2) = b × h² / 6. - Since S scales with h², doubling height halves the required width. - - The ribbed agent uses a 12mm × 35mm web → S = 12 × 35² / 6 = 2,450 mm³. - This agent uses a 4mm × 70mm web → S = 4 × 70² / 6 = 3,267 mm³ (+33% stronger). - - Same strength, but far less material: - Old: 12 × 35 = 420 mm² cross-section area - New: 4 × 70 = 280 mm² cross-section area (−33%) - - Stress check (wall section, x=0): - I = 4 × 70³ / 12 = 114,333 mm⁴ - σ_max = M × c / I = 39,240 × 35 / 114,333 ≈ 12.0 MPa < 25 MPa ✓ (SF≈4.2) - - Estimated mass vs ribbed (~155 g): - Plate 5 × 80 × 80 = 32,000 mm³ - Flange 115 × 80 × 3 = 27,600 mm³ (overlap with plate: −1,200) - Spine 115 × 4 × 70 = 32,200 mm³ (overlap with flange: −1,380; - overlap with plate: −1,400) - Bolt holes ×4 ≈ − 663 mm³ - Net ≈ 87,157 mm³ → 108 g (−47 g vs ribbed, −23%) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a slim-spine wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - plate_y = max(by_coords) + 20.0 # 80 mm - plate_z = max(bz_coords) + 20.0 # 80 mm - plate_thickness = 5.0 - - shelf_length = lp[0] + 15.0 # 115 mm - - # --- Mounting plate (at wall face, provides all 4 bolt holes) --- - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(plate_thickness, plate_y, plate_z), - ).Shape() - - # --- Bottom flange: thin slab connecting z=0 bolt row to spine base --- - flange_h = 3.0 - flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(shelf_length, plate_y, flange_h), - ).Shape() - - # --- Spine: tall narrow web centered on the load point (y=25) --- - # Tall height → high second moment of area, allowing very thin width. - # z=0 to z=70mm (limited by build volume z=100, with margin for the plate z=80). - spine_w = 4.0 - spine_h = 70.0 - spine_y0 = lp[1] - spine_w / 2.0 # 23 mm - spine_y1 = lp[1] + spine_w / 2.0 # 27 mm - spine = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, spine_y0, 0.0), - gp_Pnt(shelf_length, spine_y1, spine_h), - ).Shape() - - # Fuse all three components - body = BRepAlgoAPI_Fuse(plate, flange) - body.Build() - body = BRepAlgoAPI_Fuse(body.Shape(), spine) - body.Build() - shape = body.Shape() - - # Cut bolt holes through the mounting plate - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_d / 2.0, plate_thickness + 2.0).Shape() - cut = BRepAlgoAPI_Cut(shape, hole) - cut.Build() - shape = cut.Shape() - - # Write STEP - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/spar-web/agent.py b/agents/spar-web/agent.py deleted file mode 100644 index 0230c66..0000000 --- a/agents/spar-web/agent.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Spar-web bracket agent — three structural improvements over slim-spine. - -Design rationale: - slim-spine achieved 108.48 g by using a tall, thin spine (4mm × 70mm) instead - of a wide rib. This agent pushes further on all three load-bearing members: - - 1. Mounting plate: 3mm thick (was 5mm). - Bolt holes are 6.5mm diameter through a 3mm face. The plate sees bolt - preload but near-zero bending — there is no structural reason for it to be - 5mm. Minimum allowed wall is 1.2mm; 3mm gives ample clearance hole depth. - - 2. Bottom rail: 1.5mm thick (was 3mm full-width flange). - The flange carries compressive load at the bottom of the cantilever - I-section. At 1.5mm it still satisfies the 1.2mm minimum wall constraint - and retains the bottom-chord function of the beam while removing 60% of - the flange volume. - - 3. Spine: 3mm wide × 85mm tall (was 4mm × 70mm). - Making the spine taller increases the second moment of area faster than - width (I ∝ h³ vs I ∝ b). Going from 4×70 to 3×85 improves I by 34% while - using less material. The build volume allows z=100mm; 85mm keeps the spine - inside the plate height (80mm) with a 5mm cap for FEA node coverage. - - Stress check at x=0 (critical section, spine only): - M = 392.4 N × 100 mm = 39,240 N·mm - I = (3 × 85³) / 12 = 153,063 mm⁴ - c = 42.5 mm - σ_max = M × c / I = 39,240 × 42.5 / 153,063 ≈ 10.9 MPa < 25 MPa ✓ - Safety factor on allowable ≈ 4.6× - - Mass estimate: - Plate 3 × 80 × 80 = 19,200 mm³ - Rail 115 × 80 × 1.5 = 13,800 mm³ (plate overlap: −360) - Spine 115 × 3 × 85 = 29,325 mm³ (rail overlap: −517; - plate overlap: −765) - Bolt holes ×4 ≈ −400 mm³ - Net ≈ 60,283 mm³ → 74.8 g (−33.7 g vs slim-spine, −31%) -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a spar-web wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - plate_y = max(by_coords) + 20.0 # 80 mm - plate_z = max(bz_coords) + 20.0 # 80 mm - - plate_thickness = 3.0 # thinner than slim-spine (was 5mm) - shelf_length = lp[0] + 15.0 # 115 mm: reaches past load point - - # --- Mounting plate at wall face — carries bolt preload only --- - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(plate_thickness, plate_y, plate_z), - ).Shape() - - # --- Bottom rail: ultra-thin flange, full width --- - # Acts as the compression chord of the cantilever I-section. - # 1.5mm is above the 1.2mm minimum wall constraint. - rail_h = 1.5 - rail = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(shelf_length, plate_y, rail_h), - ).Shape() - - # --- Spar web: taller and narrower than slim-spine --- - # 3mm wide × 85mm tall centered on the load point y-coordinate. - # Height chosen to maximise I within the 80mm plate height; capped at 85mm - # so the spine sits fully within the plate's z extent (with 5mm headroom at top). - spine_w = 3.0 - spine_h = 85.0 - spine_y0 = lp[1] - spine_w / 2.0 # 23.5 mm - spine_y1 = lp[1] + spine_w / 2.0 # 26.5 mm - spine = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, spine_y0, 0.0), - gp_Pnt(shelf_length, spine_y1, spine_h), - ).Shape() - - # Fuse all three members - body = BRepAlgoAPI_Fuse(plate, rail) - body.Build() - body = BRepAlgoAPI_Fuse(body.Shape(), spine) - body.Build() - shape = body.Shape() - - # Cut bolt clearance holes through mounting plate - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder( - axis, bolt_d / 2.0, plate_thickness + 2.0 - ).Shape() - cut = BRepAlgoAPI_Cut(shape, hole) - cut.Build() - shape = cut.Shape() - - # Write STEP - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/ss-bracket-v10/agent.py b/agents/ss-bracket-v10/agent.py deleted file mode 100644 index 296be43..0000000 --- a/agents/ss-bracket-v10/agent.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -SS-bracket v10: reduce fw to 2mm (minimum viable hollow box at t_wall=0.5mm). - -Baseline: ss-bracket-v9 (PR #103): fw=6mm, h=53mm, t_wall=0.5mm, arm_len=136mm. - 92.51g, 28.03 MPa (34.2% of 82 MPa allowable). Stress headroom: 66%. - -Changes vs v9: - fw: 6mm → 2mm - -At fw=2, t_wall=0.5mm: - inner y void: fw - 2×t_wall = 2 - 1 = 1mm (valid, though sub-mesh-size at 4mm mesh) - inner z void: h - 2×t_wall = 52mm (fine) - FEA effect: coarse mesh (4mm) likely treats 1mm void as solid → even lower stress ✓ - -Mass dominated by left/right walls (0.5mm × 52mm × 135.5mm × 2 = 7,046mm³): - These don't change with fw. Only top/bottom walls scale with fw: - fw=6: top+bottom = 2×(6×0.5×136) = 816mm³ - fw=2: top+bottom = 2×(2×0.5×136) = 272mm³ - Savings from fw reduction: (816-272) = 544mm³ × 7.99e-3 ≈ 4.3g - -Total target: 92.51 - 5.2 ≈ 87.3g -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for ss-bracket-v10 (fw=2mm, target ~87g).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] # 10.5mm → bolt_r=5.25mm - lp = c["load_point_mm"] # [150, 50, 45] - min_wall = c.get("min_wall_thickness_mm", 0.5) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + min_wall # 5.75mm - - plate_t = min_wall # 0.5mm = min_wall - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = 136.0 # tip at |136-150|=14 < 15mm ✓ - fw = 2.0 # minimum for valid hollow at t_wall=0.5mm - h = 53.0 # arm top 7mm from bolt z=60 ✓ - t_wall = min_wall # 0.5mm = min_wall - y_center = lp[1] # 50mm — center bolt column ✓ - - # ── Hollow box arm (minimum viable hollow: inner y=1mm) ─────────────────── - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - fw / 2, 0.0), - gp_Pnt(arm_len, y_center + fw / 2, h), - ).Shape() - - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, y_center - fw / 2 + t_wall, t_wall), - gp_Pnt(arm_len, y_center + fw / 2 - t_wall, h - t_wall), - ).Shape() - - cut1 = BRepAlgoAPI_Cut(outer_arm, inner_void) - cut1.Build() - arm_shape = cut1.Shape() - - # ── Mounting plate ───────────────────────────────────────────────────────── - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(arm_shape, plate) - fuse1.Build() - shape = fuse1.Shape() - - # ── Bolt holes ──────────────────────────────────────────────────────────── - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - # ── Export ──────────────────────────────────────────────────────────────── - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/ss-bracket-v10/spec.txt b/agents/ss-bracket-v10/spec.txt deleted file mode 100644 index 8390c15..0000000 --- a/agents/ss-bracket-v10/spec.txt +++ /dev/null @@ -1 +0,0 @@ -003_pipe_clamp_bracket \ No newline at end of file diff --git a/agents/ss-bracket-v15/agent.py b/agents/ss-bracket-v15/agent.py deleted file mode 100644 index ff09699..0000000 --- a/agents/ss-bracket-v15/agent.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -SS-bracket v15: frame plate (cross-frame) — target ~71g. - -Insights from prior versions: -- v10 (solid plate + hollow arm, h=53mm, fw=2mm): SOTA 88.19g, 43 MPa -- v12 (h=48): FAIL — arm top inside load zone (z=40-50) -- v14 (stepped tapered): FAIL 243 MPa — step junctions → stress singularities - -Arm constraints (locked): - - arm_top = z=53 (3mm clear of load zone top z≈50) - - arm_bottom = z=0 (direct connection to lower bolt row at z=0) - - arm_len = 136mm (|136-150|=14mm < 15mm load tolerance) - - fw = 2mm, t_wall = 0.5mm (minimum viable hollow at coarse 4mm mesh) - -New idea: frame plate instead of solid plate. - - Solid plate: 111.5mm × 71.5mm × 0.5mm ≈ 21.5g - - Frame plate: 4 strips (bottom, top, left, right) around bolt positions - - Removes interior plate material (~17g) while maintaining load paths - - Bottom strip covers bolts at z=0, top strip covers bolts at z=60 - - Left/right strips cover bolt columns at y=0 and y=100 - - Arm at y=50 provides the center vertical load path - -Mass estimate: - Frame area ≈ 4 × (111.5 × 5.75 + 5.75 × 71.5) − overlaps - ≈ 3678mm² vs solid 7972mm² → saves ~4294mm² × 0.5mm × 7.99e-3 = 17.1g - Target: 88.19 − 17.1 ≈ 71g -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for ss-bracket-v15 (frame plate + hollow arm, target ~71g).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] - min_wall = c.get("min_wall_thickness_mm", 0.5) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + min_wall # 5.75mm - - plate_t = min_wall # 0.5mm - py0 = min(by_coords) - margin - py1 = max(by_coords) + margin - pz0 = min(bz_coords) - margin - pz1 = max(bz_coords) + margin - - # Frame strips: covers bolt rows (z=0, z=60) and bolt columns (y=0, y=100). - # Each strip is margin-wide on each side of the bolt row/column. - # Strip height/width = 2 × margin = 11.5mm. - strip_half = margin # 5.75mm on each side of bolt row/col - - # Bottom strip: covers bolt row at z=0 - bot = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, py0, pz0), - gp_Pnt(plate_t, py1, min(bz_coords) + strip_half), - ).Shape() - - # Top strip: covers bolt row at z=60 - top = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, py0, max(bz_coords) - strip_half), - gp_Pnt(plate_t, py1, pz1), - ).Shape() - - # Left strip: covers bolt column at y=0 (full z height) - left = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, py0, pz0), - gp_Pnt(plate_t, min(by_coords) + strip_half, pz1), - ).Shape() - - # Right strip: covers bolt column at y=100 (full z height) - right = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, max(by_coords) - strip_half, pz0), - gp_Pnt(plate_t, py1, pz1), - ).Shape() - - # Fuse all plate strips - frame = BRepAlgoAPI_Fuse(bot, top).Shape() - frame = BRepAlgoAPI_Fuse(frame, left).Shape() - frame = BRepAlgoAPI_Fuse(frame, right).Shape() - - # Hollow box arm (identical to v10 — proven topology, 43 MPa stress) - arm_len = 136.0 - fw = 2.0 - t_wall = min_wall - h_top = 53.0 - yc = lp[1] # 50mm - - outer_arm = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, yc - fw / 2, 0.0), - gp_Pnt(arm_len, yc + fw / 2, h_top), - ).Shape() - inner_void = BRepPrimAPI_MakeBox( - gp_Pnt(plate_t, yc - fw / 2 + t_wall, t_wall), - gp_Pnt(arm_len, yc + fw / 2 - t_wall, h_top - t_wall), - ).Shape() - arm = BRepAlgoAPI_Cut(outer_arm, inner_void).Shape() - - shape = BRepAlgoAPI_Fuse(arm, frame).Shape() - - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - shape = BRepAlgoAPI_Cut(shape, hole).Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/ss-bracket-v15/spec.txt b/agents/ss-bracket-v15/spec.txt deleted file mode 100644 index e238715..0000000 --- a/agents/ss-bracket-v15/spec.txt +++ /dev/null @@ -1 +0,0 @@ -003_pipe_clamp_bracket diff --git a/agents/sub-nano/agent.py b/agents/sub-nano/agent.py deleted file mode 100644 index 521210c..0000000 --- a/agents/sub-nano/agent.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Sub-nano: nano-tight with h_root=69mm (from 70mm). - -Baseline: nano-tight (23.61g, 14.88 MPa, CI-verified PR #67). -Strategy: reduce h_root by 1mm. Saves ~0.12g from the tapered web. - -Mesh convergence check (bolt-hole/arm-flange gap): - Upper bolt hole at z=60mm, bolt_r=3.25mm → hole top at z=63.25mm - Arm top flange bottom at z = h_root - flange_t = 69 - 1.2 = 67.8mm - Gap = 67.8 - 63.25 = 4.55mm > 3.75mm minimum ✓ - - (h_root=68mm gives gap=3.55mm < 3.75mm → fails. 69mm is safe.) - -Mass estimate (web taper reduction): - Web height at root: h_root - 2×flange_t = 69-2.4 = 66.6mm (vs 67.6mm) - Web height at tip: h_tip - 2×flange_t = 15-2.4 = 12.6mm (unchanged) - Volume change: arm_len × web_w × Δavg_height = 100 × 2 × 0.5 = 100mm³ - Mass savings: 100mm³ × 1.24e-3 = 0.124g - Target: 23.608 - 0.124 = ~23.48g - -Stress check (flange contribution at root, h=69mm): - I_nano = 2 × (6×1.2) × (35-0.6)² = 2 × 7.2 × 34.4² = 17020mm⁴ - I_sub = 2 × (6×1.2) × (34.5-0.6)² = 2 × 7.2 × 33.9² = 16534mm⁴ - σ_sub = 14.88 × (17020/16534) × (34.5/35.0) = 14.88 × 1.029 × 0.986 = 15.1 MPa < 25 ✓ - -All other parameters unchanged from nano-tight: - plate_t=3.0mm, pocket_depth=1.8mm, bolt_clear=4.75mm, arm_buf=1.0mm, - h_tip=15mm, flange_w=6mm, flange_t=1.2mm, web_w=2mm, arm_len=100mm. -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for sub-nano bracket (h_root=69mm).""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + min_wall # 4.45mm - - plate_t = 3.0 - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] # 100mm — at load point - - web_w = 2.0 - flange_w = 6.0 - flange_t = 1.2 - h_root = 69.0 # from 70mm; gap=4.55mm > 3.75mm minimum ✓ - h_tip = 15.0 - y_center = lp[1] # 25mm - - # ── Arm ─────────────────────────────────────────────────────────────────── - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - flange_w / 2, 0.0), - gp_Pnt(arm_len, y_center + flange_w / 2, flange_t), - ).Shape() - - web = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - web_w / 2, y1=y_center + web_w / 2, - z0_near=flange_t, z1_near=h_root - flange_t, - z0_far=flange_t, z1_far=h_tip - flange_t, - ) - - top_flange = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - flange_w / 2, y1=y_center + flange_w / 2, - z0_near=h_root - flange_t, z1_near=h_root, - z0_far=h_tip - flange_t, z1_far=h_tip, - ) - - fuse1 = BRepAlgoAPI_Fuse(bot_flange, web) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), top_flange) - fuse2.Build() - - # ── Mounting plate ──────────────────────────────────────────────────────── - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse3 = BRepAlgoAPI_Fuse(fuse2.Shape(), plate) - fuse3.Build() - shape = fuse3.Shape() - - # ── Bolt holes ──────────────────────────────────────────────────────────── - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - # ── Wall-face pockets: 1.8mm deep ──────────────────────────────────────── - pocket_depth = 1.8 - bolt_clear = bolt_r + 1.5 # 4.75mm: 1.5mm bridge (proven safe) - arm_buf = 1.0 - - arm_y_min = y_center - flange_w / 2 # 22.0mm - arm_y_max = y_center + flange_w / 2 # 28.0mm - - pkt_z0 = min(bz_coords) + bolt_clear - pkt_z1 = max(bz_coords) - bolt_clear - - # Left pocket - lpkt_y0 = min(by_coords) + bolt_clear - lpkt_y1 = arm_y_min - arm_buf - if lpkt_y1 > lpkt_y0 + 2.0 and pkt_z1 > pkt_z0 + 2.0: - left_pocket = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, lpkt_y0, pkt_z0), - gp_Pnt(pocket_depth, lpkt_y1, pkt_z1), - ).Shape() - cut_op = BRepAlgoAPI_Cut(shape, left_pocket) - cut_op.Build() - shape = cut_op.Shape() - - # Right pocket - rpkt_y0 = arm_y_max + arm_buf - rpkt_y1 = max(by_coords) - bolt_clear - if rpkt_y1 > rpkt_y0 + 2.0 and pkt_z1 > pkt_z0 + 2.0: - right_pocket = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, rpkt_y0, pkt_z0), - gp_Pnt(pocket_depth, rpkt_y1, pkt_z1), - ).Shape() - cut_op = BRepAlgoAPI_Cut(shape, right_pocket) - cut_op.Build() - shape = cut_op.Shape() - - # ── Export ──────────────────────────────────────────────────────────────── - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) - - -def _loft_rect( - x0: float, x1: float, - y0: float, y1: float, - z0_near: float, z1_near: float, - z0_far: float, z1_far: float, -): - """Loft a solid between two axis-aligned rectangles at x=x0 and x=x1.""" - from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire - from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections - from OCP.gp import gp_Pnt - - def make_rect_wire(x, ya, yb, za, zb): - pts = [ - gp_Pnt(x, ya, za), gp_Pnt(x, yb, za), - gp_Pnt(x, yb, zb), gp_Pnt(x, ya, zb), - ] - wire = BRepBuilderAPI_MakeWire() - for i in range(4): - e = BRepBuilderAPI_MakeEdge(pts[i], pts[(i + 1) % 4]).Edge() - wire.Add(e) - return wire.Wire() - - loft = BRepOffsetAPI_ThruSections(True) - loft.AddWire(make_rect_wire(x0, y0, y1, z0_near, z1_near)) - loft.AddWire(make_rect_wire(x1, y0, y1, z0_far, z1_far)) - loft.Build() - return loft.Shape() diff --git a/agents/sub-nano/spec.txt b/agents/sub-nano/spec.txt deleted file mode 100644 index 1645d0a..0000000 --- a/agents/sub-nano/spec.txt +++ /dev/null @@ -1 +0,0 @@ -001_bracket diff --git a/agents/taper-beam/agent.py b/agents/taper-beam/agent.py deleted file mode 100644 index 6a09c1c..0000000 --- a/agents/taper-beam/agent.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -Taper-beam bracket: linearly tapered I-beam arm. - -Key insight: a cantilever's bending moment M(x) = F*(L-x) is maximum at the wall -and zero at the load point. A uniform cross-section wastes material in the outer -half of the arm where the moment is low. A linearly tapered beam from full height -at the wall to a reduced height at the tip carries the same load with less mass. - -I-beam taper: - Root (x=0): h = 90 mm → σ_max = 10.7 MPa (see below) - Tip (x=L): h = 30 mm → M ≈ 0, any section works - -Structural analysis (critical section at x=0): - M = F × arm_reach = 392.4 × 100 = 39 240 N·mm - I-section (h=90, web=2mm, flanges=10×1.5mm): - I_web = 2.0 × 87³ / 12 = 109 747 mm⁴ - I_flanges = 2×(10×1.5×43.25²) = 56 090 mm⁴ - I_total = 165 837 mm⁴ - c = 45 mm - σ = 39 240 × 45 / 165 837 = 10.7 MPa < 25 MPa ✓ (SF = 4.7) - 10.7 MPa < 17.5 MPa → mesh convergence not triggered. - -Volume estimate (3mm plate, tapered arm): - Mount plate 70×70×3 − bolt holes ×4 ≈ 14 303 mm³ ( 17.7 g) - Web (trapezoid, h: 87→27, 108 mm, 2mm) ≈ 12 312 mm³ ( 15.3 g) - Top flange (10×1.5×108, slanted) ≈ 1 620 mm³ ( 2.0 g) - Bot flange (10×1.5×108, constant) ≈ 1 620 mm³ ( 2.0 g) - Net ≈ 29 855 mm³ - Mass = 29 855 × 1.24 × 10⁻³ ≈ 37.0 g - - vs trim-frame (~46.6 g): −21% - vs slim-spine (108.5 g SOTA): −66% -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a taper-beam wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - - plate_t = 3.0 # 3mm — survives bolt-hole FEA stress - bolt_r = bolt_d / 2.0 - - # Tight margins: bolt_r+1mm clearance past inner bolts, 10mm past outer bolts. - plate_y0 = min(by_coords) - bolt_r - 1.0 # ~-4.25 mm - plate_y1 = max(by_coords) + 10.0 # 70 mm - plate_z0 = min(bz_coords) - bolt_r - 1.0 # ~-4.25 mm - plate_z1 = max(bz_coords) + 10.0 # 70 mm - - arm_len = lp[0] + 8.0 # 108 mm - - # I-beam parameters - web_w = 2.0 # web width in Y - flange_w = 10.0 # flange width in Y - flange_t = 1.5 # flange thickness - - h_root = 90.0 # I-beam height at wall (x=0) - h_tip = 30.0 # I-beam height at tip (x=arm_len) - - y_center = lp[1] # 25 mm - - # Bottom flange: constant z=[0, flange_t] along full arm - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - flange_w / 2, 0.0), - gp_Pnt(arm_len, y_center + flange_w / 2, flange_t), - ).Shape() - - # Tapered web via ThruSections loft between two rectangular wire profiles - web = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - web_w / 2, y1=y_center + web_w / 2, - z0_near=flange_t, z1_near=h_root - flange_t, - z0_far=flange_t, z1_far=h_tip - flange_t, - ) - - # Tapered top flange: follows the slanted top of the web - top_flange = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - flange_w / 2, y1=y_center + flange_w / 2, - z0_near=h_root - flange_t, z1_near=h_root, - z0_far=h_tip - flange_t, z1_far=h_tip, - ) - - fuse1 = BRepAlgoAPI_Fuse(bot_flange, web) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), top_flange) - fuse2.Build() - - # Mounting plate (arm overlaps in x=[0, plate_t] for continuous junction) - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse3 = BRepAlgoAPI_Fuse(fuse2.Shape(), plate) - fuse3.Build() - shape = fuse3.Shape() - - # Drill bolt holes through the mount plate - cut_ops = [] - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - cut_ops.append(cut_op) - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) - - -def _loft_rect( - x0: float, x1: float, - y0: float, y1: float, - z0_near: float, z1_near: float, - z0_far: float, z1_far: float, -): - """Loft a solid between two axis-aligned rectangles at x=x0 and x=x1.""" - from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire - from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections - from OCP.gp import gp_Pnt - - def make_rect_wire(x, ya, yb, za, zb): - p1 = gp_Pnt(x, ya, za) - p2 = gp_Pnt(x, yb, za) - p3 = gp_Pnt(x, yb, zb) - p4 = gp_Pnt(x, ya, zb) - e1 = BRepBuilderAPI_MakeEdge(p1, p2).Edge() - e2 = BRepBuilderAPI_MakeEdge(p2, p3).Edge() - e3 = BRepBuilderAPI_MakeEdge(p3, p4).Edge() - e4 = BRepBuilderAPI_MakeEdge(p4, p1).Edge() - w = BRepBuilderAPI_MakeWire() - w.Add(e1); w.Add(e2); w.Add(e3); w.Add(e4) - return w.Wire() - - loft = BRepOffsetAPI_ThruSections(True) - loft.AddWire(make_rect_wire(x0, y0, y1, z0_near, z1_near)) - loft.AddWire(make_rect_wire(x1, y0, y1, z0_far, z1_far)) - loft.Build() - return loft.Shape() diff --git a/agents/taper-slim/agent.py b/agents/taper-slim/agent.py deleted file mode 100644 index a2f3c89..0000000 --- a/agents/taper-slim/agent.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -Taper-slim bracket: aggressively tapered I-beam with proven FEA-stable wall dims. - -Key insight from taper-thin (PR #29): 1.2 mm walls cause mesh-dependent results -in C3D4 linear tets — coarse vs fine mesh diverges 66 % at stress concentrations. -Proven minimum for mesh convergence: 2 mm web, 1.5 mm flanges (established by -taper-beam at 38.73 g verified). - -Changes from taper-beam: - - h_tip: 20 mm (vs 30 mm) — steeper taper follows M(x) = F*(100-x) more closely - - flange_w: 8 mm (vs 10 mm) — narrower flanges, still below convergence threshold - - web_w: 2 mm, flange_t: 1.5 mm — unchanged (proven safe) - - plate: 3 mm, 4.75 mm all-round margin — unchanged (proven safe) - -Structural analysis — critical section at x=0 (maximum moment): - M = 392.4 × 100 = 39 240 N·mm - h_root = 90 mm, flange_t = 1.5 mm, web_h = 87 mm, web_w = 2 mm - I_web = 2.0 × 87³ / 12 = 109 747 mm⁴ - I_flanges = 2 × (8 × 1.5 × 44.25²) = 47 097 mm⁴ - I_total = 156 844 mm⁴ - c = 45 mm - σ = 39 240 × 45 / 156 844 = 11.25 MPa < 25 MPa ✓ (SF = 2.22) - 11.25 MPa < 17.5 MPa → mesh convergence not triggered - -Structural analysis — midspan at x=50 mm: - h(50) = 90 − (90−20)/108 × 50 = 57.6 mm, web_h = 54.6 mm - I = 2×54.6³/12 + 2×(8×1.5×28.05²) = 27 152 + 18 917 = 46 069 mm⁴ - M(50) = 392.4 × 50 = 19 620 N·mm, c = 28.8 mm - σ = 19 620 × 28.8 / 46 069 = 12.27 MPa < 25 MPa ✓ - -Mass estimate (PLA, 1.24 g/cm³): - Plate 69.5 × 69.5 × 3 − bolt holes ×4 ≈ 14 081 mm³ ( 17.5 g) - Web tapered h 87→17 mm, avg 52 mm × 108 × 2 ≈ 11 232 mm³ ( 13.9 g) - Flanges 2 × (8 × 1.5 × 108) ≈ 2 592 mm³ ( 3.2 g) - Net ≈ 27 905 mm³ - Mass = 27 905 × 1.24 × 10⁻³ ≈ 34.6 g - - vs taper-beam (38.73 g verified): −11 % -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a taper-slim wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - bolt_r = bolt_d / 2.0 - margin = bolt_r + 1.5 # 4.75 mm — full clearance past hole edge - - # Mounting plate: 3 mm thick (proven minimum for bolt-hole FEA stress). - plate_t = 3.0 - plate_y0 = min(by_coords) - margin - plate_y1 = max(by_coords) + margin - plate_z0 = min(bz_coords) - margin - plate_z1 = max(bz_coords) + margin - - arm_len = lp[0] + 8.0 # 108 mm (8 mm past load point) - - # I-beam — proven wall dims for C3D4 mesh convergence. - web_w = 2.0 # minimum for reliable C3D4 meshing - flange_w = 8.0 # narrowed from 10 mm; σ < 17.5 MPa ✓ - flange_t = 1.5 # minimum for reliable C3D4 meshing - - h_root = 90.0 # section height at wall (x=0) - h_tip = 20.0 # section height at tip (x=arm_len) - - y_center = lp[1] # 25 mm - - # Bottom flange: flat, constant z=[0, flange_t], full arm length. - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, y_center - flange_w / 2, 0.0), - gp_Pnt(arm_len, y_center + flange_w / 2, flange_t), - ).Shape() - - # Tapered web: bottom at z=flange_t, top tapers from h_root to h_tip. - web = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - web_w / 2, y1=y_center + web_w / 2, - z0_near=flange_t, z1_near=h_root - flange_t, - z0_far=flange_t, z1_far=h_tip - flange_t, - ) - - # Tapered top flange: follows the slanting top of the web. - top_flange = _loft_rect( - x0=0.0, x1=arm_len, - y0=y_center - flange_w / 2, y1=y_center + flange_w / 2, - z0_near=h_root - flange_t, z1_near=h_root, - z0_far=h_tip - flange_t, z1_far=h_tip, - ) - - fuse1 = BRepAlgoAPI_Fuse(bot_flange, web) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), top_flange) - fuse2.Build() - - # Mounting plate: fused at x=[0, plate_t] for continuous junction with arm. - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - fuse3 = BRepAlgoAPI_Fuse(fuse2.Shape(), plate) - fuse3.Build() - shape = fuse3.Shape() - - # Drill bolt holes through the mounting plate. - for bby, bbz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, bby, bbz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) - - -def _loft_rect( - x0: float, x1: float, - y0: float, y1: float, - z0_near: float, z1_near: float, - z0_far: float, z1_far: float, -): - """Loft a solid between two axis-aligned rectangles at x=x0 and x=x1.""" - from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire - from OCP.BRepOffsetAPI import BRepOffsetAPI_ThruSections - from OCP.gp import gp_Pnt - - def make_rect_wire(x, ya, yb, za, zb): - pts = [ - gp_Pnt(x, ya, za), gp_Pnt(x, yb, za), - gp_Pnt(x, yb, zb), gp_Pnt(x, ya, zb), - ] - wire = BRepBuilderAPI_MakeWire() - for i in range(4): - e = BRepBuilderAPI_MakeEdge(pts[i], pts[(i + 1) % 4]).Edge() - wire.Add(e) - return wire.Wire() - - loft = BRepOffsetAPI_ThruSections(True) - loft.AddWire(make_rect_wire(x0, y0, y1, z0_near, z1_near)) - loft.AddWire(make_rect_wire(x1, y0, y1, z0_far, z1_far)) - loft.Build() - return loft.Shape() diff --git a/agents/taper-slim/spec.txt b/agents/taper-slim/spec.txt deleted file mode 100644 index 1645d0a..0000000 --- a/agents/taper-slim/spec.txt +++ /dev/null @@ -1 +0,0 @@ -001_bracket diff --git a/agents/thin-frame/agent.py b/agents/thin-frame/agent.py deleted file mode 100644 index 879e8c6..0000000 --- a/agents/thin-frame/agent.py +++ /dev/null @@ -1,128 +0,0 @@ -""" -Thin-frame bracket: minimum-wall plate + minimum-wall I-beam. - -Key improvements over razor-I-beam (PR #15): - 1. Mount plate: 3 mm → 1.2 mm (minimum printable wall) — saves ~14 g - 2. Web: 1.5 mm → 1.2 mm (minimum printable wall) — saves ~3.8 g - 3. Flanges: 10 mm → 8 mm wide — saves ~0.7 g - -Structural analysis at x = 0 (critical section, bending about Y): - M = F × L = 392.4 × 100 = 39 240 N·mm - - I-beam cross-section (1.2 mm web, 8 mm × 1.2 mm flanges, 90 mm total height): - Web (87.6 mm between flanges): - I_web = 1.2 × 87.6³ / 12 = 67 060 mm⁴ - Bot flange (8 × 1.2, centroid 44.4 mm from mid): - I_flange = 8×1.2³/12 + 8×1.2×44.4² = 18 973 mm⁴ - Top flange (mirror): 18 973 mm⁴ - I_total = 105 006 mm⁴ - - c = 45.0 mm - σ_max = 39 240 × 45 / 105 006 = 16.8 MPa < 25 MPa ✓ (SF = 2.97) - 16.8 MPa < 17.5 MPa (70 % of allowable) → mesh convergence not triggered. - -Volume estimate: - Plate 80 × 80 × 1.2 − bolt holes ×4 ≈ 7 360 mm³ ( 9.1 g) - Web 115 × 1.2 × 90 − overlaps ≈ 12 290 mm³ ( 15.2 g) - Flange 115 × 8 × 1.2 × 2 − overlaps ≈ 2 140 mm³ ( 2.7 g) - Net ≈ 21 790 mm³ - Mass = 21 790 × 1.24 × 10⁻³ ≈ 27.0 g - - vs razor-I-beam (~44.9 g): −40 % - vs slim-spine (108.48 g SOTA): −75 % -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a thin-frame wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - min_wall = c.get("min_wall_thickness_mm", 1.2) - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - plate_y = max(by_coords) + 20.0 # 80 mm - plate_z = max(bz_coords) + 20.0 # 80 mm - plate_t = min_wall # 1.2 mm — minimum, down from 3 mm - - shelf_len = lp[0] + 15.0 # 115 mm - - # I-beam geometry — centered on load y-axis. - web_w = min_wall # 1.2 mm (minimum printable wall) - flange_w = 8.0 # width in Y — enough to keep σ < 70 % allowable - flange_t = min_wall # 1.2 mm (minimum printable wall) - total_h = 90.0 # full usable Z height - - y_center = lp[1] # 25 mm - web_y0 = y_center - web_w / 2 # 24.4 - web_y1 = y_center + web_w / 2 # 25.6 - flange_y0 = y_center - flange_w / 2 # 21.0 - flange_y1 = y_center + flange_w / 2 # 29.0 - - # --- Mounting plate (minimum wall thickness) --- - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, 0.0, 0.0), - gp_Pnt(plate_t, plate_y, plate_z), - ).Shape() - - # --- Web: 1.2 mm × 90 mm, full arm length --- - web = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, web_y0, 0.0), - gp_Pnt(shelf_len, web_y1, total_h), - ).Shape() - - # --- Bottom flange at z = 0 --- - bot_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, flange_y0, 0.0), - gp_Pnt(shelf_len, flange_y1, flange_t), - ).Shape() - - # --- Top flange at z = total_h - flange_t --- - top_flange = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, flange_y0, total_h - flange_t), - gp_Pnt(shelf_len, flange_y1, total_h), - ).Shape() - - # Fuse all four components. - body = BRepAlgoAPI_Fuse(plate, web) - body.Build() - body = BRepAlgoAPI_Fuse(body.Shape(), bot_flange) - body.Build() - body = BRepAlgoAPI_Fuse(body.Shape(), top_flange) - body.Build() - shape = body.Shape() - - # Cut bolt holes through the mounting plate. - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_d / 2.0, plate_t + 2.0).Shape() - cut = BRepAlgoAPI_Cut(shape, hole) - cut.Build() - shape = cut.Shape() - - # Write STEP. - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path) diff --git a/agents/trim-frame/agent.py b/agents/trim-frame/agent.py deleted file mode 100644 index 2c64f20..0000000 --- a/agents/trim-frame/agent.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Trim-frame bracket: T-section arm with tight plate margins. - -Why T-section over I-beam: - An I-beam arm with a 2mm web showed mesh-dependent FEA results (coarse=21.2 MPa - fine=29.2 MPa, 38% deviation). Linear-tet FEA (C3D4) cannot converge at the - narrow web-to-plate junction: a 2mm strip connecting to a 74mm plate creates a - stress concentration that the coarse mesh misses by >10%. - - A full-width bottom rail distributes bolt-reaction forces across the plate, so - there is no narrow junction to concentrate stress. This is the same geometry - principle as slim-spine (verified FEA at 7.5 MPa) and spar-web. - -Design: - - Mount plate: 3mm thick, tight margins (bolt_r+1mm inside edge, +10mm outside) - - Bottom rail: 1.5mm thick, spans full plate width in Y — smooth load path - - Spine: 2mm wide × 85mm tall, centred on load axis - -Structural analysis (critical section at x=0, T-section about neutral axis): - Flange area: A_f = 74.25 × 1.5 = 111.4 mm², centroid z = 0.75 mm - Web area: A_w = 2 × 83.5 = 167.0 mm², centroid z = 1.5 + 41.75 = 43.25 mm - NA: z̄ = (111.4×0.75 + 167×43.25) / 278.4 = 26.1 mm from bottom - - I_total (parallel-axis): - Flange: (74.25×1.5³/12) + 74.25×1.5×(26.1-0.75)² = 14.8 + 71 668 = 71 683 mm⁴ - Web: (2×83.5³/12) + 2×83.5×(43.25-26.1)² = 97 247 + 49 416 = 146 663 mm⁴ - I_total = 218 346 mm⁴ - - c_top = (1.5 + 83.5) - 26.1 = 58.9 mm (extreme compressive fibre) - M = F × L = 392.4 × 100 = 39 240 N·mm - σ_max = 39 240 × 58.9 / 218 346 = 10.6 MPa < 25 MPa ✓ (SF = 4.7) - 10.6 MPa < 17.5 MPa → mesh convergence not triggered. - -Mass estimate: - Plate 74.25×74.25×3 − bolt holes ×4 ≈ 16 153 mm³ (20.0 g) - Rail 105 × 74.25 × 1.5 ≈ 11 702 mm³ (14.5 g) (net of plate overlap) - Spine 105 × 2 × 83.5 ≈ 17 535 mm³ (21.7 g) (net of plate+rail overlap) - Total ≈ 45 390 mm³ (~56.3 g) - - vs slim-spine (108.48 g SOTA): −48% -""" - -from __future__ import annotations - -import os -import tempfile - - -def generate(spec: dict) -> bytes: - """Return STEP bytes for a trim-frame T-section wall bracket.""" - from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse - from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder - from OCP.Interface import Interface_Static - from OCP.STEPControl import STEPControl_AsIs, STEPControl_Writer - from OCP.gp import gp_Ax2, gp_Dir, gp_Pnt - - c = spec["constraints"] - bolt_pattern = c["bolt_pattern_mm"] - bolt_d = c["bolt_diameter_clearance_mm"] - lp = c["load_point_mm"] # [100, 25, 25] - - by_coords = [p[0] for p in bolt_pattern] - bz_coords = [p[1] for p in bolt_pattern] - - bolt_r = bolt_d / 2.0 - - # Tight plate margins: bolt_r+1mm clearance past inner bolts, +10mm past outer. - plate_t = 3.0 - plate_y0 = min(by_coords) - bolt_r - 1.0 # ~-4.25 mm - plate_y1 = max(by_coords) + 10.0 # 70 mm - plate_z0 = min(bz_coords) - bolt_r - 1.0 # ~-4.25 mm - plate_z1 = max(bz_coords) + 10.0 # 70 mm - - arm_x = lp[0] + 8.0 # 108 mm (8mm past the 100mm load point) - - # Rail (bottom flange): spans full plate width, thin. - rail_t = 1.5 - rail_y0 = plate_y0 - rail_y1 = plate_y1 - - # Spine (web): tall, narrow, centred on load axis. - spine_w = 2.0 - spine_h = 85.0 # z=[rail_t, rail_t+spine_h] - y_center = lp[1] # 25 mm - spine_y0 = y_center - spine_w / 2 # 24.0 - spine_y1 = y_center + spine_w / 2 # 26.0 - - # --- Mounting plate --- - plate = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, plate_y0, plate_z0), - gp_Pnt(plate_t, plate_y1, plate_z1), - ).Shape() - - # --- Bottom rail: from x=0 to arm tip --- - rail = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, rail_y0, 0.0), - gp_Pnt(arm_x, rail_y1, rail_t), - ).Shape() - - # --- Spine: from x=0 to arm tip, above the rail --- - spine = BRepPrimAPI_MakeBox( - gp_Pnt(0.0, spine_y0, rail_t), - gp_Pnt(arm_x, spine_y1, rail_t + spine_h), - ).Shape() - - fuse1 = BRepAlgoAPI_Fuse(plate, rail) - fuse1.Build() - fuse2 = BRepAlgoAPI_Fuse(fuse1.Shape(), spine) - fuse2.Build() - shape = fuse2.Shape() - - # Cut bolt holes through the mounting plate. - cut_ops = [] - for by, bz in bolt_pattern: - axis = gp_Ax2(gp_Pnt(-1.0, by, bz), gp_Dir(1.0, 0.0, 0.0)) - hole = BRepPrimAPI_MakeCylinder(axis, bolt_r, plate_t + 2.0).Shape() - cut_op = BRepAlgoAPI_Cut(shape, hole) - cut_op.Build() - cut_ops.append(cut_op) - shape = cut_op.Shape() - - writer = STEPControl_Writer() - Interface_Static.SetCVal_s("write.step.schema", "AP214IS") - writer.Transfer(shape, STEPControl_AsIs) - - with tempfile.NamedTemporaryFile(suffix=".step", delete=False) as f: - path = f.name - try: - writer.Write(path) - with open(path, "rb") as f: - return f.read() - finally: - os.unlink(path)