Files

95 lines
4.9 KiB
Python

#!/usr/bin/env python3
"""Write a dependency-free SVG preview from src/model/block8.json."""
import json
import math
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MODEL = ROOT / "src/model/block8.json"
OUTPUT = ROOT / "docs/BLOCK8_3D모델_미리보기.svg"
COLORS = {"major": "#78917c", "minor": "#d0aa69", "insert": "#9a9074"}
def point_at(unit, profile_a, profile_b, index, t):
theta = math.radians(unit["startAngle"] + (unit["endAngle"] - unit["startAngle"]) * t)
cx = unit["center"][0] + unit["radius"] * math.cos(theta)
cz = unit["center"][1] + unit["radius"] * math.sin(theta)
normal_x, normal_z = -math.cos(theta), -math.sin(theta)
station_t = (0.010 + t * 19.980) / 20
u = profile_a[index][0] + (profile_b[index][0] - profile_a[index][0]) * station_t
el = profile_a[index][1] + (profile_b[index][1] - profile_a[index][1]) * station_t
return [cx + u * normal_x, el, cz + u * normal_z]
def project(point):
x, y, z = point
yaw, pitch, scale = -0.66, 0.43, 14.0
x1 = x * math.cos(yaw) - z * math.sin(yaw)
z1 = x * math.sin(yaw) + z * math.cos(yaw)
y1 = y - 104
return [600 + x1 * scale, 330 - (y1 * math.cos(pitch) - z1 * math.sin(pitch)) * scale, y1 * math.sin(pitch) + z1 * math.cos(pitch)]
def main():
model = json.loads(MODEL.read_text(encoding="utf-8"))
faces = []
for unit in model["units"]:
for part in unit["parts"]:
if len(part["a"]) != len(part["b"]):
continue
for step in range(8):
t0, t1 = step / 8, (step + 1) / 8
for edge in range(len(part["a"]) - 1):
corners = [
point_at(unit, part["a"], part["b"], edge, t0),
point_at(unit, part["a"], part["b"], edge + 1, t0),
point_at(unit, part["a"], part["b"], edge + 1, t1),
point_at(unit, part["a"], part["b"], edge, t1),
]
projected = [project(point) for point in corners]
depth = sum(point[2] for point in projected) / 4
faces.append((depth, part["group"], projected))
faces.sort(key=lambda face: face[0])
svg = [
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 660" role="img" aria-labelledby="title desc">',
'<title id="title">광터지하차도 BLOCK8 도면 기반 3D 구조 모델</title>',
'<desc id="desc">평면도와 상부평면도의 동일 구조물 중심선을 기준으로 시점과 종점 단면 구조선을 연결한 단일 열린 표면 검토 모델.</desc>',
'<defs><linearGradient id="bg" x2="0" y2="1"><stop stop-color="#f7faf6"/><stop offset="1" stop-color="#e8eee8"/></linearGradient></defs>',
'<rect width="1200" height="660" rx="22" fill="url(#bg)"/>',
'<text x="62" y="61" fill="#728579" font-family="sans-serif" font-size="14" font-weight="700" letter-spacing="2">광터지하차도 · 도면 기반 검토 모델</text>',
'<text x="62" y="105" fill="#263a32" font-family="sans-serif" font-size="29" font-weight="700">BLOCK8 · 구조선 연결 표면</text>',
'<g fill="none" stroke="#d4dfd6" stroke-width="1">',
]
for x in range(170, 1031, 60):
svg.append(f'<path d="M{x} 165 L{x} 540"/>')
for y in range(180, 541, 36):
svg.append(f'<path d="M145 {y} L1055 {y}"/>')
svg.append('</g>')
for depth, group, points in faces:
coordinates = ' '.join(f'{p[0]:.1f},{p[1]:.1f}' for p in points)
fill = COLORS.get(group, COLORS["major"])
opacity = 0.64 if group != "insert" else 0.46
svg.append(f'<polygon points="{coordinates}" fill="{fill}" fill-opacity="{opacity}" stroke="#53685a" stroke-opacity=".48" stroke-width=".55"/>')
for idx, unit in enumerate(model["units"]):
left = point_at(unit, [[0, 104]], [[0, 104]], 0, 0)
right = point_at(unit, [[0, 104]], [[0, 104]], 0, 1)
p0, p1 = project(left), project(right)
svg.append(f'<g font-family="sans-serif" font-size="12" fill="#55715c"><text x="{p0[0]:.1f}" y="{p0[1]-13:.1f}">BLOCK8 중심선 · STA 319.010</text><text x="{p1[0]:.1f}" y="{p1[1]-12:.1f}">STA 338.990</text></g>')
svg.extend([
'<g font-family="sans-serif" font-size="12"><rect x="62" y="570" width="1076" height="54" rx="9" fill="#f8f3e8" stroke="#e5dbc4"/>',
'<text x="82" y="593" fill="#8c774e">길이 19.980m · 평면 중심선 교점으로 계산 · 시점/종점 단면 구조선 114개씩 + 블록 내부 윤곽</text>',
'<text x="82" y="612" fill="#8c774e">단일 구조물 모델입니다. 열린 표면이며 닫힌 체적이 아닙니다. 상부평면도 중심선은 같은 구조물 표현으로 해석했습니다.</text></g>',
'</svg>',
])
OUTPUT.write_text('\n'.join(svg) + '\n', encoding="utf-8")
print(f"{OUTPUT.relative_to(ROOT)}: {len(faces)} polygons")
if __name__ == "__main__":
main()