feat(modeling): implement 3D parametric solid and rebar modeling engines
This commit is contained in:
1 parent
5ddc97c076
commit
42726ff1b4
3 files changed
+1790
No files matched your search
+166
@@ -0,0 +1,166 @@
|
||||
def _get_solid_objects(model_data):
|
||||
"""Safely extracts unique 3D concrete solid meshes from model_data['objects']."""
|
||||
objs = []
|
||||
seen = set()
|
||||
raw = model_data.get('objects', {})
|
||||
if 'culvert' in raw and isinstance(raw['culvert'], dict):
|
||||
c = raw['culvert']
|
||||
objs.append(c)
|
||||
seen.add(c.get('id', 'CULVERT'))
|
||||
if 'wingwalls' in raw and isinstance(raw['wingwalls'], list):
|
||||
for w in raw['wingwalls']:
|
||||
if isinstance(w, dict) and w.get('id') not in seen and 'vertices' in w:
|
||||
objs.append(w)
|
||||
seen.add(w['id'])
|
||||
else:
|
||||
for k, v in raw.items():
|
||||
if isinstance(v, dict) and v.get('id') and v['id'] not in seen and 'vertices' in v:
|
||||
objs.append(v)
|
||||
seen.add(v['id'])
|
||||
return objs
|
||||
|
||||
|
||||
def export_obj(model_data, include_rebars=True):
|
||||
"""
|
||||
Exports 3D model data (exterior concrete solids + 3D rebars) to Wavefront OBJ format.
|
||||
"""
|
||||
lines = ["# Wingwall & Culvert 3D Solid & Rebar Model", "# Generated by Antigravity CAD/BIM Modeler", ""]
|
||||
v_offset = 1
|
||||
|
||||
solid_objects = _get_solid_objects(model_data)
|
||||
for obj in solid_objects:
|
||||
lines.append(f"o {obj['id']}")
|
||||
lines.append(f"g {obj['name']}")
|
||||
|
||||
for v in obj['vertices']:
|
||||
lines.append(f"v {v[0]:.4f} {v[1]:.4f} {v[2]:.4f}")
|
||||
|
||||
for f in obj['faces']:
|
||||
lines.append(f"f {f[0] + v_offset} {f[1] + v_offset} {f[2] + v_offset}")
|
||||
|
||||
v_offset += len(obj['vertices'])
|
||||
lines.append("")
|
||||
|
||||
if include_rebars and 'rebars' in model_data:
|
||||
for ww_id, ww_rebar in model_data['rebars'].get('wingwalls', {}).items():
|
||||
for grp in ww_rebar.get('groups', []):
|
||||
lines.append(f"o REBAR_{ww_id}_{grp['mark']}_{grp['dia']}")
|
||||
for poly in grp.get('polylines', []):
|
||||
if len(poly) < 2:
|
||||
continue
|
||||
for pt in poly:
|
||||
lines.append(f"v {pt[0]:.4f} {pt[1]:.4f} {pt[2]:.4f}")
|
||||
idx_str = " ".join(str(v_offset + i) for i in range(len(poly)))
|
||||
lines.append(f"l {idx_str}")
|
||||
v_offset += len(poly)
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def export_stl(model_data):
|
||||
"""
|
||||
Exports 3D model data to ASCII STL format.
|
||||
"""
|
||||
lines = ["solid WingwallCulvertModel"]
|
||||
for obj in _get_solid_objects(model_data):
|
||||
v = obj['vertices']
|
||||
for f in obj['faces']:
|
||||
p1 = v[f[0]]
|
||||
p2 = v[f[1]]
|
||||
p3 = v[f[2]]
|
||||
v1 = [p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]]
|
||||
v2 = [p3[0]-p1[0], p3[1]-p1[1], p3[2]-p1[2]]
|
||||
nx = v1[1]*v2[2] - v1[2]*v2[1]
|
||||
ny = v1[2]*v2[0] - v1[0]*v2[2]
|
||||
nz = v1[0]*v2[1] - v1[1]*v2[0]
|
||||
mag = (nx*nx + ny*ny + nz*nz)**0.5
|
||||
if mag > 1e-8:
|
||||
nx, ny, nz = nx/mag, ny/mag, nz/mag
|
||||
else:
|
||||
nx, ny, nz = 0.0, 0.0, 1.0
|
||||
|
||||
lines.append(f" facet normal {nx:.4e} {ny:.4e} {nz:.4e}")
|
||||
lines.append(" outer loop")
|
||||
lines.append(f" vertex {p1[0]:.4f} {p1[1]:.4f} {p1[2]:.4f}")
|
||||
lines.append(f" vertex {p2[0]:.4f} {p2[1]:.4f} {p2[2]:.4f}")
|
||||
lines.append(f" vertex {p3[0]:.4f} {p3[1]:.4f} {p3[2]:.4f}")
|
||||
lines.append(" endloop")
|
||||
lines.append(" endfacet")
|
||||
lines.append("endsolid WingwallCulvertModel")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def export_dxf_3d(model_data, include_rebars=True):
|
||||
"""
|
||||
Exports 3D model data to DXF R2000 ASCII format containing 3DFACE entities for concrete
|
||||
and 3D LINE entities for internal rebars.
|
||||
"""
|
||||
lines = [
|
||||
"0", "SECTION",
|
||||
"2", "ENTITIES"
|
||||
]
|
||||
for obj in _get_solid_objects(model_data):
|
||||
layer = obj['id']
|
||||
v = obj['vertices']
|
||||
for f in obj['faces']:
|
||||
p1 = v[f[0]]
|
||||
p2 = v[f[1]]
|
||||
p3 = v[f[2]]
|
||||
lines.extend([
|
||||
"0", "3DFACE",
|
||||
"8", layer,
|
||||
"10", f"{p1[0]:.4f}", "20", f"{p1[1]:.4f}", "30", f"{p1[2]:.4f}",
|
||||
"11", f"{p2[0]:.4f}", "21", f"{p2[1]:.4f}", "31", f"{p2[2]:.4f}",
|
||||
"12", f"{p3[0]:.4f}", "22", f"{p3[1]:.4f}", "32", f"{p3[2]:.4f}",
|
||||
"13", f"{p3[0]:.4f}", "23", f"{p3[1]:.4f}", "33", f"{p3[2]:.4f}"
|
||||
])
|
||||
|
||||
if include_rebars and 'rebars' in model_data:
|
||||
color_by_dia = {'H19': '1', 'H16': '4', 'H13': '3'}
|
||||
for ww_id, ww_rebar in model_data['rebars'].get('wingwalls', {}).items():
|
||||
for grp in ww_rebar.get('groups', []):
|
||||
layer = f"REBAR_{ww_id}_{grp['mark']}_{grp['dia']}"
|
||||
c_code = color_by_dia.get(grp['dia'], '2')
|
||||
for poly in grp.get('polylines', []):
|
||||
for i in range(len(poly) - 1):
|
||||
p1 = poly[i]
|
||||
p2 = poly[i + 1]
|
||||
lines.extend([
|
||||
"0", "LINE",
|
||||
"8", layer,
|
||||
"62", c_code,
|
||||
"10", f"{p1[0]:.4f}", "20", f"{p1[1]:.4f}", "30", f"{p1[2]:.4f}",
|
||||
"11", f"{p2[0]:.4f}", "21", f"{p2[1]:.4f}", "31", f"{p2[2]:.4f}"
|
||||
])
|
||||
|
||||
lines.extend([
|
||||
"0", "ENDSEC",
|
||||
"0", "EOF"
|
||||
])
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def export_rebar_bom_csv(model_data):
|
||||
"""
|
||||
Exports 3단계 철근 재료표 (Bar Bending Schedule) to UTF-8 BOM CSV format.
|
||||
"""
|
||||
rows = [
|
||||
"\ufeff구분(날개벽),기호(Mark),직경(Dia),배치부위,역할,간격,절곡치수,1본길이(m),수량(개),총길이(m),단위중량(kg/m),총중량(TON)"
|
||||
]
|
||||
if 'rebars' in model_data:
|
||||
for ww_id, ww in model_data['rebars'].get('wingwalls', {}).items():
|
||||
ww_name = ww.get('wingwall_name', ww_id)
|
||||
for g in ww.get('groups', []):
|
||||
shape_str = " / ".join(f"{k}={v}" for k, v in g.get('shape_params', {}).items())
|
||||
rows.append(
|
||||
f'"{ww_name}",{g["mark"]},{g["dia"]},"{g["zone"]}","{g["role"]}","{g["spacing"]}",'
|
||||
f'"{shape_str}",{g["length_m"]:.3f},{g["count"]},{g["total_length_m"]:.3f},'
|
||||
f'{g["unit_weight"]:.3f},{g["weight_ton"]:.4f}'
|
||||
)
|
||||
s = ww.get('summary', {})
|
||||
rows.append(
|
||||
f'"{ww_name} 소계",-,전체,-,-,-,-,-,{s.get("total_bars",0)},'
|
||||
f'{s.get("total_length_m",0):.3f},-,{s.get("total_weight_ton",0):.3f} (할증3%: {s.get("total_surcharge_ton",0):.3f})'
|
||||
)
|
||||
return "\n".join(rows)
|
||||
Reference in new issue
Block a user