diff --git a/packaging/mother/geodesic-dome-mcp b/packaging/mother/geodesic-dome-mcp index 3b5fa7e..7d962d1 100755 --- a/packaging/mother/geodesic-dome-mcp +++ b/packaging/mother/geodesic-dome-mcp @@ -200,6 +200,97 @@ def analyze_structure(verts, edges, triangles, radius_meters=5.0): } +def analyze_strut_constraint(struts, max_length=2.0, bar_length=6.0): + """Enforce 2m max strut length for FI12 rebar (6m bars → 3× 2m cuts).""" + pieces_per_bar = int(bar_length / max_length) # 3 pieces of 2m + over = [] + ok = [] + total_bars = 0 + total_waste_m = 0.0 + + for s in struts: + L = s["length_m"] + count = s["count"] + if L > max_length: + over.append({"length_m": L, "count": count, "excess_m": round(L - max_length, 2)}) + else: + ok.append(s) + + # Calculate rebar bars needed and waste + total_pieces = sum(s["count"] for s in struts) + total_bars = math.ceil(total_pieces / pieces_per_bar) + total_cut_m = total_bars * bar_length + total_strut_m_needed = sum(s["length_m"] * s["count"] for s in struts) + total_waste_m = round(total_cut_m - total_strut_m_needed, 2) + waste_pieces = total_bars * pieces_per_bar - total_pieces + + return { + "max_strut_length_m": max_length, + "bar_length_m": bar_length, + "pieces_per_bar": pieces_per_bar, + "struts_over_limit": over, + "struts_ok": len(ok), + "total_pieces_needed": total_pieces, + "rebar_bars_needed": total_bars, + "total_bar_meters": round(total_bars * bar_length, 1), + "waste_meters": total_waste_m, + "waste_pieces": waste_pieces, + "waste_use": "corner hardening gussets" if waste_pieces > 0 else "none", + "compliant": len(over) == 0, + } + + +def construction_layers(verts, triangles, radius): + """Group triangles into construction layers ordered top-to-bottom. + + Welding sequence: start at apex (highest Z), weld that layer, + lift the completed section, weld the next layer underneath. + """ + # Compute centroid Z for each triangle + tri_zs = [] + for idx, (v0, v1, v2) in enumerate(triangles): + z = (verts[v0][2] + verts[v1][2] + verts[v2][2]) / 3 * radius + tri_zs.append((z, idx)) + + # Sort top-to-bottom (descending Z) + tri_zs.sort(key=lambda x: -x[0]) + + # Group into layers by Z proximity (every 0.5m drop = new layer) + layers = [] + current_layer = [] + current_z = None + layer_threshold = 0.5 # meters + + for z, idx in tri_zs: + if current_z is None: + current_z = z + if abs(z - current_z) > layer_threshold: + layers.append({ + "layer": len(layers) + 1, + "height_m": round(current_z, 2), + "triangles": len(current_layer), + "sequence": "weld → lift → next layer" if len(layers) > 0 else "start at apex", + }) + current_layer = [] + current_z = z + current_layer.append(idx) + + if current_layer: + layers.append({ + "layer": len(layers) + 1, + "height_m": round(current_z, 2), + "triangles": len(current_layer), + "sequence": "weld → lift → next layer" if len(layers) > 0 else "start at apex", + }) + + return { + "total_layers": len(layers), + "construction_method": "top-to-bottom welding, lift after each layer", + "layer_threshold_m": layer_threshold, + "layers": layers, + } + + def render(verts, edges, size=2048, yaw=0.3, pitch=0.4, line_color="#00b4d8", bg_color="#0d1117", lw=1): sy, cy = math.sin(yaw), math.cos(yaw) @@ -259,6 +350,14 @@ def generate(params): analysis["frequency"] = freq analysis["half_sphere"] = half + # ── Strut constraint: 2m max (FI12 rebar, 6m bars → 3× 2m cuts) ── + strut_constraint = analyze_strut_constraint(analysis["struts"], max_length=2.0, bar_length=6.0) + analysis["strut_constraint"] = strut_constraint + + # ── Construction layers: order triangles top-to-bottom for welding ── + layers = construction_layers(verts, triangles, radius) + analysis["construction_layers"] = layers + result = {"success": True, "analysis": analysis} if not analysis_only: