feat: 2 m placeholder pattern zones on lining, station/pattern readout in viewer (Task 5)

No pattern-to-station data exists in the drawings. Per user decision
(2026-10-21) the modelled range 4+745..5+480 is split into 2 m segments with
arbitrary pattern names, marked placeholder in CSV, meta and viewer, to be
replaced later. Ring step reduced to 1 m so that the 2 m segments align.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A5Cun3RHQKna9qxE6ybZ4A
This commit is contained in:
Claude committed 2026-10-21 06:12:58 +00:00
1 parent c9de6f0ef3
commit 6c7e555c25
7 files changed
+2303 -10

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
"""Task 5 (user decision 2026-10-21): no pattern-station data exists. Create a
PLACEHOLDER pattern table at 2 m segments over the modelled range 4+745..5+480,
cycling the six pattern names. Every row is marked '임의 배정 (placeholder)'.
The table is an editable CSV: replace the 'pattern' column later and rebuild.
Output: data/pattern_zones_placeholder.csv (created only if absent; never overwritten)."""
import csv
import os
import sys
ROOT = sys.argv[1]
OUT = f"{ROOT}/data/pattern_zones_placeholder.csv"
PATTERNS = ["NP-4AD-1-D-2", "NP-4BD-1-D-2", "NP-5AE-1-WL-4", "NP-5BE-1-WL-4", "NP-6A", "NP-6B"]
S0, S1, SEG = 4745, 5480, 2
if not os.path.exists(OUT):
with open(OUT, "w", newline="", encoding="utf-8") as fh:
w = csv.writer(fh)
w.writerow(["seg_from", "seg_to", "pattern", "status"])
for k, s in enumerate(range(S0, S1, SEG)):
w.writerow([s, min(s + SEG, S1), PATTERNS[k % len(PATTERNS)], "임의 배정 (placeholder, 실제 구간 아님)"])
print("pattern table:", OUT)
+42 -6
View File
@@ -30,7 +30,7 @@ R1, R_OUT, R_EX = 6.982, 7.282, 7.402 # m (mm / 1000)
CROWN_A = 6.615 # m above road
CENTRE_C = CROWN_A - R1 # arch centre height above road (m)
S0, S1 = 4745, 5480 # modelled range (fully determined)
DS = 5.0 # station step for rings (m)
DS = 1.0 # station step for rings (m); pattern segments are 2 m
ANG = np.radians(np.linspace(40.0, 140.0, 41)) # arch arc, right springline -> left
def read_centre():
@@ -80,18 +80,33 @@ def ring_world(R, s):
P = np.column_stack([x - ORIGIN[0] + xs * nx, y - ORIGIN[1] + xs * ny, zr + zs])
return P
def make_band(R, color, name):
PAL = {"NP-4AD-1-D-2": (231, 111, 81, 255), "NP-4BD-1-D-2": (244, 162, 97, 255),
"NP-5AE-1-WL-4": (233, 196, 106, 255), "NP-5BE-1-WL-4": (138, 177, 125, 255),
"NP-6A": (42, 157, 143, 255), "NP-6B": (38, 70, 83, 255)}
ZONES = [(int(r["seg_from"]), int(r["seg_to"]), r["pattern"], r["status"])
for r in csv.DictReader(open(f"{ROOT}/data/pattern_zones_placeholder.csv", encoding="utf-8"))]
def pattern_of(sta):
for a, b, pat, _ in ZONES:
if a <= sta < b:
return pat
return ZONES[-1][2]
def make_band_pattern(R, name):
rings = [ring_world(R, s) for s in stations]
n = len(rings[0]); m = len(rings)
V = np.vstack(rings)
F = []
F, C = [], []
for i in range(m - 1):
col = PAL[pattern_of(stations[i])]
for j in range(n - 1):
a = i * n + j; b = a + 1; c = a + n; d = c + 1
F += [[a, b, c], [b, d, c]]
C += [col, col]
mesh = trimesh.Trimesh(vertices=V, faces=np.array(F), process=False)
mesh.visual.face_colors = np.tile(np.array(color, dtype=np.uint8), (len(F), 1))
mesh.visual.face_colors = np.array(C, dtype=np.uint8)
mesh.metadata["name"] = name
mesh.metadata["ring_points"] = n
return mesh
def make_ribbon(z_fn, half_w, color, name):
@@ -111,9 +126,24 @@ def make_ribbon(z_fn, half_w, color, name):
mesh.metadata["name"] = name
return mesh
def make_band(R, color, name):
"""uniform-colour band (excavation outline)."""
rings = [ring_world(R, s) for s in stations]
n = len(rings[0]); m = len(rings)
V = np.vstack(rings)
F = []
for i in range(m - 1):
for j in range(n - 1):
a = i * n + j; b = a + 1; c = a + n; d = c + 1
F += [[a, b, c], [b, d, c]]
mesh = trimesh.Trimesh(vertices=V, faces=np.array(F), process=False)
mesh.visual.face_colors = np.tile(np.array(color, dtype=np.uint8), (len(F), 1))
mesh.metadata["name"] = name
return mesh
parts = [
make_band(R_OUT, (190, 190, 186, 255), "lining_outer_R7282"),
make_band(R1, (235, 235, 230, 255), "lining_inner_R6982"),
make_band_pattern(R_OUT, "lining_outer_R7282"),
make_band_pattern(R1, "lining_inner_R6982"),
make_band(R_EX, (150, 120, 90, 110), "excavation_R7402"),
make_ribbon(lambda s: float(np.interp(s, dg_s, dg_v)), 5.0, (70, 70, 75, 255), "road_surface_계획고"),
make_ribbon(lambda s: float(np.interp(s, gl_s, gl_v)), 20.0, (110, 150, 90, 200), "ground_GL_지반고"),
@@ -138,6 +168,9 @@ checks["road_z_5480_m"] = round(float(np.interp(S1, dg_s, dg_v)), 3)
L_chord = float(np.sum([math.dist(cn[i, 1:], cn[i + 1, 1:]) for i in range(len(cn) - 1)]))
checks["plan_chord_length_total_m (4+700..5+500)"] = round(L_chord, 3)
checks["station_range_modelled"] = f"{S0} .. {S1} (ring step {DS} m, {len(stations)} rings)"
checks["pattern_zones (2 m, placeholder)"] = len(ZONES)
checks["pattern_coverage_all_stations"] = bool(all(pattern_of(x) in PAL for x in stations))
checks["pattern_counts"] = {k: sum(1 for z in ZONES if z[2] == k) for k in PAL}
checks["glb_bytes"] = int(__import__("os").path.getsize(glb))
reload = trimesh.load(glb)
checks["glb_reload_geometries"] = len(reload.geometry)
@@ -154,6 +187,9 @@ meta = {
"centreline and grade linearly interpolated between nodes",
"superelevation sign convention assumed (z += S/100 * x, x = left of tangent)"],
"layers": [p.metadata["name"] for p in parts],
"ring_step_m": DS, "ring_points": int(parts[0].metadata["ring_points"]), "first_station": S0,
"pattern_status": "PLACEHOLDER: 2 m 임의 배정. 실제 측점 구간 아님 (사용자 결정 2026-10-21). data/pattern_zones_placeholder.csv 를 수정 후 재빌드",
"patterns": [{"from": a, "to": b, "pattern": pat} for a, b, pat, _ in ZONES],
"open": ["4+700~4+745 and 5+480~5+500 excluded ([미확정])", "pattern zones (03-003~008) pending Task 5",
"lining details (04-004~009) pending Task 6"]
}