167 lines
6.6 KiB
Python
167 lines
6.6 KiB
Python
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)
|