chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
964a841de1
3978 files changed
+58013356
No files matched your search
@@ -0,0 +1,110 @@
|
||||
"""G5 stage: bridge bearings (black boxes) and sole plates (wedges) on each h2 pad.
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after concrete.py.
|
||||
Ported from Code/교각모델링/build_bearing_plates_rhino.py (Ungok P1, model v0.1.09). Rules: 설계서 v0.1.10 §8. Reads params.json `bearing_seats`,
|
||||
`sole_plates`, `coping`. Bottom of the bearing = modelled pad top (= coping step + h1 + h2 = EL.'X' within 1 mm).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
import System
|
||||
from Rhino.DocObjects import ObjectAttributes, ObjectColorSource
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
H3 = P["sole_plates"].get("h3") # D2 ELEVATION TABLE h3 (common value; a table row may override)
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
def corners(cx, cy, along, across, rot_deg):
|
||||
"""Plan corners, `along` on +Y rotated clockwise by rot_deg; order: (-a,-c), (+a,-c), (+a,+c), (-a,+c)."""
|
||||
t = math.radians(rot_deg)
|
||||
ua, uc = (math.sin(t), math.cos(t)), (math.cos(t), -math.sin(t))
|
||||
out = []
|
||||
for sa, sc in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
|
||||
out.append((cx + sa * ua[0] * along / 2 + sc * uc[0] * across / 2,
|
||||
cy + sa * ua[1] * along / 2 + sc * uc[1] * across / 2, sa * along / 2))
|
||||
return out
|
||||
|
||||
|
||||
def hexa(bottom, top):
|
||||
pts = [rg.Point3d(*p) for p in bottom + top]
|
||||
b = rg.Brep.CreateFromBox(pts)
|
||||
if b is None or not b.IsSolid:
|
||||
raise RuntimeError("hexahedron failed")
|
||||
if b.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
b.Flip()
|
||||
return b
|
||||
|
||||
|
||||
def add(brep, key, oid, color, props):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = layer(key, color)
|
||||
attr.Name = oid
|
||||
attr.ObjectColor = Color.FromArgb(*color)
|
||||
attr.ColorSource = ObjectColorSource.ColorFromObject
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in props.items():
|
||||
attr.SetUserString(k, str(v))
|
||||
if doc.Objects.AddBrep(brep, attr) == System.Guid.Empty:
|
||||
raise RuntimeError(f"{oid}: AddBrep failed")
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
if (o.Attributes.GetUserString("id") or "").startswith((f"{PID}-BB", f"{PID}-SP")):
|
||||
doc.Objects.Delete(o, True)
|
||||
sp = P["sole_plates"]
|
||||
if not sp["table"]: # 2026-10-13: an empty table silently produced no bearings
|
||||
raise RuntimeError("sole_plates.table is empty")
|
||||
steps = P["coping"].get("top_steps")
|
||||
bear = {b["no"]: b for b in P["bearing_seats"]["bearings"]}
|
||||
for r in sp["table"]:
|
||||
b = bear[r["no"]]
|
||||
cx, cy = b["centre"]
|
||||
rot = b["rotation_deg"]
|
||||
seat = b["seat_z"] if "seat_z" in b else steps[b["girder_line"] - 1]["z"] # seat_z: tapered form (Ulsan)
|
||||
z0 = seat + b["h1"] + b["h2"] # modelled pad top
|
||||
h3 = r.get("h3", H3) # per bearing when tabled
|
||||
if sp.get("bearing_size") == "AB": # bearing body A (along) x B (across) (Ulsan detail drawing)
|
||||
along, across, rule = b["A"], b["B"], "A x B (교량받침 상세)"
|
||||
else: # (L-100) x (W-100) per user 2026-10-08 (Ungok)
|
||||
along, across, rule = r["L"] - sp["bearing_shrink"], r["W"] - sp["bearing_shrink"], None
|
||||
cb = corners(cx, cy, along, across, rot)
|
||||
add(hexa([(x, y, z0) for x, y, _ in cb], [(x, y, z0 + h3) for x, y, _ in cb]),
|
||||
f"{PID}::교량받침", f"{PID}-BB{r['no']:02d}", tuple(sp["colours"]["bearing"]),
|
||||
dict(role="bridge bearing", no=r["no"], size=f"{along}x{across}x{h3}", el_x=b["el_x"],
|
||||
src=f"ELEVATION TABLE h3, {rule}" if rule else "D2 ELEVATION TABLE h3, (L-100)x(W-100) per user 2026-10-08"))
|
||||
# sole plate: bottom flat, top sloped S% along the pad long axis, centre thickness T (sign of S sets thick end)
|
||||
zb = z0 + h3
|
||||
cs = corners(cx, cy, r["L"], r["W"], rot)
|
||||
top = [(x, y, zb + r["T"] + r["S"] / 100 * s) for x, y, s in cs]
|
||||
add(hexa([(x, y, zb) for x, y, _ in cs], top), f"{PID}::솔플레이트", f"{PID}-SP{r['no']:02d}",
|
||||
tuple(sp["colours"]["sole_plate"]),
|
||||
dict(role="sole plate", no=r["no"], S=r["S"], L=r["L"], W=r["W"], T=r["T"],
|
||||
end_thick=f"{r['T'] - r['S'] / 100 * r['L'] / 2:.2f}/{r['T'] + r['S'] / 100 * r['L'] / 2:.2f}",
|
||||
src=sp.get("sole_plate_src", "D2 솔플레이트 상세 TABLE; slope along pad axis, thick end -Y [HITL]")))
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"bearings": len(sp["table"]), "sole_plates": len(sp["table"])}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Shared helpers for the gate checkers (imported inside Rhino 8).
|
||||
|
||||
Checkers read only EXPECTED_PATH (independent drawing values) and never the generator params.
|
||||
"""
|
||||
import json
|
||||
|
||||
from Rhino.DocObjects import ObjectEnumeratorSettings
|
||||
|
||||
|
||||
def load_expected(path):
|
||||
return json.load(open(path, encoding="utf-8"))
|
||||
|
||||
|
||||
def all_objects(doc):
|
||||
"""All live objects incl. hidden/locked (doc.Objects alone skips hidden ones)."""
|
||||
s = ObjectEnumeratorSettings()
|
||||
s.HiddenObjects = True
|
||||
s.LockedObjects = True
|
||||
return list(doc.Objects.GetObjectList(s))
|
||||
|
||||
|
||||
def chain(spec, base=0.0):
|
||||
"""Expand a dimension chain: start offsets (cumulative) then groups [[n, pitch], ...] from the last point."""
|
||||
pts, z = [], base
|
||||
for s in spec["start"]:
|
||||
z += s
|
||||
pts.append(z)
|
||||
for n, p in spec["groups"]:
|
||||
for _ in range(n):
|
||||
z += p
|
||||
pts.append(z)
|
||||
return pts
|
||||
|
||||
|
||||
def series(spec):
|
||||
"""Expand {start, pitch, n} (or a literal list) into a list of values."""
|
||||
if isinstance(spec, list):
|
||||
return list(spec)
|
||||
return [spec["start"] + spec["pitch"] * i for i in range(spec["n"])]
|
||||
|
||||
|
||||
def row(spec):
|
||||
"""Bar positions: two end bars plus a regular series."""
|
||||
return sorted(list(spec["ends"]) + series(spec))
|
||||
|
||||
|
||||
def ychain(spec, half):
|
||||
"""Section chain measured from the -Y face: listed first bars (absolute), then n x pitch from the last of
|
||||
them, then the last bar at `end`; returned relative to the section centre (minus `half`)."""
|
||||
pts = list(spec["start"])
|
||||
pts += [pts[-1] + spec["pitch"] * k for k in range(1, spec["n"] + 1)] + [spec["end"]]
|
||||
return [y - half for y in sorted(pts)]
|
||||
|
||||
|
||||
def write_result(path, results, extra=None):
|
||||
summ = dict(total=len([r for r in results if r["result"] != "INFO"]),
|
||||
passed=sum(r["result"] == "PASS" for r in results),
|
||||
failed=sum(r["result"] == "FAIL" for r in results))
|
||||
summ.update(extra or {})
|
||||
json.dump(dict(summary=summ, results=results), open(path, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
print(json.dumps(summ, ensure_ascii=False))
|
||||
return summ
|
||||
@@ -0,0 +1,217 @@
|
||||
"""G1 gate checker, form "tapered T with shared pedestals": piles and plan skew optional (unified 2026-10-15 from g1_skew.py,
|
||||
Okseong P1, and this file, Saejae-1 P1): lean, footing, column, coping (straight soffit, stepped top, side taper), shared pedestals
|
||||
with a sloped top, pads. G1 expected "piles" absent = spread footing (no pile checks); coping.skew_deg 0 = rectangular plan.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G1) come from dimension / EL texts and
|
||||
the bearing tables; the skew angle and the pedestal outlines are drawn values ([작도]). Generator params are not read.
|
||||
Checks: C5 population, C2 closed solids, C1 extents (lean, footing, column, piles), C4 coping (front section at Y=0,
|
||||
side width at X=0, skewed end faces), C3 joints / overlaps, C6 volumes, C7 pedestals / pads.
|
||||
"""
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, TOL, G = X["pier_id"], X["tol_mm"], X["G1"]
|
||||
F, LN, CO, C, PI = G["footing"], G["lean"], G["column"], G["coping"], G.get("piles")
|
||||
FT_TOP = F["z0"] + F["thickness"]
|
||||
ZB = FT_TOP + CO["height"] # column top = coping bottom
|
||||
ZT = ZB + C["soffit_rise"]
|
||||
FLAT, TIP, W, WB = C["bottom_flat_half"], C["tip_half"], C["width_y"] / 2, C["side_bottom_width"] / 2
|
||||
K = math.tan(math.radians(C["skew_deg"])) # [작도]
|
||||
EDGES = [-TIP] + C["step_bounds"] + [TIP] # top step boundaries at Y = 0 (front dimension chain)
|
||||
STEPS = C["step_z"] # step levels (EL.'X' - h1 - h2 of the bearing rows)
|
||||
results = []
|
||||
|
||||
|
||||
def objs(prefix):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
||||
|
||||
|
||||
def brep(o):
|
||||
return o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry)
|
||||
|
||||
|
||||
def vol(b):
|
||||
return rg.VolumeMassProperties.Compute(b).Volume
|
||||
|
||||
|
||||
def bbox(b):
|
||||
return b.GetBoundingBox(True)
|
||||
|
||||
|
||||
def check(cid, name, expected, actual, tol=TOL):
|
||||
results.append(dict(check=cid, item=name, expected=round(expected, 3), actual=round(actual, 3),
|
||||
diff=round(actual - expected, 3), tol=tol, result="PASS" if abs(actual - expected) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(cid, name, ok, detail=""):
|
||||
results.append(dict(check=cid, item=name, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def top_z(x):
|
||||
for a, b, z in zip(EDGES, EDGES[1:], STEPS):
|
||||
if a <= x <= b:
|
||||
return z
|
||||
return None
|
||||
|
||||
|
||||
def bottom_z(x):
|
||||
ax = abs(x)
|
||||
return ZB if ax <= FLAT else ZB + C["soffit_rise"] * (ax - FLAT) / (TIP - FLAT)
|
||||
|
||||
|
||||
def coping_volume_expected():
|
||||
"""Shear keeps the volume: integrate the unsheared plan area (front x extent x side width) over z."""
|
||||
total, n = 0.0, 20000
|
||||
z1 = max(STEPS)
|
||||
dz = (z1 - ZB) / n
|
||||
for i in range(n):
|
||||
z = ZB + (i + 0.5) * dz
|
||||
if z < ZT:
|
||||
xh = FLAT + (TIP - FLAT) * (z - ZB) / (ZT - ZB)
|
||||
yh = WB + (W - WB) * (z - ZB) / (ZT - ZB)
|
||||
total += 2 * xh * 2 * yh * dz
|
||||
else:
|
||||
length = sum(b - a for a, b, s in zip(EDGES, EDGES[1:], STEPS) if s >= z)
|
||||
total += length * 2 * W * dz
|
||||
return total
|
||||
|
||||
|
||||
def inter_vol(a, b):
|
||||
r = rg.Brep.CreateBooleanIntersection(a, b, 0.01)
|
||||
return sum(vol(x) for x in r) if r else 0.0
|
||||
|
||||
|
||||
def main():
|
||||
ids = {k: f"{PID}-{k}" for k in ("LC", "FT", "CL", "CP", "PL")}
|
||||
counts = {k: len(objs(v)) for k, v in ids.items()}
|
||||
flag("C5", "object counts", counts == {"LC": 1, "FT": 1, "CL": 1, "CP": 1, "PL": PI["nx"] * PI["ny"] if PI else 0}, str(counts))
|
||||
lc, ft, cl, cp = (brep(objs(ids[k])[0]) for k in ("LC", "FT", "CL", "CP"))
|
||||
piles = [brep(o) for o in objs(ids["PL"])]
|
||||
allb = [lc, ft, cl, cp] + piles
|
||||
flag("C2", f"IsSolid all {len(allb)}", all(b.IsSolid for b in allb), f"{sum(b.IsSolid for b in allb)}/{len(allb)}")
|
||||
# C1 extents
|
||||
for key, b, dx, dy, z0, z1 in (("footing", ft, F["dx"], F["dy"], F["z0"], FT_TOP),
|
||||
("lean", lc, LN["dx"], LN["dy"], F["z0"] - LN["thickness"], F["z0"])):
|
||||
bb = bbox(b)
|
||||
check("C1", f"{key} X", dx, bb.Max.X - bb.Min.X)
|
||||
check("C1", f"{key} Y", dy, bb.Max.Y - bb.Min.Y)
|
||||
check("C1", f"{key} Zbot", z0, bb.Min.Z)
|
||||
check("C1", f"{key} Ztop", z1, bb.Max.Z)
|
||||
check("C1", f"{key} centre X", 0, (bb.Max.X + bb.Min.X) / 2)
|
||||
check("C1", f"{key} centre Y", 0, (bb.Max.Y + bb.Min.Y) / 2)
|
||||
bb = bbox(cl)
|
||||
check("C1", "column diameter X", CO["d"], bb.Max.X - bb.Min.X)
|
||||
check("C1", "column diameter Y", CO["d"], bb.Max.Y - bb.Min.Y)
|
||||
check("C1", "column Zbot", FT_TOP, bb.Min.Z)
|
||||
check("C1", "column Ztop", ZB, bb.Max.Z)
|
||||
xs = sorted({round((bbox(b).Min.X + bbox(b).Max.X) / 2, 1) for b in piles})
|
||||
ys = sorted({round((bbox(b).Min.Y + bbox(b).Max.Y) / 2, 1) for b in piles})
|
||||
if PI:
|
||||
flag("C1", f"pile grid {PI['nx']} x {PI['ny']}", len(xs) == PI["nx"] and len(ys) == PI["ny"], f"x={xs} y={ys}")
|
||||
if PI and xs and ys:
|
||||
check("C1", "pile edge X", PI["edge"], xs[0] + F["dx"] / 2)
|
||||
check("C1", "pile edge Y", PI["edge"], ys[0] + F["dy"] / 2)
|
||||
check("C1", "pile pitch X (worst)", 0, max(abs(b - a - PI["pitch"]) for a, b in zip(xs, xs[1:])))
|
||||
check("C1", "pile pitch Y (worst)", 0, max(abs(b - a - PI["pitch"]) for a, b in zip(ys, ys[1:])))
|
||||
pb = bbox(piles[0])
|
||||
check("C1", "pile diameter", PI["d"], pb.Max.X - pb.Min.X)
|
||||
check("C1", "pile length", PI["length"], pb.Max.Z - pb.Min.Z)
|
||||
check("C1", "pile head embedment", PI["head_embedment"], pb.Max.Z - F["z0"])
|
||||
# C4 coping: front section at Y = 0
|
||||
ok, curves, pts = rg.Intersect.Intersection.BrepPlane(cp, rg.Plane(rg.Point3d(0, 0, 0), rg.Vector3d.YAxis), 0.01)
|
||||
sec = rg.Curve.JoinCurves(curves, 0.1)
|
||||
flag("C4", "front section (Y=0) closed single loop", len(sec) == 1 and sec[0].IsClosed, f"{len(sec)} loop(s)")
|
||||
|
||||
def z_at(x, upper):
|
||||
line = rg.LineCurve(rg.Point3d(x, 0, 0), rg.Point3d(x, 0, 200000))
|
||||
zs = sorted(ev.PointA.Z for ev in rg.Intersect.Intersection.CurveCurve(sec[0], line, 0.01, 0.01))
|
||||
return zs[-1] if upper else zs[0]
|
||||
|
||||
for x in (0, FLAT - 1, -(FLAT - 1), 3500, -3500, TIP - 5, -(TIP - 5)):
|
||||
check("C4", f"bottom at X={x:+.0f}", bottom_z(x), z_at(x, False))
|
||||
for a, b in zip(EDGES, EDGES[1:]):
|
||||
x = (a + b) / 2
|
||||
check("C4", f"top step at X={x:+.0f}", top_z(x), z_at(x, True))
|
||||
bb = bbox(cp)
|
||||
check("C4", "coping Zbot", ZB, bb.Min.Z)
|
||||
check("C4", "coping Ztop (highest step)", max(STEPS), bb.Max.Z)
|
||||
# side width at X = 0 (side taper)
|
||||
ok, curves, pts = rg.Intersect.Intersection.BrepPlane(cp, rg.Plane(rg.Point3d(0, 0, 0), rg.Vector3d.XAxis), 0.01)
|
||||
ss = rg.Curve.JoinCurves(curves, 0.1)
|
||||
for zz in (ZB + 1, (ZB + ZT) / 2, ZT - 1, ZT + 500):
|
||||
hits = sorted(ev.PointA.Y for c in ss for ev in rg.Intersect.Intersection.CurveCurve(
|
||||
c, rg.LineCurve(rg.Point3d(0, -9000, zz), rg.Point3d(0, 9000, zz)), 0.01, 0.01))
|
||||
w = WB + (W - WB) * min(zz - ZB, ZT - ZB) / (ZT - ZB)
|
||||
check("C4", f"side width at Z={zz:.0f}", 2 * w, (hits[-1] - hits[0]) if hits else 0)
|
||||
# skewed end faces: points 3 mm inside / outside the sheared ends at y = +-(W - 50), z mid of the end face
|
||||
for sx in (-1, 1):
|
||||
for y in (-(W - 50), W - 50):
|
||||
zz = (ZT + top_z(sx * (TIP - 10))) / 2
|
||||
xe = sx * TIP - K * y
|
||||
ins = cp.IsPointInside(rg.Point3d(xe - sx * 3, y, zz), 0.01, False)
|
||||
out = not cp.IsPointInside(rg.Point3d(xe + sx * 3, y, zz), 0.01, False)
|
||||
flag("C4", f"skewed end {'left' if sx < 0 else 'right'} at y={y:+.0f} [작도 skew {C['skew_deg']}]", ins and out,
|
||||
f"x_end {xe:.1f}")
|
||||
# C3 joints / overlaps
|
||||
check("C3", "LC top = FT bottom", bbox(lc).Max.Z, bbox(ft).Min.Z, tol=0.01)
|
||||
check("C3", "FT top = CL bottom", bbox(ft).Max.Z, bbox(cl).Min.Z, tol=0.01)
|
||||
check("C3", "CL top = CP bottom", bbox(cl).Max.Z, bbox(cp).Min.Z, tol=0.01)
|
||||
check("C3", "overlap FT-CL (mm3)", 0, inter_vol(ft, cl), tol=1.0)
|
||||
check("C3", "overlap CL-CP (mm3)", 0, inter_vol(cl, cp), tol=1.0)
|
||||
check("C3", "overlap LC-piles (mm3)", 0, sum(inter_vol(lc, b) for b in piles), tol=1.0)
|
||||
if PI:
|
||||
emb = len(piles) * math.pi * (PI["d"] / 2) ** 2 * PI["head_embedment"]
|
||||
check("C3", "intended overlap FT-piles (mm3)", emb, sum(inter_vol(ft, b) for b in piles), tol=emb * 0.001)
|
||||
# C6 volumes
|
||||
fv = F["dx"] * F["dy"] * F["thickness"] / 1e9
|
||||
check("C6", "footing volume m3", fv, vol(ft) / 1e9, tol=fv * 0.001)
|
||||
lv = (LN["dx"] * LN["dy"] - (len(piles) * math.pi * (PI["d"] / 2) ** 2 if PI else 0)) * LN["thickness"] / 1e9
|
||||
check("C6", "lean volume m3 (minus pile holes)", lv, vol(lc) / 1e9, tol=lv * 0.001)
|
||||
cv_ = math.pi * (CO["d"] / 2000) ** 2 * CO["height"] / 1000
|
||||
check("C6", "column volume m3", cv_, vol(cl) / 1e9, tol=cv_ * 0.001)
|
||||
cpv = coping_volume_expected() / 1e9
|
||||
check("C6", "coping volume m3 (dimension outline, shear keeps volume)", cpv, vol(cp) / 1e9, tol=cpv * 0.001)
|
||||
# C7 shared pedestals and pads
|
||||
S = G["seats"]
|
||||
n0 = len(PID) + 3
|
||||
seats = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BS")}
|
||||
pads = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BP")}
|
||||
flag("C7", "pedestal / pad counts", len(seats) == S["n_pedestals"] and sorted(pads) == sorted(int(k) for k in S["table"]),
|
||||
f"pedestals={len(seats)} pads={len(pads)}")
|
||||
flag("C7", "IsSolid pedestals+pads", all(b.IsSolid for b in list(seats.values()) + list(pads.values())))
|
||||
for no_s, row in S["table"].items():
|
||||
no = int(no_s)
|
||||
elx, h1, h2, a, b = row
|
||||
x, y = S["pos"][no_s]
|
||||
p = pads[no]
|
||||
top = top_z(x + K * y) # back to the Y = 0 step of this bearing line
|
||||
check("C7", f"NO.{no} table EL.'X' - h1 - h2 = coping step", top, elx - h1 - h2)
|
||||
check("C7", f"NO.{no} pad top = EL.'X'", elx, bbox(p).Max.Z)
|
||||
pb = bbox(p) # plan centre: the sloped pad bottom shifts the volume centroid
|
||||
check("C7", f"NO.{no} pad centre X (bbox)", x, (pb.Min.X + pb.Max.X) / 2)
|
||||
check("C7", f"NO.{no} pad centre Y (bbox)", y, (pb.Min.Y + pb.Max.Y) / 2)
|
||||
ped = [s for s in seats.values() if s.IsPointInside(rg.Point3d(x, y, top + 1), 0.01, False)]
|
||||
flag("C7", f"NO.{no} on one pedestal", len(ped) == 1, f"{len(ped)}")
|
||||
if ped:
|
||||
line = rg.LineCurve(rg.Point3d(x, y, top - 10), rg.Point3d(x, y, top + 1000))
|
||||
_ok, _crv, hits = rg.Intersect.Intersection.CurveBrep(line, ped[0], 0.01)
|
||||
zs = [pt.Z for pt in hits]
|
||||
check("C7", f"NO.{no} pedestal top at the bearing = step + h1", top + h1, max(zs) if zs else 0)
|
||||
check("C7", f"NO.{no} pedestal bottom at the bearing = step", top, min(zs) if zs else 0)
|
||||
check("C7", f"NO.{no} overlap pad-pedestal (mm3)", 0, inter_vol(p, ped[0]), tol=1.0)
|
||||
for no, s in seats.items():
|
||||
check("C7", f"pedestal {no} overlap with coping (mm3)", 0, inter_vol(s, cp), tol=1.0)
|
||||
write_result(RESULT_PATH, results, dict(volumes_m3={"lean": round(vol(lc) / 1e9, 3), "footing": round(vol(ft) / 1e9, 3),
|
||||
"column": round(vol(cl) / 1e9, 3), "coping": round(vol(cp) / 1e9, 3),
|
||||
"pile_each": round(vol(piles[0]) / 1e9, 3) if piles else 0}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,264 @@
|
||||
"""G1 gate checker for the "single shaft" form (Ramp-A P1, 2026-10-13): drilled shaft, column, Y coping,
|
||||
pedestals, pads.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G1 of EXPECTED_PATH) come from
|
||||
drawing dimension texts / EL texts / tables; generator params are not read. Two inputs have no dimension text
|
||||
and are drawn values (marked "[작도]" in the item names): the plan end arc corner and the pedestal rotations.
|
||||
Checks: C5 population, C2 closed solids, C1 extents, C4 coping sections (front Y=0, side X=0, plan end),
|
||||
C3 joints and overlaps, C6 volumes (coping integrated from the dimension outline), C7 pedestals / pads.
|
||||
"""
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, TOL, G = X["pier_id"], X["tol_mm"], X["G1"]
|
||||
PI, CO, C = G["pile"], G["column"], G["coping"]
|
||||
results = []
|
||||
|
||||
# expected outline from the dimensions
|
||||
ZB = CO["z0"] + CO["length"] # column top = coping bottom
|
||||
ZT = ZB + C["soffit_rise"] # tip level (end face bottom)
|
||||
ZTL, ZTR = C["z_top_left"], C["z_top_right"]
|
||||
HB, HT = C["column_face_half"], C["bottom_length"] / 2
|
||||
TOPH, R = C["top_length"] / 2, C["soffit_radius"]
|
||||
WT, WB = C["width_y"] / 2, C["side_bottom_width"] / 2
|
||||
PCORNER = C["plan_end_corner_x"] # [작도]
|
||||
|
||||
|
||||
def soffit_centre():
|
||||
"""Centre of the R arc through (-HB, ZB) and (-HT, ZT), on the outer (lower-left) side."""
|
||||
ax, az, bx, bz = -HB, ZB, -HT, ZT
|
||||
mx, mz = (ax + bx) / 2, (az + bz) / 2
|
||||
d = math.hypot(bx - ax, bz - az)
|
||||
h = math.sqrt(R * R - d * d / 4)
|
||||
ux, uz = (bz - az) / d, -(bx - ax) / d # unit normal of the chord
|
||||
c1, c2 = (mx + h * ux, mz + h * uz), (mx - h * ux, mz - h * uz)
|
||||
return min(c1, c2, key=lambda c: c[1]) # the centre below the chord
|
||||
|
||||
|
||||
CX, CZ = soffit_centre()
|
||||
|
||||
|
||||
def soffit_x(z):
|
||||
"""|x| of the soffit at height z (ZB..ZT)."""
|
||||
return abs(CX + math.sqrt(R * R - (z - CZ) ** 2))
|
||||
|
||||
|
||||
def plan_arc(y):
|
||||
sag = HT - PCORNER
|
||||
rr = (WT * WT + sag * sag) / (2 * sag)
|
||||
return HT - (rr - math.sqrt(rr * rr - y * y))
|
||||
|
||||
|
||||
def end_x(y, z, zt):
|
||||
"""|x| of the ruled end face at (y, z): plan arc at ZT, straight top edge TOPH at zt."""
|
||||
t = (z - ZT) / (zt - ZT)
|
||||
return plan_arc(y) + t * (TOPH - plan_arc(y))
|
||||
|
||||
|
||||
def objs(prefix):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
||||
|
||||
|
||||
def brep(o):
|
||||
return o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry)
|
||||
|
||||
|
||||
def vol(b):
|
||||
return rg.VolumeMassProperties.Compute(b).Volume
|
||||
|
||||
|
||||
def bbox(b):
|
||||
return b.GetBoundingBox(True)
|
||||
|
||||
|
||||
def check(cid, name, expected, actual, tol=TOL):
|
||||
results.append(dict(check=cid, item=name, expected=round(expected, 3), actual=round(actual, 3),
|
||||
diff=round(actual - expected, 3), tol=tol, result="PASS" if abs(actual - expected) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(cid, name, ok, detail=""):
|
||||
results.append(dict(check=cid, item=name, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def coping_volume_expected():
|
||||
"""Sum over z slices and y strips of the x extent: soffit (below ZT) limited by the end face continued
|
||||
below ZT, end face above ZT; left half up to ZTL, right half up to ZTR."""
|
||||
total, nz, ny = 0.0, 3000, 200
|
||||
z1 = max(ZTL, ZTR)
|
||||
dz = (z1 - ZB) / nz
|
||||
for i in range(nz):
|
||||
z = ZB + (i + 0.5) * dz
|
||||
w = WB + (WT - WB) * (z - ZB) / (ZT - ZB) if z < ZT else WT
|
||||
dy = 2 * w / ny
|
||||
area = 0.0
|
||||
for zt in (ZTL, ZTR):
|
||||
if z > zt:
|
||||
continue
|
||||
for j in range(ny):
|
||||
y = -w + (j + 0.5) * dy
|
||||
xe = end_x(y, z, zt)
|
||||
area += (min(soffit_x(z), xe) if z < ZT else xe) * dy
|
||||
total += area * dz
|
||||
return total
|
||||
|
||||
|
||||
def main():
|
||||
pl = sorted(objs(f"{PID}-PL"), key=lambda o: o.Attributes.GetUserString("id"))
|
||||
counts = {k: len(objs(f"{PID}-{k}")) for k in ("LC", "FT", "CL", "CP", "PL")}
|
||||
flag("C5", "object counts", counts == {"LC": 0, "FT": 0, "CL": 1, "CP": 1, "PL": len(PI["segments"])}, str(counts))
|
||||
cl, cp = brep(objs(f"{PID}-CL")[0]), brep(objs(f"{PID}-CP")[0])
|
||||
pls = [brep(o) for o in pl]
|
||||
allb = pls + [cl, cp]
|
||||
flag("C2", f"IsSolid all {len(allb)}", all(b.IsSolid for b in allb), f"{sum(b.IsSolid for b in allb)}/{len(allb)}")
|
||||
|
||||
# C1 pile segments (bottom to top), column, coping extents
|
||||
z = PI["z_bottom"]
|
||||
for k, (s, b) in enumerate(zip(PI["segments"], pls), 1):
|
||||
bb = bbox(b)
|
||||
check("C1", f"pile segment {k} diameter X", s["d"], bb.Max.X - bb.Min.X)
|
||||
check("C1", f"pile segment {k} diameter Y", s["d"], bb.Max.Y - bb.Min.Y)
|
||||
check("C1", f"pile segment {k} Zbot", z, bb.Min.Z)
|
||||
check("C1", f"pile segment {k} Ztop", z + s["length"], bb.Max.Z)
|
||||
z += s["length"]
|
||||
check("C1", "pile top = EL text", CO["z0"], z)
|
||||
bb = bbox(cl)
|
||||
check("C1", "column diameter X", CO["d"], bb.Max.X - bb.Min.X)
|
||||
check("C1", "column diameter Y", CO["d"], bb.Max.Y - bb.Min.Y)
|
||||
check("C1", "column Zbot", CO["z0"], bb.Min.Z)
|
||||
check("C1", "column Ztop", ZB, bb.Max.Z)
|
||||
bb = bbox(cp)
|
||||
check("C1", "coping length X (bottom tips)", 2 * HT, bb.Max.X - bb.Min.X)
|
||||
check("C1", "coping width Y", 2 * WT, bb.Max.Y - bb.Min.Y)
|
||||
check("C1", "coping Zbot", ZB, bb.Min.Z)
|
||||
check("C1", "coping Ztop", max(ZTL, ZTR), bb.Max.Z)
|
||||
check("C1", "coping centre X", 0, (bb.Max.X + bb.Min.X) / 2)
|
||||
check("C1", "total height (pile bottom - coping top left)", G["total_height"], ZTL - PI["z_bottom"])
|
||||
|
||||
# C4 front section at Y = 0
|
||||
def section(plane):
|
||||
ok, curves, pts = rg.Intersect.Intersection.BrepPlane(cp, plane, 0.01)
|
||||
return rg.Curve.JoinCurves(curves, 0.1)
|
||||
|
||||
sec = section(rg.Plane(rg.Point3d(0, 0, 0), rg.Vector3d.YAxis))
|
||||
flag("C4", "front section closed single loop", len(sec) == 1 and sec[0].IsClosed, f"{len(sec)} loop(s)")
|
||||
|
||||
def hits(crv, a, b):
|
||||
return sorted(ev.PointA for ev in rg.Intersect.Intersection.CurveCurve(crv, rg.LineCurve(a, b), 0.01, 0.01))
|
||||
|
||||
def z_at(x, upper):
|
||||
zs = sorted(p.Z for p in hits(sec[0], rg.Point3d(x, 0, 0), rg.Point3d(x, 0, 200000)))
|
||||
return zs[-1] if upper else zs[0]
|
||||
|
||||
for x in (0, HB - 1, -(HB - 1)):
|
||||
check("C4", f"bottom at X={x:+.0f} (column top)", ZB, z_at(x, False))
|
||||
for x in (1500, -1500, 2500, -2500, 3500, -3500, HT - 5, -(HT - 5)):
|
||||
check("C4", f"soffit R{R:.0f} at X={x:+.0f}", CZ + math.sqrt(R * R - (abs(x) + CX) ** 2), z_at(x, False))
|
||||
for x in (-3000, -1000, 1000, 3000):
|
||||
check("C4", f"top at X={x:+.0f}", ZTL if x < 0 else ZTR, z_at(x, True))
|
||||
for x in (-(TOPH + HT) / 2, (TOPH + HT) / 2):
|
||||
zt = ZTL if x < 0 else ZTR
|
||||
check("C4", f"end face at X={x:+.0f} (top {TOPH:.0f} -> bottom {HT:.0f})",
|
||||
ZT + (zt - ZT) * (HT - abs(x)) / (HT - TOPH), z_at(x, True))
|
||||
check("C4", "top step at the pier centre (right - left)", C["top_step"], z_at(1, True) - z_at(-1, True))
|
||||
|
||||
# C4 side section at X = 1 (X = 0 lies on the top step face, 2026-10-13): width 2,000 at the column top ->
|
||||
# 2,200 at the tip level, constant above
|
||||
sec_s = section(rg.Plane(rg.Point3d(1, 0, 0), rg.Vector3d.XAxis))
|
||||
flag("C4", "side section closed single loop", len(sec_s) == 1 and sec_s[0].IsClosed, f"{len(sec_s)} loop(s)")
|
||||
for zz in (ZB + 1, ZB + 750, ZT - 1, ZT + 500):
|
||||
ys = [p.Y for p in hits(sec_s[0], rg.Point3d(1, -5000, zz), rg.Point3d(1, 5000, zz))]
|
||||
w = WB + (WT - WB) * min(zz - ZB, ZT - ZB) / (ZT - ZB)
|
||||
check("C4", f"side width at Z={zz:.0f}", 2 * w, max(ys) - min(ys) if ys else 0)
|
||||
|
||||
# C4 plan end (ruled face: convex arc at the tip level, straight top edge)
|
||||
for sx in (-1, 1):
|
||||
zt = ZTL if sx < 0 else ZTR
|
||||
for zz, y in ((ZT + 5, 0), (ZT + 5, WT - 10), ((ZT + zt) / 2, 0), ((ZT + zt) / 2, WT - 10), (zt - 5, WT - 10)):
|
||||
xe = end_x(y, zz, zt)
|
||||
ins = cp.IsPointInside(rg.Point3d(sx * (xe - 3), y, zz), 0.01, False)
|
||||
out = not cp.IsPointInside(rg.Point3d(sx * (xe + 3), y, zz), 0.01, False)
|
||||
flag("C4", f"end face {'left' if sx < 0 else 'right'} at y={y:.0f} z={zz:.0f} [작도 corner {PCORNER:.0f}]",
|
||||
ins and out, f"x_end {sx * xe:.1f}: inside 3 mm in {ins}, outside 3 mm out {out}")
|
||||
|
||||
# C3 joints and overlaps
|
||||
def inter_vol(a, b):
|
||||
r = rg.Brep.CreateBooleanIntersection(a, b, 0.01)
|
||||
return sum(vol(x) for x in r) if r else 0.0
|
||||
chain = pls + [cl, cp]
|
||||
names = [f"PL{k:02d}" for k in range(1, len(pls) + 1)] + ["CL", "CP"]
|
||||
for (na, a), (nb, b) in zip(zip(names, chain), zip(names[1:], chain[1:])):
|
||||
check("C3", f"{na} top = {nb} bottom", bbox(a).Max.Z, bbox(b).Min.Z, tol=0.01)
|
||||
check("C3", f"overlap {na}-{nb} (mm3)", 0, inter_vol(a, b), tol=1.0)
|
||||
|
||||
# C6 volumes
|
||||
for k, (s, b) in enumerate(zip(PI["segments"], pls), 1):
|
||||
v = math.pi * (s["d"] / 2000) ** 2 * s["length"] / 1000
|
||||
check("C6", f"pile segment {k} volume m3", v, vol(b) / 1e9, tol=v * 0.001)
|
||||
cv_ = math.pi * (CO["d"] / 2000) ** 2 * CO["length"] / 1000
|
||||
check("C6", "column volume m3", cv_, vol(cl) / 1e9, tol=cv_ * 0.001)
|
||||
cpv = coping_volume_expected() / 1e9
|
||||
check("C6", "coping volume m3 (dimension outline)", cpv, vol(cp) / 1e9, tol=cpv * 0.001)
|
||||
|
||||
# C7 pedestals and pads
|
||||
S = G["seats"]
|
||||
nos = sorted(int(k) for k in S["table"])
|
||||
n0 = len(PID) + 3
|
||||
seats = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BS")}
|
||||
pads = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BP")}
|
||||
flag("C7", "pedestal / pad counts", sorted(seats) == nos and sorted(pads) == nos, f"seats={len(seats)} pads={len(pads)}")
|
||||
allb = list(seats.values()) + list(pads.values())
|
||||
flag("C7", "IsSolid pedestals+pads", all(b.IsSolid for b in allb), f"{sum(b.IsSolid for b in allb)}/{len(allb)}")
|
||||
p_along, p_across = S["pedestal_along"], S["pedestal_across"]
|
||||
m0, m1 = S["pad_margin"]
|
||||
|
||||
def bottom_edges(b):
|
||||
zmin = min(v.Location.Z for v in b.Vertices)
|
||||
return [e.PointAtEnd - e.PointAtStart for e in b.Edges
|
||||
if abs(e.PointAtStart.Z - zmin) < 0.01 and abs(e.PointAtEnd.Z - zmin) < 0.01
|
||||
and e.PointAtStart.DistanceTo(e.PointAtEnd) > 1]
|
||||
|
||||
for no in nos:
|
||||
elx, h1, h2, a, b = S["table"][str(no)]
|
||||
x, y = S["pos"][str(no)]
|
||||
top = ZTL if x < 0 else ZTR
|
||||
s, p = seats[no], pads[no]
|
||||
sb, pb = bbox(s), bbox(p)
|
||||
check("C7", f"NO.{no} table EL.'X' - h1 - h2 = coping top ({'left' if x < 0 else 'right'})", top, elx - h1 - h2)
|
||||
check("C7", f"NO.{no} pedestal bottom = coping top", top, sb.Min.Z)
|
||||
check("C7", f"NO.{no} pedestal top = top + h1", top + h1, sb.Max.Z)
|
||||
check("C7", f"NO.{no} pad top = EL.'X'", elx, pb.Max.Z)
|
||||
ed = bottom_edges(s)
|
||||
al = [v for v in ed if abs(v.Y) > abs(v.X)]
|
||||
ac = [v for v in ed if abs(v.Y) <= abs(v.X)]
|
||||
check("C7", f"NO.{no} pedestal along (C3)", p_along, al[0].Length if al else 0)
|
||||
check("C7", f"NO.{no} pedestal across (C6)", p_across, ac[0].Length if ac else 0)
|
||||
if al:
|
||||
v = al[0] if al[0].Y > 0 else -al[0]
|
||||
check("C7", f"NO.{no} pedestal rotation deg [작도]", S["rotation_deg"][str(no)],
|
||||
math.degrees(math.atan2(v.X, v.Y)), tol=0.05)
|
||||
check("C7", f"NO.{no} pedestal centre X", x, (sb.Max.X + sb.Min.X) / 2)
|
||||
check("C7", f"NO.{no} pedestal centre Y", y, (sb.Max.Y + sb.Min.Y) / 2)
|
||||
c = rg.VolumeMassProperties.Compute(p).Centroid
|
||||
check("C7", f"NO.{no} pad centre X", x, c.X)
|
||||
check("C7", f"NO.{no} pad centre Y", y, c.Y)
|
||||
mm_ = (m0 + m1) / 2
|
||||
pv = h2 / 6 * ((a + m0) * (b + m0) + (a + m1) * (b + m1) + 4 * (a + mm_) * (b + mm_))
|
||||
check("C7", f"NO.{no} pad volume (prismatoid A,B)", pv, vol(p), tol=pv * 0.001)
|
||||
check("C7", f"NO.{no} overlap pedestal-coping (mm3)", 0, inter_vol(s, cp), tol=1.0)
|
||||
check("C7", f"NO.{no} overlap pad-pedestal (mm3)", 0, inter_vol(p, s), tol=1.0)
|
||||
corners = [rg.Point3d(q.X, q.Y, sb.Min.Z - 1) for q in [v.Location for v in s.Vertices] if abs(q.Z - sb.Min.Z) < 0.01]
|
||||
flag("C7", f"NO.{no} pedestal footprint on the coping top", all(cp.IsPointInside(q, 0.01, False) for q in corners),
|
||||
f"{len(corners)} corners tested 1 mm below")
|
||||
write_result(RESULT_PATH, results, dict(volumes_m3={**{f"pile{k}": round(vol(b) / 1e9, 3) for k, b in enumerate(pls, 1)},
|
||||
"column": round(vol(cl) / 1e9, 3), "coping": round(vol(cp) / 1e9, 3)}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,242 @@
|
||||
"""G1 gate checker: concrete, piles, pedestals and pads (checks C1-C7) inside Rhino 8.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Independent expected values from EXPECTED_PATH
|
||||
section G1 (drawing texts / DXF); generator params are not read.
|
||||
Ported from Code/교각모델링/verify_rhino.py (same checks and order; fingerprint part moved to the gate runner).
|
||||
"""
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, TOL_MM, G = X["pier_id"], X["tol_mm"], X["G1"]
|
||||
results = []
|
||||
|
||||
f, c_, cl_, pl_ = G["footing"], G["coping"], G["column"], G["piles"]
|
||||
FT_TOP = f["z0"] + f["thickness"]
|
||||
EXPECTED = {
|
||||
"footing": dict(dx=f["dx"], dy=f["dy"], z0=f["z0"], z1=FT_TOP),
|
||||
"lean": dict(dx=f["dx"] + 2 * G["lean"]["margin"], dy=f["dy"] + 2 * G["lean"]["margin"],
|
||||
z0=f["z0"] - G["lean"]["thickness"], z1=f["z0"]),
|
||||
"column": dict(d=cl_["d"], z0=FT_TOP, z1=FT_TOP + cl_["height"]),
|
||||
"coping": dict(dx=c_["dx"], dy=c_["dy"], z_bottom=c_["z_top_left"] - c_["rise_upper"] - c_["rise_lower"],
|
||||
z_tip_bottom=c_["z_top_left"] - c_["rise_upper"], z_top_left=c_["z_top_left"],
|
||||
z_top_right=c_["z_top_right"], z_top_center=c_["z_top_center"],
|
||||
flat_half=c_["dx"] / 2 - c_["flat_from_tip"], top_half=c_["dx"] / 2 - c_["end_inset"],
|
||||
radius=c_["radius"]),
|
||||
"piles": dict(nx=pl_["nx"], ny=pl_["ny"], pitch_x=pl_.get("pitch_x", pl_.get("pitch")),
|
||||
pitch_y=pl_.get("pitch_y", pl_.get("pitch")), edge=pl_["edge"], d=pl_["d"], length=pl_["length"]),
|
||||
}
|
||||
_a = G["dxf_arc_centres"]
|
||||
DXF_LEFT_ARC_CENTER = (_a["left"][0] - _a["datum_x"], _a["datum_el"] + (_a["left"][1] - _a["datum_y"]))
|
||||
DXF_RIGHT_ARC_CENTER = (_a["right"][0] - _a["datum_x"], _a["datum_el"] + (_a["right"][1] - _a["datum_y"]))
|
||||
|
||||
S = G["seats"]
|
||||
TABLE = {int(k): tuple(v) for k, v in S["table"].items()}
|
||||
NG = S["girders"]
|
||||
STEP_TOP = {int(k): v for k, v in S["step_top"].items()}
|
||||
STEP_X = {int(k): tuple(v) for k, v in S["step_x"].items()}
|
||||
ROW_Y = S["row_y"]
|
||||
PITCH = S["pitch_chain"][1] / S["pitch_chain"][0]
|
||||
|
||||
|
||||
def objs(prefix):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
||||
|
||||
|
||||
def brep(o):
|
||||
return o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry)
|
||||
|
||||
|
||||
def vol(b):
|
||||
return rg.VolumeMassProperties.Compute(b).Volume
|
||||
|
||||
|
||||
def bbox(b):
|
||||
return b.GetBoundingBox(True)
|
||||
|
||||
|
||||
def check(cid, name, expected, actual, tol=TOL_MM):
|
||||
ok = abs(expected - actual) <= tol
|
||||
results.append(dict(check=cid, item=name, expected=round(expected, 3), actual=round(actual, 3),
|
||||
diff=round(actual - expected, 3), tol=tol, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def flag(cid, name, ok, detail):
|
||||
results.append(dict(check=cid, item=name, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def check_seats(cp):
|
||||
n0 = len(PID) + 3
|
||||
seats = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BS")}
|
||||
pads = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BP")}
|
||||
nos = sorted(TABLE)
|
||||
flag("C7", "pedestal / pad counts", sorted(seats) == nos and sorted(pads) == nos,
|
||||
f"seats={len(seats)} pads={len(pads)}")
|
||||
allb = list(seats.values()) + list(pads.values())
|
||||
flag("C7", "IsSolid pedestals+pads", all(b.IsSolid for b in allb), f"{sum(b.IsSolid for b in allb)}/{2 * len(nos)}")
|
||||
m0, m1 = S["pad_margin"]
|
||||
pa, pc = S["pedestal_plan"]
|
||||
half_w = c_["dy"] / 2
|
||||
pad_x = {}
|
||||
for no, (elx, h1, h2, a, b) in TABLE.items():
|
||||
g = (no - 1) % NG + 1
|
||||
row = "front" if no <= NG else "rear"
|
||||
s, p = seats[no], pads[no]
|
||||
sb, pb = bbox(s), bbox(p)
|
||||
check("C7", f"NO.{no} pedestal bottom = step {g}", STEP_TOP[g], sb.Min.Z)
|
||||
check("C7", f"NO.{no} pedestal top = step + h1", STEP_TOP[g] + h1, sb.Max.Z)
|
||||
check("C7", f"NO.{no} pad top = EL.'X'", elx, pb.Max.Z)
|
||||
flag("C7", f"NO.{no} pedestal within step {g} / coping width",
|
||||
STEP_X[g][0] - TOL_MM <= sb.Min.X and sb.Max.X <= STEP_X[g][1] + TOL_MM
|
||||
and -half_w - TOL_MM <= sb.Min.Y and sb.Max.Y <= half_w + TOL_MM,
|
||||
f"x {sb.Min.X:.1f}..{sb.Max.X:.1f} y {sb.Min.Y:.1f}..{sb.Max.Y:.1f}")
|
||||
area = pa / 1000 * pc / 1000
|
||||
check("C7", f"NO.{no} pedestal half area (m2)", area, vol(s) / h1 / 1e6, tol=area * 0.002)
|
||||
mm_ = (m0 + m1) / 2
|
||||
pv = h2 / 6 * ((a + m0) * (b + m0) + (a + m1) * (b + m1) + 4 * (a + mm_) * (b + mm_))
|
||||
check("C7", f"NO.{no} pad volume (prismatoid A,B)", pv, vol(p), tol=pv * 0.001)
|
||||
c = rg.VolumeMassProperties.Compute(p).Centroid
|
||||
check("C7", f"NO.{no} pad centre Y (S1)", ROW_Y[row], c.Y, tol=2.0)
|
||||
pad_x.setdefault(row, {})[g] = c.X
|
||||
check("C7", f"NO.{no} overlap pedestal-coping (mm3)", 0,
|
||||
sum(vol(x) for x in (rg.Brep.CreateBooleanIntersection(s, cp, 0.01) or [])), tol=1.0)
|
||||
check("C7", f"NO.{no} overlap pad-pedestal (mm3)", 0,
|
||||
sum(vol(x) for x in (rg.Brep.CreateBooleanIntersection(p, s, 0.01) or [])), tol=1.0)
|
||||
for row, xs in pad_x.items():
|
||||
for g in range(1, NG):
|
||||
check("C7", f"{row} pad pitch girder {g}-{g + 1}", PITCH, xs[g + 1] - xs[g], tol=1.0)
|
||||
first = S["rear_first_from_tip"]
|
||||
for g in range(1, NG + 1):
|
||||
check("C7", f"rear pad centre X girder {g} (dim {first / 1000:.3f}+{PITCH / 1000:.3f}n)",
|
||||
-c_["dx"] / 2 + first + (g - 1) * PITCH, pad_x["rear"][g], tol=2.0)
|
||||
for row, rng in (("front", range(1, NG + 1)), ("rear", range(NG + 1, 2 * NG + 1))):
|
||||
cxs = [rg.VolumeMassProperties.Compute(seats[n]).Centroid.X for n in rng]
|
||||
for g in range(NG - 1):
|
||||
check("C7", f"{row} pedestal pitch girder {g + 1}-{g + 2}", PITCH, cxs[g + 1] - cxs[g], tol=1.0)
|
||||
|
||||
|
||||
def main():
|
||||
ids = {k: f"{PID}-{k}" for k in ("LC", "FT", "CL", "CP", "PL")}
|
||||
lc, ft, cl, cp = (brep(objs(ids[k])[0]) for k in ("LC", "FT", "CL", "CP"))
|
||||
piles = [brep(o) for o in objs(ids["PL"])]
|
||||
e = EXPECTED
|
||||
p = e["piles"]
|
||||
|
||||
counts = {ids[k]: len(objs(ids[k])) for k in ("LC", "FT", "CL", "CP", "PL")}
|
||||
exp_counts = {ids["LC"]: 1, ids["FT"]: 1, ids["CL"]: 1, ids["CP"]: 1, ids["PL"]: p["nx"] * p["ny"]}
|
||||
flag("C5", "object counts", counts == exp_counts, str(counts))
|
||||
|
||||
all_b = [lc, ft, cl, cp] + piles
|
||||
flag("C2", f"IsSolid all {len(all_b)}", all(b.IsSolid for b in all_b), f"{sum(b.IsSolid for b in all_b)}/{len(all_b)}")
|
||||
|
||||
for key, b in (("footing", ft), ("lean", lc)):
|
||||
bb = bbox(b)
|
||||
check("C1", f"{key} X", e[key]["dx"], bb.Max.X - bb.Min.X)
|
||||
check("C1", f"{key} Y", e[key]["dy"], bb.Max.Y - bb.Min.Y)
|
||||
check("C1", f"{key} Zbot", e[key]["z0"], bb.Min.Z)
|
||||
check("C1", f"{key} Ztop", e[key]["z1"], bb.Max.Z)
|
||||
check("C1", f"{key} centre X", 0, (bb.Max.X + bb.Min.X) / 2)
|
||||
check("C1", f"{key} centre Y", 0, (bb.Max.Y + bb.Min.Y) / 2)
|
||||
bb = bbox(cl)
|
||||
check("C1", "column diameter X", e["column"]["d"], bb.Max.X - bb.Min.X)
|
||||
check("C1", "column diameter Y", e["column"]["d"], bb.Max.Y - bb.Min.Y)
|
||||
check("C1", "column Zbot", e["column"]["z0"], bb.Min.Z)
|
||||
check("C1", "column Ztop", e["column"]["z1"], bb.Max.Z)
|
||||
bb = bbox(cp)
|
||||
check("C1", "coping length X", e["coping"]["dx"], bb.Max.X - bb.Min.X)
|
||||
check("C1", "coping width Y", e["coping"]["dy"], bb.Max.Y - bb.Min.Y)
|
||||
check("C1", "coping Zbot", e["coping"]["z_bottom"], bb.Min.Z)
|
||||
check("C1", "coping Ztop (right end)", e["coping"]["z_top_right"], bb.Max.Z)
|
||||
xs = sorted({round((bbox(b).Min.X + bbox(b).Max.X) / 2, 1) for b in piles})
|
||||
ys = sorted({round((bbox(b).Min.Y + bbox(b).Max.Y) / 2, 1) for b in piles})
|
||||
flag("C1", "pile grid", len(xs) == p["nx"] and len(ys) == p["ny"], f"x={xs} y={ys}")
|
||||
check("C1", "pile edge X", p["edge"], xs[0] - (-e["footing"]["dx"] / 2))
|
||||
check("C1", "pile edge Y", p["edge"], ys[0] - (-e["footing"]["dy"] / 2))
|
||||
for a, b2 in zip(xs, xs[1:]):
|
||||
check("C1", "pile pitch X", p["pitch_x"], b2 - a)
|
||||
for a, b2 in zip(ys, ys[1:]):
|
||||
check("C1", "pile pitch Y", p["pitch_y"], b2 - a)
|
||||
pb = bbox(piles[0])
|
||||
check("C1", "pile diameter", p["d"], pb.Max.X - pb.Min.X)
|
||||
check("C1", "pile length", p["length"], pb.Max.Z - pb.Min.Z)
|
||||
check("C1", "pile head embedment", pl_["head_embedment"], pb.Max.Z - e["footing"]["z0"])
|
||||
|
||||
# C4 coping front section at Y=0 (re-projection to drawing)
|
||||
plane = rg.Plane(rg.Point3d(0, 0, 0), rg.Vector3d.YAxis)
|
||||
ok, curves, pts = rg.Intersect.Intersection.BrepPlane(cp, plane, 0.01)
|
||||
sec = rg.Curve.JoinCurves(curves, 0.1)
|
||||
flag("C4", "section closed single loop", len(sec) == 1 and sec[0].IsClosed, f"{len(sec)} loop(s)")
|
||||
c = e["coping"]
|
||||
|
||||
def z_at(x, upper):
|
||||
line = rg.LineCurve(rg.Point3d(x, 0, 0), rg.Point3d(x, 0, 60000))
|
||||
zs = sorted(ev.PointA.Z for ev in rg.Intersect.Intersection.CurveCurve(sec[0], line, 0.01, 0.01))
|
||||
return zs[-1] if upper else zs[0]
|
||||
|
||||
sx = {k: (v[0] + v[1]) / 2 for k, v in STEP_X.items()} # sample each top step at its middle
|
||||
check("C4", "bottom flat at X=0", c["z_bottom"], z_at(0, False))
|
||||
check("C4", f"bottom flat at X=+{c['flat_half'] - 1:.0f}", c["z_bottom"], z_at(c["flat_half"] - 1, False))
|
||||
check("C4", f"bottom at X=-{c['top_half'] + 199:.0f} (tip)", c["z_tip_bottom"], z_at(-c["top_half"] - 199, False), tol=2.0)
|
||||
check("C4", f"top left step (X={sx[1]:.0f})", c["z_top_left"], z_at(sx[1], True))
|
||||
check("C4", "top centre step (X=0)", c["z_top_center"], z_at(0, True))
|
||||
check("C4", f"top right step (X={sx[NG]:.0f})", c["z_top_right"], z_at(sx[NG], True))
|
||||
worst = 0.0
|
||||
step = (c["dx"] / 2 - c["flat_half"] - 200) / 50
|
||||
for side, (cx, cz) in ((1, DXF_RIGHT_ARC_CENTER), (-1, DXF_LEFT_ARC_CENTER)):
|
||||
for k in range(1, 50):
|
||||
x = side * (c["flat_half"] + 100 + k * step)
|
||||
zb = z_at(x, False)
|
||||
worst = max(worst, abs(math.hypot(x - cx, zb - cz) - c["radius"]))
|
||||
check("C4", "soffit radius max deviation vs DXF ARC centre (98 samples)", 0, worst)
|
||||
|
||||
# C3 joints and overlaps
|
||||
def inter_vol(a, b):
|
||||
r = rg.Brep.CreateBooleanIntersection(a, b, 0.01)
|
||||
return sum(vol(x) for x in r) if r else 0.0
|
||||
|
||||
check("C3", "LC top = FT bottom", bbox(lc).Max.Z, bbox(ft).Min.Z, tol=0.01)
|
||||
check("C3", "FT top = CL bottom", bbox(ft).Max.Z, bbox(cl).Min.Z, tol=0.01)
|
||||
check("C3", "CL top = CP bottom", bbox(cl).Max.Z, bbox(cp).Min.Z, tol=0.01)
|
||||
check("C3", "overlap LC-FT (mm3)", 0, inter_vol(lc, ft), tol=1.0)
|
||||
check("C3", "overlap FT-CL (mm3)", 0, inter_vol(ft, cl), tol=1.0)
|
||||
check("C3", "overlap CL-CP (mm3)", 0, inter_vol(cl, cp), tol=1.0)
|
||||
check("C3", "overlap LC-piles (mm3)", 0, sum(inter_vol(lc, b) for b in piles), tol=1.0)
|
||||
emb = len(piles) * math.pi * (p["d"] / 2) ** 2 * pl_["head_embedment"]
|
||||
check("C3", "intended overlap FT-piles (mm3)", emb, sum(inter_vol(ft, b) for b in piles), tol=emb * 0.001)
|
||||
flag("C3", "column within coping flat zone", e["column"]["d"] / 2 <= c["flat_half"]
|
||||
and e["column"]["d"] / 2 <= c["dy"] / 2,
|
||||
f"r={e['column']['d'] / 2:.0f} <= {c['flat_half']:.0f} (X), {c['dy'] / 2:.0f} (Y)")
|
||||
|
||||
# C6 volumes vs analytic formulas
|
||||
fv = f["dx"] / 1000 * f["dy"] / 1000 * f["thickness"] / 1000
|
||||
check("C6", "footing volume m3", fv, vol(ft) / 1e9, tol=fv * 0.001)
|
||||
cv_ = math.pi * (cl_["d"] / 2000) ** 2 * cl_["height"] / 1000
|
||||
check("C6", "column volume m3", cv_, vol(cl) / 1e9, tol=round(cv_, 2) * 0.001)
|
||||
lv = (e["lean"]["dx"] / 1000 * e["lean"]["dy"] / 1000 * G["lean"]["thickness"] / 1000
|
||||
- len(piles) * math.pi * (p["d"] / 2000) ** 2 * G["lean"]["thickness"] / 1000)
|
||||
check("C6", "lean volume m3", lv, vol(lc) / 1e9, tol=lv * 0.001)
|
||||
poly = [tuple(q) for q in G["coping_outline"]]
|
||||
area = 0.5 * abs(sum(x1 * y2 - x2 * y1 for (x1, y1), (x2, y2) in zip(poly, poly[1:] + poly[:1])))
|
||||
(x1, z1), (x2, z2) = poly[1], poly[2]
|
||||
chord = math.hypot(x2 - x1, z2 - z1)
|
||||
theta = 2 * math.asin(chord / 2 / c["radius"])
|
||||
seg = c["radius"] ** 2 / 2 * (theta - math.sin(theta))
|
||||
cv = (area - 2 * seg) * c["dy"] / 1e9
|
||||
check("C6", "coping volume m3", cv, vol(cp) / 1e9, tol=cv * 0.001)
|
||||
|
||||
if objs(f"{PID}-BS") or objs(f"{PID}-BP"):
|
||||
check_seats(cp)
|
||||
|
||||
write_result(RESULT_PATH, results, dict(
|
||||
volumes_m3={k: round(vol(b) / 1e9, 3) for k, b in (("lean", lc), ("footing", ft), ("column", cl), ("coping", cp))},
|
||||
pile_volume_each_m3=round(vol(piles[0]) / 1e9, 3)))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,257 @@
|
||||
"""G1 gate checker for the "tapered T" form (Ulsan P1, 2026-10-13): footing, lean, column, coping, pedestals, pads.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G1 of EXPECTED_PATH) come from
|
||||
drawing dimension texts / EL texts / tables; generator params are not read.
|
||||
Optional (Ulsan P2, 2026-10-15; absent -> the checks are unchanged): G1.footing.rot_deg (foundation drawn in its own axes, rotated
|
||||
counter-clockwise about the pier centre; lean / footing / piles are checked after rotating them back and their rotated plan extents are
|
||||
compared with the closed form), G1.piles {nx, ny, pitch_x, pitch_y, edge_x, edge_y, d, length, head_embedment}.
|
||||
Checks: C5 population, C2 closed solids, C1 extents, C4 coping section at Y=0 and plan corner rounding,
|
||||
C3 joints and overlaps, C6 volumes (coping: plan section areas integrated over Z from the
|
||||
dimensions, R corner fillets in closed form), C7 pedestals / pads per bearing.
|
||||
"""
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, TOL, G = X["pier_id"], X["tol_mm"], X["G1"]
|
||||
F, LN, CO, C, PI = G["footing"], G["lean"], G["column"], G["coping"], G.get("piles")
|
||||
ROT = F.get("rot_deg", 0.0)
|
||||
FT_TOP = F["z0"] + F["thickness"]
|
||||
Z_CB = FT_TOP + CO["height"] # column top = coping bottom
|
||||
results = []
|
||||
|
||||
|
||||
def objs(prefix):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
||||
|
||||
|
||||
def brep(o):
|
||||
return o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry)
|
||||
|
||||
|
||||
def vol(b):
|
||||
return rg.VolumeMassProperties.Compute(b).Volume
|
||||
|
||||
|
||||
def bbox(b):
|
||||
return b.GetBoundingBox(True)
|
||||
|
||||
|
||||
def unrot(b):
|
||||
"""Copy of b rotated back by the foundation rotation (identity when the foundation is not rotated)."""
|
||||
b = b.DuplicateBrep()
|
||||
if ROT:
|
||||
b.Transform(rg.Transform.Rotation(-math.radians(ROT), rg.Vector3d.ZAxis, rg.Point3d.Origin))
|
||||
return b
|
||||
|
||||
|
||||
def check(cid, name, expected, actual, tol=TOL):
|
||||
results.append(dict(check=cid, item=name, expected=round(expected, 3), actual=round(actual, 3),
|
||||
diff=round(actual - expected, 3), tol=tol, result="PASS" if abs(actual - expected) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(cid, name, ok, detail=""):
|
||||
results.append(dict(check=cid, item=name, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
# expected coping outline from the dimensions (front view, Y = 0)
|
||||
FLAT, TIP, TOPH, RISE = C["bottom_flat_half"], C["tip_half"], C["top_half"], C["soffit_rise"]
|
||||
ZTL, ZTR = C["z_top_left"], C["z_top_right"]
|
||||
Z_TIP = Z_CB + RISE
|
||||
|
||||
|
||||
def bottom_z(x):
|
||||
ax = abs(x)
|
||||
return Z_CB if ax <= FLAT else Z_CB + RISE * (ax - FLAT) / (TIP - FLAT)
|
||||
|
||||
|
||||
def top_z(x):
|
||||
"""Upper outline: top level of the side, then the battered end face from top_half down to the tip."""
|
||||
zt = ZTL if x < 0 else ZTR
|
||||
ax = abs(x)
|
||||
if ax <= TOPH:
|
||||
return zt
|
||||
return zt - (zt - Z_TIP) * (ax - TOPH) / (TIP - TOPH)
|
||||
|
||||
|
||||
def coping_volume_expected():
|
||||
"""Integrate the plan section area over Z. At height z each side reaches x_end(z): the soffit line below the
|
||||
tip level (plan outline: tip length, corner radius R), the battered end face above it (rounded outline
|
||||
shrinking from the tip length to the top length). Corner cuts use the closed form of the R fillet area."""
|
||||
W, R = C["width_y"], C["plan_corner_radius"]
|
||||
|
||||
def cut_area(e): # area cut by one R fillet over 0..e beyond its start
|
||||
e = min(max(e, 0.0), R)
|
||||
return R * e - (e / 2 * math.sqrt(R * R - e * e) + R * R / 2 * math.asin(e / R))
|
||||
|
||||
total, n = 0.0, 40000
|
||||
z0, z1 = Z_CB, max(ZTL, ZTR)
|
||||
dz = (z1 - z0) / n
|
||||
for i in range(n):
|
||||
z = z0 + (i + 0.5) * dz
|
||||
area = 0.0
|
||||
for zt in (ZTL, ZTR):
|
||||
if z > zt:
|
||||
continue
|
||||
if z < Z_TIP:
|
||||
x_end = FLAT + (TIP - FLAT) * (z - Z_CB) / RISE # soffit line
|
||||
rect_end = TIP
|
||||
else:
|
||||
x_end = TIP - (TIP - TOPH) * (z - Z_TIP) / (zt - Z_TIP)
|
||||
rect_end = x_end
|
||||
area += W * x_end - 2 * cut_area(x_end - (rect_end - R))
|
||||
total += area * dz
|
||||
return total
|
||||
|
||||
|
||||
def main():
|
||||
ids = {k: f"{PID}-{k}" for k in ("LC", "FT", "CL", "CP", "PL")}
|
||||
S = G["seats"]
|
||||
nos = sorted(int(k) for k in S["table"])
|
||||
counts = {k: len(objs(v)) for k, v in ids.items()}
|
||||
flag("C5", "object counts", counts == {"LC": 1, "FT": 1, "CL": 1, "CP": 1, "PL": PI["nx"] * PI["ny"] if PI else 0}, str(counts))
|
||||
lc, ft, cl, cp = (brep(objs(ids[k])[0]) for k in ("LC", "FT", "CL", "CP"))
|
||||
piles = [brep(o) for o in objs(ids["PL"])]
|
||||
allb = [lc, ft, cl, cp] + piles
|
||||
flag("C2", f"IsSolid all {len(allb)}", all(b.IsSolid for b in allb), f"{sum(b.IsSolid for b in allb)}/{len(allb)}")
|
||||
|
||||
for key, b, dx, dy, z0, z1 in (("footing", ft, F["dx"], F["dy"], F["z0"], FT_TOP),
|
||||
("lean", lc, F["dx"] + 2 * LN["margin"], F["dy"] + 2 * LN["margin"],
|
||||
F["z0"] - LN["thickness"], F["z0"])):
|
||||
if ROT: # raw plan extent of the rotated rectangle (closed form)
|
||||
t = math.radians(abs(ROT))
|
||||
rb = bbox(b)
|
||||
check("C1", f"{key} rotated extent X", dx * math.cos(t) + dy * math.sin(t), rb.Max.X - rb.Min.X)
|
||||
check("C1", f"{key} rotated extent Y", dx * math.sin(t) + dy * math.cos(t), rb.Max.Y - rb.Min.Y)
|
||||
bb = bbox(unrot(b))
|
||||
check("C1", f"{key} X", dx, bb.Max.X - bb.Min.X)
|
||||
check("C1", f"{key} Y", dy, bb.Max.Y - bb.Min.Y)
|
||||
check("C1", f"{key} Zbot", z0, bb.Min.Z)
|
||||
check("C1", f"{key} Ztop", z1, bb.Max.Z)
|
||||
check("C1", f"{key} centre X", 0, (bb.Max.X + bb.Min.X) / 2)
|
||||
check("C1", f"{key} centre Y", 0, (bb.Max.Y + bb.Min.Y) / 2)
|
||||
bb = bbox(cl)
|
||||
check("C1", "column diameter X", CO["d"], bb.Max.X - bb.Min.X)
|
||||
check("C1", "column diameter Y", CO["d"], bb.Max.Y - bb.Min.Y)
|
||||
check("C1", "column Zbot", FT_TOP, bb.Min.Z)
|
||||
check("C1", "column Ztop", Z_CB, bb.Max.Z)
|
||||
if PI and piles:
|
||||
pr = [bbox(unrot(b)) for b in piles]
|
||||
xs = sorted({round((q.Min.X + q.Max.X) / 2, 1) for q in pr})
|
||||
ys = sorted({round((q.Min.Y + q.Max.Y) / 2, 1) for q in pr})
|
||||
flag("C1", f"pile grid {PI['nx']} x {PI['ny']}", len(xs) == PI["nx"] and len(ys) == PI["ny"], f"x={xs} y={ys}")
|
||||
if len(xs) == PI["nx"] and len(ys) == PI["ny"]:
|
||||
check("C1", "pile edge X", PI["edge_x"], xs[0] + F["dx"] / 2)
|
||||
check("C1", "pile edge Y", PI["edge_y"], ys[0] + F["dy"] / 2)
|
||||
check("C1", "pile pitch X (worst)", 0, max(abs(b - a - PI["pitch_x"]) for a, b in zip(xs, xs[1:])))
|
||||
check("C1", "pile pitch Y (worst)", 0, max(abs(b - a - PI["pitch_y"]) for a, b in zip(ys, ys[1:])))
|
||||
check("C1", "pile diameter", PI["d"], pr[0].Max.X - pr[0].Min.X)
|
||||
check("C1", "pile length", PI["length"], pr[0].Max.Z - pr[0].Min.Z)
|
||||
check("C1", "pile head embedment", PI["head_embedment"], pr[0].Max.Z - F["z0"])
|
||||
bb = bbox(cp)
|
||||
check("C1", "coping length X (tip to tip)", 2 * TIP, bb.Max.X - bb.Min.X)
|
||||
check("C1", "coping width Y", C["width_y"], bb.Max.Y - bb.Min.Y)
|
||||
check("C1", "coping Zbot", Z_CB, bb.Min.Z)
|
||||
check("C1", "coping Ztop", max(ZTL, ZTR), bb.Max.Z)
|
||||
check("C1", "coping centre X", 0, (bb.Max.X + bb.Min.X) / 2)
|
||||
|
||||
# C4 section at Y = 0 against the dimension outline
|
||||
ok, curves, pts = rg.Intersect.Intersection.BrepPlane(cp, rg.Plane(rg.Point3d(0, 0, 0), rg.Vector3d.YAxis), 0.01)
|
||||
sec = rg.Curve.JoinCurves(curves, 0.1)
|
||||
flag("C4", "section closed single loop", len(sec) == 1 and sec[0].IsClosed, f"{len(sec)} loop(s)")
|
||||
|
||||
def z_at(x, upper):
|
||||
line = rg.LineCurve(rg.Point3d(x, 0, 0), rg.Point3d(x, 0, 200000))
|
||||
zs = sorted(ev.PointA.Z for ev in rg.Intersect.Intersection.CurveCurve(sec[0], line, 0.01, 0.01))
|
||||
return zs[-1] if upper else zs[0]
|
||||
|
||||
for x in (0, FLAT - 1, -(FLAT - 1), 3000, -3000, TIP - 5, -(TIP - 5)):
|
||||
check("C4", f"bottom at X={x:+.0f}", bottom_z(x), z_at(x, False))
|
||||
for x in (-2000, 2000, -(TOPH - 10), TOPH - 10, -(TOPH + TIP) / 2, (TOPH + TIP) / 2):
|
||||
check("C4", f"top at X={x:+.0f}", top_z(x), z_at(x, True))
|
||||
check("C4", "top step at the pier centre (left - right)", ZTL - ZTR, z_at(-1, True) - z_at(1, True))
|
||||
# plan corner rounding (R) just above the tips
|
||||
R, W = C["plan_corner_radius"], C["width_y"]
|
||||
zc = Z_TIP + 50
|
||||
inside = lambda x, y: cp.IsPointInside(rg.Point3d(x, y, zc), 0.01, False)
|
||||
# test point on the 45 deg diagonal from the fillet centre, half way between the arc (R) and the sharp corner
|
||||
d = (R + R * math.sqrt(2)) / 2 / math.sqrt(2)
|
||||
corner_x, corner_y = TIP - R + d, W / 2 - R + d
|
||||
flag("C4", f"plan corners rounded R{R} (4 corners outside, edges inside)",
|
||||
all(not inside(sx * corner_x, sy * corner_y) for sx in (1, -1) for sy in (1, -1))
|
||||
and all(inside(sx * (TIP - R - 50), sy * (W / 2 - 10)) for sx in (1, -1) for sy in (1, -1)),
|
||||
f"z {zc}, test point ({corner_x:.0f}, {corner_y:.0f})")
|
||||
|
||||
# C3 joints and overlaps
|
||||
def inter_vol(a, b):
|
||||
r = rg.Brep.CreateBooleanIntersection(a, b, 0.01)
|
||||
return sum(vol(x) for x in r) if r else 0.0
|
||||
check("C3", "LC top = FT bottom", bbox(lc).Max.Z, bbox(ft).Min.Z, tol=0.01)
|
||||
check("C3", "FT top = CL bottom", bbox(ft).Max.Z, bbox(cl).Min.Z, tol=0.01)
|
||||
check("C3", "CL top = CP bottom", bbox(cl).Max.Z, bbox(cp).Min.Z, tol=0.01)
|
||||
check("C3", "overlap LC-FT (mm3)", 0, inter_vol(lc, ft), tol=1.0)
|
||||
check("C3", "overlap FT-CL (mm3)", 0, inter_vol(ft, cl), tol=1.0)
|
||||
check("C3", "overlap CL-CP (mm3)", 0, inter_vol(cl, cp), tol=1.0)
|
||||
if PI:
|
||||
check("C3", "overlap LC-piles (mm3)", 0, sum(inter_vol(lc, b) for b in piles), tol=1.0)
|
||||
emb = len(piles) * math.pi * (PI["d"] / 2) ** 2 * PI["head_embedment"]
|
||||
check("C3", "intended overlap FT-piles (mm3)", emb, sum(inter_vol(ft, b) for b in piles), tol=emb * 0.001)
|
||||
flag("C3", "column within coping bottom flat zone", CO["d"] / 2 <= FLAT and CO["d"] / 2 <= W / 2,
|
||||
f"r={CO['d'] / 2:.0f} <= {FLAT:.0f} (X), {W / 2:.0f} (Y)")
|
||||
|
||||
# C6 volumes
|
||||
fv = F["dx"] * F["dy"] * F["thickness"] / 1e9
|
||||
check("C6", "footing volume m3", fv, vol(ft) / 1e9, tol=fv * 0.001)
|
||||
cv_ = math.pi * (CO["d"] / 2000) ** 2 * CO["height"] / 1000
|
||||
check("C6", "column volume m3", cv_, vol(cl) / 1e9, tol=cv_ * 0.001)
|
||||
holes = len(piles) * math.pi * (PI["d"] / 2) ** 2 if PI else 0
|
||||
lv = ((F["dx"] + 2 * LN["margin"]) * (F["dy"] + 2 * LN["margin"]) - holes) * LN["thickness"] / 1e9
|
||||
check("C6", "lean volume m3 (minus pile holes)", lv, vol(lc) / 1e9, tol=lv * 0.001)
|
||||
cpv = coping_volume_expected() / 1e9
|
||||
check("C6", "coping volume m3 (dimension outline, R corners)", cpv, vol(cp) / 1e9, tol=cpv * 0.001)
|
||||
|
||||
# C7 pedestals and pads
|
||||
n0 = len(PID) + 3
|
||||
seats = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BS")}
|
||||
pads = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BP")}
|
||||
flag("C7", "pedestal / pad counts", sorted(seats) == nos and sorted(pads) == nos, f"seats={len(seats)} pads={len(pads)}")
|
||||
allb = list(seats.values()) + list(pads.values())
|
||||
flag("C7", "IsSolid pedestals+pads", all(b.IsSolid for b in allb), f"{sum(b.IsSolid for b in allb)}/{len(allb)}")
|
||||
px, py = S["pedestal"]
|
||||
m0, m1 = S["pad_margin"]
|
||||
for no in nos:
|
||||
elx, h1, h2, a, b = S["table"][str(no)]
|
||||
x, y = S["pos"][str(no)]
|
||||
top = ZTL if x < 0 else ZTR
|
||||
s, p = seats[no], pads[no]
|
||||
sb, pb = bbox(s), bbox(p)
|
||||
check("C7", f"NO.{no} table EL.'X' - h1 - h2 = coping top ({'left' if x < 0 else 'right'})", top, elx - h1 - h2)
|
||||
check("C7", f"NO.{no} pedestal bottom = coping top", top, sb.Min.Z)
|
||||
check("C7", f"NO.{no} pedestal top = top + h1", top + h1, sb.Max.Z)
|
||||
check("C7", f"NO.{no} pad top = EL.'X'", elx, pb.Max.Z)
|
||||
check("C7", f"NO.{no} pedestal size X", px, sb.Max.X - sb.Min.X)
|
||||
check("C7", f"NO.{no} pedestal size Y", py, sb.Max.Y - sb.Min.Y)
|
||||
check("C7", f"NO.{no} pedestal centre X", x, (sb.Max.X + sb.Min.X) / 2)
|
||||
check("C7", f"NO.{no} pedestal centre Y", y, (sb.Max.Y + sb.Min.Y) / 2)
|
||||
c = rg.VolumeMassProperties.Compute(p).Centroid
|
||||
check("C7", f"NO.{no} pad centre X", x, c.X)
|
||||
check("C7", f"NO.{no} pad centre Y", y, c.Y)
|
||||
mm_ = (m0 + m1) / 2
|
||||
pv = h2 / 6 * ((a + m0) * (b + m0) + (a + m1) * (b + m1) + 4 * (a + mm_) * (b + mm_))
|
||||
check("C7", f"NO.{no} pad volume (prismatoid A,B)", pv, vol(p), tol=pv * 0.001)
|
||||
check("C7", f"NO.{no} overlap pedestal-coping (mm3)", 0,
|
||||
sum(vol(q) for q in (rg.Brep.CreateBooleanIntersection(s, cp, 0.01) or [])), tol=1.0)
|
||||
check("C7", f"NO.{no} overlap pad-pedestal (mm3)", 0,
|
||||
sum(vol(q) for q in (rg.Brep.CreateBooleanIntersection(p, s, 0.01) or [])), tol=1.0)
|
||||
write_result(RESULT_PATH, results, dict(volumes_m3={k: round(vol(b) / 1e9, 3) for k, b in
|
||||
(("lean", lc), ("footing", ft), ("column", cl), ("coping", cp))}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,143 @@
|
||||
"""G2 gate checker: column rebar P1-P6 (check C8) inside Rhino 8.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values from EXPECTED_PATH sections G1 (footing
|
||||
bottom) and G2 (D3 schedule L / counts, D1 dimension chains, section radii); generator params are not read.
|
||||
Ported from Code/교각모델링/verify_rebar.py (same checks and order).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, chain, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, TOL, G = X["pier_id"], X["tol_mm"], X["G2"]
|
||||
SCHED_L, SCHED_N, DIA = G["schedule_L"], G["schedule_n"], G["dia"]
|
||||
FB = X["G1"]["footing"]["z0"]
|
||||
LEVELS = chain(G["hoop_chain"], FB)
|
||||
TIE_LEVELS = chain(G["tie_chain"], FB)
|
||||
R_OUT, R_IN, R_HOOP = G["r_outer"], G["r_inner"], G["r_hoop"]
|
||||
TOP = FB + sum(G["p1_top_parts"])
|
||||
NDIV = G["main_divisions"]
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=TOL):
|
||||
results.append(dict(check="C8", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail):
|
||||
results.append(dict(check="C8", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def ids(prefix):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
||||
|
||||
|
||||
def concrete():
|
||||
out = []
|
||||
for oid in (f"{PID}-CL", f"{PID}-FT", f"{PID}-CP"):
|
||||
o = [x for x in all_objects(doc) if x.Attributes.GetUserString("id") == oid][0]
|
||||
out.append(o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry))
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [o for o in ids(f"{PID}-RC-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
||||
pipes = {m: [o for o in ids(f"{PID}-R-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
||||
for m in SCHED_L:
|
||||
occ = {o.Attributes.GetUserString("occurrence").rstrip("ab") for o in cl[m]}
|
||||
check(f"{m} bar count", SCHED_N[m], len(occ), tol=0)
|
||||
flag(f"{m} pipes closed & paired with centrelines",
|
||||
len(pipes[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pipes[m]),
|
||||
f"pipes {len(pipes[m])} / centrelines {len(cl[m])}")
|
||||
for m in SCHED_L:
|
||||
per = {}
|
||||
for o in cl[m]:
|
||||
k = o.Attributes.GetUserString("occurrence").rstrip("ab")
|
||||
per[k] = per.get(k, 0) + float(o.Attributes.GetUserString("design_length")) + float(
|
||||
o.Attributes.GetUserString("coupler_length") or 0) # schedule L includes coupler b (D3 V, IX)
|
||||
worst = max(abs(v - SCHED_L[m]) for v in per.values())
|
||||
check(f"{m} original length (+coupler b) vs schedule L (worst of {len(per)})", 0, worst)
|
||||
curve = {}
|
||||
for o in cl[m]:
|
||||
k = o.Attributes.GetUserString("occurrence").rstrip("ab")
|
||||
curve[k] = curve.get(k, 0) + o.Geometry.GetLength() + float(
|
||||
o.Attributes.GetUserString("coupler_length") or 0)
|
||||
dev = [v - SCHED_L[m] for v in curve.values()]
|
||||
results.append(dict(check="C8", item=f"{m} curve length - L (record, fillets shorten hooks)",
|
||||
detail=f"min {min(dev):.1f} / max {max(dev):.1f} mm", result="INFO"))
|
||||
if m in G["no_fillet_marks"]:
|
||||
check(f"{m} curve length (+coupler b) vs L (no fillet)", 0, max(abs(d) for d in dev))
|
||||
for m, r in (("P1", R_OUT), ("P2", R_IN)):
|
||||
angs, worst_r = [], 0
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
p = c.PointAtEnd if c.PointAtEnd.Z > c.PointAtStart.Z else c.PointAtStart
|
||||
angs.append(math.degrees(math.atan2(p.Y, p.X)) % 360)
|
||||
worst_r = max(worst_r, abs(math.hypot(p.X, p.Y) - r))
|
||||
check(f"{m} top EL", TOP, p.Z) if o is cl[m][0] else None
|
||||
angs.sort()
|
||||
worst_a = max(abs(a - i * 360 / NDIV) for i, a in enumerate(angs))
|
||||
check(f"{m} radius (worst)", 0, worst_r, tol=0.5)
|
||||
check(f"{m} angle vs i*360/{NDIV} (worst, deg)", 0, worst_a, tol=0.01)
|
||||
p1b = min(min(o.Geometry.PointAtStart.Z, o.Geometry.PointAtEnd.Z) for o in cl["P1"])
|
||||
p2b = min(min(o.Geometry.PointAtStart.Z, o.Geometry.PointAtEnd.Z) for o in cl["P2"])
|
||||
dz = G["bottom_above_footing"]
|
||||
check(f"P1 hook leg EL (footing bottom + {dz})", FB + dz, p1b)
|
||||
check("P2 bottom EL", FB + dz, p2b)
|
||||
hz = {}
|
||||
for m in ("P3", "P4", "P5"):
|
||||
for o in cl[m]:
|
||||
bb = o.Geometry.GetBoundingBox(True)
|
||||
hz.setdefault(round(bb.Max.Z, 1), set()).add(m)
|
||||
if m != "P3":
|
||||
p = o.Geometry.PointAtStart
|
||||
check(f"{m} radius", R_HOOP, math.hypot(p.X, p.Y), tol=0.5) if o is cl[m][0] else None
|
||||
zs = sorted(hz)
|
||||
flag(f"hoop level count {len(LEVELS)}", len(zs) == len(LEVELS), f"{len(zs)}")
|
||||
check("hoop levels vs dim chain (worst)", 0, max(abs(a - b) for a, b in zip(zs, LEVELS)))
|
||||
marks_by_level = [list(hz[z])[0] for z in zs]
|
||||
zones = G["hoop_marks_by_zone"]
|
||||
flag("hoop marks by zone (" + ", ".join(f"{m} x{n}" for m, n in zones) + ")",
|
||||
marks_by_level == [m for m, n in zones for _ in range(n)], "")
|
||||
tz = sorted({round(o.Geometry.GetBoundingBox(True).Max.Z, 1) for o in cl["P6"]})
|
||||
off = G["tie_offset"]
|
||||
check(f"tie levels vs chain + {off} (worst)", 0, max(abs(a - (b + off)) for a, b in zip(tz, TIE_LEVELS)))
|
||||
solids = concrete()
|
||||
union = rg.Brep.CreateBooleanUnion(solids, 0.01)
|
||||
shell = union[0] if union and len(union) == 1 else None # outer surface only (no internal joint faces)
|
||||
results.append(dict(check="C8", item="concrete union for cover", detail=f"{len(union) if union else 0} brep",
|
||||
result="INFO"))
|
||||
min_cover = {}
|
||||
for m in SCHED_L:
|
||||
bad, cov = 0, 1e9
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
n = max(4, int(c.GetLength() / 100))
|
||||
for t in c.DivideByCount(n, True):
|
||||
p = c.PointAt(t)
|
||||
radial = rg.Vector3d(p.X, p.Y, 0)
|
||||
radial.Unitize() if radial.Length > 1e-6 else None
|
||||
for q in (p, p + radial * (DIA[m] / 2), p - radial * (DIA[m] / 2),
|
||||
p + rg.Vector3d(0, 0, DIA[m] / 2), p - rg.Vector3d(0, 0, DIA[m] / 2)):
|
||||
if not any(s.IsPointInside(q, 0.01, False) for s in solids):
|
||||
bad += 1
|
||||
d = p.DistanceTo(shell.ClosestPoint(p)) if shell and shell.IsPointInside(p, 0.01, False) else -1
|
||||
cov = min(cov, d - DIA[m] / 2)
|
||||
min_cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside concrete", 0, bad, tol=0)
|
||||
results.append(dict(check="C8", item="min cover to concrete surface per mark (record)",
|
||||
detail=json.dumps(min_cover), result="INFO"))
|
||||
results.append(dict(check="C8", item="clearance P1 outer surface vs hoop inner surface (record)",
|
||||
detail=f"{R_HOOP - DIA['P4'] / 2 - (R_OUT + DIA['P1'] / 2):.1f} mm", result="INFO"))
|
||||
write_result(RESULT_PATH, results, dict(min_cover=min_cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,156 @@
|
||||
"""G2 gate checker, form "ring" (Ulsan P1, 2026-10-13): column rebar with one main ring, hoop zones, cross ties.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G2 + G1 footing / column) come
|
||||
from the schedule (L, counts, R) and the dimension chains of the rebar drawing; generator params are not read.
|
||||
Checks (C8): counts, original length (+coupler) vs schedule L, curve length for bars without fillets, main bar
|
||||
radius / division / leg and top level, hoop level chain / zones / radius, tie levels / count per level,
|
||||
containment in concrete and minimum cover (record).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, chain, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, TOL, G = X["pier_id"], X["tol_mm"], X["G2"]
|
||||
SCHED_L, SCHED_N, DIA = G["schedule_L"], G["schedule_n"], G["dia"]
|
||||
FB = X["G1"]["footing"]["z0"]
|
||||
M = G["main"]
|
||||
LEVELS = chain(G["hoop_chain"], FB)
|
||||
TIE = G["tie"]
|
||||
TIE_LEVELS = chain(TIE["chain"], FB)
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=TOL):
|
||||
results.append(dict(check="C8", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C8", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C8", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def ids(prefix):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [o for o in ids(f"{PID}-RC-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
||||
pipes = {m: [o for o in ids(f"{PID}-R-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
||||
occ = lambda o: o.Attributes.GetUserString("occurrence").rstrip("ab")
|
||||
tie_mark = TIE["mark"]
|
||||
for m in SCHED_L:
|
||||
n_occ = len(cl[m]) if m == tie_mark else len({occ(o) for o in cl[m]})
|
||||
check(f"{m} bar count", SCHED_N[m], n_occ, tol=0)
|
||||
flag(f"{m} pipes closed & paired with centrelines",
|
||||
len(pipes[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pipes[m]), f"{len(pipes[m])}/{len(cl[m])}")
|
||||
for m in SCHED_L:
|
||||
per, curve = {}, {}
|
||||
for o in cl[m]:
|
||||
k = o.Attributes.GetUserString("occurrence") if m == tie_mark else occ(o)
|
||||
cp = float(o.Attributes.GetUserString("coupler_length") or 0)
|
||||
per[k] = per.get(k, 0) + float(o.Attributes.GetUserString("design_length")) + cp
|
||||
curve[k] = curve.get(k, 0) + o.Geometry.GetLength() + cp
|
||||
if not per:
|
||||
continue
|
||||
check(f"{m} original length (+coupler) vs schedule L (worst of {len(per)})", 0,
|
||||
max(abs(v - SCHED_L[m]) for v in per.values()))
|
||||
dev = [v - SCHED_L[m] for v in curve.values()]
|
||||
info(f"{m} curve length - L (record)", f"min {min(dev):.1f} / max {max(dev):.1f} mm")
|
||||
if m in G["no_fillet_marks"]:
|
||||
check(f"{m} curve length (+coupler) vs L (no fillet)", 0, max(abs(d) for d in dev))
|
||||
# main bars
|
||||
mm_, R0, ND = M["mark"], M["radius"], M["divisions"]
|
||||
angs, worst_r, tops, legs = [], 0, [], []
|
||||
for o in cl[mm_]:
|
||||
c = o.Geometry
|
||||
p = c.PointAtEnd if c.PointAtEnd.Z > c.PointAtStart.Z else c.PointAtStart
|
||||
angs.append(math.degrees(math.atan2(p.Y, p.X)) % 360)
|
||||
worst_r = max(worst_r, abs(math.hypot(p.X, p.Y) - R0))
|
||||
tops.append(p.Z)
|
||||
legs.append(min(c.PointAtStart.Z, c.PointAtEnd.Z))
|
||||
angs.sort()
|
||||
check(f"{mm_} radius (worst)", 0, worst_r, tol=0.5)
|
||||
check(f"{mm_} angle vs start + i*360/{ND} (worst, deg)", 0,
|
||||
max(abs(a - (M.get("start_deg", 0) + i * 360 / ND)) for i, a in enumerate(angs)), tol=0.01)
|
||||
check(f"{mm_} top EL (worst)", 0, max(abs(t - (FB + sum(M["top_parts"]))) for t in tops))
|
||||
check(f"{mm_} hook leg EL (footing bottom + {M['bottom_above_footing']}) (worst)", 0,
|
||||
max(abs(z - (FB + M["bottom_above_footing"])) for z in legs))
|
||||
# hoops
|
||||
hz = {}
|
||||
for m, _ in G["hoop_zones"]:
|
||||
for o in cl[m]:
|
||||
hz.setdefault(round(o.Geometry.GetBoundingBox(True).Max.Z, 1), set()).add(m)
|
||||
zs = sorted(hz)
|
||||
flag(f"hoop level count {len(LEVELS)}", len(zs) == len(LEVELS), f"{len(zs)}")
|
||||
check("hoop levels vs dim chain (worst)", 0, max(abs(a - b) for a, b in zip(zs, LEVELS)) if zs else 1e9)
|
||||
flag("hoop marks by zone (" + ", ".join(f"{m} x{n}" for m, n in G["hoop_zones"]) + ")",
|
||||
[list(hz[z])[0] for z in zs] == [m for m, n in G["hoop_zones"] for _ in range(n)])
|
||||
for m, _ in G["hoop_zones"]:
|
||||
o = cl[m][0]
|
||||
p = o.Geometry.PointAt(o.Geometry.Domain.Mid)
|
||||
check(f"{m} radius (mid point)", G.get("r_hoop_by_mark", {}).get(m, G["r_hoop"]), math.hypot(p.X, p.Y), tol=0.5)
|
||||
if "inner" in M: # Okseong P1: inner ring (2026-10-14)
|
||||
im = M["inner"]
|
||||
tops = [max((o.Geometry.PointAtStart, o.Geometry.PointAtEnd), key=lambda q: q.Z) for o in cl[im["mark"]]]
|
||||
lows = [min((o.Geometry.PointAtStart, o.Geometry.PointAtEnd), key=lambda q: q.Z) for o in cl[im["mark"]]]
|
||||
check(f"{im['mark']} inner ring radius (worst)", 0, max(abs(math.hypot(q.X, q.Y) - im["radius"]) for q in tops) if tops else 1e9, tol=0.5)
|
||||
flag(f"{im['mark']} hooks bent inwards", bool(lows) and all(math.hypot(q.X, q.Y) < im["radius"] for q in lows))
|
||||
# ties
|
||||
if TIE.get("y_lift"): # Okseong P1: X tie at the level, Y tie lifted
|
||||
occ_ = lambda o: o.Attributes.GetUserString("occurrence")
|
||||
lx = sorted(round(o.Geometry.GetBoundingBox(True).Max.Z, 1) for o in cl[tie_mark] if occ_(o).endswith("X"))
|
||||
ly = sorted(round(o.Geometry.GetBoundingBox(True).Max.Z, 1) for o in cl[tie_mark] if occ_(o).endswith("Y"))
|
||||
flag(f"tie X / Y levels {len(TIE_LEVELS)} each", len(lx) == len(ly) == len(TIE_LEVELS), f"X {len(lx)} Y {len(ly)}")
|
||||
check("tie X levels vs chain (worst)", 0, max(abs(a - b - TIE["offset"]) for a, b in zip(lx, TIE_LEVELS)) if lx else 1e9)
|
||||
check(f"tie Y = X + {TIE['y_lift']} (worst)", 0, max(abs(b - a - TIE["y_lift"]) for a, b in zip(lx, ly)) if ly else 1e9)
|
||||
else:
|
||||
tz = {}
|
||||
for o in cl[tie_mark]:
|
||||
tz.setdefault(round(o.Geometry.GetBoundingBox(True).Max.Z, 1), []).append(o)
|
||||
lv = sorted(tz)
|
||||
flag(f"tie levels {len(TIE_LEVELS)} x {TIE['per_level']}", len(lv) == len(TIE_LEVELS)
|
||||
and all(len(v) == TIE["per_level"] for v in tz.values()), f"{len(lv)} levels")
|
||||
check(f"tie levels vs chain + {TIE['offset']} (worst)", 0,
|
||||
max(abs(a - (b + TIE["offset"])) for a, b in zip(lv, TIE_LEVELS)) if lv else 1e9)
|
||||
# containment and cover
|
||||
solids = []
|
||||
for oid in (f"{PID}-CL", f"{PID}-FT", f"{PID}-CP"):
|
||||
o = [x for x in all_objects(doc) if x.Attributes.GetUserString("id") == oid][0]
|
||||
solids.append(o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry))
|
||||
union = rg.Brep.CreateBooleanUnion(solids, 0.01)
|
||||
shell = union[0] if union and len(union) == 1 else None
|
||||
info("concrete union for cover", f"{len(union) if union else 0} brep")
|
||||
min_cover = {}
|
||||
for m in SCHED_L:
|
||||
bad, cov = 0, 1e9
|
||||
r = DIA[m] / 2
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
for t in c.DivideByCount(max(4, int(c.GetLength() / 150)), True):
|
||||
p = c.PointAt(t)
|
||||
radial = rg.Vector3d(p.X, p.Y, 0)
|
||||
radial.Unitize() if radial.Length > 1e-6 else None
|
||||
for q in (p, p + radial * r, p - radial * r, p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not any(s.IsPointInside(q, 0.01, False) for s in solids):
|
||||
bad += 1
|
||||
d = p.DistanceTo(shell.ClosestPoint(p)) if shell and shell.IsPointInside(p, 0.01, False) else -1
|
||||
cov = min(cov, d - r)
|
||||
min_cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside concrete", 0, bad, tol=0)
|
||||
info("min cover to concrete surface per mark (record)", json.dumps(min_cover))
|
||||
write_result(RESULT_PATH, results, dict(min_cover=min_cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,163 @@
|
||||
"""G2 gate checker, form "straight" (Ramp-A P1, 2026-10-13): straight main ring with a bottom coupler, lapped
|
||||
half-ring hoops, X / Y cross ties; column on a drilled shaft (no footing).
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G2) come from the schedule (L, N, r)
|
||||
and the D2 dimension chains (anchor EL 45.492: '2,000' below, 14@150 / 78@150 hoops, 7@300 / 39@300 ties) with
|
||||
the user decisions K1-K3; generator params are not read.
|
||||
Checks (C8): counts, original length (+coupler) vs schedule L, main radius / division / bottom / top, hoop and tie
|
||||
level grids and radius, X / Y ties apart, containment in concrete (pile segments, column, coping), min cover (record).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, TOL, G = X["pier_id"], X["tol_mm"], X["G2"]
|
||||
SCHED_L, SCHED_N, DIA = G["schedule_L"], G["schedule_n"], G["dia"]
|
||||
M, HP, TI = G["main"], G["hoops"], G["ties"]
|
||||
A = G["chain_anchor"]
|
||||
|
||||
|
||||
def grid(g):
|
||||
return [A - g["below"] * g["pitch"] + k * g["pitch"] for k in range(SCHED_N[g["mark"]] // g.get("per_level", 1))]
|
||||
|
||||
|
||||
HOOP_LEVELS, TIE_LEVELS = grid(HP), grid(TI)
|
||||
CO = X["G1"]["column"]
|
||||
R_COL, Z_CL0, Z_CL1 = CO["d"] / 2, CO["z0"], CO["z0"] + CO["length"]
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=TOL):
|
||||
results.append(dict(check="C8", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C8", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C8", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def ids(prefix):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [o for o in ids(f"{PID}-RC-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
||||
pipes = {m: [o for o in ids(f"{PID}-R-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
||||
occ = lambda o: o.Attributes.GetUserString("occurrence").rstrip("ab")
|
||||
tie = TI["mark"]
|
||||
for m in SCHED_L:
|
||||
n_occ = len(cl[m]) if m == tie else len({occ(o) for o in cl[m]})
|
||||
check(f"{m} bar count", SCHED_N[m], n_occ, tol=0)
|
||||
flag(f"{m} pipes closed & paired with centrelines",
|
||||
len(pipes[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pipes[m]), f"{len(pipes[m])}/{len(cl[m])}")
|
||||
for m in SCHED_L:
|
||||
per, curve = {}, {}
|
||||
for o in cl[m]:
|
||||
k = o.Attributes.GetUserString("occurrence") if m == tie else occ(o)
|
||||
cp = float(o.Attributes.GetUserString("coupler_length") or 0)
|
||||
per[k] = per.get(k, 0) + float(o.Attributes.GetUserString("design_length")) + cp
|
||||
curve[k] = curve.get(k, 0) + o.Geometry.GetLength() + cp
|
||||
if per:
|
||||
check(f"{m} original length (+coupler) vs schedule L (worst of {len(per)})", 0,
|
||||
max(abs(v - SCHED_L[m]) for v in per.values()))
|
||||
dev = [v - SCHED_L[m] for v in curve.values()]
|
||||
info(f"{m} curve length (+coupler) - L (record)", f"min {min(dev):.1f} / max {max(dev):.1f} mm")
|
||||
# main bars: straight, chain bottom (anchor - 2,000), top = bottom + L - coupler (K1)
|
||||
mm_ = M["mark"]
|
||||
bottom = A - M["below_anchor"]
|
||||
top = bottom + SCHED_L[mm_] - M["coupler"]
|
||||
angs, worst_r, zb, zt = [], 0, [], []
|
||||
for o in cl[mm_]:
|
||||
c = o.Geometry
|
||||
lo, hi = sorted((c.PointAtStart, c.PointAtEnd), key=lambda p: p.Z)
|
||||
angs.append(math.degrees(math.atan2(hi.Y, hi.X)) % 360)
|
||||
worst_r = max(worst_r, abs(math.hypot(lo.X, lo.Y) - M["radius"]), abs(math.hypot(hi.X, hi.Y) - M["radius"]))
|
||||
zb.append(lo.Z)
|
||||
zt.append(hi.Z)
|
||||
angs.sort()
|
||||
check(f"{mm_} radius (worst)", 0, worst_r, tol=0.5)
|
||||
check(f"{mm_} angle vs {M['start_deg']} + i*360/{M['divisions']} (worst, deg)", 0,
|
||||
max(abs(a - (M["start_deg"] + i * 360 / M["divisions"])) for i, a in enumerate(angs)), tol=0.01)
|
||||
check(f"{mm_} bottom EL = chain {A / 1000:.3f} - {M['below_anchor']} (worst)", 0, max(abs(z - bottom) for z in zb))
|
||||
check(f"{mm_} top EL = bottom + L - coupler {M['coupler']} (worst)", 0, max(abs(z - top) for z in zt))
|
||||
# hoops: levels on the chain grid, two pieces per level, radius
|
||||
hz = {}
|
||||
for o in cl[HP["mark"]]:
|
||||
bb = o.Geometry.GetBoundingBox(True)
|
||||
hz.setdefault(round((bb.Max.Z + bb.Min.Z) / 2, 1), []).append(o)
|
||||
zs = sorted(hz)
|
||||
flag(f"{HP['mark']} levels {len(HOOP_LEVELS)} x 2 pieces", len(zs) == len(HOOP_LEVELS)
|
||||
and all(len(v) == 2 for v in hz.values()), f"{len(zs)} levels")
|
||||
check(f"{HP['mark']} levels vs chain grid {HP['pitch']} (worst)", 0,
|
||||
max(abs(a - b) for a, b in zip(zs, HOOP_LEVELS)) if len(zs) == len(HOOP_LEVELS) else 1e9)
|
||||
for o in cl[HP["mark"]][:4]:
|
||||
c = o.Geometry
|
||||
p = c.PointAt(c.Domain.Mid)
|
||||
check(f"{HP['mark']} radius at mid point ({o.Attributes.GetUserString('occurrence')})", HP["radius"],
|
||||
math.hypot(p.X, p.Y), tol=0.5)
|
||||
# ties: X at the grid level, Y lifted (K3); levels = top of the straight part
|
||||
by = lambda s: [o.Geometry for o in cl[tie] if o.Attributes.GetUserString("occurrence").endswith(s)]
|
||||
xs, ys = by("X"), by("Y")
|
||||
txm = sorted(round(g.GetBoundingBox(True).Max.Z, 1) for g in xs)
|
||||
tym = sorted(round(g.GetBoundingBox(True).Max.Z, 1) for g in ys)
|
||||
flag(f"{tie} X / Y per level, {len(TIE_LEVELS)} levels", len(txm) == len(tym) == len(TIE_LEVELS), f"X {len(txm)} Y {len(tym)}")
|
||||
if len(txm) == len(TIE_LEVELS):
|
||||
check(f"{tie} X levels vs chain grid {TI['pitch']} (worst)", 0, max(abs(a - b) for a, b in zip(txm, TIE_LEVELS)))
|
||||
if len(tym) == len(TIE_LEVELS):
|
||||
check(f"{tie} Y levels = X + {TI['y_lift']} (worst)", 0, max(abs(a - b - TI["y_lift"]) for a, b in zip(tym, TIE_LEVELS)))
|
||||
xs.sort(key=lambda g: g.GetBoundingBox(True).Max.Z)
|
||||
ys.sort(key=lambda g: g.GetBoundingBox(True).Max.Z)
|
||||
gaps = []
|
||||
for a, b in zip(xs, ys):
|
||||
ok, pa, pb = a.ClosestPoints(b)
|
||||
gaps.append(pa.DistanceTo(pb) if ok else 0.0)
|
||||
gap = min(gaps) if gaps else 0.0
|
||||
check(f"{tie} X / Y centrelines apart >= bar diameter {DIA[tie]} (min, capped)", DIA[tie], min(gap, DIA[tie]), tol=0.01)
|
||||
info(f"{tie} X / Y centreline distance (min, record)", f"{gap:.2f} mm")
|
||||
# containment and cover
|
||||
solids = []
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid in (f"{PID}-CL", f"{PID}-CP") or oid.startswith(f"{PID}-PL"):
|
||||
solids.append(o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry))
|
||||
union = rg.Brep.CreateBooleanUnion(solids, 0.01)
|
||||
shell = union[0] if union and len(union) == 1 else None
|
||||
info("concrete union for cover", f"{len(union) if union else 0} brep from {len(solids)} solids"
|
||||
+ ("" if shell else " -> min cover = column side cover in the column zone (R - radial distance - bar radius)"))
|
||||
min_cover = {}
|
||||
for m in SCHED_L:
|
||||
bad, cov = 0, 1e9
|
||||
r = DIA[m] / 2
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
for t in c.DivideByCount(max(4, int(c.GetLength() / 150)), True):
|
||||
p = c.PointAt(t)
|
||||
radial = rg.Vector3d(p.X, p.Y, 0)
|
||||
radial.Unitize() if radial.Length > 1e-6 else None
|
||||
for q in (p, p + radial * r, p - radial * r, p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not any(s.IsPointInside(q, 0.01, False) for s in solids):
|
||||
bad += 1
|
||||
if shell:
|
||||
d = p.DistanceTo(shell.ClosestPoint(p)) if shell.IsPointInside(p, 0.01, False) else -1
|
||||
cov = min(cov, d - r)
|
||||
elif Z_CL0 <= p.Z <= Z_CL1: # no union (coplanar contact faces): side cover in the column zone
|
||||
cov = min(cov, R_COL - math.hypot(p.X, p.Y) - r)
|
||||
min_cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside concrete", 0, bad, tol=0)
|
||||
info("min cover to concrete surface per mark (record)", json.dumps(min_cover))
|
||||
write_result(RESULT_PATH, results, dict(min_cover=min_cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,146 @@
|
||||
"""G3 gate checker: footing rebar B1-B12 (check C9) and rebar colours inside Rhino 8.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values from EXPECTED_PATH sections G1 (footing
|
||||
bottom) and G3 (D3 schedule, D1/D2 dimension chains, F1 layer heights); mid-layer positions are drawn dots
|
||||
(derived, not independent). Generator params are not read.
|
||||
Ported from Code/교각모델링/verify_footing_rebar.py (same checks and order).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectColorSource
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, row, series, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G3"]
|
||||
FB = X["G1"]["footing"]["z0"]
|
||||
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
||||
DIA = G["dia"]
|
||||
Y_BOT, Y_250, X_BOT, X_250 = (row(G["rows"][k]) for k in ("Y_BOT", "Y_250", "X_BOT", "X_250"))
|
||||
Z = G["layer_z"]
|
||||
B10_Z = series(G["b10_levels"])
|
||||
B9_X, B9_Y = series(G["b9_grid"]["x"]), series(G["b9_grid"]["y"])
|
||||
PALETTE = {int(k): tuple(v) for k, v in X["rebar_palette"].items()}
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C9", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def objs(prefix, mark):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)
|
||||
and o.Attributes.GetUserString("mark") == mark]
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: objs(f"{PID}-RC-", m) for m in SCHED}
|
||||
pp = {m: objs(f"{PID}-R-", m) for m in SCHED}
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]),
|
||||
f"{len(pp[m])}/{len(cl[m])}")
|
||||
if cl[m]:
|
||||
worst = max(abs(float(o.Attributes.GetUserString("design_length")) - L) for o in cl[m])
|
||||
check(f"{m} original length vs L (worst)", 0, worst)
|
||||
dev = [o.Geometry.GetLength() - L for o in cl[m]]
|
||||
info(f"{m} curve length - L (bend fillets)", f"min {min(dev):.1f} / max {max(dev):.1f}")
|
||||
|
||||
def mids(m):
|
||||
return [o.Geometry.PointAt(o.Geometry.Domain.Mid) for o in cl[m]]
|
||||
for m in ("B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"):
|
||||
ps = mids(m)
|
||||
check(f"{m} layer z (worst)", 0, max(abs(p.Z - (FB + Z[m])) for p in ps))
|
||||
|
||||
def pos_check(marks, axis, expected, name):
|
||||
got = sorted(round(getattr(p, axis), 1) for m in marks for p in mids(m))
|
||||
ok = len(got) == len(expected)
|
||||
worst = max(abs(a - b) for a, b in zip(got, expected)) if ok else 1e9
|
||||
check(f"{name} positions vs chain (worst, n={len(got)}/{len(expected)})", 0, worst)
|
||||
pos_check(("B2", "B4"), "X", Y_BOT, "bottom Y-bars B2+B4")
|
||||
pos_check(("B6", "B8"), "Y", X_BOT, "bottom X-bars B6+B8")
|
||||
pos_check(("B3",), "X", Y_250, "B3")
|
||||
pos_check(("B1",), "X", Y_250, "B1")
|
||||
pos_check(("B7",), "Y", X_250, "B7")
|
||||
pos_check(("B5",), "Y", X_250, "B5")
|
||||
ye, xe = G["rows"]["Y_BOT"], G["rows"]["X_BOT"]
|
||||
b2x = sorted(round(p.X) for p in mids("B2"))
|
||||
flag(f"B2 at ends and alternate from {ye['start']}", b2x == sorted(list(ye["ends"]) + Y_BOT[1:-1][0::2]), "")
|
||||
b6y = sorted(round(p.Y) for p in mids("B6"))
|
||||
flag(f"B6 at ends and alternate from {xe['start']}", b6y == sorted(list(xe["ends"]) + X_BOT[1:-1][0::2]), "")
|
||||
mid = G["mid_levels"]
|
||||
for m in ("B11", "B12"):
|
||||
zs = sorted({round(p.Z - FB) for p in mids(m)})
|
||||
flag(f"{m} mid levels {'/'.join(str(v) for v in mid)} [derived]", zs == mid, str(zs))
|
||||
zs = sorted(round(o.Geometry.GetBoundingBox(True).Min.Z - FB, 1) for o in cl["B10"])
|
||||
check("B10 levels vs chain (worst)", 0, max(abs(a - b) for a, b in zip(zs, B10_Z)) if len(zs) == len(B10_Z) else 1e9)
|
||||
bb = cl["B10"][0].Geometry.GetBoundingBox(True)
|
||||
check("B10 centreline size X", G["b10_size"][0], bb.Max.X - bb.Min.X)
|
||||
check("B10 centreline size Y", G["b10_size"][1], bb.Max.Y - bb.Min.Y)
|
||||
# B9: vertical leg x = bbox min X (hooks point +X); y constant
|
||||
pts = {(round(o.Geometry.GetBoundingBox(True).Min.X), round(o.Geometry.GetBoundingBox(True).Center.Y))
|
||||
for o in cl["B9"]}
|
||||
exp = {(x, y) for i, x in enumerate(B9_X) for j, y in enumerate(B9_Y) if (i + j) % 2 == 0}
|
||||
flag(f"B9 positions = checkerboard of {len(B9_X)}x{len(B9_Y)} grid", pts == exp, f"{len(pts)} / {len(exp)}")
|
||||
zr = [o.Geometry.GetBoundingBox(True) for o in cl["B9"]]
|
||||
e0, e1 = G["b9_erec"]
|
||||
a9, c9 = G["b9_a"], G["b9_c"]
|
||||
check("B9 bottom z (worst of all)", 0, max(abs(b.Min.Z - (FB + (e0 + e1) / 2 - a9 / 2)) for b in zr))
|
||||
check("B9 vertical extent a (bottom leg z to top of straight)", a9,
|
||||
max(b.Max.Z for b in zr[:1]) - min(b.Min.Z for b in zr[:1]) - c9 / math.pi, tol=1.0)
|
||||
solids = []
|
||||
for oid in (f"{PID}-FT", f"{PID}-CL", f"{PID}-CP"):
|
||||
o = [x for x in all_objects(doc) if x.Attributes.GetUserString("id") == oid][0]
|
||||
solids.append(o.Geometry)
|
||||
ft = solids[0]
|
||||
shell = None
|
||||
for order in ([solids[1], solids[0], solids[2]], solids): # union is order-sensitive in Rhino 8
|
||||
u = rg.Brep.CreateBooleanUnion(order, 0.01)
|
||||
if u and len(u) == 1:
|
||||
shell = u[0]
|
||||
break
|
||||
if shell is None:
|
||||
raise RuntimeError("concrete union failed")
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
for t in c.DivideByCount(max(4, int(c.GetLength() / 200)), True):
|
||||
p = c.PointAt(t)
|
||||
r = DIA[m] / 2
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not ft.IsPointInside(q, 0.01, False):
|
||||
bad += 1
|
||||
cov = min(cov, p.DistanceTo(shell.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside footing", 0, bad, tol=0)
|
||||
info("min cover to concrete exterior per mark", json.dumps(cover))
|
||||
wrong = 0
|
||||
for o in all_objects(doc):
|
||||
if (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-R-"):
|
||||
d = int(o.Attributes.GetUserString("dia")[1:])
|
||||
c = o.Attributes.ObjectColor
|
||||
if o.Attributes.ColorSource != ObjectColorSource.ColorFromObject or (c.R, c.G, c.B) != PALETTE[d]:
|
||||
wrong += 1
|
||||
check("rebar pipe colour by diameter mismatches (all rebar)", 0, wrong, tol=0)
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,158 @@
|
||||
"""G3 gate checker, form "mats" (Ulsan P1, 2026-10-13): footing rebar groups, perimeter hoops, verticals.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G3) from the schedule and the
|
||||
position chains of the rebar drawing (layer heights are drawn values, marked [derived]); params are not read.
|
||||
expected.G3: schedule {mark: [L, n]}, dia {mark: d}, rows {name: {first, start, pitch, n, last}},
|
||||
layers {mark: {axis, z, row | pos, subset}} (subset "on"/"off": positions on / off the `of` row)
|
||||
hoops {mark, half, chain {start, groups}}, verticals {mark, grid_x row, grid_y row, parity, z0, a}
|
||||
Optional (Ulsan P2, 2026-10-15; absent -> unchanged): G1.footing.rot_deg (bars are checked after turning their centrelines back by the
|
||||
foundation rotation), hoops.half_x / half_y, verticals {exclude {x, y} (grid points with |x| <= x and |y| <= y are absent), legs {bottom, top}}.
|
||||
Checks (C9): counts, original length, layer z, positions, hoop levels / size, vertical grid, containment in the
|
||||
footing, minimum cover (record), rebar colours by diameter.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectColorSource
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, chain, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G3"]
|
||||
FB = X["G1"]["footing"]["z0"]
|
||||
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
||||
DIA = G["dia"]
|
||||
PALETTE = {int(k): tuple(v) for k, v in X["rebar_palette"].items()}
|
||||
ROT = X["G1"]["footing"].get("rot_deg", 0.0)
|
||||
results = []
|
||||
|
||||
|
||||
def bbx(o):
|
||||
"""Bounding box of the bar centreline in the foundation's own axes."""
|
||||
g = o.Geometry
|
||||
if ROT:
|
||||
g = g.DuplicateCurve()
|
||||
g.Transform(rg.Transform.Rotation(-math.radians(ROT), rg.Vector3d.ZAxis, rg.Point3d.Origin))
|
||||
return g.GetBoundingBox(True)
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C9", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def row(spec):
|
||||
return sorted(list(spec.get("first", [])) + [spec["start"] + spec["pitch"] * i for i in range(spec["n"])]
|
||||
+ list(spec.get("last", [])))
|
||||
|
||||
|
||||
def positions(lay):
|
||||
if "pos" in lay:
|
||||
return sorted(lay["pos"])
|
||||
base = row(G["rows"][lay["row"]])
|
||||
if "subset" in lay:
|
||||
ref = set(row(G["rows"][lay["of"]]))
|
||||
base = [v for v in base if (v in ref) == (lay["subset"] == "on")]
|
||||
return base
|
||||
|
||||
|
||||
def objs(prefix, mark):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)
|
||||
and o.Attributes.GetUserString("mark") == mark]
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: objs(f"{PID}-RC-", m) for m in SCHED}
|
||||
pp = {m: objs(f"{PID}-R-", m) for m in SCHED}
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]),
|
||||
f"{len(pp[m])}/{len(cl[m])}")
|
||||
if cl[m]:
|
||||
check(f"{m} original length vs L (worst)", 0,
|
||||
max(abs(float(o.Attributes.GetUserString("design_length")) - L) for o in cl[m]))
|
||||
dev = [o.Geometry.GetLength() - L for o in cl[m]]
|
||||
info(f"{m} curve length - L (bend fillets / laps)", f"min {min(dev):.1f} / max {max(dev):.1f}")
|
||||
for m, lay in G["layers"].items():
|
||||
if not cl[m]:
|
||||
continue
|
||||
# horizontal part: the lowest (legs up / none) or highest (legs down) straight segment -> bbox mid level
|
||||
zs, ps = [], []
|
||||
for o in cl[m]:
|
||||
bb = bbx(o)
|
||||
leg_down = lay.get("leg", 0) < 0
|
||||
zs.append((bb.Max.Z if leg_down else bb.Min.Z) - FB)
|
||||
ps.append(round((bb.Min.X + bb.Max.X) / 2 if lay["axis"] == "Y" else (bb.Min.Y + bb.Max.Y) / 2, 1))
|
||||
check(f"{m} layer z (worst) [derived]", 0, max(abs(z - lay["z"]) for z in zs))
|
||||
exp = positions(lay)
|
||||
got = sorted(ps)
|
||||
ok = len(got) == len(exp)
|
||||
check(f"{m} positions vs chain (worst, n={len(got)}/{len(exp)})", 0,
|
||||
max(abs(a - b) for a, b in zip(got, exp)) if ok else 1e9)
|
||||
hp = G["hoops"]
|
||||
lv = chain(hp["chain"], 0)
|
||||
hz = sorted(round(bbx(o).Min.Z - FB, 1) for o in cl[hp["mark"]])
|
||||
check(f"{hp['mark']} levels vs chain (worst)", 0, max(abs(a - b) for a, b in zip(hz, lv)) if len(hz) == len(lv) else 1e9)
|
||||
if cl[hp["mark"]]:
|
||||
bb = bbx(cl[hp["mark"]][0])
|
||||
check(f"{hp['mark']} centreline size X [derived]", 2 * hp.get("half_x", hp.get("half")), bb.Max.X - bb.Min.X)
|
||||
check(f"{hp['mark']} centreline size Y [derived]", 2 * hp.get("half_y", hp.get("half")), bb.Max.Y - bb.Min.Y)
|
||||
vt = G["verticals"]
|
||||
gx, gy = row(G["rows"][vt["grid_x"]]), row(G["rows"][vt["grid_y"]])
|
||||
pts = {(round(bbx(o).Min.X), round(bbx(o).Min.Y)) for o in cl[vt["mark"]]}
|
||||
if "exclude" in vt: # Ulsan P2: grid minus the central rectangle
|
||||
ex = vt["exclude"]
|
||||
exp = {(round(x), round(y)) for x in gx for y in gy if not (abs(x) <= ex["x"] and abs(y) <= ex["y"])}
|
||||
flag(f"{vt['mark']} positions = {len(gx)}x{len(gy)} grid minus |x|<={ex['x']} & |y|<={ex['y']} [추론]", pts == exp, f"{len(pts)} / {len(exp)}")
|
||||
else:
|
||||
exp = {(round(x), round(y)) for i, x in enumerate(gx) for j, y in enumerate(gy) if (i + j) % 2 == vt["parity"]}
|
||||
flag(f"{vt['mark']} positions = checkerboard of {len(gx)}x{len(gy)} grid [추론]", pts == exp, f"{len(pts)} / {len(exp)}")
|
||||
if cl[vt["mark"]]:
|
||||
check(f"{vt['mark']} bottom z (worst)", 0,
|
||||
max(abs(bbx(o).Min.Z - FB - vt["z0"]) for o in cl[vt["mark"]]))
|
||||
bb = bbx(cl[vt["mark"]][0])
|
||||
check(f"{vt['mark']} vertical extent a", vt["a"], bb.Max.Z - bb.Min.Z)
|
||||
if "legs" in vt: # both legs run towards +Y from the same point: Y extent = the longer leg
|
||||
check(f"{vt['mark']} leg extent along Y (longer leg)", max(vt["legs"].values()), bb.Max.Y - bb.Min.Y)
|
||||
ft = [o for o in all_objects(doc) if o.Attributes.GetUserString("id") == f"{PID}-FT"][0].Geometry
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
r = DIA[m] / 2
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
for t in c.DivideByCount(max(4, int(c.GetLength() / 200)), True):
|
||||
p = c.PointAt(t)
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not ft.IsPointInside(q, 0.01, False):
|
||||
bad += 1
|
||||
cov = min(cov, p.DistanceTo(ft.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside footing", 0, bad, tol=0)
|
||||
info("min cover to footing surface per mark (record; top face under the column counted)", json.dumps(cover))
|
||||
wrong = 0
|
||||
for o in all_objects(doc):
|
||||
if (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-R-"):
|
||||
d = int(o.Attributes.GetUserString("dia")[1:])
|
||||
c = o.Attributes.ObjectColor
|
||||
if o.Attributes.ColorSource != ObjectColorSource.ColorFromObject or (c.R, c.G, c.B) != PALETTE.get(d):
|
||||
wrong += 1
|
||||
check("rebar pipe colour by diameter mismatches (all rebar)", 0, wrong, tol=0)
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,115 @@
|
||||
"""G3 gate checker, footing rebar as centreline polylines (Okseong P1, 2026-10-14): mats B1~B8, mid layers B11 / B12,
|
||||
perimeter loops B10, vertical ties B9.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G3) come from the schedule (L, N), the
|
||||
front / side dimension chains (bar positions), the leg lengths b of the schedule, the drawn mat / loop levels and
|
||||
the B9 grid rule with the user decisions F1-F4; generator params are not read.
|
||||
Checks (C9): counts, original length vs L, per mat: positions vs chain, straight level, span, leg length and
|
||||
direction; mid layers; B10 levels / size; B9 grid / height; containment in the footing; min cover (record).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, chain, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G3"]
|
||||
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C9", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def bb(o):
|
||||
return o.Geometry.GetBoundingBox(True)
|
||||
|
||||
|
||||
def worst(a, b):
|
||||
return max(abs(x - y) for x, y in zip(a, b)) if len(a) == len(b) and a else 1e9
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [] for m in SCHED}
|
||||
pp = {m: [] for m in SCHED}
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
m = o.Attributes.GetUserString("mark")
|
||||
if m in SCHED and oid.startswith(f"{PID}-RC-"):
|
||||
cl[m].append(o)
|
||||
elif m in SCHED and oid.startswith(f"{PID}-R-"):
|
||||
pp[m].append(o)
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]), f"{len(pp[m])}/{len(cl[m])}")
|
||||
if cl[m]:
|
||||
check(f"{m} original length vs L (worst)", 0, max(abs(float(o.Attributes.GetUserString("design_length")) - L) for o in cl[m]))
|
||||
half = G["half_a"]
|
||||
for name, mt in G["mats"].items():
|
||||
objs = [o for m in mt["marks"] for o in cl[m]]
|
||||
axis = mt["axis"]
|
||||
pos = sorted(round((bb(o).Min.X + bb(o).Max.X) / 2 if axis == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in objs)
|
||||
want = chain(mt["chain"])
|
||||
check(f"{name} positions vs chain ({len(pos)}/{len(want)}, worst)", 0, worst(pos, want))
|
||||
leg = mt["leg"]
|
||||
straight = [bb(o).Min.Z if leg > 0 else bb(o).Max.Z for o in objs]
|
||||
check(f"{name} straight part level {mt['z']} (worst)", 0, max(abs(z - mt["z"]) for z in straight) if straight else 1e9)
|
||||
span = [(bb(o).Max.Y - bb(o).Min.Y) if axis == "Y" else (bb(o).Max.X - bb(o).Min.X) for o in objs]
|
||||
check(f"{name} span 2 x {half} (worst)", 0, max(abs(s - 2 * half) for s in span) if span else 1e9)
|
||||
legs = [(bb(o).Max.Z - bb(o).Min.Z) for o in objs]
|
||||
check(f"{name} leg {abs(leg)} {'up' if leg > 0 else 'down'} (worst)", 0, max(abs(h - abs(leg)) for h in legs) if legs else 1e9)
|
||||
a, b = mt["marks"]
|
||||
ia = sorted(round((bb(o).Min.X + bb(o).Max.X) / 2 if axis == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in cl[a])
|
||||
flag(f"{name} {a}/{b} alternate from the edge (F3)", ia == want[0::2], f"{a} {len(ia)}")
|
||||
for m, md in G["mid"].items():
|
||||
got = sorted((round(bb(o).Max.Z, 1), round((bb(o).Min.X + bb(o).Max.X) / 2 if md["axis"] == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1)) for o in cl[m])
|
||||
want = sorted((md["z_hi"] if round(p) % 1000 == 0 else md["z_lo"], round(p, 1)) for p in chain(md["chain"]))
|
||||
flag(f"{m} levels / positions ({len(want)})", len(got) == len(want) and all(abs(g[0] - w[0]) <= 1 and abs(g[1] - w[1]) <= 1 for g, w in zip(got, want)),
|
||||
f"{got[:3]} vs {want[:3]}")
|
||||
b10 = G["b10"]
|
||||
zs = sorted(round(bb(o).Min.Z, 1) for o in cl["B10"])
|
||||
check("B10 levels (worst)", 0, worst(zs, sorted(b10["levels"])))
|
||||
check("B10 loop size X (worst)", 0, max(abs(bb(o).Max.X - bb(o).Min.X - b10["size"]) for o in cl["B10"]) if cl["B10"] else 1e9)
|
||||
b9 = G["b9"]
|
||||
cols = sorted({round((bb(o).Min.X + bb(o).Max.X) / 2, 1) for o in cl["B9"]})
|
||||
flag(f"B9 columns {b9['n_cols']} @{b9['pitch_x']}", len(cols) == b9["n_cols"] and all(abs(b - a - b9["pitch_x"]) < 1 for a, b in zip(cols, cols[1:])), f"{len(cols)}")
|
||||
check("B9 height top-bottom (worst)", b9["height"], max((bb(o).Max.Z - bb(o).Min.Z for o in cl["B9"]), key=lambda h: abs(h - b9["height"])) if cl["B9"] else 0)
|
||||
flag("B9 hooks the top and bottom Y bars", all(bb(o).Max.Z > G["mats"]["y_top"]["z"] and bb(o).Min.Z < G["mats"]["y_bottom"]["z"] for o in cl["B9"]))
|
||||
# containment and cover
|
||||
ft = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-FT"][0]
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
r = G["dia"][m] / 2
|
||||
for o in cl[m]:
|
||||
cu = o.Geometry
|
||||
for t in cu.DivideByCount(max(6, int(cu.GetLength() / 300)), True):
|
||||
p = cu.PointAt(t)
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not ft.IsPointInside(q, 0.01, False):
|
||||
bad += 1
|
||||
cov = min(cov, p.DistanceTo(ft.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside the footing", 0, bad, tol=0)
|
||||
info("min cover to the footing surface per mark (record)", json.dumps(cover))
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,181 @@
|
||||
"""G3 gate checker, form "shaft cage" (Ramp-A P1, 2026-10-13): drilled-shaft cage bars and the anti-uplift plate.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G3) come from the D4 schedule (L, N),
|
||||
the 구조물 치수표 chains measured from the shaft top EL (D1), the section radii and the user decisions S1-S6;
|
||||
generator params are not read.
|
||||
Checks (C9): counts, original length (+coupler) vs schedule L, main radius / division / piece levels, hoop and
|
||||
tie level grids, hoop radius, X / Y ties apart, U bars (count, bottom level, width, planes), plate (size,
|
||||
thickness, centre, cover), containment in the pile segments, min side cover (record).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, TOL, G = X["pier_id"], X["tol_mm"], X["G3"]
|
||||
SCHED_L, SCHED_N, DIA = G["schedule_L"], G["schedule_n"], G["dia"]
|
||||
M, HP, TI, U, PL = G["main"], G["hoops"], G["ties"], G["u_bars"], G["plate"]
|
||||
TOP = G["shaft_top"]
|
||||
PILE = X["G1"]["pile"]
|
||||
Z_BOT = PILE["z_bottom"]
|
||||
SEG = [] # (z0, z1, radius) bottom to top
|
||||
z = Z_BOT
|
||||
for s in PILE["segments"]:
|
||||
SEG.append((z, z + s["length"], s["d"] / 2))
|
||||
z += s["length"]
|
||||
|
||||
|
||||
def chain(c):
|
||||
"""Levels from the shaft top: first offset, then (count, pitch) groups downwards; returned bottom to top."""
|
||||
out = [TOP - c["first"]]
|
||||
for n, p in c["groups"]:
|
||||
out += [out[-1] - p * k for k in range(1, n + 1)]
|
||||
return sorted(out)
|
||||
|
||||
|
||||
HOOP_LEVELS, TIE_LEVELS = chain(HP["chain"]), chain(TI["chain"])
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=TOL):
|
||||
results.append(dict(check="C9", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def ids(prefix):
|
||||
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [o for o in ids(f"{PID}-RC-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
||||
pipes = {m: [o for o in ids(f"{PID}-R-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
||||
occ = lambda o: o.Attributes.GetUserString("occurrence").rstrip("ab")
|
||||
hm, tm = HP["mark"], TI["mark"]
|
||||
for m in SCHED_L:
|
||||
n_occ = len({occ(o) for o in cl[m]}) if m == hm else len(cl[m])
|
||||
check(f"{m} bar count", SCHED_N[m], n_occ, tol=0)
|
||||
flag(f"{m} pipes closed & paired with centrelines",
|
||||
len(pipes[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pipes[m]), f"{len(pipes[m])}/{len(cl[m])}")
|
||||
for m in SCHED_L:
|
||||
per, curve = {}, {}
|
||||
for o in cl[m]:
|
||||
k = occ(o) if m == hm else o.Attributes.GetUserString("occurrence")
|
||||
cp = float(o.Attributes.GetUserString("coupler_length") or 0)
|
||||
per[k] = per.get(k, 0) + float(o.Attributes.GetUserString("design_length")) + cp
|
||||
curve[k] = curve.get(k, 0) + o.Geometry.GetLength() + cp
|
||||
if per:
|
||||
check(f"{m} original length (+coupler) vs schedule L (worst of {len(per)})", 0,
|
||||
max(abs(v - SCHED_L[m]) for v in per.values()))
|
||||
dev = [v - SCHED_L[m] for v in curve.values()]
|
||||
info(f"{m} curve length (+coupler) - L (record)", f"min {min(dev):.1f} / max {max(dev):.1f} mm")
|
||||
# main bars: pieces stacked between the first and the last hoop level (S6: coupler length 0)
|
||||
zb, zt = HOOP_LEVELS[0], HOOP_LEVELS[-1]
|
||||
check("main span (last - first hoop level) = sum of piece L", zt - zb, sum(SCHED_L[mk] for mk in M["pieces"]))
|
||||
z = zb
|
||||
for mk in M["pieces"]: # bottom to top
|
||||
lo = [min(o.Geometry.PointAtStart.Z, o.Geometry.PointAtEnd.Z) for o in cl[mk]]
|
||||
hi = [max(o.Geometry.PointAtStart.Z, o.Geometry.PointAtEnd.Z) for o in cl[mk]]
|
||||
check(f"{mk} bottom EL (worst)", 0, max(abs(v - z) for v in lo))
|
||||
check(f"{mk} top EL = bottom + L (worst)", 0, max(abs(v - (z + SCHED_L[mk])) for v in hi))
|
||||
z += SCHED_L[mk]
|
||||
rr = [math.hypot(o.Geometry.PointAtStart.X, o.Geometry.PointAtStart.Y) for o in cl[mk]]
|
||||
check(f"{mk} radius (worst)", 0, max(abs(r - M["radius"]) for r in rr), tol=0.5)
|
||||
a = sorted(math.degrees(math.atan2(o.Geometry.PointAtStart.Y, o.Geometry.PointAtStart.X)) % 360 for o in cl[mk])
|
||||
check(f"{mk} angle vs {M['start_deg']} + i*360/{M['divisions']} (worst, deg)", 0,
|
||||
max(abs(x - (M["start_deg"] + i * 360 / M["divisions"])) for i, x in enumerate(a)), tol=0.01)
|
||||
# hoops
|
||||
hz = {}
|
||||
for o in cl[hm]:
|
||||
bb = o.Geometry.GetBoundingBox(True)
|
||||
hz.setdefault(round((bb.Max.Z + bb.Min.Z) / 2, 1), []).append(o)
|
||||
zs = sorted(hz)
|
||||
flag(f"{hm} levels {len(HOOP_LEVELS)} x 2 pieces", len(zs) == len(HOOP_LEVELS) and all(len(v) == 2 for v in hz.values()),
|
||||
f"{len(zs)} levels")
|
||||
check(f"{hm} levels vs 구조물 치수표 chain (worst)", 0,
|
||||
max(abs(a - b) for a, b in zip(zs, HOOP_LEVELS)) if len(zs) == len(HOOP_LEVELS) else 1e9)
|
||||
for o in cl[hm][:4]:
|
||||
c = o.Geometry
|
||||
p = c.PointAt(c.Domain.Mid)
|
||||
check(f"{hm} radius at mid point ({o.Attributes.GetUserString('occurrence')})", HP["radius"], math.hypot(p.X, p.Y), tol=0.5)
|
||||
# ties
|
||||
by = lambda s: sorted((o.Geometry for o in cl[tm] if o.Attributes.GetUserString("occurrence").endswith(s)),
|
||||
key=lambda g: g.GetBoundingBox(True).Max.Z)
|
||||
xs, ys = by("X"), by("Y")
|
||||
txm = [g.GetBoundingBox(True).Max.Z for g in xs]
|
||||
tym = [g.GetBoundingBox(True).Max.Z for g in ys]
|
||||
flag(f"{tm} X / Y per level, {len(TIE_LEVELS)} levels", len(txm) == len(tym) == len(TIE_LEVELS), f"X {len(txm)} Y {len(tym)}")
|
||||
if len(txm) == len(TIE_LEVELS) == len(tym):
|
||||
check(f"{tm} X levels vs chain (worst)", 0, max(abs(a - b) for a, b in zip(txm, TIE_LEVELS)))
|
||||
check(f"{tm} Y levels = X + {TI['y_lift']} (worst)", 0, max(abs(a - b - TI["y_lift"]) for a, b in zip(tym, TIE_LEVELS)))
|
||||
gaps = []
|
||||
for a, b in zip(xs, ys):
|
||||
ok, pa, pb = a.ClosestPoints(b)
|
||||
gaps.append(pa.DistanceTo(pb) if ok else 0.0)
|
||||
gap = min(gaps) if gaps else 0.0
|
||||
check(f"{tm} X / Y centrelines apart >= bar diameter {DIA[tm]} (min, capped)", DIA[tm], min(gap, DIA[tm]), tol=0.01)
|
||||
# U bars: bottom level, overall width (vertical legs on the hoop circle), one plane each through the centre
|
||||
um = U["mark"]
|
||||
bots = [o.Geometry.GetBoundingBox(True).Min.Z for o in cl[um]]
|
||||
check(f"{um} bottom EL = shaft bottom + {U['bottom_above']} (worst, centreline)", 0,
|
||||
max(abs(b - (Z_BOT + U["bottom_above"])) for b in bots) if bots else 1e9)
|
||||
w, planar = [], 0
|
||||
for o in cl[um]:
|
||||
c = o.Geometry
|
||||
a, b = c.PointAtStart, c.PointAtEnd
|
||||
w.append(math.hypot(a.X - b.X, a.Y - b.Y))
|
||||
ok, pl = c.TryGetPlane(0.01)
|
||||
if ok and abs(pl.Normal.Z) < 1e-6 and abs(pl.DistanceTo(rg.Point3d(0, 0, pl.Origin.Z))) < 0.5:
|
||||
planar += 1
|
||||
check(f"{um} leg spacing (worst) = 2 x hoop radius {2 * HP['radius']:.0f}", 0, max(abs(x - 2 * HP["radius"]) for x in w) if w else 1e9)
|
||||
check(f"{um} vertical planes through the centre", len(cl[um]), planar, tol=0)
|
||||
# plate
|
||||
pts = ids(f"{PID}-PT")
|
||||
flag("plate count 1", len(pts) == 1, str(len(pts)))
|
||||
if pts:
|
||||
bb = pts[0].Geometry.GetBoundingBox(True)
|
||||
check("plate size X", PL["size"], bb.Max.X - bb.Min.X)
|
||||
check("plate size Y", PL["size"], bb.Max.Y - bb.Min.Y)
|
||||
check("plate thickness", PL["thickness"], bb.Max.Z - bb.Min.Z, tol=0.01)
|
||||
check("plate centre X / Y offset", 0, math.hypot((bb.Max.X + bb.Min.X) / 2, (bb.Max.Y + bb.Min.Y) / 2))
|
||||
check(f"plate top = U bottom - {DIA[um] / 2:.0f} (S4)", Z_BOT + U["bottom_above"] - DIA[um] / 2, bb.Max.Z, tol=0.01)
|
||||
info("plate bottom cover (record)", f"{bb.Min.Z - Z_BOT:.1f} mm")
|
||||
# containment and side cover in the pile segments
|
||||
solids = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-PL")]
|
||||
min_cover = {}
|
||||
for m in SCHED_L:
|
||||
bad, cov = 0, 1e9
|
||||
r = DIA[m] / 2
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
for t in c.DivideByCount(max(4, int(c.GetLength() / 150)), True):
|
||||
p = c.PointAt(t)
|
||||
radial = rg.Vector3d(p.X, p.Y, 0)
|
||||
radial.Unitize() if radial.Length > 1e-6 else None
|
||||
for q in (p, p + radial * r, p - radial * r, p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not any(s.IsPointInside(q, 0.01, False) for s in solids):
|
||||
bad += 1
|
||||
R = next((rad for z0, z1, rad in SEG if z0 <= p.Z <= z1), None)
|
||||
if R is not None:
|
||||
cov = min(cov, R - math.hypot(p.X, p.Y) - r, p.Z - Z_BOT - r)
|
||||
min_cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside the pile concrete", 0, bad, tol=0)
|
||||
info("min cover per mark (side or bottom, record)", json.dumps(min_cover))
|
||||
write_result(RESULT_PATH, results, dict(min_cover=min_cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,124 @@
|
||||
"""G3 gate checker, footing rebar as centreline polylines with drawn position lists (Saejae-1 P1, 2026-10-15; adapted from g3_poly.py,
|
||||
Okseong P1): mats B1~B8, mid layers B11 / B12, perimeter loops B10, vertical ties B9 (6 rows, 8 / 7 per row alternating).
|
||||
Positions are explicit lists (expected G3 "pos" / "levels" / "b9") typed from the drawn points, not dimension chains.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G3) come from the schedule (L, N), the
|
||||
front / side dimension chains (bar positions), the leg lengths b of the schedule, the drawn mat / loop levels and
|
||||
the B9 grid rule with the user decisions F1-F4; generator params are not read.
|
||||
Checks (C9): counts, original length vs L, per mat: positions vs chain, straight level, span, leg length and
|
||||
direction; mid layers; B10 levels / size; B9 grid / height; containment in the footing; min cover (record).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G3"]
|
||||
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C9", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C9", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def bb(o):
|
||||
return o.Geometry.GetBoundingBox(True)
|
||||
|
||||
|
||||
def mid_(o):
|
||||
return (bb(o).Min.X + bb(o).Max.X) / 2, (bb(o).Min.Y + bb(o).Max.Y) / 2
|
||||
|
||||
|
||||
def worst(a, b):
|
||||
return max(abs(x - y) for x, y in zip(a, b)) if len(a) == len(b) and a else 1e9
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [] for m in SCHED}
|
||||
pp = {m: [] for m in SCHED}
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
m = o.Attributes.GetUserString("mark")
|
||||
if m in SCHED and oid.startswith(f"{PID}-RC-"):
|
||||
cl[m].append(o)
|
||||
elif m in SCHED and oid.startswith(f"{PID}-R-"):
|
||||
pp[m].append(o)
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]), f"{len(pp[m])}/{len(cl[m])}")
|
||||
if cl[m]:
|
||||
check(f"{m} original length vs L (worst)", 0, max(abs(float(o.Attributes.GetUserString("design_length")) - L) for o in cl[m]))
|
||||
half = G["half_a"]
|
||||
for name, mt in G["mats"].items():
|
||||
objs = [o for m in mt["marks"] for o in cl[m]]
|
||||
axis = mt["axis"]
|
||||
pos = sorted(round((bb(o).Min.X + bb(o).Max.X) / 2 if axis == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in objs)
|
||||
want = sorted(mt["pos"])
|
||||
check(f"{name} positions vs chain ({len(pos)}/{len(want)}, worst)", 0, worst(pos, want))
|
||||
leg = mt["leg"]
|
||||
straight = [bb(o).Min.Z if leg > 0 else bb(o).Max.Z for o in objs]
|
||||
check(f"{name} straight part level {mt['z']} (worst)", 0, max(abs(z - mt["z"]) for z in straight) if straight else 1e9)
|
||||
span = [(bb(o).Max.Y - bb(o).Min.Y) if axis == "Y" else (bb(o).Max.X - bb(o).Min.X) for o in objs]
|
||||
check(f"{name} span 2 x {half} (worst)", 0, max(abs(s - 2 * half) for s in span) if span else 1e9)
|
||||
legs = [(bb(o).Max.Z - bb(o).Min.Z) for o in objs]
|
||||
check(f"{name} leg {abs(leg)} {'up' if leg > 0 else 'down'} (worst)", 0, max(abs(h - abs(leg)) for h in legs) if legs else 1e9)
|
||||
a, b = mt["marks"]
|
||||
ia = sorted(round((bb(o).Min.X + bb(o).Max.X) / 2 if axis == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in cl[a])
|
||||
flag(f"{name} {a}/{b} alternate from the edge (F3)", ia == want[0::2], f"{a} {len(ia)}")
|
||||
for m, md in G["mid"].items():
|
||||
lift = md.get("lift", 0)
|
||||
got = sorted((round(bb(o).Max.Z, 1), round((bb(o).Min.X + bb(o).Max.X) / 2 if md["axis"] == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1)) for o in cl[m])
|
||||
want = sorted((float(z) + lift, p) for z, ps in md["levels"].items() for p in ps)
|
||||
flag(f"{m} levels / positions ({len(want)})", len(got) == len(want) and all(abs(g[0] - w[0]) <= 1 and abs(g[1] - w[1]) <= 1 for g, w in zip(got, want)),
|
||||
f"{got[:3]} vs {want[:3]}")
|
||||
b10 = G["b10"]
|
||||
zs = sorted(round(bb(o).Min.Z, 1) for o in cl["B10"])
|
||||
check("B10 levels (worst)", 0, worst(zs, sorted(b10["levels"])))
|
||||
check("B10 loop size X (worst)", 0, max(abs(bb(o).Max.X - bb(o).Min.X - b10["size"]) for o in cl["B10"]) if cl["B10"] else 1e9)
|
||||
b9 = G["b9"]
|
||||
got9 = sorted((round(mid_(o)[0], 1), round(mid_(o)[1], 1)) for o in cl["B9"])
|
||||
want9 = sorted((x, y) for k, y in enumerate(b9["rows"]) for x in (b9["cols_even"] if k % 2 == 0 else b9["cols_odd"]))
|
||||
flag(f"B9 grid {len(want9)} (rows {len(b9['rows'])}, 8 / 7 per row)", len(got9) == len(want9) and all(abs(g[0] - w[0]) <= 1 and abs(g[1] - w[1]) <= 1 for g, w in zip(got9, want9)),
|
||||
f"{len(got9)}")
|
||||
check("B9 height top-bottom (worst)", b9["height"], max((bb(o).Max.Z - bb(o).Min.Z for o in cl["B9"]), key=lambda h: abs(h - b9["height"])) if cl["B9"] else 0)
|
||||
check("B9 span along Y a (worst)", b9["a"], max((bb(o).Max.Y - bb(o).Min.Y for o in cl["B9"]), key=lambda h: abs(h - b9["a"])) if cl["B9"] else 0)
|
||||
flag("B9 hooks the top and bottom Y bars", all(bb(o).Max.Z > G["mats"]["y_top"]["z"] and bb(o).Min.Z < G["mats"]["y_bottom"]["z"] for o in cl["B9"]))
|
||||
# containment and cover
|
||||
ft = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-FT"][0]
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
r = G["dia"][m] / 2
|
||||
for o in cl[m]:
|
||||
cu = o.Geometry
|
||||
for t in cu.DivideByCount(max(6, int(cu.GetLength() / 300)), True):
|
||||
p = cu.PointAt(t)
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not ft.IsPointInside(q, 0.01, False):
|
||||
bad += 1
|
||||
cov = min(cov, p.DistanceTo(ft.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside the footing", 0, bad, tol=0)
|
||||
info("min cover to the footing surface per mark (record)", json.dumps(cover))
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,138 @@
|
||||
"""G4 gate checker: coping rebar C1-C16, S1, S2 (check C10) inside Rhino 8.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values from EXPECTED_PATH section G4 (D3 counts, L,
|
||||
a/b ranges; D1/D2 dimension chains). Width decisions (K1) make the original-length checks of the range marks
|
||||
fail by design; they are reported as FAIL and listed in `planned_fail`. Generator params are not read.
|
||||
Ported from Code/교각모델링/verify_coping_rebar.py (same checks and order).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectColorSource
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, series, write_result, ychain # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G4"]
|
||||
COUNT, L, DIA = G["count"], G["L"], G["dia"]
|
||||
RANGE = {m: tuple(v) for m, v in G["range"].items()}
|
||||
Y24 = ychain(G["y24_chain"], G["half_width"])
|
||||
Y13 = ychain(G["y13_chain"], G["half_width"])
|
||||
TIER = G["tier_z"]
|
||||
STIR_X = {m: sorted(series(v)) for m, v in G["stirrup_x"].items()}
|
||||
LOOP_Z = {m: series(v) for m, v in G["loop_z"].items()}
|
||||
PALETTE = {int(k): tuple(v) for k, v in X["rebar_palette"].items()}
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C10", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def main():
|
||||
objs = all_objects(doc)
|
||||
cl = {m: [o for o in objs if (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-RC-")
|
||||
and o.Attributes.GetUserString("mark") == m] for m in COUNT}
|
||||
pp = {m: [o for o in objs if (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-R-")
|
||||
and o.Attributes.GetUserString("mark") == m] for m in COUNT}
|
||||
for m in COUNT:
|
||||
check(f"{m} bar count", COUNT[m], len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]))
|
||||
if not cl[m]:
|
||||
continue
|
||||
lens = [float(o.Attributes.GetUserString("design_length")) for o in cl[m]]
|
||||
if m in RANGE:
|
||||
key, hi, lo = RANGE[m]
|
||||
vals = [float(o.Attributes.GetUserString(key)) for o in cl[m]]
|
||||
check(f"{m} {key} max vs schedule", hi, max(vals))
|
||||
check(f"{m} {key} min vs schedule", lo, min(vals))
|
||||
cv = G["range_convention"][key]
|
||||
fixed = cv["a"] if key == "b_sched" else cv["b"]
|
||||
avg_conv = 2 * fixed + (hi + lo) + cv["c_times"] * cv["c"] # schedule convention: (max+min)/2
|
||||
check(f"{m} schedule L reproduced by convention (max+min)/2", L[m], avg_conv)
|
||||
ws = sorted({o.Attributes.GetUserString("w_model") for o in cl[m]})
|
||||
results.append(dict(check="C10", item=f"{m} original length vs L (drawn widths {ws}, K1 decision)",
|
||||
detail=f"model mean {sum(lens) / len(lens):.1f} vs L {L[m]}", result="FAIL"))
|
||||
else:
|
||||
check(f"{m} original length vs L (worst)", 0, max(abs(v - L[m]) for v in lens))
|
||||
dev = [o.Geometry.GetLength() - float(o.Attributes.GetUserString("design_length")) for o in cl[m]]
|
||||
info(f"{m} curve - original length", f"{min(dev):.1f} / {max(dev):.1f}")
|
||||
for m, z in TIER.items():
|
||||
zs = [o.Geometry.PointAt(o.Geometry.Domain.Mid).Z for o in cl[m]]
|
||||
check(f"{m} tier EL (worst)", 0, max(abs(v - z) for v in zs))
|
||||
for pair in G["pairs"]:
|
||||
ys = sorted((round(o.Geometry.PointAt(o.Geometry.Domain.Mid).Y), m) for m in pair for o in cl[m])
|
||||
flag(f"{'/'.join(pair)} Y = A-A chain, alternate from {pair[0]} at {Y24[0]:.0f}",
|
||||
[y for y, _ in ys] == Y24 and [m for _, m in ys] == [pair[i % 2] for i in range(len(Y24))])
|
||||
for m, xs in STIR_X.items():
|
||||
got = sorted({round(o.Geometry.GetBoundingBox(True).Center.X) for o in cl[m]})
|
||||
flag(f"{m} stirrup X positions", got == xs, f"{len(got)} positions")
|
||||
per = {}
|
||||
for o in cl[m]:
|
||||
k = round(o.Geometry.GetBoundingBox(True).Center.X)
|
||||
per[k] = per.get(k, 0) + 1
|
||||
flag(f"{m} {G['stirrups_per_x']} stirrups per position", set(per.values()) == {G["stirrups_per_x"]})
|
||||
zb = [round(o.Geometry.GetBoundingBox(True).Min.Z) for o in cl["C10"]]
|
||||
check(f"C10 stirrup bottom EL {G['c10_bottom'] / 1000:.3f} (worst)", 0, max(abs(v - G["c10_bottom"]) for v in zb))
|
||||
for m, zs in LOOP_Z.items():
|
||||
got = sorted({round(o.Geometry.GetBoundingBox(True).Center.Z) for o in cl[m]})
|
||||
flag(f"{m} loop levels", got == zs, str(got))
|
||||
for m in ("C15", "C16"):
|
||||
got = sorted(round(o.Geometry.GetBoundingBox(True).Center.Y) for o in cl[m])
|
||||
flag(f"{m} Y = B-B chain", got == Y13, f"{len(got)}")
|
||||
check(f"C15 EL {G['c15_top'] / 1000:.3f}", G["c15_top"], max(o.Geometry.GetBoundingBox(True).Max.Z for o in cl["C15"]))
|
||||
check(f"C16 EL {G['c16_top'] / 1000:.3f}", G["c16_top"], max(o.Geometry.GetBoundingBox(True).Max.Z for o in cl["C16"]))
|
||||
# horizontal-segment height (bbox max picks up upward hooks)
|
||||
c14z = sorted(round(o.Geometry.PointAt(o.Geometry.Domain.Mid).Z, 1) for o in cl["C14"])
|
||||
exp14 = sorted(z for z, n in G["c14_levels"] for _ in range(n))
|
||||
flag("C14 levels/count per level", c14z == exp14, "")
|
||||
step = {int(k): v for k, v in G["step_top"].items()}
|
||||
for m, (n_per, dz) in G["s_per_girder"].items():
|
||||
by = {}
|
||||
for o in cl[m]:
|
||||
g = int(o.Attributes.GetUserString("occurrence")[1])
|
||||
by.setdefault(g, []).append(o.Geometry.GetBoundingBox(True).Max.Z)
|
||||
flag(f"{m} per girder line = {n_per}", sorted(len(v) for v in by.values()) == [n_per] * len(step))
|
||||
check(f"{m} EL = step top - {dz} (worst)", 0, max(abs(z - (step[g] - dz)) for g, v in by.items() for z in v))
|
||||
cp = [o for o in objs if o.Attributes.GetUserString("id") == f"{PID}-CP"][0].Geometry
|
||||
cover = {}
|
||||
for m in COUNT:
|
||||
bad, cov = 0, 1e9
|
||||
r = DIA[m] / 2
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
for t in c.DivideByCount(max(4, int(c.GetLength() / 200)), True):
|
||||
p = c.PointAt(t)
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not cp.IsPointInside(q, 0.01, False):
|
||||
bad += 1
|
||||
cov = min(cov, p.DistanceTo(cp.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside coping", 0, bad, tol=0)
|
||||
info("min cover to coping surface per mark (top face under pedestals counted)", json.dumps(cover))
|
||||
wrong = 0
|
||||
for o in objs:
|
||||
if (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-R-"):
|
||||
d = int(o.Attributes.GetUserString("dia")[1:])
|
||||
c = o.Attributes.ObjectColor
|
||||
if o.Attributes.ColorSource != ObjectColorSource.ColorFromObject or (c.R, c.G, c.B) != PALETTE[d]:
|
||||
wrong += 1
|
||||
check("rebar colour mismatches (all rebar)", 0, wrong, tol=0)
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,222 @@
|
||||
"""G4 gate checker, Okseong P1 coping + bearing-seat rebar (2026-10-14): C1~C20 and S1~S8 as centreline polylines (plan skew 18 deg).
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G4, plus the G1 pedestal table) come from the schedule
|
||||
(L, N, polyline length = L - lap), the drawn levels / positions of the rebar drawing, the section dimension chains and the user
|
||||
decisions H1-H6; generator params are not read. Coping bars are sheared like the coping: x_model = x - tan(skew) * y, so the
|
||||
section x of a bar is bbox mid X + tan * bbox mid Y. Seat bars are checked in the pedestal frame.
|
||||
Checks (C10): counts / pipes / polyline length, top layers (rows, ends, legs), C19 / C20, C7, stirrups (x, top, bottom, y), side loops
|
||||
and ties, C18, seat bars per pedestal, containment in the coping + pedestals, minimum cover (record).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import statistics
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G4"]
|
||||
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
||||
T = math.tan(math.radians(G["skew_deg"]))
|
||||
COS = math.cos(math.radians(G["skew_deg"]))
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C10", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def bb(o):
|
||||
return o.Geometry.GetBoundingBox(True)
|
||||
|
||||
|
||||
def mid(o):
|
||||
b = bb(o)
|
||||
return (b.Min.X + b.Max.X) / 2, (b.Min.Y + b.Max.Y) / 2
|
||||
|
||||
|
||||
def xs(o):
|
||||
"""Section x of a coping bar (shear removed)."""
|
||||
mx, my = mid(o)
|
||||
return mx + T * my
|
||||
|
||||
|
||||
def worst(got, want):
|
||||
got, want = sorted(got), sorted(want)
|
||||
return max((abs(a - b) for a, b in zip(got, want)), default=1e9) if len(got) == len(want) else 1e9
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [] for m in SCHED}
|
||||
pp = {m: [] for m in SCHED}
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
m = o.Attributes.GetUserString("mark")
|
||||
if m in SCHED and oid.startswith(f"{PID}-RC-"):
|
||||
cl[m].append(o)
|
||||
elif m in SCHED and oid.startswith(f"{PID}-R-"):
|
||||
pp[m].append(o)
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]), f"{len(pp[m])}/{len(cl[m])}")
|
||||
dl = [float(o.Attributes.GetUserString("design_length")) for o in cl[m]]
|
||||
if m in G["fixed_length"]:
|
||||
check(f"{m} polyline length vs L - lap (worst)", 0, max(abs(v - G["fixed_length"][m]) for v in dl) if dl else 1e9)
|
||||
elif dl:
|
||||
info(f"{m} mean polyline length vs L (variable mark, record)",
|
||||
f"mean {statistics.mean(dl):.1f} / L {L} (diff {statistics.mean(dl) - L:+.1f}); min {min(dl):.0f} max {max(dl):.0f}")
|
||||
# top layers C1~C6
|
||||
tp, y25 = G["top"], G["y25"]
|
||||
for m in ("C1", "C2", "C3", "C4", "C5", "C6"):
|
||||
s = tp[m]
|
||||
want = [y for i, y in enumerate(y25) if i % 2 == s["parity"]]
|
||||
got = [mid(o)[1] for o in cl[m]]
|
||||
check(f"{m} y rows vs chain (worst, n={len(got)}/{len(want)})", 0, worst(got, want))
|
||||
check(f"{m} level of the straight part (worst)", 0, max(abs(bb(o).Max.Z - s["z"]) for o in cl[m]))
|
||||
check(f"{m} leg {s['leg']} (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - s["leg"]) for o in cl[m]))
|
||||
check(f"{m} x ends +-{tp['x_end']} sheared (worst)", 0,
|
||||
max(max(abs(bb(o).Min.X + T * mid(o)[1] + tp["x_end"]), abs(bb(o).Max.X + T * mid(o)[1] - tp["x_end"])) for o in cl[m]))
|
||||
# C19, C20
|
||||
c19 = G["c19"]
|
||||
check("C19 y rows vs chain (worst)", 0, worst([mid(o)[1] for o in cl["C19"]], y25))
|
||||
check("C19 level (worst)", 0, max(abs(bb(o).Max.Z - c19["z"]) for o in cl["C19"]))
|
||||
check("C19 x ends (worst)", 0, max(max(abs(bb(o).Min.X + T * mid(o)[1] - c19["x"][0]), abs(bb(o).Max.X + T * mid(o)[1] - c19["x"][1])) for o in cl["C19"]))
|
||||
check("C19 leg 370 (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - G["c20_leg"]) for o in cl["C19"]))
|
||||
for k, pc in enumerate(G["c20"]):
|
||||
grp = [o for o in cl["C20"] if abs(bb(o).Max.Z - pc["z"]) < 0.5 and abs(bb(o).Min.X + T * mid(o)[1] - pc["x"][0]) < 3]
|
||||
flag(f"C20 piece {k + 1}: 25 bars at EL {pc['z']}, x {pc['x']}", len(grp) == 25, f"{len(grp)}")
|
||||
if grp:
|
||||
check(f"C20 piece {k + 1} x ends (worst)", 0, max(max(abs(bb(o).Min.X + T * mid(o)[1] - pc["x"][0]), abs(bb(o).Max.X + T * mid(o)[1] - pc["x"][1])) for o in grp))
|
||||
check(f"C20 piece {k + 1} leg 370 (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - G["c20_leg"]) for o in grp))
|
||||
check(f"C20 piece {k + 1} y rows (worst)", 0, worst([mid(o)[1] for o in grp], y25))
|
||||
# C7
|
||||
c7 = G["c7"]
|
||||
check("C7 lowest level per bar vs drawn (worst)", 0, worst([bb(o).Min.Z for o in cl["C7"]], c7["flat_z"]))
|
||||
normal = [o for o in cl["C7"] if abs(mid(o)[1]) < 1400]
|
||||
check("C7 top end EL 20.3672 (bars not on the side face, worst)", 0,
|
||||
max(abs(bb(o).Max.Z - (c7["top_end_z"] + (0 if abs(mid(o)[1]) <= 1000 else 365.2))) for o in normal))
|
||||
check("C7 x ends +-5875 (worst)", 0, max(max(abs(bb(o).Min.X + T * mid(o)[1] + c7["x_end"]), abs(bb(o).Max.X + T * mid(o)[1] - c7["x_end"])) for o in normal))
|
||||
edge = [o for o in cl["C7"] if abs(mid(o)[1]) >= 1400]
|
||||
check("C7 side-face bars end where the slope meets the coping top (x end)", 0,
|
||||
max(max(abs(bb(o).Min.X + T * mid(o)[1] + c7["capped"]["x_end"]), abs(bb(o).Max.X + T * mid(o)[1] - c7["capped"]["x_end"])) for o in edge) if edge else 1e9, tol=8)
|
||||
got = sorted(round(mid(o)[1]) for o in cl["C7"])
|
||||
flag("C7 y rows: 11 @250 + 2 on the side face", got == sorted([-1250 + 250 * k for k in range(11)] + [-1525, 1525]), f"{got}")
|
||||
# stirrups
|
||||
st = G["stirrups"]
|
||||
|
||||
def top_of(x):
|
||||
return next(z for a, b, z in st["top"] if a <= x < b)
|
||||
|
||||
def bottom_of(x):
|
||||
s = st["bottom"]
|
||||
return s["flat_z"] if abs(x) <= s["flat_half"] else s["flat_z"] + s["slope"] * (abs(x) - s["flat_half"])
|
||||
|
||||
for zone, (rect, pent) in st["marks"].items():
|
||||
want = st["x"][zone]
|
||||
got_r = [xs(o) for o in cl[rect]]
|
||||
check(f"{rect} positions = drawn x ({len(want)}, worst)", 0, worst(got_r, want))
|
||||
got_p = [xs(o) - st["pent_dx"] for o in cl[pent]]
|
||||
check(f"{pent} positions = drawn x + {st['pent_dx']}, 2 per x (worst)", 0, worst(got_p, [x for x in want for _ in (0, 1)]))
|
||||
dt = max(abs(bb(o).Max.Z - top_of(xs(o))) for o in cl[rect] + cl[pent]) if cl[rect] else 1e9
|
||||
db = max(abs(bb(o).Min.Z - bottom_of(xs(o) - (st["pent_dx"] if o in cl[pent] else 0))) for o in cl[rect] + cl[pent]) if cl[rect] else 1e9
|
||||
check(f"{rect}/{pent} top = coping top - 97.5 (drawn zones, worst)", 0, dt)
|
||||
check(f"{rect}/{pent} bottom = soffit + 120 - 22.5 (worst)", 0, db, tol=2.5)
|
||||
check(f"{rect} legs y +-{st['rect_y']} (worst)", 0, max(max(abs(bb(o).Max.Y - st["rect_y"] - 0), abs(bb(o).Min.Y + st["rect_y"])) for o in cl[rect]) if cl[rect] else 1e9, tol=2)
|
||||
lo, hi = st["pent_y"]
|
||||
okp = all((abs(bb(o).Min.Y - lo) < 2 and abs(bb(o).Max.Y - hi) < 2) or (abs(bb(o).Max.Y + lo) < 2 and abs(bb(o).Min.Y + hi) < 2) for o in cl[pent])
|
||||
flag(f"{pent} y between +-{lo} and +-{hi} (A-A)", okp)
|
||||
# side loops and ties
|
||||
lp = G["loops"]
|
||||
for key in ("upper", "lower"):
|
||||
sec = lp[key]
|
||||
mloop, mtie = sec["marks"]
|
||||
gl = sorted((round(bb(o).Min.Z, 1), round(bb(o).Max.X - bb(o).Min.X - 2 * T * (bb(o).Max.Y - bb(o).Min.Y) / 2, 1), round((bb(o).Max.Y - bb(o).Min.Y) / 2, 1)) for o in cl[mloop])
|
||||
wl = sorted((z, round(2 * x, 1), y) for z, x, y in zip(sec["z"], sec["x"], sec["y"]))
|
||||
flag(f"{mloop} loops: level / x length (corner fillets <= 50) / half width ({len(wl)})", len(gl) == len(wl) and all(abs(a[0] - b[0]) <= 1 and abs(a[1] - b[1]) <= 50 and abs(a[2] - b[2]) <= 1 for a, b in zip(gl, wl)), f"{gl[:2]}")
|
||||
flat = max((bb(o).Max.Z - bb(o).Min.Z) for o in cl[mloop] + cl[mtie])
|
||||
check(f"{mloop}/{mtie} horizontal (worst dz)", 0, flat, tol=0.01)
|
||||
gt = sorted((round(bb(o).Min.Z, 1), round(mid(o)[1], 1), round(bb(o).Max.X - bb(o).Min.X, 1)) for o in cl[mtie])
|
||||
wt = sorted((z, y, round(2 * x, 1)) for z, x in zip(sec["z"], sec["x"]) for y in lp["tie_y"])
|
||||
flag(f"{mtie} ties: level / y / x length ({len(wt)})", len(gt) == len(wt) and all(abs(a[0] - b[0]) <= 1 and abs(a[1] - b[1]) <= 1 and abs(a[2] - b[2]) <= 1 for a, b in zip(gt, wt)), f"{len(gt)}")
|
||||
# C18
|
||||
c18 = G["c18"]
|
||||
got = {}
|
||||
for o in cl["C18"]: # lower rows have the legs UP (the straight part is the lowest level)
|
||||
z = bb(o).Max.Z if bb(o).Max.Z - bb(o).Min.Z < 1 or abs((bb(o).Max.Z - bb(o).Min.Z) - 370) < 1 and bb(o).Min.Z > 20000 else bb(o).Min.Z
|
||||
got.setdefault(f"{z:.1f}", []).append(o)
|
||||
flag("C18 levels and counts", {k: len(v) for k, v in got.items()} == c18["levels"], f"{ {k: len(v) for k, v in got.items()} }")
|
||||
check("C18 widths per level (worst; fillets on the slanted legs <= 30)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - c18["width"][k]) for k, v in got.items() if k in c18["width"] for o in v), tol=30)
|
||||
# seats
|
||||
se = G["seats"]
|
||||
ped = {}
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith(f"{PID}-BS"):
|
||||
ped[int(oid[len(PID) + 3:])] = o.Geometry.GetBoundingBox(True)
|
||||
flag("5 pedestals found", sorted(ped) == [1, 2, 3, 4, 5], f"{sorted(ped)}")
|
||||
for k, pb in sorted(ped.items()):
|
||||
cx, cy = (pb.Min.X + pb.Max.X) / 2, (pb.Min.Y + pb.Max.Y) / 2
|
||||
yf, yr = se["y"]["front"], se["y"]["rear"]
|
||||
zp = pb.Min.Z + se["h1"]["front"] + (se["h1"]["rear"] - se["h1"]["front"]) * (cy - yf) / (yr - yf)
|
||||
ml, ms, mll, mss = se["marks_heavy"] if k == se["heavy"] else se["marks_light"]
|
||||
dl_, ds_ = G["dia"][ml], G["dia"][ms]
|
||||
grp = lambda m: [o for o in cl[m] if o.Attributes.GetUserString("occurrence").startswith(f"P{k}-")]
|
||||
gl, gs, gll, gss = grp(ml), grp(ms), grp(mll), grp(mss)
|
||||
flag(f"pedestal {k}: {ml} {ms} {mll} {mss} counts 11 / 21 / 22 / 42", (len(gl), len(gs), len(gll), len(gss)) == (11, 21, 22, 42), f"{len(gl)} {len(gs)} {len(gll)} {len(gss)}")
|
||||
check(f"pedestal {k} {ml} level {se['top_below']} below the pedestal top (worst)", 0, max(abs(bb(o).Max.Z - (zp - se["top_below"])) for o in gl), tol=1.5)
|
||||
check(f"pedestal {k} {ml} legs {se['leg']} (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - se["leg"]) for o in gl + gs))
|
||||
check(f"pedestal {k} {ml} lateral positions (worst)", 0, worst([(mid(o)[0] - cx) * COS for o in gl], se["long_t"]), tol=1.5)
|
||||
check(f"pedestal {k} {ml} length along the end face (worst)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - se["long_len"] * COS) for o in gl), tol=1.5)
|
||||
check(f"pedestal {k} {ms} level {ds_ if False else dl_} under {ml} (worst)", 0, max(abs(bb(o).Max.Z - (zp - se["top_below"] - dl_)) for o in gs), tol=1.5)
|
||||
check(f"pedestal {k} {ms} y positions (worst)", 0, worst([mid(o)[1] - cy for o in gs], se["short_o"]), tol=1.5)
|
||||
check(f"pedestal {k} {ms} x length (worst)", 0, max(abs((bb(o).Max.X - bb(o).Min.X) - se["short_len"]) for o in gs), tol=1.5)
|
||||
for layer, dz in enumerate(se["lower_below"]):
|
||||
l1 = [o for o in gll if abs(bb(o).Min.Z - (zp - dz)) < 1.5]
|
||||
l2 = [o for o in gss if abs(bb(o).Min.Z - (zp - dz + (G["dia"][mll] + G["dia"][mss]) / 2)) < 1.5]
|
||||
flag(f"pedestal {k} layer {layer + 1} EL pedestal top - {dz}: {mll} 11 / {mss} 21", (len(l1), len(l2)) == (11, 21), f"{len(l1)} {len(l2)}")
|
||||
if l1:
|
||||
check(f"pedestal {k} {mll} layer {layer + 1} positions (worst)", 0, worst([(mid(o)[0] - cx) * COS for o in l1], se["low_long_t"]), tol=1.5)
|
||||
check(f"pedestal {k} {mll} layer {layer + 1} length (worst)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - se["low_long_len"] * COS) for o in l1), tol=1.5)
|
||||
if l2:
|
||||
check(f"pedestal {k} {mss} layer {layer + 1} y positions (worst)", 0, worst([mid(o)[1] - cy for o in l2], se["low_short_o"]), tol=1.5)
|
||||
# containment and cover
|
||||
solids = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"
|
||||
or (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-BS")]
|
||||
solids = [s if isinstance(s, rg.Brep) else rg.Brep.TryConvertBrep(s) for s in solids]
|
||||
cp = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"][0]
|
||||
info("concrete solids for containment", f"{len(solids)} (coping + pedestals)")
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
r = G["dia"][m] / 2
|
||||
for o in cl[m]:
|
||||
cu = o.Geometry
|
||||
for t in cu.DivideByCount(max(6, int(cu.GetLength() / 200)), True):
|
||||
p = cu.PointAt(t)
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not any(s.IsPointInside(q, 0.01, False) for s in solids):
|
||||
bad += 1
|
||||
if cp.IsPointInside(p, 0.01, False):
|
||||
cov = min(cov, p.DistanceTo(cp.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside concrete", 0, bad, tol=0)
|
||||
info("min cover to the coping surface per mark (record; seat bars measured to the coping only)", json.dumps(cover))
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,207 @@
|
||||
"""G4 gate checker, form "polylines" (Ulsan P1, 2026-10-13): coping rebar C1~C15 and bearing-seat S1~S4.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G4) are the schedule (L, n, ranges)
|
||||
and drawn coordinates of the rebar drawing (front / side views, sections, seat details); params are not read.
|
||||
Checks (C10): counts, fixed-length marks vs L, variable dimension ranges vs schedule (record mean length),
|
||||
top / bottom layer level, y rows and x extents, stirrup positions / pairs / heights, side-bar and hoop levels
|
||||
and ends, C14 positions, seat bars, containment in the coping + pedestals, minimum cover (record).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import statistics
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G4"]
|
||||
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C10", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def row(spec):
|
||||
return [spec["start"] + spec["pitch"] * i for i in range(spec["n"])]
|
||||
|
||||
|
||||
def bb(o):
|
||||
return o.Geometry.GetBoundingBox(True)
|
||||
|
||||
|
||||
def worst(pairs):
|
||||
return max((abs(a - b) for a, b in pairs), default=1e9)
|
||||
|
||||
|
||||
def match(got, exp, tol=1.0):
|
||||
"""Every expected tuple has one got tuple within tol (and same count)."""
|
||||
got = list(got)
|
||||
if len(got) != len(exp):
|
||||
return False
|
||||
for e in exp:
|
||||
k = next((i for i, g in enumerate(got) if all(abs(a - b) <= tol for a, b in zip(g, e))), None)
|
||||
if k is None:
|
||||
return False
|
||||
got.pop(k)
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [] for m in SCHED}
|
||||
pp = {m: [] for m in SCHED}
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
m = o.Attributes.GetUserString("mark")
|
||||
if m in SCHED and oid.startswith(f"{PID}-RC-"):
|
||||
cl[m].append(o)
|
||||
elif m in SCHED and oid.startswith(f"{PID}-R-"):
|
||||
pp[m].append(o)
|
||||
dl = {m: [float(o.Attributes.GetUserString("design_length")) for o in cl[m]] for m in SCHED}
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]),
|
||||
f"{len(pp[m])}/{len(cl[m])}")
|
||||
if not dl[m]:
|
||||
continue
|
||||
if m in G["fixed_length"]:
|
||||
check(f"{m} original length vs L (worst)", 0, max(abs(v - L) for v in dl[m]))
|
||||
else:
|
||||
info(f"{m} mean original length vs L (variable mark, record)",
|
||||
f"mean {statistics.mean(dl[m]):.1f} / L {L} (diff {statistics.mean(dl[m]) - L:+.1f}); min {min(dl[m]):.0f} max {max(dl[m]):.0f}")
|
||||
# top / bottom layers
|
||||
rows = {k: row(v) for k, v in G["rows"].items()}
|
||||
cy_abs = G["corner"]["y_abs"]
|
||||
clipped_x = set(G["corner"]["x_clipped"])
|
||||
for m, lay in G["layers"].items():
|
||||
if not cl[m]:
|
||||
continue
|
||||
ys = rows[lay["y"]]
|
||||
if lay["parity"] is not None:
|
||||
ys = [y for i, y in enumerate(ys) if i % 2 == lay["parity"]]
|
||||
got = sorted(round((bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in cl[m])
|
||||
check(f"{m} y rows vs chain (worst, n={len(got)}/{len(ys)})", 0, worst(zip(got, ys)) if len(got) == len(ys) else 1e9)
|
||||
if "z" in lay:
|
||||
check(f"{m} level (top straight, worst)", 0, max(abs(bb(o).Max.Z - lay["z"]) for o in cl[m]))
|
||||
else:
|
||||
check(f"{m} bottom level (worst)", 0, max(abs(bb(o).Min.Z - lay["z_min"]) for o in cl[m]))
|
||||
if m in G["x_extent"]:
|
||||
x0, x1 = G["x_extent"][m]
|
||||
inner = [o for o in cl[m] if abs((bb(o).Min.Y + bb(o).Max.Y) / 2) <= cy_abs]
|
||||
check(f"{m} drawn x ends, |y| <= {cy_abs} (worst; corner fillets)", 0,
|
||||
max(max(abs(bb(o).Min.X - x0), abs(bb(o).Max.X - x1)) for o in inner), tol=12)
|
||||
info(f"{m} bars shortened at the R500 plan corners [추론]",
|
||||
f"{sum(1 for o in cl[m] if o not in inner and bb(o).Max.X - bb(o).Min.X < x1 - x0 - 30)}"
|
||||
f" of {len(cl[m]) - len(inner)} outer bars")
|
||||
# stirrups
|
||||
st = G["stirrups"]
|
||||
for m, xs in st["x"].items():
|
||||
got = {}
|
||||
for o in cl[m]:
|
||||
b = bb(o)
|
||||
got.setdefault(round((b.Min.X + b.Max.X) / 2), []).append(o)
|
||||
flag(f"{m} stirrup positions = drawn x ({len(xs)}) x 3", sorted(got) == sorted(round(x) for x in xs)
|
||||
and all(len(v) == 3 for v in got.values()), f"{len(got)} positions")
|
||||
pairs_ok, hw = True, 0
|
||||
for x, os_ in got.items():
|
||||
if x in clipped_x:
|
||||
continue
|
||||
pairs_ok &= match([(bb(o).Min.Y, bb(o).Max.Y) for o in os_], [tuple(p) for p in st["y_pairs"]])
|
||||
exp_h = st["height"].get(f"{x:.0f}")
|
||||
hw = max([hw] + [abs((bb(o).Max.Z - bb(o).Min.Z) - exp_h) for o in os_]) if exp_h else 1e9
|
||||
flag(f"{m} legs at y pairs {st['y_pairs']} (A-A; corner positions {sorted(clipped_x)} excluded)", pairs_ok)
|
||||
check(f"{m} centreline height vs drawn + 54 (worst)", 0, hw)
|
||||
# variable ranges (schedule)
|
||||
for m, (dim, lo, hi) in G["ranges"].items():
|
||||
if not cl[m]:
|
||||
continue
|
||||
if dim == "B":
|
||||
vals = [bb(o).Max.Z - bb(o).Min.Z for o in cl[m]]
|
||||
check(f"{m} B min vs schedule '{lo}~{hi}'", lo, min(vals), tol=2)
|
||||
check(f"{m} B max vs schedule '{lo}~{hi}'", hi, max(vals), tol=2)
|
||||
else:
|
||||
vals = []
|
||||
for o in cl[m]:
|
||||
b = bb(o)
|
||||
a = b.Max.X - b.Min.X + (44 if m in ("C11", "C13") else (54 if m == "C10" else 44 - 790))
|
||||
vals.append(a)
|
||||
rng = f"model {min(vals):.0f}~{max(vals):.0f} / schedule {lo}~{hi}"
|
||||
ok = abs(min(vals) - lo) <= 2 and abs(max(vals) - hi) <= 2
|
||||
results.append(dict(check="C10", item=f"{m} A range vs schedule", detail=rng,
|
||||
result="PASS" if ok else "INFO"))
|
||||
# side bars and hoops per level
|
||||
lv = G["levels"]
|
||||
for key, yset in (("side", G["side"]["y"]), ("hoops", None)):
|
||||
k0 = 0
|
||||
for m, nlev in G[key]["marks"]:
|
||||
exp = lv[k0:k0 + nlev]
|
||||
k0 += nlev
|
||||
got = []
|
||||
for o in cl[m]:
|
||||
b = bb(o)
|
||||
c = o.Geometry.PointAt(o.Geometry.Domain.Mid)
|
||||
got.append((round(b.Min.Z, 1), b.Min.X, b.Max.X, c.Y if yset else b.Max.Y - b.Min.Y))
|
||||
want = [(zz, xl, xr, y) for zz, xl, xr in exp for y in (yset or [2 * G["hoops"]["y"]])]
|
||||
flag(f"{m} levels / drawn ends / y ({len(want)})", match(got, want), f"{len(got)} bars")
|
||||
# C14
|
||||
c = G["c14"]
|
||||
got = [((bb(o).Min.X + bb(o).Max.X) / 2, bb(o).Min.Z, bb(o).Min.Y, bb(o).Max.Y) for o in cl["C14"]]
|
||||
n_corner = sum(1 for p in c["pts"] if round(abs(p[0]) + 19) in clipped_x)
|
||||
full = [g for g in got if abs(g[2] - c["y"][0]) <= 1 and abs(g[3] - c["y"][1]) <= 1]
|
||||
flag("C14 levels (drawn section marks)", match([(g[1],) for g in got], [(p[1],) for p in c["pts"]]), f"{len(got)}")
|
||||
flag(f"C14 y span {c['y']} except {n_corner} corner bars", len(full) == len(got) - n_corner
|
||||
and all(g[2] >= c["y"][0] - 1 and g[3] <= c["y"][1] + 1 for g in got), f"{len(full)} full")
|
||||
# seat bars
|
||||
se = G["seats"]
|
||||
for no, s in se["bearings"].items():
|
||||
cx, cy = s["centre"]
|
||||
my, mx = s["marks"]
|
||||
oy = [o for o in cl[my] if o.Attributes.GetUserString("occurrence").startswith(f"B{no}")]
|
||||
ox = [o for o in cl[mx] if o.Attributes.GetUserString("occurrence").startswith(f"B{no}")]
|
||||
flag(f"bearing {no} {my} x positions / y span", match([((bb(o).Min.X + bb(o).Max.X) / 2, bb(o).Min.Y, bb(o).Max.Y) for o in oy],
|
||||
[(cx + d, cy - se["half"], cy + se["half"]) for d in se["offsets"]]), f"{len(oy)}")
|
||||
flag(f"bearing {no} {mx} y positions / x span", match([((bb(o).Min.Y + bb(o).Max.Y) / 2, bb(o).Min.X, bb(o).Max.X) for o in ox],
|
||||
[(cy + d, cx - se["half"], cx + se["half"]) for d in se["offsets"]]), f"{len(ox)}")
|
||||
check(f"bearing {no} {my} level 50 below pedestal top (worst)", 0, max(abs(bb(o).Max.Z - s["z_top"]) for o in oy))
|
||||
check(f"bearing {no} legs {se['leg']} (worst)", se["leg"], min(bb(o).Max.Z - bb(o).Min.Z for o in oy + ox))
|
||||
# containment and cover
|
||||
solids = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"
|
||||
or (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-BS")]
|
||||
solids = [s if isinstance(s, rg.Brep) else rg.Brep.TryConvertBrep(s) for s in solids]
|
||||
cp = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"][0]
|
||||
info("concrete solids for containment", f"{len(solids)} (coping + pedestals)")
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
r = G["dia"][m] / 2
|
||||
for o in cl[m]:
|
||||
cu = o.Geometry
|
||||
for t in cu.DivideByCount(max(6, int(cu.GetLength() / 150)), True):
|
||||
p = cu.PointAt(t)
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not any(s.IsPointInside(q, 0.01, False) for s in solids):
|
||||
bad += 1
|
||||
if cp.IsPointInside(p, 0.01, False):
|
||||
cov = min(cov, p.DistanceTo(cp.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside concrete", 0, bad, tol=0)
|
||||
info("min cover to coping surface per mark (record; seat bars measured to the coping only)", json.dumps(cover))
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,185 @@
|
||||
"""G4 gate checker, Ramp-A P1 coping rebar (2026-10-14): C1~C16 and bearing-seat S1~S4 as centreline polylines.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G4, plus the G1 coping dimensions) come
|
||||
from the D3 schedule (L, N), the D2 / D3 drawn positions and section dimensions, and the user decisions T1-T6;
|
||||
generator params are not read.
|
||||
Checks (C10): counts, fixed-length marks vs L (variable marks recorded), top layers (level, y rows, x ends),
|
||||
stirrup positions per zone, stirrup top / bottom cover along the soffit, inner stirrup offset, side loops per level,
|
||||
C6 bottom level, C15 / C16, seat bars per bearing, containment in the coping + pedestals, min cover (record).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import statistics
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G, C, CO = X["pier_id"], X["G4"], X["G1"]["coping"], X["G1"]["column"]
|
||||
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
||||
ZB = CO["z0"] + CO["length"] # column top = coping bottom
|
||||
ZT = ZB + C["soffit_rise"]
|
||||
ZTL, ZTR = C["z_top_left"], C["z_top_right"]
|
||||
R, HB, HT = C["soffit_radius"], C["column_face_half"], C["bottom_length"] / 2
|
||||
results = []
|
||||
|
||||
|
||||
def soffit_centre():
|
||||
ax, az, bx, bz = -HB, ZB, -HT, ZT # R arc through (-HB, ZB) and (-HT, ZT), centre below the chord
|
||||
mx, mz = (ax + bx) / 2, (az + bz) / 2
|
||||
d = math.hypot(bx - ax, bz - az)
|
||||
h = math.sqrt(R * R - d * d / 4)
|
||||
ux, uz = (bz - az) / d, -(bx - ax) / d
|
||||
return min(((mx + h * ux, mz + h * uz), (mx - h * ux, mz - h * uz)), key=lambda c: c[1])
|
||||
|
||||
|
||||
SCX, SCZ = soffit_centre()
|
||||
SCX = abs(SCX)
|
||||
|
||||
|
||||
def soffit_z(x):
|
||||
return ZB if abs(x) <= HB else SCZ + math.sqrt(R * R - (SCX - abs(x)) ** 2)
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C10", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def row(s):
|
||||
return [s["start"] + s["pitch"] * i for i in range(s["n"])]
|
||||
|
||||
|
||||
def bb(o):
|
||||
return o.Geometry.GetBoundingBox(True)
|
||||
|
||||
|
||||
def worst(pairs):
|
||||
pairs = list(pairs)
|
||||
return max((abs(a - b) for a, b in pairs), default=1e9)
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [] for m in SCHED}
|
||||
pp = {m: [] for m in SCHED}
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
m = o.Attributes.GetUserString("mark")
|
||||
if m in SCHED and oid.startswith(f"{PID}-RC-"):
|
||||
cl[m].append(o)
|
||||
elif m in SCHED and oid.startswith(f"{PID}-R-"):
|
||||
pp[m].append(o)
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]),
|
||||
f"{len(pp[m])}/{len(cl[m])}")
|
||||
dl = [float(o.Attributes.GetUserString("design_length")) for o in cl[m]]
|
||||
if not dl:
|
||||
continue
|
||||
if m in G["fixed_length"]:
|
||||
check(f"{m} original length vs L (worst)", 0, max(abs(v - L) for v in dl))
|
||||
else:
|
||||
info(f"{m} mean original length vs L (variable mark, record)",
|
||||
f"mean {statistics.mean(dl):.1f} / L {L} (diff {statistics.mean(dl) - L:+.1f}); min {min(dl):.0f} max {max(dl):.0f}")
|
||||
# top layers (straight part = top of the bar)
|
||||
for m, s in G["layers"].items():
|
||||
got = sorted(round((bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in cl[m])
|
||||
want = row(s["y"])
|
||||
check(f"{m} y rows (worst, {len(got)}/{len(want)})", 0, worst(zip(got, want)) if len(got) == len(want) else 1e9)
|
||||
check(f"{m} level of the straight part (worst)", 0, max(abs(bb(o).Max.Z - s["z"]) for o in cl[m]))
|
||||
if "x_top" in s:
|
||||
x0, x1 = s["x_top"]
|
||||
ends = []
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
top_pts = [p for p in (c.PointAt(t) for t in c.DivideByLength(5, True)) if abs(p.Z - s["z"]) < 0.5]
|
||||
ends.append(max(abs(min(p.X for p in top_pts) - x0), abs(max(p.X for p in top_pts) - x1)))
|
||||
check(f"{m} straight part x ends vs drawn (worst; fillet tangent points)", 0, max(ends), tol=s.get("x_tol", 70))
|
||||
# stirrups
|
||||
st = G["stirrups"]
|
||||
xs = row(st["x"])
|
||||
for zone, (m_out, m_in, x0, x1) in st["zones"].items():
|
||||
want = [x for x in xs if x0 <= x <= x1]
|
||||
got_o = sorted(round(bb(o).Min.X, 1) for o in cl[m_out])
|
||||
got_i = sorted(round(bb(o).Min.X, 1) for o in cl[m_in])
|
||||
check(f"{m_out} positions = drawn x ({len(want)})", 0, worst(zip(got_o, want)) if len(got_o) == len(want) else 1e9)
|
||||
check(f"{m_in} positions = drawn x + {st['inner_dx']} (2 per x)", 0,
|
||||
worst(zip(got_i, sorted(x + st["inner_dx"] for x in want for _ in (0, 1)))) if len(got_i) == 2 * len(want) else 1e9)
|
||||
for m in (m_out, m_in):
|
||||
dt, dbm = [], []
|
||||
for o in cl[m]:
|
||||
b = bb(o)
|
||||
x = b.Min.X - (st["inner_dx"] if m == m_in else 0)
|
||||
t = ZTL if x <= 0 else ZTR
|
||||
dt.append(abs((t - b.Max.Z) - st["top_cover"]))
|
||||
dbm.append(abs((b.Min.Z - soffit_z(x)) - st["bottom_cover"] * (1 if abs(x) < HB else
|
||||
# on the arc the 77.5 cover is radial: vertical gap = R' - R projected
|
||||
(SCZ + math.sqrt((R + st["bottom_cover"]) ** 2 - (SCX - abs(x)) ** 2) - soffit_z(x)) / st["bottom_cover"])))
|
||||
check(f"{m} top cover {st['top_cover']} (worst)", 0, max(dt))
|
||||
check(f"{m} bottom cover {st['bottom_cover']} to the soffit (worst)", 0, max(dbm))
|
||||
ys = [(bb(o).Min.Y, bb(o).Max.Y) for o in cl[m_out]]
|
||||
check(f"{m_out} legs y {st['outer_y']} (worst)", 0, worst([(a, st["outer_y"][0]) for a, _ in ys] + [(b, st["outer_y"][1]) for _, b in ys]))
|
||||
# side loops
|
||||
lp = G["loops"]
|
||||
for m, levels in lp["levels"].items():
|
||||
zs = {}
|
||||
for o in cl[m]:
|
||||
zs.setdefault(round(bb(o).Min.Z, 1), []).append(o)
|
||||
flag(f"{m} levels {levels} x {lp['per_level']}", sorted(zs) == sorted(levels)
|
||||
and all(len(v) == lp["per_level"] for v in zs.values()), f"{sorted(zs)}")
|
||||
flat = [abs(bb(o).Max.Z - bb(o).Min.Z) for o in cl[m]]
|
||||
check(f"{m} loops horizontal (worst dz)", 0, max(flat) if flat else 1e9, tol=0.01)
|
||||
# C6, C15, C16
|
||||
c6 = G["c6"]
|
||||
check("C6 bottom straight level (worst)", 0, max(abs(bb(o).Min.Z - c6["z_bottom"]) for o in cl["C6"]) if cl["C6"] else 1e9)
|
||||
got = sorted(round(o.Geometry.PointAt(o.Geometry.Domain.Mid).Y, 1) for o in cl["C6"])
|
||||
check("C6 y rows at mid-span (worst)", 0, worst(zip(got, c6["y"])) if len(got) == len(c6["y"]) else 1e9)
|
||||
for m in ("C15",):
|
||||
w = [bb(o).Max.Y - bb(o).Min.Y for o in cl[m]]
|
||||
check(f"{m} width a (worst)", G["c15"]["a"], max(w, key=lambda v: abs(v - G["c15"]["a"])) if w else 0)
|
||||
# seats
|
||||
for no, want in G["seats"].items():
|
||||
for m, n in want.items():
|
||||
k = sum(1 for o in cl[m] if o.Attributes.GetUserString("occurrence").startswith(f"B{no}-"))
|
||||
check(f"bearing {no} {m} count", n, k, tol=0)
|
||||
# containment and cover
|
||||
solids = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"
|
||||
or (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-BS")]
|
||||
solids = [s if isinstance(s, rg.Brep) else rg.Brep.TryConvertBrep(s) for s in solids]
|
||||
cp = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"][0]
|
||||
col = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CL"]
|
||||
info("concrete solids for containment", f"{len(solids)} (coping + pedestals); bars below the coping bottom checked in the column")
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
r = G["dia"][m] / 2
|
||||
for o in cl[m]:
|
||||
cu = o.Geometry
|
||||
for t in cu.DivideByCount(max(6, int(cu.GetLength() / 150)), True):
|
||||
p = cu.PointAt(t)
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not any(s.IsPointInside(q, 0.01, False) for s in solids + col):
|
||||
bad += 1
|
||||
if cp.IsPointInside(p, 0.01, False):
|
||||
cov = min(cov, p.DistanceTo(cp.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside concrete", 0, bad, tol=0)
|
||||
info("min cover to the coping surface per mark (record; seat bars measured to the coping only)", json.dumps(cover))
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,207 @@
|
||||
"""G4 gate checker, Saejae-1 P1 coping + bearing-seat rebar (2026-10-15): C1~C20 and S1~S2 as centreline polylines (rectangular plan, no skew).
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G4) come from the schedule (L, N, polyline length = a + legs),
|
||||
the drawn levels / positions of the rebar drawing, the section dimension chains and the Okseong rules H1-H6 applied again; generator params
|
||||
are not read. Checks (C10): counts / pipes / polyline length, top layers, C19 / C20, C7, stirrups (x, top, bottom, y), side loops and ties,
|
||||
C18, seat bars per pedestal, containment in the coping + pedestals, minimum cover (record).
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import statistics
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected, write_result # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G4"]
|
||||
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C10", item=item, expected=round(exp, 3), actual=round(act, 3),
|
||||
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def info(item, detail):
|
||||
results.append(dict(check="C10", item=item, detail=detail, result="INFO"))
|
||||
|
||||
|
||||
def bb(o):
|
||||
return o.Geometry.GetBoundingBox(True)
|
||||
|
||||
|
||||
def mid(o):
|
||||
b = bb(o)
|
||||
return (b.Min.X + b.Max.X) / 2, (b.Min.Y + b.Max.Y) / 2
|
||||
|
||||
|
||||
def worst(got, want):
|
||||
got, want = sorted(got), sorted(want)
|
||||
return max((abs(a - b) for a, b in zip(got, want)), default=1e9) if len(got) == len(want) else 1e9
|
||||
|
||||
|
||||
def occ(o):
|
||||
return o.Attributes.GetUserString("occurrence") or ""
|
||||
|
||||
|
||||
def main():
|
||||
cl = {m: [] for m in SCHED}
|
||||
pp = {m: [] for m in SCHED}
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
m = o.Attributes.GetUserString("mark")
|
||||
if m in SCHED and oid.startswith(f"{PID}-RC-"):
|
||||
cl[m].append(o)
|
||||
elif m in SCHED and oid.startswith(f"{PID}-R-"):
|
||||
pp[m].append(o)
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]), f"{len(pp[m])}/{len(cl[m])}")
|
||||
dl = [float(o.Attributes.GetUserString("design_length")) for o in cl[m]]
|
||||
if m in G["fixed_length"]:
|
||||
check(f"{m} polyline length vs a + legs (worst)", 0, max(abs(v - G["fixed_length"][m]) for v in dl) if dl else 1e9)
|
||||
elif dl:
|
||||
info(f"{m} mean polyline length vs L (variable mark, record)",
|
||||
f"mean {statistics.mean(dl):.1f} / L {L} (diff {statistics.mean(dl) - L:+.1f}); min {min(dl):.0f} max {max(dl):.0f}")
|
||||
# top layers C1~C6
|
||||
tp, y23 = G["top"], G["y23"]
|
||||
for m in ("C1", "C2", "C3", "C4", "C5", "C6"):
|
||||
s = tp[m]
|
||||
want = [y for i, y in enumerate(y23) if i % 2 == s["parity"]]
|
||||
check(f"{m} y rows vs chain (worst, n={len(cl[m])}/{len(want)})", 0, worst([mid(o)[1] for o in cl[m]], want))
|
||||
check(f"{m} level of the straight part (worst)", 0, max(abs(bb(o).Max.Z - s["z"]) for o in cl[m]))
|
||||
check(f"{m} leg {s['leg']} (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - s["leg"]) for o in cl[m]))
|
||||
check(f"{m} x ends +-{tp['x_end']} (worst)", 0, max(max(abs(bb(o).Min.X + tp["x_end"]), abs(bb(o).Max.X - tp["x_end"])) for o in cl[m]))
|
||||
# C19, C20
|
||||
c19 = G["c19"]
|
||||
check("C19 y rows vs chain (worst)", 0, worst([mid(o)[1] for o in cl["C19"]], y23))
|
||||
check("C19 level (worst)", 0, max(abs(bb(o).Max.Z - c19["z"]) for o in cl["C19"]))
|
||||
check("C19 x ends (worst)", 0, max(max(abs(bb(o).Min.X - c19["x"][0]), abs(bb(o).Max.X - c19["x"][1])) for o in cl["C19"]))
|
||||
check("C19 legs 370 (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - G["c20_leg"]) for o in cl["C19"]))
|
||||
for k, pc in enumerate(G["c20"]):
|
||||
grp = [o for o in cl["C20"] if abs(bb(o).Max.Z - pc["z"]) < 0.5 and abs(bb(o).Min.X - pc["x"][0]) < 3]
|
||||
flag(f"C20 piece {k + 1}: 23 bars at EL {pc['z']}, x {pc['x']}", len(grp) == 23, f"{len(grp)}")
|
||||
if grp:
|
||||
check(f"C20 piece {k + 1} x ends (worst)", 0, max(max(abs(bb(o).Min.X - pc["x"][0]), abs(bb(o).Max.X - pc["x"][1])) for o in grp))
|
||||
check(f"C20 piece {k + 1} leg 370 (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - G["c20_leg"]) for o in grp))
|
||||
check(f"C20 piece {k + 1} y rows (worst)", 0, worst([mid(o)[1] for o in grp], y23))
|
||||
# C7
|
||||
c7 = G["c7"]
|
||||
check("C7 lowest level per bar vs drawn (worst)", 0, worst([bb(o).Min.Z for o in cl["C7"]], c7["flat_z"]))
|
||||
flat = [o for o in cl["C7"] if abs(mid(o)[1]) <= 875]
|
||||
check("C7 top end EL 31.8453 (flat bars, worst)", 0, max(abs(bb(o).Max.Z - c7["top_end_z"]) for o in flat))
|
||||
check("C7 x ends +-5875 (flat bars, worst)", 0, max(max(abs(bb(o).Min.X + c7["x_end"]), abs(bb(o).Max.X - c7["x_end"])) for o in flat))
|
||||
raised = [o for o in cl["C7"] if 875 < abs(mid(o)[1]) <= 1125]
|
||||
check("C7 raised bars (y +-1125) top end (worst)", 0, max(abs(bb(o).Max.Z - c7["raised_top_z"]) for o in raised))
|
||||
edge = [o for o in cl["C7"] if abs(mid(o)[1]) > 1125]
|
||||
check("C7 side-face bars capped at the cap level (worst)", 0, max(abs(bb(o).Max.Z - c7["cap_z"]) for o in edge), tol=1.5)
|
||||
flag("C7 y rows: 12 @250", sorted(round(mid(o)[1]) for o in cl["C7"]) == [-1375 + 250 * k for k in range(12)])
|
||||
# stirrups
|
||||
st = G["stirrups"]
|
||||
|
||||
def zone(x):
|
||||
return "left" if x < st["zone_cut"][0] else ("centre" if x <= st["zone_cut"][1] else "right")
|
||||
|
||||
def top_of(x):
|
||||
return st["step_z"][sum(1 for b in st["step_b"] if x >= b)] - st["top_below"]
|
||||
|
||||
def bottom_of(x):
|
||||
if abs(x) <= st["flat_half"]:
|
||||
return st["flat_bottom"]
|
||||
return st["flat_bottom"] - st["cover_n"] + st["soffit_slope"] * (abs(x) - st["flat_half"]) + st["cover_n"] * (1 + st["soffit_slope"] ** 2) ** 0.5
|
||||
|
||||
for zn, (rect, pent) in st["marks"].items():
|
||||
sx = [x for x in st["x"] if zone(x) == zn]
|
||||
want_r = [x + o for x in sx for o in st["off"]["R"]]
|
||||
want_p = [x + o for x in sx for o in st["off"]["P"]]
|
||||
check(f"{rect} positions = stations + x offset ({len(want_r)}, worst)", 0, worst([mid(o)[0] for o in cl[rect]], want_r), tol=0.5)
|
||||
check(f"{pent} positions = stations + x offset ({len(want_p)}, worst)", 0, worst([mid(o)[0] for o in cl[pent]], want_p), tol=0.5)
|
||||
dt = db = 0.0
|
||||
for o in cl[rect] + cl[pent]:
|
||||
xb = mid(o)[0]
|
||||
dt = max(dt, abs(bb(o).Max.Z - top_of(xb)))
|
||||
db = max(db, abs(bb(o).Min.Z - bottom_of(xb)))
|
||||
check(f"{rect}/{pent} top = step top - {st['top_below']:.0f} (worst)", 0, dt, tol=1.5)
|
||||
check(f"{rect}/{pent} bottom = soffit + cover along the normal (worst, sloped part <= 4)", 0, db, tol=4.0)
|
||||
okr = all(any(abs(bb(o).Min.Y - a) < 2 and abs(bb(o).Max.Y - b) < 2 for a, b in st["rect_y"]) for o in cl[rect])
|
||||
flag(f"{rect} y legs {st['rect_y']} (B-B)", okr)
|
||||
pin, pout = st["pent_in"], st["pent_out"]
|
||||
okp = all((abs(bb(o).Min.Y + pin) < 2 and abs(bb(o).Max.Y - pout) < 2) or (abs(bb(o).Max.Y - pin) < 2 and abs(bb(o).Min.Y + pout) < 2) for o in cl[pent])
|
||||
flag(f"{pent} y between {pin:.0f} and {pout:.0f} (B-B)", okp)
|
||||
# side loops and ties
|
||||
lp = G["loops"]
|
||||
for key in ("upper", "lower"):
|
||||
sec = lp[key]
|
||||
mloop, mtie = sec["marks"]
|
||||
gl = sorted((round(bb(o).Min.Z, 1), round(bb(o).Max.X - bb(o).Min.X, 1), round((bb(o).Max.Y - bb(o).Min.Y) / 2, 1)) for o in cl[mloop])
|
||||
wl = sorted((z, round(2 * x, 1), y) for z, x, y in zip(sec["z"], sec["x"], sec["y"]))
|
||||
flag(f"{mloop} loops: level / x length / half width ({len(wl)})", len(gl) == len(wl) and all(abs(a[0] - b[0]) <= 1 and abs(a[1] - b[1]) <= 1 and abs(a[2] - b[2]) <= 1 for a, b in zip(gl, wl)), f"{gl[:2]}")
|
||||
check(f"{mloop}/{mtie} horizontal (worst dz)", 0, max((bb(o).Max.Z - bb(o).Min.Z) for o in cl[mloop] + cl[mtie]), tol=0.01)
|
||||
gt = sorted((round(bb(o).Min.Z, 1), round(mid(o)[1], 1), round(bb(o).Max.X - bb(o).Min.X, 1)) for o in cl[mtie])
|
||||
wt = sorted((z, y, round(2 * x, 1)) for z, x in zip(sec["z"], sec["x"]) for y in lp["tie_y"])
|
||||
flag(f"{mtie} ties: level / y / x length ({len(wt)})", len(gt) == len(wt) and all(abs(a[0] - b[0]) <= 1 and abs(a[1] - b[1]) <= 1 and abs(a[2] - b[2]) <= 1 for a, b in zip(gt, wt)), f"{len(gt)}")
|
||||
# C18
|
||||
c18 = G["c18"]
|
||||
got = {}
|
||||
for o in cl["C18"]: # upper rows: legs DOWN (straight part highest); lower rows: legs UP (lowest)
|
||||
z = bb(o).Max.Z if bb(o).Max.Z > 32100 else bb(o).Min.Z
|
||||
got.setdefault(f"{z:.1f}", []).append(o)
|
||||
flag("C18 levels and counts", {k: len(v) for k, v in got.items()} == c18["levels"], f"{ {k: len(v) for k, v in got.items()} }")
|
||||
check("C18 widths per level (worst; fillets on the slanted legs <= 30)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - c18["width"][k]) for k, v in got.items() if k in c18["width"] for o in v), tol=30)
|
||||
# seats
|
||||
se = G["seats"]
|
||||
ped = {}
|
||||
for o in all_objects(doc):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith(f"{PID}-BS"):
|
||||
ped[int(oid[len(PID) + 3:])] = o.Geometry.GetBoundingBox(True)
|
||||
flag("5 pedestals found", sorted(ped) == [1, 2, 3, 4, 5], f"{sorted(ped)}")
|
||||
for k, pb in sorted(ped.items()):
|
||||
cx, cy = (pb.Min.X + pb.Max.X) / 2, (pb.Min.Y + pb.Max.Y) / 2
|
||||
g1 = [o for o in cl["S1"] if occ(o).startswith(f"P{k}-")]
|
||||
g2 = [o for o in cl["S2"] if occ(o).startswith(f"P{k}-")]
|
||||
flag(f"pedestal {k}: S1 11 / S2 20", (len(g1), len(g2)) == (11, 20), f"{len(g1)} {len(g2)}")
|
||||
zt = pb.Max.Z - se["top_below"]
|
||||
check(f"pedestal {k} S1 level {se['top_below']} below the pedestal top (worst, plane slope <= 20)", 0, max(abs(bb(o).Max.Z - zt) for o in g1), tol=20)
|
||||
check(f"pedestal {k} S1 legs {se['leg']} (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - se["leg"]) for o in g1 + g2))
|
||||
check(f"pedestal {k} S2 level 13 under S1 (worst)", 0, max(abs(bb(o).Max.Z - (min(bb(p).Max.Z for p in g1) - 13)) for o in g2), tol=1.5)
|
||||
check(f"pedestal {k} S1 x positions (worst)", 0, worst([mid(o)[0] - cx for o in g1], se["s1_t"]), tol=1.5)
|
||||
check(f"pedestal {k} S1 length along Y (worst)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - se["s1_len"]) for o in g1), tol=1.5)
|
||||
check(f"pedestal {k} S2 y positions (worst)", 0, worst([mid(o)[1] - cy for o in g2], se["s2_o"]), tol=1.5)
|
||||
check(f"pedestal {k} S2 length along X (worst)", 0, max(abs((bb(o).Max.X - bb(o).Min.X) - se["s2_len"]) for o in g2), tol=1.5)
|
||||
# containment and cover
|
||||
solids = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"
|
||||
or (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-BS")]
|
||||
solids = [s if isinstance(s, rg.Brep) else rg.Brep.TryConvertBrep(s) for s in solids]
|
||||
cp = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"][0]
|
||||
info("concrete solids for containment", f"{len(solids)} (coping + pedestals)")
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
r = G["dia"][m] / 2
|
||||
for o in cl[m]:
|
||||
cu = o.Geometry
|
||||
for t in cu.DivideByCount(max(6, int(cu.GetLength() / 200)), True):
|
||||
p = cu.PointAt(t)
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not any(s.IsPointInside(q, 0.01, False) for s in solids):
|
||||
bad += 1
|
||||
if cp.IsPointInside(p, 0.01, False):
|
||||
cov = min(cov, p.DistanceTo(cp.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside concrete", 0, bad, tol=0)
|
||||
info("min cover to the coping surface per mark (record; seat bars measured to the coping only)", json.dumps(cover))
|
||||
write_result(RESULT_PATH, results, dict(cover=cover))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,135 @@
|
||||
"""G5 gate checker: bridge bearings (BB) and sole plates (SP), check C11, inside Rhino 8.
|
||||
|
||||
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values from EXPECTED_PATH section G5 (D2 ELEVATION
|
||||
TABLE, 솔플레이트 상세 TABLE, user rules); orientation is compared with the modelled h2 pads (BP).
|
||||
Generator params are not read. Ported from Code/교각모델링/verify_bearing_plates.py (same checks and order).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectColorSource
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _common import all_objects, load_expected # noqa: E402
|
||||
|
||||
doc = __rhino_doc__
|
||||
X = load_expected(EXPECTED_PATH)
|
||||
PID, G = X["pier_id"], X["G5"]
|
||||
ELX = {int(k): v for k, v in G["el_x"].items()}
|
||||
S = {int(k): v for k, v in G["S"].items()}
|
||||
W = {int(k): v for k, v in G["W"].items()}
|
||||
L, T, H3, SHRINK = G["L"], G["T"], G["h3"], G.get("bearing_shrink")
|
||||
# per-bearing values (Ulsan 2026-10-13): L / T / h3 may be {no: value}; bearing_size {no: [along, across]} replaces
|
||||
# the (L-shrink) x (W-shrink) rule; thick_end "-Y" (Ungok, default) or "+Y"
|
||||
per = lambda v, no: v[str(no)] if isinstance(v, dict) else v
|
||||
BSIZE = {int(k): v for k, v in G.get("bearing_size", {}).items()}
|
||||
THICK = G.get("thick_end", "-Y")
|
||||
COL_B, COL_S = tuple(G["colours"]["bearing"]), tuple(G["colours"]["sole_plate"])
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
results.append(dict(check="C11", item=item, expected=round(exp, 4), actual=round(act, 4),
|
||||
diff=round(act - exp, 4), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
|
||||
|
||||
|
||||
def flag(item, ok, detail=""):
|
||||
results.append(dict(check="C11", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
||||
|
||||
|
||||
def edges_xy(brep, top=False):
|
||||
"""Return horizontal edge vectors of the lowest (or highest) face (4 edges). Pads use the top face: their bottom
|
||||
may follow a sloped pedestal top (Okseong P1, 2026-10-14)."""
|
||||
zref = (max if top else min)(v.Location.Z for v in brep.Vertices)
|
||||
out = []
|
||||
for e in brep.Edges:
|
||||
a, b = e.PointAtStart, e.PointAtEnd
|
||||
if abs(a.Z - zref) < 0.01 and abs(b.Z - zref) < 0.01 and a.DistanceTo(b) > 1:
|
||||
out.append(b - a)
|
||||
return out
|
||||
|
||||
|
||||
def split_axes(vecs):
|
||||
"""Bridge-axis (along) edge = within 45 deg of +Y; the other is across. Not by length (W > L occurs)."""
|
||||
along = [v for v in vecs if abs(v.Y) > abs(v.X)]
|
||||
across = [v for v in vecs if abs(v.Y) <= abs(v.X)]
|
||||
return along[0], across[0]
|
||||
|
||||
|
||||
def axis_deg(vec):
|
||||
"""Angle of a plan vector from +Y, clockwise positive, folded to [-90, 90)."""
|
||||
a = math.degrees(math.atan2(vec.X, vec.Y))
|
||||
while a >= 90:
|
||||
a -= 180
|
||||
while a < -90:
|
||||
a += 180
|
||||
return a
|
||||
|
||||
|
||||
def main():
|
||||
objs = all_objects(doc)
|
||||
by = lambda p: {int(o.Attributes.GetUserString("id")[len(p):]): o for o in objs
|
||||
if (o.Attributes.GetUserString("id") or "").startswith(p)}
|
||||
bb, sp, pad = by(f"{PID}-BB"), by(f"{PID}-SP"), by(f"{PID}-BP")
|
||||
n = len(ELX)
|
||||
check("bearing count", n, len(bb), tol=0)
|
||||
check("sole plate count", n, len(sp), tol=0)
|
||||
flag(f"all {2 * n} closed solids", all(o.Geometry.IsSolid for o in list(bb.values()) + list(sp.values())))
|
||||
for no in sorted(ELX):
|
||||
gb, gs, gp = bb[no].Geometry, sp[no].Geometry, pad[no].Geometry
|
||||
bx, sx = gb.GetBoundingBox(True), gs.GetBoundingBox(True)
|
||||
check(f"NO.{no} bearing bottom = EL.'X'", ELX[no], bx.Min.Z)
|
||||
check(f"NO.{no} bearing height h3", per(H3, no), bx.Max.Z - bx.Min.Z, tol=0.01)
|
||||
ab, cb_ = split_axes(edges_xy(gb))
|
||||
if no in BSIZE:
|
||||
check(f"NO.{no} bearing across (B)", BSIZE[no][1], cb_.Length, tol=0.5)
|
||||
check(f"NO.{no} bearing along (A)", BSIZE[no][0], ab.Length, tol=0.5)
|
||||
else:
|
||||
check(f"NO.{no} bearing across (W-{SHRINK})", W[no] - SHRINK, cb_.Length, tol=0.5)
|
||||
check(f"NO.{no} bearing along (L-{SHRINK})", per(L, no) - SHRINK, ab.Length, tol=0.5)
|
||||
as_, cs_ = split_axes(edges_xy(gs))
|
||||
check(f"NO.{no} sole plate across W", W[no], cs_.Length, tol=0.5)
|
||||
check(f"NO.{no} sole plate along L", per(L, no), as_.Length, tol=0.5)
|
||||
check(f"NO.{no} sole plate bottom = bearing top", bx.Max.Z, sx.Min.Z, tol=0.01)
|
||||
long_b, long_s, long_p = ab, as_, split_axes(edges_xy(gp, top=True))[0]
|
||||
pc = gp.GetBoundingBox(True).Center
|
||||
for nm, g in (("bearing", gb), ("sole plate", gs)):
|
||||
c = g.GetBoundingBox(True).Center
|
||||
check(f"NO.{no} {nm} plan centre offset from pad (mm)", 0, math.hypot(c.X - pc.X, c.Y - pc.Y), tol=1.0)
|
||||
check(f"NO.{no} bearing axis vs pad (deg)", axis_deg(long_p), axis_deg(long_b), tol=0.01)
|
||||
check(f"NO.{no} sole plate axis vs pad (deg)", axis_deg(long_p), axis_deg(long_s), tol=0.01)
|
||||
top = [v.Location for v in gs.Vertices if v.Location.Z > sx.Min.Z + 1]
|
||||
ua = rg.Vector3d(long_s.X, long_s.Y, 0)
|
||||
ua.Unitize()
|
||||
if ua.Y < 0:
|
||||
ua = -ua # along points to +Y
|
||||
c = rg.Point3d(sum(p.X for p in top) / 4, sum(p.Y for p in top) / 4, 0)
|
||||
proj = [((p - c) * ua, p.Z) for p in top]
|
||||
lo = [z for s_, z in proj if s_ < 0]
|
||||
hi = [z for s_, z in proj if s_ > 0]
|
||||
slope = (sum(hi) / len(hi) - sum(lo) / len(lo)) / per(L, no) * 100
|
||||
check(f"NO.{no} sole plate slope S (%) along +Y-ward axis", S[no], slope, tol=0.01)
|
||||
check(f"NO.{no} sole plate centre thickness T", per(T, no), sum(z for _, z in proj) / 4 - sx.Min.Z, tol=0.01)
|
||||
flag(f"NO.{no} thick end at {THICK}", sum(lo) > sum(hi) if THICK == "-Y" else sum(hi) > sum(lo))
|
||||
pairs = sorted(proj)
|
||||
check(f"NO.{no} no transverse slope", 0, max(abs(pairs[0][1] - pairs[1][1]), abs(pairs[2][1] - pairs[3][1])),
|
||||
tol=0.01)
|
||||
for a, b2, name in ((gb, gp, "bearing-pad"), (gb, gs, "bearing-sole plate")):
|
||||
r = rg.Brep.CreateBooleanIntersection(a, b2, 0.01)
|
||||
v = sum(rg.VolumeMassProperties.Compute(x).Volume for x in r) if r else 0.0
|
||||
check(f"NO.{no} overlap {name} (mm3)", 0, v, tol=1.0)
|
||||
cb, cs = bb[no].Attributes, sp[no].Attributes
|
||||
rgb = lambda a: (a.ObjectColor.R, a.ObjectColor.G, a.ObjectColor.B)
|
||||
flag(f"NO.{no} colours (bearing {COL_B}, sole plate {COL_S})",
|
||||
cb.ColorSource == ObjectColorSource.ColorFromObject and rgb(cb) == COL_B
|
||||
and cs.ColorSource == ObjectColorSource.ColorFromObject and rgb(cs) == COL_S)
|
||||
summ = dict(total=len(results), passed=sum(r["result"] == "PASS" for r in results),
|
||||
failed=sum(r["result"] == "FAIL" for r in results))
|
||||
json.dump(dict(summary=summ, results=results), open(RESULT_PATH, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
print(json.dumps(summ))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,180 @@
|
||||
"""G2 stage: column rebar P1-P6 (centreline + pipe solid) from params `column_rebar`.
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after concrete.py.
|
||||
Ported from Code/교각모델링/build_rebar_rhino.py (Ungok P1, model v0.1.09). Rules: 설계서 v0.1.04 §5 (HITL H1-H3 approved 2026-10-07). Units mm, Z = EL x 1000.
|
||||
Each centreline carries `design_length` (schedule-style original length: straight + arc lengths
|
||||
before hook fillets) and the actual curve length is measured separately (box rule: 원길이 vs 곡선길이).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
MARK_DIA = {m: v["dia"] for m, v in P["rebar_marks"].items()}
|
||||
DIA = {d: float(d[1:]) for d in MARK_DIA.values()} # 'S32' -> 32.0
|
||||
COLORS = {m: tuple(c) for m, c in P["display"]["column_mark_colours"].items()}
|
||||
CL_RGB = tuple(P["display"]["centreline_colour"])
|
||||
Z_FT_TOP = P["footing"]["z_bottom"] + P["footing"]["thickness"] # ties/hoops below -> anchored in footing
|
||||
Z_CP_BOT = P["coping"]["z_bottom_flat"] # above -> anchored in coping
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
idx = doc.Layers.FindByFullPath(path, -1)
|
||||
if idx >= 0:
|
||||
return idx
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
sub = "::".join(parts[:i])
|
||||
idx = doc.Layers.FindByFullPath(sub, -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
def min_bend_radius(dia_name):
|
||||
d = DIA[dia_name]
|
||||
inner = 2 * d if d <= 16 else 3.5 * d # box project rule (REBAR_BENDS_20260914)
|
||||
return (inner + d) / 2
|
||||
|
||||
|
||||
def add_bar(curve, mark, occ, design_len, hosts, src, coupler=0.0):
|
||||
d = DIA[MARK_DIA[mark]]
|
||||
# fitRail=False: fitRail=True fails on line-arc-line rails and crashed a headless Rhino (2026-10-07)
|
||||
pipes = rg.Brep.CreatePipe(curve, d / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
|
||||
if not pipes or not pipes[0].IsSolid:
|
||||
raise RuntimeError(f"{mark} {occ}: pipe failed")
|
||||
common = dict(mark=mark, dia=MARK_DIA[mark], shape=P["column_rebar"]["schedule"][mark]["shape"],
|
||||
occurrence=occ, host=f"{PID}-CL", anchor_hosts=hosts, design_length=f"{design_len:.3f}",
|
||||
coupler_length=f"{coupler:.3f}",
|
||||
src=src)
|
||||
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
||||
(pipes[0], layer(f"{PID}::철근::기둥::{mark}", COLORS[mark]), f"{PID}-R")):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = lay
|
||||
oid = f"{prefix}-{mark}-{occ}"
|
||||
attr.Name = oid
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in common.items():
|
||||
attr.SetUserString(k, v)
|
||||
if isinstance(geo, rg.Brep):
|
||||
doc.Objects.AddBrep(geo, attr)
|
||||
else:
|
||||
doc.Objects.AddCurve(geo, attr)
|
||||
|
||||
|
||||
def pt(r, deg, z):
|
||||
t = math.radians(deg)
|
||||
return rg.Point3d(r * math.cos(t), r * math.sin(t), z)
|
||||
|
||||
|
||||
def arc_xy(r, a0, a1, z):
|
||||
"""Horizontal arc at height z from angle a0 to a1 (deg, counter-clockwise)."""
|
||||
am = (a0 + a1) / 2
|
||||
return rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, am, z), pt(r, a1, z)))
|
||||
|
||||
|
||||
def hooked_half_ring(r, a0, z, hook, bend_r):
|
||||
"""Semicircle a0 -> a0+180 with inward 135-degree hooks at both ends (P3, shape VI)."""
|
||||
arc = arc_xy(r, a0, a0 + 180, z)
|
||||
pieces = []
|
||||
for at_start in (True, False):
|
||||
p = arc.PointAtStart if at_start else arc.PointAtEnd
|
||||
t = arc.TangentAtStart if at_start else arc.TangentAtEnd
|
||||
if at_start:
|
||||
t = -t # travel direction away from the arc
|
||||
n = rg.Vector3d(-p.X, -p.Y, 0)
|
||||
n.Unitize()
|
||||
d = t * math.cos(math.radians(135)) + n * math.sin(math.radians(135)) # back + inward
|
||||
d.Unitize()
|
||||
pieces.append(rg.LineCurve(p, p + d * hook))
|
||||
joined = rg.Curve.JoinCurves([pieces[0], arc, pieces[1]], TOL)[0]
|
||||
fil = rg.Curve.CreateFilletCornersCurve(joined, bend_r, TOL, math.radians(1))
|
||||
return fil or joined
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
if (o.Attributes.GetUserString("id") or "").startswith((f"{PID}-R-", f"{PID}-RC-")):
|
||||
doc.Objects.Delete(o, True)
|
||||
cr = P["column_rebar"]
|
||||
mb, hp, tie = cr["main_bars"], cr["hoops"], cr["ties"]
|
||||
sch = cr["schedule"]
|
||||
count = 0
|
||||
# Main bars P1 (outer, hooked) / P2 (inner, straight)
|
||||
p1, p2 = mb["p1"], mb["p2"]
|
||||
for i in range(mb["divisions"]):
|
||||
th = mb["start_deg"] * 0 + i * mb["pitch_deg"] # start angle 0 (CAD 0.001 deg)
|
||||
ro, ri = mb["r_outer"], mb["r_inner"]
|
||||
z_leg, R = p1["hook_leg_z"], p1["bend_r"]
|
||||
top = p1["straight_from"] + p1["straight_len"]
|
||||
leg = rg.LineCurve(pt(ro + R + p1["hook_leg"], th, z_leg), pt(ro + R, th, z_leg))
|
||||
bend = rg.ArcCurve(rg.Arc(pt(ro + R, th, z_leg),
|
||||
pt(ro + R - R * math.sin(math.radians(45)), th,
|
||||
z_leg + R - R * math.cos(math.radians(45))),
|
||||
pt(ro, th, z_leg + R)))
|
||||
vert = rg.LineCurve(pt(ro, th, z_leg + R), pt(ro, th, top))
|
||||
c1 = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0]
|
||||
add_bar(c1, "P1", f"{i + 1:02d}", p1["straight_len"] + math.pi / 2 * R + p1["hook_leg"],
|
||||
f"{PID}-FT,{PID}-CP", f"angle {th:.4f} deg; D1 단면 C-C / 정면도; D3 P1")
|
||||
c2 = rg.LineCurve(pt(ri, th, p2["z0"]), pt(ri, th, p2["z0"] + p2["length"]))
|
||||
add_bar(c2, "P2", f"{i + 1:02d}", p2["length"], f"{PID}-FT,{PID}-CP",
|
||||
f"angle {th:.4f} deg; D1 단면 C-C / 정면도; D3 P2")
|
||||
count += 2
|
||||
# Hoops: P4 (V: 2 half arcs + 2 coupler gaps), P5 (IX: ring + 1 gap), P3 (VI: 2 hooked halves)
|
||||
r = hp["radius"]
|
||||
n3 = 0
|
||||
for k, (z, mark) in enumerate(zip(hp["levels"], hp["marks"])):
|
||||
r = hp.get("radius_by_mark", {}).get(mark, hp["radius"]) # Ungok P2: P3 R899, P4/P5 R904
|
||||
s = sch[mark]
|
||||
a = float(s["A"]) * 1000 if float(s["A"]) < 100 else float(s["A"])
|
||||
if mark in ("P4", "P5"):
|
||||
gap = float(s["B"])
|
||||
gap_deg = math.degrees(gap / r)
|
||||
arcs = ([(90 + gap_deg / 2, 270 - gap_deg / 2), (270 + gap_deg / 2, 450 - gap_deg / 2)]
|
||||
if mark == "P4" else [(90 + gap_deg / 2, 450 - gap_deg / 2)])
|
||||
curves = [arc_xy(r, a0, a1, z) for a0, a1 in arcs]
|
||||
for j, c in enumerate(curves):
|
||||
add_bar(c, mark, f"L{k + 1:02d}{'ab'[j] if len(curves) > 1 else ''}", a, "",
|
||||
f"EL {z / 1000:.3f}; coupler gap {gap:.0f} at {'90/270' if mark == 'P4' else '90'} deg",
|
||||
coupler=gap) # one coupler (b) per piece: P4 2 pieces/2 gaps, P5 1/1
|
||||
else:
|
||||
n3 += 1
|
||||
a0 = 90 if n3 % 2 else 0 # joins staggered by 90 deg
|
||||
hook = float(s["B"])
|
||||
for j, start in enumerate((a0, a0 + 180)):
|
||||
c = hooked_half_ring(r, start, z, hook, min_bend_radius(MARK_DIA["P3"]))
|
||||
add_bar(c, mark, f"L{k + 1:02d}{'ab'[j]}", a + 2 * hook,
|
||||
f"{PID}-CP" if z > Z_CP_BOT else "", f"EL {z / 1000:.3f}; joins at {a0}/{a0 + 180} deg")
|
||||
count += 1
|
||||
# Cross ties P6 (III): straight 1836 on diameters, 150 hooks down at r=918, sitting on hoop
|
||||
half = tie["straight"] / 2
|
||||
zoff = DIA[MARK_DIA["P4"]] / 2 + DIA[MARK_DIA["P6"]] / 2
|
||||
for k, z in enumerate(tie["levels"]):
|
||||
for ang in tie["angles_deg"]:
|
||||
zt = z + zoff
|
||||
pts = [pt(half, ang + 180, zt - tie["hook"]), pt(half, ang + 180, zt), pt(half, ang, zt),
|
||||
pt(half, ang, zt - tie["hook"])]
|
||||
pl = rg.PolylineCurve(pts)
|
||||
c = rg.Curve.CreateFilletCornersCurve(pl, min_bend_radius(MARK_DIA["P6"]), TOL, math.radians(1)) or pl
|
||||
add_bar(c, "P6", f"L{k + 1}-{ang:03d}", tie["straight"] + 2 * tie["hook"],
|
||||
f"{PID}-FT" if z < Z_FT_TOP else (f"{PID}-CP" if z > Z_CP_BOT else ""),
|
||||
f"EL {z / 1000:.3f} (+{zoff} on hoop); angle {ang} deg")
|
||||
count += 1
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"bars": count}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,205 @@
|
||||
"""G2 stage, form "ring" (Ulsan P1, 2026-10-13): circular column rebar with one main-bar ring, hoop zones of
|
||||
different hoop types and cross ties, from params `column_rebar` (centreline + pipe solid per bar).
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after the G1 builder. Units mm, Z = EL x 1000.
|
||||
params column_rebar:
|
||||
main {mark, count, start_deg, radius, hook_leg_z, bend_r, hook_leg, straight_from, straight_len}
|
||||
bar = horizontal leg pointing outwards at hook_leg_z, R bend, vertical straight part (shape VI)
|
||||
hoops {radius, zones: [{mark, type, levels, a, gap | lap+hook}]}
|
||||
type "coupler_half": two half arcs, coupler gaps `gap` at 90 / 270 deg (shape V)
|
||||
"coupler_ring": one ring, one coupler gap at 90 deg (shape X)
|
||||
"lap_half": two half rings, each extended by lap/2 at both ends, 135-deg hooks (shape VI);
|
||||
joins alternate between 90/270 and 0/180 deg
|
||||
ties {mark, levels, angles_deg, straight, hook, z_offset}: straight bars on diameters, hooks down
|
||||
`design_length` is the schedule-style original length; `coupler_length` the gap per piece (schedule L
|
||||
includes the coupler).
|
||||
Optional (Okseong P1, 2026-10-14; absent -> unchanged): main.inner {mark, radius} second ring at the same angles
|
||||
with the hook bent inwards; zone.radius per hoop zone; ties.shape "90-135" {leg, tail, y_lift, design_length}:
|
||||
straight on the X / Y diameters, 90-deg leg down at one end, 135-deg hook at the other, ends swapped per level,
|
||||
the Y tie y_lift above the X tie.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
MARK = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items()}
|
||||
DIA = {d: float(d[1:]) for d, _ in MARK.values()}
|
||||
CL_RGB = tuple(P["display"]["centreline_colour"])
|
||||
COLORS = {m: tuple(c) for m, c in P["display"].get("column_mark_colours", {}).items()}
|
||||
Z_FT_TOP = P["footing"]["z_bottom"] + P["footing"]["thickness"]
|
||||
Z_CP_BOT = Z_FT_TOP + P["column"]["height"]
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
def min_bend_radius(dia_name):
|
||||
d = DIA[dia_name]
|
||||
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2
|
||||
|
||||
|
||||
def hosts(z):
|
||||
return f"{PID}-FT" if z < Z_FT_TOP else (f"{PID}-CP" if z > Z_CP_BOT else "")
|
||||
|
||||
|
||||
def add_bar(curve, mark, occ, design_len, anchor, src, coupler=0.0):
|
||||
dia, shape = MARK[mark]
|
||||
pipes = rg.Brep.CreatePipe(curve, DIA[dia] / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
|
||||
if not pipes or not pipes[0].IsSolid:
|
||||
raise RuntimeError(f"{mark} {occ}: pipe failed")
|
||||
common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=f"{PID}-CL", anchor_hosts=anchor,
|
||||
design_length=f"{design_len:.3f}", coupler_length=f"{coupler:.3f}", src=src)
|
||||
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
||||
(pipes[0], layer(f"{PID}::철근::기둥::{mark}", COLORS.get(mark, (200, 140, 90))), f"{PID}-R")):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = lay
|
||||
oid = f"{prefix}-{mark}-{occ}"
|
||||
attr.Name = oid
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in common.items():
|
||||
attr.SetUserString(k, v)
|
||||
gid = doc.Objects.AddBrep(geo, attr) if isinstance(geo, rg.Brep) else doc.Objects.AddCurve(geo, attr)
|
||||
if gid == __import__("System").Guid.Empty:
|
||||
raise RuntimeError(f"{oid}: add failed")
|
||||
|
||||
|
||||
def pt(r, deg, z):
|
||||
t = math.radians(deg)
|
||||
return rg.Point3d(r * math.cos(t), r * math.sin(t), z)
|
||||
|
||||
|
||||
def arc_xy(r, a0, a1, z):
|
||||
return rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, (a0 + a1) / 2, z), pt(r, a1, z)))
|
||||
|
||||
|
||||
def hooked_arc(r, a0, a1, z, hook, bend_r):
|
||||
"""Arc a0 -> a1 (deg, ccw) with inward 135-deg hooks at both ends, fillet bend_r."""
|
||||
arc = arc_xy(r, a0, a1, z)
|
||||
pieces = []
|
||||
for at_start in (True, False):
|
||||
p = arc.PointAtStart if at_start else arc.PointAtEnd
|
||||
t = -arc.TangentAtStart if at_start else arc.TangentAtEnd
|
||||
n = rg.Vector3d(-p.X, -p.Y, 0)
|
||||
n.Unitize()
|
||||
d = t * math.cos(math.radians(135)) + n * math.sin(math.radians(135))
|
||||
d.Unitize()
|
||||
pieces.append(rg.LineCurve(p, p + d * hook))
|
||||
joined = rg.Curve.JoinCurves([pieces[0], arc, pieces[1]], TOL)[0]
|
||||
return rg.Curve.CreateFilletCornersCurve(joined, bend_r, TOL, math.radians(1)) or joined
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith((f"{PID}-R-P", f"{PID}-RC-P")):
|
||||
doc.Objects.Delete(o, True)
|
||||
cr = P["column_rebar"]
|
||||
m = cr["main"]
|
||||
n = 0
|
||||
ro, R = m["radius"], m["bend_r"]
|
||||
top = m["straight_from"] + m["straight_len"]
|
||||
for i in range(m["count"]):
|
||||
th = m["start_deg"] + i * 360.0 / m["count"]
|
||||
leg = rg.LineCurve(pt(ro + R + m["hook_leg"], th, m["hook_leg_z"]), pt(ro + R, th, m["hook_leg_z"]))
|
||||
bend = rg.ArcCurve(rg.Arc(pt(ro + R, th, m["hook_leg_z"]),
|
||||
pt(ro + R - R * math.sin(math.radians(45)), th, m["hook_leg_z"] + R - R * math.cos(math.radians(45))),
|
||||
pt(ro, th, m["hook_leg_z"] + R)))
|
||||
vert = rg.LineCurve(pt(ro, th, m["hook_leg_z"] + R), pt(ro, th, top))
|
||||
c = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0]
|
||||
add_bar(c, m["mark"], f"{i + 1:02d}", m["straight_len"] + math.pi / 2 * R + m["hook_leg"],
|
||||
f"{PID}-FT,{PID}-CP", f"angle {th:.4f} deg; 배근도(1) 정면도 갈고리 R{R:.0f}·{m['hook_leg']:.0f}; 단면 DIVISION {m['count']}")
|
||||
n += 1
|
||||
if "inner" in m: # second ring, hook bent inwards (Okseong P1)
|
||||
ri = m["inner"]["radius"]
|
||||
z0 = m["hook_leg_z"]
|
||||
leg = rg.LineCurve(pt(ri - R - m["hook_leg"], th, z0), pt(ri - R, th, z0))
|
||||
bend = rg.ArcCurve(rg.Arc(pt(ri - R, th, z0),
|
||||
pt(ri - R + R * math.sin(math.radians(45)), th, z0 + R - R * math.cos(math.radians(45))),
|
||||
pt(ri, th, z0 + R)))
|
||||
vert = rg.LineCurve(pt(ri, th, z0 + R), pt(ri, th, top))
|
||||
c = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0]
|
||||
add_bar(c, m["inner"]["mark"], f"{i + 1:02d}", m["straight_len"] + math.pi / 2 * R + m["hook_leg"],
|
||||
f"{PID}-FT,{PID}-CP", f"angle {th:.4f} deg; inner ring r {ri:.0f}, hook inwards")
|
||||
n += 1
|
||||
hp = cr["hoops"]
|
||||
r = hp["radius"]
|
||||
k = 0
|
||||
for zone in hp["zones"]:
|
||||
mark, typ = zone["mark"], zone["type"]
|
||||
r = zone.get("radius", hp["radius"]) # per zone (Okseong P3 1,150 / P4 1,147)
|
||||
for j, z in enumerate(zone["levels"]):
|
||||
k += 1
|
||||
if typ == "coupler_half":
|
||||
gd = math.degrees(zone["gap"] / r)
|
||||
arcs = [(90 + gd / 2, 270 - gd / 2), (270 + gd / 2, 450 - gd / 2)]
|
||||
for q, (a0, a1) in enumerate(arcs):
|
||||
add_bar(arc_xy(r, a0, a1, z), mark, f"L{k:03d}{'ab'[q]}", zone["a"], hosts(z),
|
||||
f"EL {z / 1000:.3f}; coupler gaps {zone['gap']:.0f} at 90/270 deg [추론]", coupler=zone["gap"])
|
||||
elif typ == "coupler_ring":
|
||||
gd = math.degrees(zone["gap"] / r)
|
||||
add_bar(arc_xy(r, 90 + gd / 2, 450 - gd / 2, z), mark, f"L{k:03d}", zone["a"], hosts(z),
|
||||
f"EL {z / 1000:.3f}; coupler gap {zone['gap']:.0f} at 90 deg [추론]", coupler=zone["gap"])
|
||||
elif typ == "lap_half":
|
||||
ext = math.degrees(zone["lap"] / 2 / r)
|
||||
a0 = 90 if j % 2 == 0 else 0 # joins alternate by 90 deg [추론]
|
||||
for q, s in enumerate((a0, a0 + 180)):
|
||||
c = hooked_arc(r, s - ext, s + 180 + ext, z, zone["hook"], min_bend_radius(MARK[mark][0]))
|
||||
add_bar(c, mark, f"L{k:03d}{'ab'[q]}", zone["a"] + zone["lap"] + 2 * zone["hook"], hosts(z),
|
||||
f"EL {z / 1000:.3f}; laps {zone['lap']:.0f} at {a0}/{a0 + 180} deg [추론]")
|
||||
else:
|
||||
raise RuntimeError(f"unknown hoop type {typ}")
|
||||
n += 1
|
||||
tie = cr["ties"]
|
||||
if tie.get("shape") == "90-135": # Okseong P1 (2026-10-14), as column_rebar_straight.py
|
||||
rb = min_bend_radius(MARK[tie["mark"]][0])
|
||||
for q, z in enumerate(tie["levels"]):
|
||||
for axis, name in ((0, "X"), (1, "Y")):
|
||||
zt = z + (tie["y_lift"] if axis else 0)
|
||||
u = rg.Vector3d(1, 0, 0) if axis == 0 else rg.Vector3d(0, 1, 0)
|
||||
s = -1 if q % 2 else 1
|
||||
a = rg.Point3d(0, 0, zt) - u * (s * tie["straight"] / 2)
|
||||
b = rg.Point3d(0, 0, zt) + u * (s * tie["straight"] / 2)
|
||||
d = -u * s * math.cos(math.radians(45)) + rg.Vector3d(0, 0, -1) * math.sin(math.radians(45))
|
||||
pl = rg.PolylineCurve([a + rg.Vector3d(0, 0, -tie["leg"]), a, b, b + d * tie["tail"]])
|
||||
c = rg.Curve.CreateFilletCornersCurve(pl, rb, TOL, math.radians(1)) or pl
|
||||
add_bar(c, tie["mark"], f"L{q + 1:02d}{name}", tie["design_length"], hosts(zt),
|
||||
f"EL {zt / 1000:.3f}; {name} (Y +{tie['y_lift']}, 90/135 교번 [추론])")
|
||||
n += 1
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"bars": n}))
|
||||
return
|
||||
half = tie["straight"] / 2
|
||||
for q, z in enumerate(tie["levels"]):
|
||||
zt = z + tie["z_offset"]
|
||||
for ang in tie["angles_deg"]:
|
||||
pts = [pt(half, ang + 180, zt - tie["hook"]), pt(half, ang + 180, zt), pt(half, ang, zt), pt(half, ang, zt - tie["hook"])]
|
||||
pl = rg.PolylineCurve(pts)
|
||||
c = rg.Curve.CreateFilletCornersCurve(pl, min_bend_radius(MARK[tie["mark"]][0]), TOL, math.radians(1)) or pl
|
||||
add_bar(c, tie["mark"], f"L{q + 1:02d}-{ang:05.1f}", tie["straight"] + 2 * tie["hook"], hosts(z),
|
||||
f"EL {z / 1000:.3f} (+{tie['z_offset']} on hoop); angle {ang:.2f} deg")
|
||||
n += 1
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"bars": n}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,150 @@
|
||||
"""G2 stage, form "straight" (Ramp-A P1, 2026-10-13): column on a drilled shaft, no footing. Straight main bars
|
||||
in one ring (bottom coupler to the shaft cage, not modelled as a solid), lapped half-ring hoops with 135-deg hooks,
|
||||
cross ties in X and Y (90-deg leg at one end, 135-deg hook at the other), from params `column_rebar`.
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after the G1 builder. Units mm, Z = EL x 1000.
|
||||
params column_rebar:
|
||||
main {mark, count, start_deg, radius, z0, z1, coupler_length}
|
||||
hoops {mark, radius, levels, a, lap, hook, join_deg [first, second]} joins alternate per level (K4)
|
||||
ties {mark, levels, straight, leg, hook_tail, y_lift} X tie at the level, Y tie y_lift above (K3);
|
||||
the 90-deg leg (down) and the 135-deg hook swap ends per level (K4)
|
||||
design lengths {mark: schedule-style original length per piece}
|
||||
Hosts for cover / anchorage: below the column bottom = pile top segment, above the column top = coping.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
MARK = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items()}
|
||||
DIA = {d: float(d[1:]) for d, _ in MARK.values()}
|
||||
CL_RGB = tuple(P["display"]["centreline_colour"])
|
||||
COLORS = {m: tuple(c) for m, c in P["display"].get("column_mark_colours", {}).items()}
|
||||
Z_CL0, Z_CL1 = P["column"]["z0"], P["column"]["z1"]
|
||||
PILE_TOP = f"{PID}-PL{len(P['pile']['segments']):02d}"
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
def min_bend_radius(dia_name):
|
||||
d = DIA[dia_name]
|
||||
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2
|
||||
|
||||
|
||||
def hosts(z):
|
||||
return PILE_TOP if z < Z_CL0 else (f"{PID}-CP" if z > Z_CL1 else "")
|
||||
|
||||
|
||||
def add_bar(curve, mark, occ, design_len, anchor, src, coupler=0.0):
|
||||
dia, shape = MARK[mark]
|
||||
pipes = rg.Brep.CreatePipe(curve, DIA[dia] / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
|
||||
if not pipes or not pipes[0].IsSolid:
|
||||
raise RuntimeError(f"{mark} {occ}: pipe failed")
|
||||
common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=f"{PID}-CL", anchor_hosts=anchor,
|
||||
design_length=f"{design_len:.3f}", coupler_length=f"{coupler:.3f}", src=src)
|
||||
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
||||
(pipes[0], layer(f"{PID}::철근::기둥::{mark}", COLORS.get(mark, (200, 140, 90))), f"{PID}-R")):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = lay
|
||||
oid = f"{prefix}-{mark}-{occ}"
|
||||
attr.Name = oid
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in common.items():
|
||||
attr.SetUserString(k, v)
|
||||
gid = doc.Objects.AddBrep(geo, attr) if isinstance(geo, rg.Brep) else doc.Objects.AddCurve(geo, attr)
|
||||
if gid == __import__("System").Guid.Empty:
|
||||
raise RuntimeError(f"{oid}: add failed")
|
||||
|
||||
|
||||
def pt(r, deg, z):
|
||||
t = math.radians(deg)
|
||||
return rg.Point3d(r * math.cos(t), r * math.sin(t), z)
|
||||
|
||||
|
||||
def hooked_arc(r, a0, a1, z, hook, bend_r):
|
||||
"""Arc a0 -> a1 (deg, ccw) with inward 135-deg hooks at both ends, fillet bend_r (as column_rebar_ring.py)."""
|
||||
arc = rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, (a0 + a1) / 2, z), pt(r, a1, z)))
|
||||
pieces = []
|
||||
for at_start in (True, False):
|
||||
p = arc.PointAtStart if at_start else arc.PointAtEnd
|
||||
t = -arc.TangentAtStart if at_start else arc.TangentAtEnd
|
||||
n = rg.Vector3d(-p.X, -p.Y, 0)
|
||||
n.Unitize()
|
||||
d = t * math.cos(math.radians(135)) + n * math.sin(math.radians(135))
|
||||
d.Unitize()
|
||||
pieces.append(rg.LineCurve(p, p + d * hook))
|
||||
joined = rg.Curve.JoinCurves([pieces[0], arc, pieces[1]], TOL)[0]
|
||||
return rg.Curve.CreateFilletCornersCurve(joined, bend_r, TOL, math.radians(1)) or joined
|
||||
|
||||
|
||||
def tie_curve(axis, z, half, leg, tail, flip):
|
||||
"""Straight part on the X (axis 0) or Y (axis 1) diameter at z; 90-deg leg `leg` down at one end, 135-deg hook
|
||||
(tail pointing down and back inwards) at the other; `flip` swaps the ends."""
|
||||
u = rg.Vector3d(1, 0, 0) if axis == 0 else rg.Vector3d(0, 1, 0)
|
||||
s = -1 if flip else 1
|
||||
a, b = rg.Point3d(0, 0, z) - u * (s * half), rg.Point3d(0, 0, z) + u * (s * half)
|
||||
d = -u * s * math.cos(math.radians(45)) + rg.Vector3d(0, 0, -1) * math.sin(math.radians(45))
|
||||
return rg.PolylineCurve([a + rg.Vector3d(0, 0, -leg), a, b, b + d * tail])
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith((f"{PID}-R-P", f"{PID}-RC-P")):
|
||||
doc.Objects.Delete(o, True)
|
||||
cr = P["column_rebar"]
|
||||
DL = cr["design_lengths"]
|
||||
m = cr["main"]
|
||||
n = 0
|
||||
for i in range(m["count"]):
|
||||
th = m["start_deg"] + i * 360.0 / m["count"]
|
||||
c = rg.LineCurve(pt(m["radius"], th, m["z0"]), pt(m["radius"], th, m["z1"]))
|
||||
add_bar(c, m["mark"], f"{i + 1:02d}", DL[m["mark"]], f"{PILE_TOP},{PID}-CP",
|
||||
f"angle {th:.4f} deg; D2 단면 C-C DIVISION {m['count']} r {m['radius']:.0f}; 정면 EL {m['z0'] / 1000:.3f}"
|
||||
f"~{m['z1'] / 1000:.3f}; 하단 커플러 {m['coupler_length']:.0f} (K1)", coupler=m["coupler_length"])
|
||||
n += 1
|
||||
h = cr["hoops"]
|
||||
r, ext = h["radius"], math.degrees(h["lap"] / 2 / h["radius"])
|
||||
for j, z in enumerate(h["levels"]):
|
||||
a0 = h["join_deg"][j % 2]
|
||||
for q, s in enumerate((a0, a0 + 180)):
|
||||
c = hooked_arc(r, s - ext, s + 180 + ext, z, h["hook"], min_bend_radius(MARK[h["mark"]][0]))
|
||||
add_bar(c, h["mark"], f"L{j + 1:03d}{'ab'[q]}", DL[h["mark"]], hosts(z),
|
||||
f"EL {z / 1000:.3f}; 겹이음 {h['lap']:.0f} at {a0}/{a0 + 180} deg (K4 [추론])")
|
||||
n += 1
|
||||
t = cr["ties"]
|
||||
rb = min_bend_radius(MARK[t["mark"]][0])
|
||||
for j, z in enumerate(t["levels"]):
|
||||
for axis, name in ((0, "X"), (1, "Y")):
|
||||
zt = z + (t["y_lift"] if axis == 1 else 0)
|
||||
pl = tie_curve(axis, zt, t["straight"] / 2, t["leg"], t["hook_tail"], flip=(j % 2 == 1))
|
||||
c = rg.Curve.CreateFilletCornersCurve(pl, rb, TOL, math.radians(1)) or pl
|
||||
add_bar(c, t["mark"], f"L{j + 1:02d}{name}", DL[t["mark"]], hosts(zt),
|
||||
f"EL {zt / 1000:.3f}; {name} 방향{' +' + str(t['y_lift']) + ' (K3 [추론])' if axis else ''}; "
|
||||
f"90° 다리·135° 갈고리 {'좌우 교번' if j % 2 else '기본'} (K4 [추론])")
|
||||
n += 1
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"bars": n}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,190 @@
|
||||
"""G1 stage: T-pier concrete (lean, footing, circular column, coping, piles, pedestals, pads).
|
||||
|
||||
Run inside Rhino 8 via runner with globals PARAMS_PATH (app schema pier-T/1).
|
||||
Only params are read; no drawing-name branching. Units: mm, Z = EL x 1000.
|
||||
Ported from Code/교각모델링/build_rhino.py (Ungok P1, model v0.1.09).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
FCK = P["materials"]["fck"]
|
||||
|
||||
LAYERS = {k: (f"{PID}::{name}", Color.FromArgb(*rgb)) for k, (name, rgb) in P["display"]["concrete_layers"].items()}
|
||||
|
||||
|
||||
def layer_index(full_path, color):
|
||||
idx = doc.Layers.FindByFullPath(full_path, -1)
|
||||
if idx >= 0:
|
||||
return idx
|
||||
parent_name, child = full_path.split("::")
|
||||
parent = doc.Layers.FindByFullPath(parent_name, -1)
|
||||
if parent < 0:
|
||||
parent = doc.Layers.Add(parent_name, Color.Black)
|
||||
layer = __import__("Rhino").DocObjects.Layer()
|
||||
layer.Name, layer.Color = child, color
|
||||
layer.ParentLayerId = doc.Layers[parent].Id
|
||||
return doc.Layers.Add(layer)
|
||||
|
||||
|
||||
def add(brep, key, oid, role, fck, views):
|
||||
if brep is None or not brep.IsSolid:
|
||||
raise RuntimeError(f"{oid}: not a closed solid")
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = layer_index(*LAYERS[key])
|
||||
attr.Name = oid
|
||||
for k, v in (("id", oid), ("role", role), ("fck", fck), ("source_views", views)):
|
||||
attr.SetUserString(k, v)
|
||||
return doc.Objects.AddBrep(brep, attr)
|
||||
|
||||
|
||||
def box(x0, x1, y0, y1, z0, z1):
|
||||
return rg.Box(rg.BoundingBox(x0, y0, z0, x1, y1, z1)).ToBrep()
|
||||
|
||||
|
||||
def cylinder(x, y, z0, z1, r):
|
||||
circle = rg.Circle(rg.Plane(rg.Point3d(x, y, z0), rg.Vector3d.ZAxis), r)
|
||||
return rg.Cylinder(circle, z1 - z0).ToBrep(True, True)
|
||||
|
||||
|
||||
def arc_through(p_start, p_end, radius, y):
|
||||
"""Arc of given radius between two XZ points; center on the lower side (soffit)."""
|
||||
(x1, z1), (x2, z2) = p_start, p_end
|
||||
mx, mz = (x1 + x2) / 2, (z1 + z2) / 2
|
||||
dx, dz = x2 - x1, z2 - z1
|
||||
chord = math.hypot(dx, dz)
|
||||
h = math.sqrt(radius ** 2 - (chord / 2) ** 2)
|
||||
nx, nz = -dz / chord, dx / chord
|
||||
centers = [(mx + nx * h, mz + nz * h), (mx - nx * h, mz - nz * h)]
|
||||
cx, cz = min(centers, key=lambda c: c[1])
|
||||
a1, a2 = math.atan2(z1 - cz, x1 - cx), math.atan2(z2 - cz, x2 - cx)
|
||||
am = (a1 + a2) / 2
|
||||
mid = rg.Point3d(cx + radius * math.cos(am), y, cz + radius * math.sin(am))
|
||||
arc = rg.Arc(rg.Point3d(x1, y, z1), mid, rg.Point3d(x2, y, z2))
|
||||
return rg.ArcCurve(arc), (cx, cz)
|
||||
|
||||
|
||||
def coping_brep(c):
|
||||
y0 = -c["width_y"] / 2
|
||||
half = c["length_bottom_tip_to_tip"] / 2
|
||||
zb, rise, r = c["z_bottom_flat"], c["tip_bottom_rise"], c["soffit_radius"]
|
||||
flat, inset = c["flat_bottom_half_width"], c["end_batter_top_inset"]
|
||||
steps = c["top_steps"]
|
||||
right_arc, rc = arc_through((flat, zb), (half, zb + rise), r, y0)
|
||||
left_arc, lc = arc_through((-half, zb + rise), (-flat, zb), r, y0)
|
||||
pts = [(half, zb + rise), (half - inset, steps[-1]["z"])]
|
||||
for s_prev, s in zip(reversed(steps[1:]), reversed(steps[:-1])):
|
||||
pts += [(s_prev["x0"], s_prev["z"]), (s["x1"], s["z"])]
|
||||
pts += [(-half + inset, steps[0]["z"]), (-half, zb + rise)]
|
||||
top = rg.PolylineCurve([rg.Point3d(x, y0, z) for x, z in pts])
|
||||
bottom = rg.LineCurve(rg.Point3d(-flat, y0, zb), rg.Point3d(flat, y0, zb))
|
||||
joined = rg.Curve.JoinCurves([bottom, right_arc, top, left_arc], TOL)
|
||||
if len(joined) != 1 or not joined[0].IsClosed:
|
||||
raise RuntimeError("coping profile not closed")
|
||||
srf = rg.Surface.CreateExtrusion(joined[0], rg.Vector3d(0, c["width_y"], 0))
|
||||
brep = srf.ToBrep().CapPlanarHoles(TOL)
|
||||
if brep.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
brep.Flip()
|
||||
return brep, {"right_arc_center": rc, "left_arc_center": lc}
|
||||
|
||||
|
||||
def prism(pts_xy, z0, z1):
|
||||
poly = rg.PolylineCurve([rg.Point3d(x, y, z0) for x, y in pts_xy + pts_xy[:1]])
|
||||
srf = rg.Surface.CreateExtrusion(poly, rg.Vector3d(0, 0, z1 - z0))
|
||||
b = srf.ToBrep().CapPlanarHoles(TOL)
|
||||
if b is not None and b.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
b.Flip()
|
||||
return b
|
||||
|
||||
|
||||
def rotated_rect(cx, cy, along, across, rot_deg, z):
|
||||
"""Rectangle with `along` side on +Y rotated clockwise by rot_deg (plan view)."""
|
||||
t = math.radians(rot_deg)
|
||||
ua, uc = (math.sin(t), math.cos(t)), (math.cos(t), -math.sin(t))
|
||||
pts = [(cx + sa * ua[0] * along / 2 + sc * uc[0] * across / 2,
|
||||
cy + sa * ua[1] * along / 2 + sc * uc[1] * across / 2)
|
||||
for sa, sc in ((-1, -1), (1, -1), (1, 1), (-1, 1))]
|
||||
return rg.PolylineCurve([rg.Point3d(x, y, z) for x, y in pts + pts[:1]])
|
||||
|
||||
|
||||
def frustum(bottom, top):
|
||||
lofts = rg.Brep.CreateFromLoft([bottom, top], rg.Point3d.Unset, rg.Point3d.Unset,
|
||||
rg.LoftType.Straight, False)
|
||||
b = lofts[0].CapPlanarHoles(TOL)
|
||||
if b is not None and b.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
b.Flip()
|
||||
return b
|
||||
|
||||
|
||||
def add_bearing_seats(bs, steps):
|
||||
"""Pedestals (h1) per girder line split front/rear, and h2 pads (D2 TABLE A, B, h2)."""
|
||||
ped = bs["pedestal_girder5"]
|
||||
ref = bs["reference_girder"]
|
||||
mb, mt = bs["pad_margin_bottom"], bs["pad_margin_top"]
|
||||
by_no = {b["no"]: b for b in bs["bearings"]}
|
||||
for b in bs["bearings"]:
|
||||
shift = (b["girder_line"] - ref) * bs["pitch_x"]
|
||||
half = ped["front_half"] if b["row"] == "front" else ped["rear_half"]
|
||||
z0 = steps[b["girder_line"] - 1]["z"]
|
||||
z1 = z0 + b["h1"]
|
||||
tag = " [추론: 평행 이동]" if b["inferred"] else ""
|
||||
add(prism([(x + shift, y) for x, y in half], z0, z1), "seat", f"{PID}-BS{b['no']:02d}",
|
||||
f"bearing pedestal h1 NO.{b['no']} girder {b['girder_line']} {b['row']}{tag}", FCK["seat"],
|
||||
"D1 평면도, D1 단면도, D2 ELEVATION TABLE")
|
||||
cx, cy = b["centre"]
|
||||
bottom = rotated_rect(cx, cy, b["A"] + mb, b["B"] + mb, b["rotation_deg"], z1)
|
||||
top = rotated_rect(cx, cy, b["A"] + mt, b["B"] + mt, b["rotation_deg"], z1 + b["h2"])
|
||||
add(frustum(bottom, top), "pad", f"{PID}-BP{b['no']:02d}",
|
||||
f"bearing pad h2 NO.{b['no']} {b['type']}{tag}", "-",
|
||||
"D1 평면도, D1 단면도, D2 교량받침 상세·ELEVATION TABLE")
|
||||
return len(by_no) * 2
|
||||
|
||||
|
||||
def main():
|
||||
# Rebuild from scratch: remove previous P1 objects only.
|
||||
for o in list(doc.Objects):
|
||||
if o.Attributes.GetUserString("id"):
|
||||
doc.Objects.Delete(o, True)
|
||||
lc, ft, cl, cp, pl = (P[k] for k in ("lean_concrete", "footing", "column", "coping", "piles"))
|
||||
|
||||
ft_top = ft["z_bottom"] + ft["thickness"]
|
||||
col_top = ft_top + cl["height"]
|
||||
if abs(col_top - cp["z_bottom_flat"]) > TOL:
|
||||
raise RuntimeError(f"column top {col_top} != coping bottom {cp['z_bottom_flat']}")
|
||||
|
||||
pile_top = ft["z_bottom"] + pl["head_embedment"]
|
||||
px, py = pl.get("pitch_x", pl.get("pitch")), pl.get("pitch_y", pl.get("pitch")) # Ungok P2: X 1750 / Y 1550
|
||||
pile_xy = [(-((pl["count_x"] - 1) * px) / 2 + i * px,
|
||||
-((pl["count_y"] - 1) * py) / 2 + j * py)
|
||||
for j in range(pl["count_y"]) for i in range(pl["count_x"])]
|
||||
piles = [cylinder(x, y, pile_top - pl["length"], pile_top, pl["diameter"] / 2) for x, y in pile_xy]
|
||||
|
||||
lean = box(-lc["size_x"] / 2, lc["size_x"] / 2, -lc["size_y"] / 2, lc["size_y"] / 2,
|
||||
lc["z_bottom"], lc["z_top"])
|
||||
cut = rg.Brep.CreateBooleanDifference([lean], piles, TOL)
|
||||
if not cut or len(cut) != 1:
|
||||
raise RuntimeError("lean concrete boolean failed")
|
||||
add(cut[0], "lean", f"{PID}-LC", "lean concrete", FCK["lean"], "D1 정면도, D1 단면도")
|
||||
add(box(-ft["size_x"] / 2, ft["size_x"] / 2, -ft["size_y"] / 2, ft["size_y"] / 2,
|
||||
ft["z_bottom"], ft_top), "footing", f"{PID}-FT", "footing", FCK["footing"],
|
||||
"D1 정면도, D1 단면도, D2 기초평면도")
|
||||
add(cylinder(0, 0, ft_top, col_top, cl["diameter"] / 2), "column", f"{PID}-CL", "column", FCK["column"],
|
||||
"D1 정면도, D1 단면도, D1 단면 C-C/D-D/E-E, D2 기초평면도")
|
||||
cop, centers = coping_brep(cp)
|
||||
add(cop, "coping", f"{PID}-CP", "coping", FCK["coping"], "D1 평면도, D1 정면도, D1 단면도, D1 단면 A-A/B-B")
|
||||
pile_role = f"{pl['kind']} D{pl['diameter']:.0f} L{pl['length'] / 1000:g}m"
|
||||
for n, b in enumerate(piles, 1):
|
||||
add(b, "pile", f"{PID}-PL{n:02d}", pile_role, "-", "D1 정면도, D1 단면도")
|
||||
seats = add_bearing_seats(P["bearing_seats"], cp["top_steps"]) if "bearing_seats" in P else 0
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"objects": 4 + len(piles) + seats, "coping_arc_centers": centers}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,205 @@
|
||||
"""G1 stage, form "single shaft" (Ramp-A P1, 2026-10-13): drilled shaft in two diameters (no footing, no lean
|
||||
concrete), circular column, Y-shaped coping (R soffit arcs flaring from the column, battered ends that are convex
|
||||
in plan, a step in the top surface), one pedestal (box) and one h2 pad (frustum) per bearing.
|
||||
|
||||
Run inside Rhino 8 via runner with globals PARAMS_PATH. Units mm, Z = EL x 1000, origin = pier centre.
|
||||
params:
|
||||
pile {segments [{d, z0, z1}, ...] bottom to top}; column {shape "circular", diameter, z0, z1}
|
||||
coping {form "y_shaft", z_bottom (= column top), z_tip, half_bottom (column face), soffit {centre_x, centre_z,
|
||||
radius} (left arc; the right one is mirrored), z_top_left, z_top_right, top_half (straight top edge),
|
||||
plan_end {corner_x, tip_x, half_width} (bottom edge arc through (corner_x, +-half_width) and (tip_x, 0)),
|
||||
side {half_width_top, half_width_bottom} (2,200 above z_tip, straight taper to 2,000 at z_bottom)}
|
||||
bearing_seats {pedestal {size_x, size_y}, pad_margin_bottom, pad_margin_top,
|
||||
bearings [{no, centre [x, y], rotation_deg, seat_z, h1, h2, A, B, type}]}
|
||||
Coping = (front outline extruded along Y) n (side outline extruded along X) - two end cutters. The end face is
|
||||
ruled: straight top edge x = +-top_half at the top level, convex plan arc at z_tip. Each extrusion reaches past
|
||||
the faces the other one defines (no coincident or tangent faces in the booleans, see concrete_tapered.py).
|
||||
Not modelled (user decision 2026-10-13): the 30 mm decorative grooves (D1 detail "B").
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
import System
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
FCK = P["materials"]["fck"]
|
||||
LAYERS = {k: (f"{PID}::{name}", Color.FromArgb(*rgb)) for k, (name, rgb) in P["display"]["concrete_layers"].items()}
|
||||
|
||||
|
||||
def layer_index(full_path, color):
|
||||
idx = doc.Layers.FindByFullPath(full_path, -1)
|
||||
if idx >= 0:
|
||||
return idx
|
||||
parent_name, child = full_path.split("::")
|
||||
parent = doc.Layers.FindByFullPath(parent_name, -1)
|
||||
if parent < 0:
|
||||
parent = doc.Layers.Add(parent_name, Color.Black)
|
||||
layer = __import__("Rhino").DocObjects.Layer()
|
||||
layer.Name, layer.Color = child, color
|
||||
layer.ParentLayerId = doc.Layers[parent].Id
|
||||
return doc.Layers.Add(layer)
|
||||
|
||||
|
||||
def add(brep, key, oid, role, fck, views):
|
||||
if brep is None or not brep.IsSolid:
|
||||
raise RuntimeError(f"{oid}: not a closed solid")
|
||||
if not brep.IsValid: # AddBrep silently refuses invalid breps
|
||||
ok, log = brep.IsValidWithLog()
|
||||
raise RuntimeError(f"{oid}: invalid brep: {log[:400]}")
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = layer_index(*LAYERS[key])
|
||||
attr.Name = oid
|
||||
for k, v in (("id", oid), ("role", role), ("fck", fck), ("source_views", views)):
|
||||
attr.SetUserString(k, v)
|
||||
gid = doc.Objects.AddBrep(brep, attr)
|
||||
if gid == System.Guid.Empty:
|
||||
raise RuntimeError(f"{oid}: AddBrep failed")
|
||||
return gid
|
||||
|
||||
|
||||
def solid(b):
|
||||
if b is not None and b.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
b.Flip()
|
||||
return b
|
||||
|
||||
|
||||
def cylinder(z0, z1, r):
|
||||
return rg.Cylinder(rg.Circle(rg.Plane(rg.Point3d(0, 0, z0), rg.Vector3d.ZAxis), r), z1 - z0).ToBrep(True, True)
|
||||
|
||||
|
||||
def extrude_closed(curve, vec):
|
||||
return solid(rg.Surface.CreateExtrusion(curve, vec).ToBrep().CapPlanarHoles(TOL))
|
||||
|
||||
|
||||
def rotated_rect(cx, cy, along, across, rot_deg, z):
|
||||
"""Rectangle with `along` side on +Y rotated clockwise by rot_deg (plan view)."""
|
||||
t = math.radians(rot_deg)
|
||||
ua, uc = (math.sin(t), math.cos(t)), (math.cos(t), -math.sin(t))
|
||||
pts = [(cx + sa * ua[0] * along / 2 + sc * uc[0] * across / 2, cy + sa * ua[1] * along / 2 + sc * uc[1] * across / 2)
|
||||
for sa, sc in ((-1, -1), (1, -1), (1, 1), (-1, 1))]
|
||||
return rg.PolylineCurve([rg.Point3d(x, y, z) for x, y in pts + pts[:1]])
|
||||
|
||||
|
||||
def frustum(bottom, top):
|
||||
lofts = rg.Brep.CreateFromLoft([bottom, top], rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
|
||||
return solid(lofts[0].CapPlanarHoles(TOL))
|
||||
|
||||
|
||||
def boolean(kind, a, b):
|
||||
f = rg.Brep.CreateBooleanIntersection if kind == "and" else rg.Brep.CreateBooleanDifference
|
||||
out = f(a, b, TOL)
|
||||
if not out or len(out) != 1:
|
||||
raise RuntimeError(f"coping boolean {kind} failed ({len(out) if out else 0} pieces)")
|
||||
return solid(out[0])
|
||||
|
||||
|
||||
def front_extrusion(c):
|
||||
"""Front outline (XZ): soffit arcs from the column face, continued past the end (cut later), vertical outer
|
||||
ends at +-ext, top levels with the step at x = 0. Extruded over y = +-(half_width_top + 100)."""
|
||||
s, zb = c["soffit"], c["z_bottom"]
|
||||
cx, cz, r = s["centre_x"], s["centre_z"], s["radius"]
|
||||
a0 = math.atan2(zb - cz, -c["half_bottom"] - cx) # left arc start (column face)
|
||||
ext = c["plan_end"]["tip_x"] + 400 # past the tip (abs)
|
||||
a1 = math.acos((-ext - cx) / r) if abs((-ext - cx) / r) <= 1 else None
|
||||
if a1 is None:
|
||||
raise RuntimeError("soffit arc does not reach the extension")
|
||||
am = (a0 + a1) / 2 # left arc through start, mid, end
|
||||
pl = [(cx + r * math.cos(a), cz + r * math.sin(a)) for a in (a0, am, a1)] # (x, z)
|
||||
w = c["side"]["half_width_top"] + 100
|
||||
to_xyz = lambda x, z: rg.Point3d(x, -w, z)
|
||||
arc_l = rg.ArcCurve(rg.Arc(*[to_xyz(x, z) for x, z in pl]))
|
||||
arc_r = rg.ArcCurve(rg.Arc(*[to_xyz(-x, z) for x, z in reversed(pl)]))
|
||||
zl, zr = c["z_top_left"], c["z_top_right"]
|
||||
xe, ze = pl[2] # left extension end (xe < 0)
|
||||
poly_top = rg.PolylineCurve([to_xyz(xe, ze), to_xyz(xe, zl), to_xyz(0, zl), to_xyz(0, zr), to_xyz(-xe, zr),
|
||||
to_xyz(-xe, ze)])
|
||||
bottom = rg.LineCurve(to_xyz(c["half_bottom"], zb), to_xyz(-c["half_bottom"], zb))
|
||||
j = rg.Curve.JoinCurves([arc_l, poly_top, arc_r, bottom], 0.01)
|
||||
if len(j) != 1 or not j[0].IsClosed:
|
||||
raise RuntimeError(f"front outline not closed ({len(j)} pieces)")
|
||||
return extrude_closed(j[0], rg.Vector3d(0, 2 * w, 0))
|
||||
|
||||
|
||||
def side_extrusion(c):
|
||||
"""Side outline (YZ): straight taper from the column width at z_bottom to the full width at z_tip, continued
|
||||
100 below z_bottom (no face on the coping bottom plane), vertical above, up past the top. Extruded over X."""
|
||||
sd, zb, zt = c["side"], c["z_bottom"], c["z_tip"]
|
||||
hb, ht = sd["half_width_bottom"], sd["half_width_top"]
|
||||
k = (ht - hb) / (zt - zb)
|
||||
y_low, z_low = hb - k * 100, zb - 100
|
||||
ztop = max(c["z_top_left"], c["z_top_right"]) + 200
|
||||
x0 = c["plan_end"]["tip_x"] + 600
|
||||
pts = [(-y_low, z_low), (y_low, z_low), (ht, zt), (ht, ztop), (-ht, ztop), (-ht, zt)]
|
||||
crv = rg.PolylineCurve([rg.Point3d(-x0, y, z) for y, z in pts + pts[:1]])
|
||||
return extrude_closed(crv, rg.Vector3d(2 * x0, 0, 0))
|
||||
|
||||
|
||||
def end_cutter(c, sx):
|
||||
"""Waste beyond the ruled end face of one end (sx = -1 left, +1 right). Two straight-lofted sections, below the
|
||||
tip level and above the top: on each, x(y) = arc(y) + t * (top_edge - arc(y)), t = (z - z_tip) / (z_top - z_tip),
|
||||
so the loft lines are the rulings of the end face. Plan arc sampled every 25 mm (chord error < 0.1 mm)."""
|
||||
pe, zt = c["plan_end"], c["z_tip"]
|
||||
ztop = c["z_top_left"] if sx < 0 else c["z_top_right"]
|
||||
hw, corner, tip, top = pe["half_width"], pe["corner_x"], pe["tip_x"], c["top_half"]
|
||||
sag = tip - corner
|
||||
R = (hw * hw + sag * sag) / (2 * sag)
|
||||
ys = [-hw - 200 + 25 * i for i in range(int((2 * hw + 400) / 25) + 1)]
|
||||
arc = lambda y: tip - (R - math.sqrt(R * R - y * y)) # abs x of the bottom edge
|
||||
secs = []
|
||||
for z in (zt - 700, ztop + 150):
|
||||
t = (z - zt) / (ztop - zt)
|
||||
pts = [(arc(y) + t * (top - arc(y)), y) for y in ys]
|
||||
pts += [(tip + 1500, ys[-1]), (tip + 1500, ys[0])]
|
||||
secs.append(rg.PolylineCurve([rg.Point3d(sx * x, y, z) for x, y in pts + pts[:1]]))
|
||||
lofts = rg.Brep.CreateFromLoft(secs, rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
|
||||
b = solid(lofts[0].CapPlanarHoles(TOL))
|
||||
if b is None or not b.IsSolid:
|
||||
raise RuntimeError("end cutter is not a closed solid")
|
||||
return b
|
||||
|
||||
|
||||
def coping_brep(c):
|
||||
body = boolean("and", [front_extrusion(c)], [side_extrusion(c)])
|
||||
return boolean("diff", [body], [end_cutter(c, -1), end_cutter(c, 1)])
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
if o.Attributes.GetUserString("id"):
|
||||
doc.Objects.Delete(o, True)
|
||||
pl, cl, cp, bs = (P[k] for k in ("pile", "column", "coping", "bearing_seats"))
|
||||
segs = pl["segments"]
|
||||
for a, b in zip(segs, segs[1:]):
|
||||
if abs(a["z1"] - b["z0"]) > TOL:
|
||||
raise RuntimeError("pile segments not continuous")
|
||||
if abs(segs[-1]["z1"] - cl["z0"]) > TOL or abs(cl["z1"] - cp["z_bottom"]) > TOL:
|
||||
raise RuntimeError("pile top / column / coping bottom levels do not meet")
|
||||
for i, s in enumerate(segs, 1):
|
||||
add(cylinder(s["z0"], s["z1"], s["d"] / 2), "pile", f"{PID}-PL{i:02d}",
|
||||
f"drilled shaft segment {i} D{s['d']:.0f}", FCK["pile"], "일반도 정면도·측면도")
|
||||
add(cylinder(cl["z0"], cl["z1"], cl["diameter"] / 2), "column", f"{PID}-CL", "column", FCK["column"],
|
||||
"일반도 정면도·측면도·기둥 단면도")
|
||||
add(coping_brep(cp), "coping", f"{PID}-CP", "coping (Y-shaped, single shaft)", FCK["coping"],
|
||||
"일반도 정면도·측면도·평면도")
|
||||
ped, mb, mt = bs["pedestal"], bs["pad_margin_bottom"], bs["pad_margin_top"]
|
||||
for b in bs["bearings"]:
|
||||
cx, cy = b["centre"]
|
||||
z0, z1 = b["seat_z"], b["seat_z"] + b["h1"]
|
||||
base = rotated_rect(cx, cy, ped["size_y"], ped["size_x"], b["rotation_deg"], z0)
|
||||
add(extrude_closed(base, rg.Vector3d(0, 0, b["h1"])), "seat", f"{PID}-BS{b['no']:02d}",
|
||||
f"bearing pedestal h1 NO.{b['no']}", FCK["seat"], "일반도 평면도·교량받침 상세도·ELEVATION TABLE")
|
||||
bottom = rotated_rect(cx, cy, b["A"] + mb, b["B"] + mb, b["rotation_deg"], z1)
|
||||
top = rotated_rect(cx, cy, b["A"] + mt, b["B"] + mt, b["rotation_deg"], z1 + b["h2"])
|
||||
add(frustum(bottom, top), "pad", f"{PID}-BP{b['no']:02d}", f"bearing pad h2 NO.{b['no']} {b['type']}", "-",
|
||||
"일반도 평면도·교량받침 상세도·ELEVATION TABLE")
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"objects": len(segs) + 2 + 2 * len(bs["bearings"])}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,267 @@
|
||||
"""G1 stage, form "tapered T" (Ulsan P1, 2026-10-13): lean, spread footing (no piles), circular column, coping
|
||||
with a straight sloped soffit, battered ends, a step in the top surface and rounded plan corners,
|
||||
one pedestal (box) and one h2 pad (frustum) per bearing.
|
||||
|
||||
Run inside Rhino 8 via runner with globals PARAMS_PATH. Units mm, Z = EL x 1000, origin = pier centre.
|
||||
params:
|
||||
lean_concrete {size_x, size_y, z_bottom, z_top}; footing {size_x, size_y, z_bottom, thickness}
|
||||
column {shape "circular", diameter, height}
|
||||
coping {form "tapered", profile_xz [[x, z], ...] (front-view outline, closed implicitly), width_y,
|
||||
plan_length (at the tips), top_length, z_tip, z_top_left, z_top_right, plan_corner_radius}
|
||||
bearing_seats {pedestal {size_x, size_y}, pad_margin_bottom, pad_margin_top,
|
||||
bearings [{no, centre [x, y], rotation_deg, seat_z, h1, h2, A, B, type}]}
|
||||
Coping = profile extrusion minus the four plan-corner wastes outside R (faceted 16-segment arcs, +0.5 mm).
|
||||
Optional (Okseong P1, 2026-10-14; absent -> the code paths above are unchanged):
|
||||
piles {count_x, count_y, pitch_x, pitch_y, diameter, length, head_embedment, kind}: pile grid under the footing,
|
||||
the lean concrete is cut by the piles (as concrete.py)
|
||||
coping.side_taper {half_width_top, z_taper_top, half_width_bottom}: body intersected with the side outline
|
||||
(full width above z_taper_top, straight taper to the bottom width at the coping bottom)
|
||||
coping.skew_deg: plan skew; the coping is sheared x' = x - tan(skew) * y (end faces / top-step lines at 90 + skew)
|
||||
footing.rot_deg (Ulsan P2, 2026-10-15; absent = 0): lean, footing and piles are drawn in the foundation's own axes and then
|
||||
rotated counter-clockwise (plan view, X right / Y up) about the pier centre; column, coping and seats stay in the pier axes
|
||||
bearing_seats.pedestals [{no, corners [[x, y] x4], z_bottom, top {y0, z0, y1, z1}}]: explicit pedestal solids (plan
|
||||
quadrilateral, top plane linear in y); bearings[].pedestal = its index; pads then sit on that plane
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
import System
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
FCK = P["materials"]["fck"]
|
||||
LAYERS = {k: (f"{PID}::{name}", Color.FromArgb(*rgb)) for k, (name, rgb) in P["display"]["concrete_layers"].items()}
|
||||
|
||||
|
||||
def layer_index(full_path, color):
|
||||
idx = doc.Layers.FindByFullPath(full_path, -1)
|
||||
if idx >= 0:
|
||||
return idx
|
||||
parent_name, child = full_path.split("::")
|
||||
parent = doc.Layers.FindByFullPath(parent_name, -1)
|
||||
if parent < 0:
|
||||
parent = doc.Layers.Add(parent_name, Color.Black)
|
||||
layer = __import__("Rhino").DocObjects.Layer()
|
||||
layer.Name, layer.Color = child, color
|
||||
layer.ParentLayerId = doc.Layers[parent].Id
|
||||
return doc.Layers.Add(layer)
|
||||
|
||||
|
||||
def add(brep, key, oid, role, fck, views):
|
||||
if brep is None or not brep.IsSolid:
|
||||
raise RuntimeError(f"{oid}: not a closed solid")
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = layer_index(*LAYERS[key])
|
||||
attr.Name = oid
|
||||
for k, v in (("id", oid), ("role", role), ("fck", fck), ("source_views", views)):
|
||||
attr.SetUserString(k, v)
|
||||
if not brep.IsValid: # AddBrep silently refuses invalid breps (2026-10-13: coping lost)
|
||||
ok, log = brep.IsValidWithLog()
|
||||
raise RuntimeError(f"{oid}: invalid brep: {log[:400]}")
|
||||
gid = doc.Objects.AddBrep(brep, attr)
|
||||
if gid == System.Guid.Empty:
|
||||
raise RuntimeError(f"{oid}: AddBrep failed")
|
||||
return gid
|
||||
|
||||
|
||||
def solid(b):
|
||||
if b is not None and b.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
b.Flip()
|
||||
return b
|
||||
|
||||
|
||||
def box(x0, x1, y0, y1, z0, z1):
|
||||
return rg.Box(rg.BoundingBox(x0, y0, z0, x1, y1, z1)).ToBrep()
|
||||
|
||||
|
||||
def cylinder(x, y, z0, z1, r):
|
||||
return rg.Cylinder(rg.Circle(rg.Plane(rg.Point3d(x, y, z0), rg.Vector3d.ZAxis), r), z1 - z0).ToBrep(True, True)
|
||||
|
||||
|
||||
def extrude_closed(curve, vec):
|
||||
return solid(rg.Surface.CreateExtrusion(curve, vec).ToBrep().CapPlanarHoles(TOL))
|
||||
|
||||
|
||||
def rotated_rect(cx, cy, along, across, rot_deg, z):
|
||||
"""Rectangle with `along` side on +Y rotated clockwise by rot_deg (plan view)."""
|
||||
t = math.radians(rot_deg)
|
||||
ua, uc = (math.sin(t), math.cos(t)), (math.cos(t), -math.sin(t))
|
||||
pts = [(cx + sa * ua[0] * along / 2 + sc * uc[0] * across / 2, cy + sa * ua[1] * along / 2 + sc * uc[1] * across / 2)
|
||||
for sa, sc in ((-1, -1), (1, -1), (1, 1), (-1, 1))]
|
||||
return rg.PolylineCurve([rg.Point3d(x, y, z) for x, y in pts + pts[:1]])
|
||||
|
||||
|
||||
def frustum(bottom, top):
|
||||
lofts = rg.Brep.CreateFromLoft([bottom, top], rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
|
||||
return solid(lofts[0].CapPlanarHoles(TOL))
|
||||
|
||||
|
||||
def rrect(length, width, r, z):
|
||||
"""Closed rounded rectangle centred on the axis at height z (corners filleted with radius r)."""
|
||||
rect = rg.Rectangle3d(rg.Plane(rg.Point3d(0, 0, z), rg.Vector3d.ZAxis), rg.Interval(-length / 2, length / 2),
|
||||
rg.Interval(-width / 2, width / 2)).ToNurbsCurve()
|
||||
return rg.Curve.CreateFilletCornersCurve(rect, r, TOL, math.radians(1)) if r > 0 else rect
|
||||
|
||||
|
||||
def corner_waste(sx, sy, c, z_top):
|
||||
"""Region outside the R fillet of one plan corner (x sign sx, y sign sy), as a straight loft through three
|
||||
equal sections: vertical below the tip level (fillet centre at tip - R), then following the end batter up to
|
||||
z_top + 100 (centre moves with the end face). The arc is a 16-segment polyline offset 0.5 mm outwards
|
||||
(chord error < 0.6 mm), so no cutter face is tangent to or coincident with a coping face / knife edge."""
|
||||
w, R = c["width_y"], c["plan_corner_radius"]
|
||||
tip, top, z_tip = c["plan_length"] / 2, c["top_length"] / 2, c["z_tip"]
|
||||
batter = (tip - top) / (z_top - z_tip) # x shift per mm of height above the tip
|
||||
zs = [z_tip - 300, z_tip, z_top + 100]
|
||||
cxs = [tip - R, tip - R, tip - R - batter * (z_top + 100 - z_tip)]
|
||||
cy, r = w / 2 - R, R + 0.5
|
||||
secs = []
|
||||
for z, cx in zip(zs, cxs):
|
||||
pts = [(cx + r * math.cos(math.radians(a)), cy + r * math.sin(math.radians(a))) for a in
|
||||
[90 * i / 16 for i in range(17)]]
|
||||
pts += [(cx, cy + R + 100), (cx + R + 100, cy + R + 100), (cx + R + 100, cy)]
|
||||
secs.append(rg.PolylineCurve([rg.Point3d(sx * x, sy * y, z) for x, y in pts + pts[:1]]))
|
||||
lofts = rg.Brep.CreateFromLoft(secs, rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
|
||||
return solid(lofts[0].CapPlanarHoles(TOL))
|
||||
|
||||
|
||||
def side_cut(c, body):
|
||||
"""Intersect with the side outline extruded along X (Ramp-A style taper); reaches past the X ends."""
|
||||
st = c["side_taper"]
|
||||
zb = min(z for _, z in c["profile_xz"])
|
||||
zt, ht, hb = st["z_taper_top"], st["half_width_top"], st["half_width_bottom"]
|
||||
k = (ht - hb) / (zt - zb)
|
||||
y_low, z_low = hb - k * 100, zb - 100
|
||||
ztop = max(c["z_top_left"], c["z_top_right"], max(z for _, z in c["profile_xz"])) + 200
|
||||
x0 = c["plan_length"] / 2 + 3000
|
||||
pts = [(-y_low, z_low), (y_low, z_low), (ht, zt), (ht, ztop), (-ht, ztop), (-ht, zt)]
|
||||
side = extrude_closed(rg.PolylineCurve([rg.Point3d(-x0, y, z) for y, z in pts + pts[:1]]), rg.Vector3d(2 * x0, 0, 0))
|
||||
out = rg.Brep.CreateBooleanIntersection(body, side, TOL)
|
||||
if not out or len(out) != 1:
|
||||
raise RuntimeError(f"coping side taper failed ({len(out) if out else 0})")
|
||||
return solid(out[0])
|
||||
|
||||
|
||||
def skew_xf(deg):
|
||||
xf = rg.Transform.Identity
|
||||
xf.M01 = -math.tan(math.radians(deg)) # x' = x - tan(skew) * y
|
||||
return xf
|
||||
|
||||
|
||||
def coping_brep(c):
|
||||
"""Front profile (sloped soffit, battered ends, top step) extruded over the width as one solid, minus the four
|
||||
plan-corner wastes outside the R fillets (see corner_waste). Splitting the coping into parts and uniting them,
|
||||
or filleting the corner edges, failed on coplanar faces / the knife-edge tips (2026-10-13)."""
|
||||
w, R = c["width_y"], c["plan_corner_radius"]
|
||||
if "side_taper" in c or "skew_deg" in c: # Okseong form (2026-10-14): extrusion wider than the taper
|
||||
w2 = w + 400
|
||||
prof = rg.PolylineCurve([rg.Point3d(x, -w2 / 2, z) for x, z in c["profile_xz"] + c["profile_xz"][:1]])
|
||||
a = extrude_closed(prof, rg.Vector3d(0, w2, 0))
|
||||
if "side_taper" in c:
|
||||
a = side_cut(c, a)
|
||||
if c.get("skew_deg"):
|
||||
a.Transform(skew_xf(c["skew_deg"]))
|
||||
return a
|
||||
prof = rg.PolylineCurve([rg.Point3d(x, -w / 2, z) for x, z in c["profile_xz"] + c["profile_xz"][:1]])
|
||||
a = extrude_closed(prof, rg.Vector3d(0, w, 0))
|
||||
if R <= 0:
|
||||
return a
|
||||
cutters = [corner_waste(sx, sy, c, c["z_top_left"] if sx < 0 else c["z_top_right"]) for sx in (1, -1) for sy in (1, -1)]
|
||||
for q in cutters:
|
||||
if q is None or not q.IsSolid:
|
||||
raise RuntimeError("corner cutter is not a closed solid")
|
||||
out = rg.Brep.CreateBooleanDifference([a], cutters, TOL)
|
||||
if not out or len(out) != 1:
|
||||
raise RuntimeError(f"coping corner cut failed ({len(out) if out else 0})")
|
||||
return solid(out[0])
|
||||
|
||||
|
||||
def plane_z(top, y):
|
||||
return top["z0"] + (y - top["y0"]) * (top["z1"] - top["z0"]) / (top["y1"] - top["y0"])
|
||||
|
||||
|
||||
def add_shared_seats(bs):
|
||||
"""Pedestal = plan quadrilateral extruded from z_bottom to its sloped top plane; pad = frustum whose bottom lies
|
||||
on that plane and whose top is flat at (pedestal top at the bearing centre) + h2 (Okseong P1, O2 2026-10-14)."""
|
||||
mb, mt = bs["pad_margin_bottom"], bs["pad_margin_top"]
|
||||
for pd in bs["pedestals"]:
|
||||
cs = pd["corners"]
|
||||
bot = [rg.Point3d(x, y, pd["z_bottom"]) for x, y in cs]
|
||||
top = [rg.Point3d(x, y, plane_z(pd["top"], y)) for x, y in cs]
|
||||
b = rg.Brep.CreateFromBox(bot + top)
|
||||
if b is None or not b.IsSolid:
|
||||
raise RuntimeError(f"pedestal {pd['no']} failed")
|
||||
add(solid(b), "seat", f"{PID}-BS{pd['no']:02d}", f"bearing pedestal (shared) NO.{pd['no']}", FCK["seat"],
|
||||
"일반도 평면도·교량받침 상세·ELEVATION TABLE")
|
||||
for b in bs["bearings"]:
|
||||
top = bs["pedestals"][b["pedestal"]]["top"]
|
||||
cx, cy = b["centre"]
|
||||
bottom = rotated_rect(cx, cy, b["A"] + mb, b["B"] + mb, b["rotation_deg"], 0)
|
||||
bottom = rg.PolylineCurve([rg.Point3d(p.X, p.Y, plane_z(top, p.Y)) for p in bottom.ToPolyline()])
|
||||
zt = plane_z(top, cy) + b["h2"]
|
||||
add(frustum(bottom, rotated_rect(cx, cy, b["A"] + mt, b["B"] + mt, b["rotation_deg"], zt)), "pad",
|
||||
f"{PID}-BP{b['no']:02d}", f"bearing pad h2 NO.{b['no']} {b['type']}", "-", "일반도 교량받침 상세·ELEVATION TABLE")
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
if o.Attributes.GetUserString("id"):
|
||||
doc.Objects.Delete(o, True)
|
||||
lc, ft, cl, cp, bs = (P[k] for k in ("lean_concrete", "footing", "column", "coping", "bearing_seats"))
|
||||
ft_top = ft["z_bottom"] + ft["thickness"]
|
||||
col_top = ft_top + cl["height"]
|
||||
zmin = min(z for _, z in cp["profile_xz"])
|
||||
if abs(col_top - zmin) > TOL:
|
||||
raise RuntimeError(f"column top {col_top} != coping bottom {zmin}")
|
||||
rot = rg.Transform.Rotation(math.radians(ft.get("rot_deg", 0.0)), rg.Vector3d.ZAxis, rg.Point3d.Origin)
|
||||
lean = box(-lc["size_x"] / 2, lc["size_x"] / 2, -lc["size_y"] / 2, lc["size_y"] / 2, lc["z_bottom"], lc["z_top"])
|
||||
piles = []
|
||||
if "piles" in P: # Okseong P1 (2026-10-14): pile grid, lean cut by the piles
|
||||
pl = P["piles"]
|
||||
top = ft["z_bottom"] + pl["head_embedment"]
|
||||
piles = [cylinder(-(pl["count_x"] - 1) * pl["pitch_x"] / 2 + i * pl["pitch_x"],
|
||||
-(pl["count_y"] - 1) * pl["pitch_y"] / 2 + j * pl["pitch_y"], top - pl["length"], top,
|
||||
pl["diameter"] / 2) for j in range(pl["count_y"]) for i in range(pl["count_x"])]
|
||||
cut = rg.Brep.CreateBooleanDifference([lean], piles, TOL)
|
||||
if not cut or len(cut) != 1:
|
||||
raise RuntimeError("lean concrete boolean failed")
|
||||
lean = solid(cut[0])
|
||||
footing = box(-ft["size_x"] / 2, ft["size_x"] / 2, -ft["size_y"] / 2, ft["size_y"] / 2, ft["z_bottom"], ft_top)
|
||||
if ft.get("rot_deg"):
|
||||
for b in [lean, footing] + piles:
|
||||
b.Transform(rot)
|
||||
add(lean, "lean", f"{PID}-LC", "lean concrete", FCK["lean"], "일반도 정면도·단면도")
|
||||
add(footing, "footing", f"{PID}-FT", "footing on piles" if piles else "spread footing (no piles)", FCK["footing"],
|
||||
"일반도 정면도·단면도·기초 평면도")
|
||||
add(cylinder(0, 0, ft_top, col_top, cl["diameter"] / 2), "column", f"{PID}-CL", "column", FCK["column"],
|
||||
"일반도 정면도·단면도·단면 A-A")
|
||||
add(coping_brep(cp), "coping", f"{PID}-CP", "coping (tapered T)", FCK["coping"], "일반도 평면도·정면도·단면도")
|
||||
for n, b in enumerate(piles, 1):
|
||||
pl = P["piles"]
|
||||
add(b, "pile", f"{PID}-PL{n:02d}", f"{pl['kind']} D{pl['diameter']:.0f} L{pl['length'] / 1000:g}m", "-",
|
||||
"일반도 정면도·단면도·기초 평면도")
|
||||
if "pedestals" in bs: # Okseong P1: shared pedestals with a sloped top
|
||||
add_shared_seats(bs)
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"objects": 4 + len(piles) + len(bs["pedestals"]) + len(bs["bearings"])}))
|
||||
return
|
||||
ped, mb, mt = bs["pedestal"], bs["pad_margin_bottom"], bs["pad_margin_top"]
|
||||
for b in bs["bearings"]:
|
||||
cx, cy = b["centre"]
|
||||
z0, z1 = b["seat_z"], b["seat_z"] + b["h1"]
|
||||
base = rotated_rect(cx, cy, ped["size_y"], ped["size_x"], b["rotation_deg"], z0)
|
||||
add(extrude_closed(base, rg.Vector3d(0, 0, b["h1"])), "seat", f"{PID}-BS{b['no']:02d}",
|
||||
f"bearing pedestal h1 NO.{b['no']}", FCK["seat"], "일반도 교량받침 상세·ELEVATION TABLE")
|
||||
bottom = rotated_rect(cx, cy, b["A"] + mb, b["B"] + mb, b["rotation_deg"], z1)
|
||||
top = rotated_rect(cx, cy, b["A"] + mt, b["B"] + mt, b["rotation_deg"], z1 + b["h2"])
|
||||
add(frustum(bottom, top), "pad", f"{PID}-BP{b['no']:02d}", f"bearing pad h2 NO.{b['no']} {b['type']}", "-",
|
||||
"일반도 교량받침 상세·ELEVATION TABLE")
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"objects": 4 + 2 * len(bs["bearings"])}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,241 @@
|
||||
"""G4 stage: coping rebar C1-C16, S1, S2 (centreline + pipe) from params `coping_rebar`.
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after concrete.py, then rebar_colours.py.
|
||||
Ported from Code/교각모델링/build_coping_rebar_rhino.py (Ungok P1, model v0.1.09). Rules: 설계서 v0.1.07 §7. Units mm, X transverse, Y longitudinal, Z = EL x 1000.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
MARK = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items() if m[0] in "CS"}
|
||||
DIA = {d: float(d[1:]) for d, _ in MARK.values()} # 'H29' -> 29.0
|
||||
CL_RGB = tuple(P["display"]["centreline_colour"])
|
||||
CP_RGB = tuple(P["display"]["coping_layer_colour"])
|
||||
ARC_C = tuple(P["coping_rebar"]["soffit_arc_centre"]) # soffit arc centre (right); left is mirrored
|
||||
# coping end faces (concrete) from the coping outline: bottom tip (+-half, zb+rise) -> top (+-(half-inset), end step z)
|
||||
_C = P["coping"]
|
||||
HALF, INSET = _C["length_bottom_tip_to_tip"] / 2, _C["end_batter_top_inset"]
|
||||
Z_TIP = _C["z_bottom_flat"] + _C["tip_bottom_rise"]
|
||||
ZL, ZR = _C["top_steps"][0]["z"], _C["top_steps"][-1]["z"]
|
||||
|
||||
|
||||
def xl(z):
|
||||
return -(HALF - INSET) - INSET * (ZL - z) / (ZL - Z_TIP)
|
||||
|
||||
|
||||
def xr(z):
|
||||
return (HALF - INSET) + INSET * (ZR - z) / (ZR - Z_TIP)
|
||||
|
||||
|
||||
def unit(dx, dz):
|
||||
n = math.hypot(dx, dz)
|
||||
return dx / n, dz / n
|
||||
|
||||
|
||||
U_LEFT_DOWN, U_RIGHT_DOWN = unit(-INSET, -(ZL - Z_TIP)), unit(INSET, -(ZR - Z_TIP))
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
def min_r(dia):
|
||||
d = DIA[dia]
|
||||
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2 # box rule (REBAR_BENDS_20260914)
|
||||
|
||||
|
||||
def fillet(curve, dia):
|
||||
f = rg.Curve.CreateFilletCornersCurve(curve, min_r(dia), TOL, math.radians(1))
|
||||
return f or curve
|
||||
|
||||
|
||||
def add_bar(curve, mark, occ, design_len, src, extra=None):
|
||||
dia, shape = MARK[mark]
|
||||
pipes = rg.Brep.CreatePipe(curve, DIA[dia] / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
|
||||
if not pipes or not pipes[0].IsSolid:
|
||||
raise RuntimeError(f"{mark} {occ}: pipe failed")
|
||||
common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=f"{PID}-CP", anchor_hosts="",
|
||||
design_length=f"{design_len:.3f}", coupler_length="0.000", src=src)
|
||||
common.update(extra or {})
|
||||
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
||||
(pipes[0], layer(f"{PID}::철근::코핑::{mark}", CP_RGB), f"{PID}-R")):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = lay
|
||||
oid = f"{prefix}-{mark}-{occ}"
|
||||
attr.Name = oid
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in common.items():
|
||||
attr.SetUserString(k, v)
|
||||
(doc.Objects.AddBrep if isinstance(geo, rg.Brep) else doc.Objects.AddCurve)(geo, attr)
|
||||
|
||||
|
||||
def soffit_offset_z(x, cp):
|
||||
st = cp["stirrups"]
|
||||
if abs(x) <= cp["bottom"]["half_flat"]:
|
||||
return cp["bottom"]["z_flat"]
|
||||
cx = ARC_C[0] if x > 0 else -ARC_C[0]
|
||||
R = cp["bottom"]["R_offset"]
|
||||
return ARC_C[1] + math.sqrt(R * R - (x - cx) ** 2)
|
||||
|
||||
|
||||
def stirrup_top(x, cp):
|
||||
st = cp["stirrups"]
|
||||
bands = st["top_right"] if x >= 0 else st["top_left"]
|
||||
return [z for x0, z in bands if abs(x) >= x0][-1]
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith(tuple(f"{PID}-{k}-{m}" for k in ("R", "RC") for m in "CS")):
|
||||
doc.Objects.Delete(o, True)
|
||||
cp = P["coping_rebar"]
|
||||
ys = cp["y_positions"]
|
||||
n = 0
|
||||
# top bars (VIII)
|
||||
for tier, t in cp["top"]["tiers"].items():
|
||||
z, a, (bl, br) = t["z"], t["a"], t["legs"]
|
||||
d = (xr(z) - xl(z) - a) / 2
|
||||
x0, x1 = xl(z) + d, xr(z) - d
|
||||
for i, y in enumerate(ys):
|
||||
mark = t["marks"][i % 2]
|
||||
pts = [rg.Point3d(x0 + U_LEFT_DOWN[0] * bl, y, z + U_LEFT_DOWN[1] * bl), rg.Point3d(x0, y, z),
|
||||
rg.Point3d(x1, y, z), rg.Point3d(x1 + U_RIGHT_DOWN[0] * br, y, z + U_RIGHT_DOWN[1] * br)]
|
||||
add_bar(fillet(rg.PolylineCurve(pts), MARK[mark][0]), mark, f"t{tier}y{y:+.0f}", a + bl + br,
|
||||
f"tier {tier} EL {z / 1000:.3f}; end inset {d:.1f}; legs parallel to end faces",
|
||||
{"a_model": f"{x1 - x0:.3f}"})
|
||||
n += 1
|
||||
# bottom bars (VII)
|
||||
b = cp["bottom"]
|
||||
R, zf = b["R_offset"], b["z_flat"]
|
||||
hf = ARC_C[0] - math.sqrt(R * R - (zf - ARC_C[1]) ** 2) # exact flat/arc junction (params 1,073.9 is rounded)
|
||||
dth = b["arc_len"] / R
|
||||
for i, y in enumerate(ys):
|
||||
mark = b["marks"][i % 2]
|
||||
pieces = [rg.LineCurve(rg.Point3d(-hf, y, zf), rg.Point3d(hf, y, zf))]
|
||||
for side in (1, -1):
|
||||
cx = side * ARC_C[0]
|
||||
th0 = math.atan2(zf - ARC_C[1], side * hf - cx)
|
||||
th1 = th0 - side * dth
|
||||
thm = (th0 + th1) / 2
|
||||
p = lambda th: rg.Point3d(cx + R * math.cos(th), y, ARC_C[1] + R * math.sin(th))
|
||||
arc = rg.ArcCurve(rg.Arc(p(th0), p(thm), p(th1)))
|
||||
up = (-U_RIGHT_DOWN[0], -U_RIGHT_DOWN[1]) if side > 0 else (-U_LEFT_DOWN[0], -U_LEFT_DOWN[1])
|
||||
e = p(th1)
|
||||
leg = rg.LineCurve(e, rg.Point3d(e.X + up[0] * b["leg"], y, e.Z + up[1] * b["leg"]))
|
||||
pieces += [arc, leg]
|
||||
joined = rg.Curve.JoinCurves(pieces, TOL)
|
||||
if len(joined) != 1:
|
||||
raise RuntimeError(f"{mark} y{y}: bottom bar did not join ({len(joined)} pieces)")
|
||||
c = joined[0]
|
||||
add_bar(c, mark, f"y{y:+.0f}", 2 * hf + 2 * b["arc_len"] + 2 * b["leg"],
|
||||
"flat EL 40.511 +-1,073.9; arcs R 12,100 concentric with soffit, length 5,273; legs 650 // end faces")
|
||||
n += 1
|
||||
# stirrups (IV as closed rectangles, widths as drawn in section A-A)
|
||||
st = cp["stirrups"]
|
||||
for mark, xs in (("C10", st["x_c10"]), ("C11", st["x_c11"]), ("C9", st["x_c9"])):
|
||||
for x in xs:
|
||||
zb, zt = soffit_offset_z(x, cp), stirrup_top(x, cp)
|
||||
for k, (y0, y1) in enumerate(st["loops_y"]):
|
||||
ym = (y0 + y1) / 2
|
||||
pts = [rg.Point3d(x, ym, zb), rg.Point3d(x, y1, zb), rg.Point3d(x, y1, zt), rg.Point3d(x, y0, zt),
|
||||
rg.Point3d(x, y0, zb), rg.Point3d(x, ym, zb)]
|
||||
c = fillet(rg.PolylineCurve(pts), MARK[mark][0])
|
||||
w, h = y1 - y0, zt - zb
|
||||
b_s = h + st["b_offset"]
|
||||
add_bar(c, mark, f"x{x:+.0f}k{k + 1}", 2 * w + 2 * b_s + 2 * st["c_sched"],
|
||||
f"X {x}; loop {k + 1} Y {y0}..{y1}; laps 2x750 not drawn (length only)",
|
||||
{"b_sched": f"{b_s:.1f}", "w_model": f"{w:.1f}"})
|
||||
n += 1
|
||||
# horizontal closed loops C12 (lower 6 levels) / C13 (upper 3 levels), one inside each stirrup
|
||||
lp = cp.get("loops")
|
||||
if lp:
|
||||
R, inset = cp["bottom"]["R_offset"], lp["end_inset_face"]
|
||||
for z, mark in zip(lp["levels"], lp["marks_by_level"]):
|
||||
disc = R * R - (z - ARC_C[1]) ** 2
|
||||
xs = ARC_C[0] - math.sqrt(disc) if disc > 0 else 1e9 # soffit-offset limit (symmetric)
|
||||
x0, x1 = max(-xs, xl(z) + inset), min(xs, xr(z) - inset)
|
||||
for k, (y0, y1) in enumerate(lp["loops_y"]):
|
||||
xm = (x0 + x1) / 2
|
||||
pts = [rg.Point3d(xm, y0, z), rg.Point3d(x1, y0, z), rg.Point3d(x1, y1, z), rg.Point3d(x0, y1, z),
|
||||
rg.Point3d(x0, y0, z), rg.Point3d(xm, y0, z)]
|
||||
c = fillet(rg.PolylineCurve(pts), MARK[mark][0])
|
||||
a_m, w = x1 - x0, y1 - y0
|
||||
add_bar(c, mark, f"z{z}k{k + 1}", 2 * (a_m + lp["a_offset"]) + 2 * w + 4 * lp["c_sched"],
|
||||
f"EL {z / 1000:.3f}; X {x0:.1f}..{x1:.1f}; loop {k + 1} Y {y0}..{y1}; laps 4x580 length only",
|
||||
{"a_sched": f"{a_m + lp['a_offset']:.1f}", "w_model": f"{w:.1f}"})
|
||||
n += 1
|
||||
# C14 transverse (Y) bars with hooks down; C15 / C16 longitudinal (X) bars in the raised step zones
|
||||
if "c14" in cp:
|
||||
c14 = cp["c14"]
|
||||
h, hk = c14["a"] / 2, c14["hook"]
|
||||
up = [tuple(v) for v in c14.get("hooks_up", [])]
|
||||
for x, z in c14["points"]:
|
||||
s = 1 if (x, z) in up else -1 # hooks up only where a down hook would leave the soffit
|
||||
pts = [rg.Point3d(x, -h, z + s * hk), rg.Point3d(x, -h, z), rg.Point3d(x, h, z), rg.Point3d(x, h, z + s * hk)]
|
||||
add_bar(fillet(rg.PolylineCurve(pts), MARK["C14"][0]), "C14", f"x{x:+.0f}z{z:.0f}", c14["a"] + 2 * hk,
|
||||
"D1 정면도 점; hooks " + ("up (HITL fix)" if s > 0 else "down [추론]"))
|
||||
n += 1
|
||||
c15, c16 = cp["c15"], cp["c16"]
|
||||
for y in c15["y_positions"]:
|
||||
z, (h0, h1) = c15["z"], c15["hooks"]
|
||||
pts = [rg.Point3d(c15["x0"], y, z - h0), rg.Point3d(c15["x0"], y, z), rg.Point3d(c15["x1"], y, z),
|
||||
rg.Point3d(c15["x1"], y, z - h1)]
|
||||
add_bar(fillet(rg.PolylineCurve(pts), MARK["C15"][0]), "C15", f"y{y:+.0f}", c15["x1"] - c15["x0"] + h0 + h1,
|
||||
"D1 정면도 43,598.4 / 3,816~5,900")
|
||||
z = c16["z"]
|
||||
pts = [rg.Point3d(c16["x0"], y, z - c16["hook_left"]), rg.Point3d(c16["x0"], y, z),
|
||||
rg.Point3d(c16["x1"], y, z)]
|
||||
add_bar(fillet(rg.PolylineCurve(pts), MARK["C16"][0]), "C16", f"y{y:+.0f}", c16["x1"] - c16["x0"] + c16["hook_left"],
|
||||
"D1 정면도 43,454.7 / -1,195~4,066; hook at left end [추론]")
|
||||
n += 2
|
||||
# S1 / S2 bearing-seat reinforcement under each pedestal (rotated with the pedestal)
|
||||
if "s1s2" in cp and "bearing_seats" in P:
|
||||
bs, ss = P["bearing_seats"], cp["s1s2"]
|
||||
corners = bs["pedestal_girder5"]["front_half"] + bs["pedestal_girder5"]["rear_half"]
|
||||
c5 = (sum(p[0] for p in corners) / len(corners), sum(p[1] for p in corners) / len(corners))
|
||||
t = math.radians(bs["pedestal_girder5"]["rotation_deg"])
|
||||
ua, uc = (math.sin(t), math.cos(t)), (math.cos(t), -math.sin(t)) # along (2,500) / across (1,200)
|
||||
steps = P["coping"]["top_steps"]
|
||||
for g in range(1, len(steps) + 1):
|
||||
cx_, cy_ = c5[0] + (g - bs["reference_girder"]) * bs["pitch_x"], c5[1]
|
||||
top = steps[g - 1]["z"]
|
||||
pt = lambda s_al, s_ac, z: rg.Point3d(cx_ + ua[0] * s_al + uc[0] * s_ac, cy_ + ua[1] * s_al + uc[1] * s_ac, z)
|
||||
s1 = ss["s1"]
|
||||
for off in s1["offsets_across"]:
|
||||
z, h = top - s1["z_below_top"], s1["a"] / 2
|
||||
pts = [pt(-h, off, z - s1["leg"]), pt(-h, off, z), pt(h, off, z), pt(h, off, z - s1["leg"])]
|
||||
add_bar(fillet(rg.PolylineCurve(pts), MARK["S1"][0]), "S1", f"g{g}c{off:+.0f}", s1["a"] + 2 * s1["leg"],
|
||||
f"girder {g} pedestal; upper layer [추론]")
|
||||
n += 1
|
||||
s2 = ss["s2"]
|
||||
for off in s2["offsets_along"]:
|
||||
z, h = top - s2["z_below_top"], s2["a"] / 2
|
||||
pts = [pt(off, -h, z - s2["leg"]), pt(off, -h, z), pt(off, h, z), pt(off, h, z - s2["leg"])]
|
||||
add_bar(fillet(rg.PolylineCurve(pts), MARK["S2"][0]), "S2", f"g{g}a{off:+.0f}", s2["a"] + 2 * s2["leg"],
|
||||
f"girder {g} pedestal; lower layer")
|
||||
n += 1
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"coping_bars": n}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Final stage: apply the display state recorded in params `display` (hidden objects).
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after all build stages.
|
||||
"""
|
||||
import json
|
||||
|
||||
from Rhino.DocObjects import ObjectEnumeratorSettings
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
hide = set(P["display"].get("hidden_ids", []))
|
||||
s = ObjectEnumeratorSettings()
|
||||
s.HiddenObjects = True
|
||||
done = []
|
||||
for o in doc.Objects.GetObjectList(s):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid in hide and not o.IsHidden:
|
||||
doc.Objects.Hide(o, True)
|
||||
done.append(oid)
|
||||
missing = sorted(hide - {o.Attributes.GetUserString("id") for o in doc.Objects.GetObjectList(s)})
|
||||
print(json.dumps({"hidden": done, "missing": missing}))
|
||||
if missing:
|
||||
raise RuntimeError(f"display: hidden_ids not found {missing}")
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Fingerprint a .3dm (run inside Rhino 8 via runner). Globals: MODEL_PATH, OUT_PATH, optional FAST.
|
||||
|
||||
Opens MODEL_PATH headless (the active doc is untouched) and writes, per object id (user string `id`,
|
||||
falling back to the object name): layer path, geometry type, visibility, colour, bbox (0.01 mm),
|
||||
brep face count / solidity / volume (mm3, 1 decimal), curve length (0.001 mm) and all user strings.
|
||||
Hidden and locked objects are included.
|
||||
"""
|
||||
import json
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectEnumeratorSettings
|
||||
|
||||
FAST = globals().get("FAST", False)
|
||||
d = Rhino.RhinoDoc.OpenHeadless(MODEL_PATH)
|
||||
if d is None:
|
||||
raise RuntimeError(f"cannot open {MODEL_PATH}")
|
||||
try:
|
||||
s = ObjectEnumeratorSettings()
|
||||
s.HiddenObjects = True
|
||||
s.LockedObjects = True
|
||||
objs, layers = {}, {}
|
||||
for o in d.Objects.GetObjectList(s):
|
||||
a = o.Attributes
|
||||
oid = a.GetUserString("id") or a.Name or str(o.Id)
|
||||
lay = d.Layers[a.LayerIndex]
|
||||
layers[lay.FullPath] = [lay.Color.R, lay.Color.G, lay.Color.B, lay.IsVisible]
|
||||
g = o.Geometry
|
||||
bb = g.GetBoundingBox(True)
|
||||
rec = {"layer": lay.FullPath, "type": type(g).__name__, "hidden": o.IsHidden, "locked": o.IsLocked,
|
||||
"colour_source": int(a.ColorSource), "colour": [a.ObjectColor.R, a.ObjectColor.G, a.ObjectColor.B],
|
||||
"bbox": [round(v, 2) for v in (bb.Min.X, bb.Min.Y, bb.Min.Z, bb.Max.X, bb.Max.Y, bb.Max.Z)],
|
||||
"user": {k: a.GetUserString(k) for k in sorted(a.GetUserStrings().AllKeys)}}
|
||||
b = g if isinstance(g, rg.Brep) else (rg.Brep.TryConvertBrep(g) if isinstance(g, rg.Extrusion) else None)
|
||||
if b is not None:
|
||||
rec["faces"], rec["solid"] = b.Faces.Count, b.IsSolid
|
||||
if FAST and "-R-" in oid: # rebar pipe: bbox + face count only (volume dominates run time)
|
||||
rec["volume"] = "skipped"
|
||||
else:
|
||||
mp = rg.VolumeMassProperties.Compute(b) if b.IsSolid else None
|
||||
rec["volume"] = round(mp.Volume, 1) if mp else None
|
||||
elif isinstance(g, rg.Curve):
|
||||
rec["length"] = round(g.GetLength(), 3)
|
||||
if oid in objs:
|
||||
oid = f"{oid}#dup{sum(k.startswith(oid) for k in objs)}"
|
||||
objs[oid] = rec
|
||||
out = {"model": MODEL_PATH, "units": str(d.ModelUnitSystem), "tolerance": d.ModelAbsoluteTolerance,
|
||||
"count": len(objs), "layers": layers, "objects": objs}
|
||||
json.dump(out, open(OUT_PATH, "w", encoding="utf-8"), ensure_ascii=False)
|
||||
print(json.dumps({"count": len(objs), "units": out["units"], "tolerance": out["tolerance"]}))
|
||||
finally:
|
||||
d.Dispose()
|
||||
@@ -0,0 +1,186 @@
|
||||
"""G3 stage: footing rebar B1-B12 (centreline + pipe solid) from params `footing_rebar`.
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after concrete.py.
|
||||
Ported from Code/교각모델링/build_footing_rebar_rhino.py (Ungok P1, model v0.1.09).
|
||||
Rules: 설계서 v0.1.06 §6 (HITL F1-F3 approved 2026-10-07). z in params is above the footing bottom.
|
||||
`design_length` = schedule original length (a, a+2b, 2(a+b), a+b+c); curve length is measured separately.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
FB = P["footing"]["z_bottom"]
|
||||
MARK = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items() if m.startswith("B")}
|
||||
DIA = {d: float(d[1:]) for d, _ in MARK.values()} # 'S25' -> 25.0
|
||||
CL_RGB = tuple(P["display"]["centreline_colour"])
|
||||
FT_RGB = tuple(P["display"]["footing_layer_colour"])
|
||||
LEGS = P["footing_rebar"]["legs"]
|
||||
|
||||
|
||||
def leg(mark):
|
||||
"""Signed end-leg length of a footing bar mark (+ up / - down) from params `legs`."""
|
||||
v = LEGS[mark]
|
||||
n = v.get("leg", v.get("hook"))
|
||||
return n if v["dir"].startswith("up") else -n
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
sub = "::".join(parts[:i])
|
||||
idx = doc.Layers.FindByFullPath(sub, -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
def min_r(dia):
|
||||
d = DIA[dia]
|
||||
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2 # box rule (REBAR_BENDS_20260914)
|
||||
|
||||
|
||||
def poly(pts, dia):
|
||||
pl = rg.PolylineCurve([rg.Point3d(*p) for p in pts])
|
||||
if len(pts) < 3:
|
||||
return pl
|
||||
f = rg.Curve.CreateFilletCornersCurve(pl, min_r(dia), TOL, math.radians(1))
|
||||
return f or pl
|
||||
|
||||
|
||||
def add_bar(curve, mark, occ, design_len, src):
|
||||
dia, shape = MARK[mark]
|
||||
pipes = rg.Brep.CreatePipe(curve, DIA[dia] / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
|
||||
if not pipes or not pipes[0].IsSolid:
|
||||
raise RuntimeError(f"{mark} {occ}: pipe failed")
|
||||
common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=f"{PID}-FT", anchor_hosts="",
|
||||
design_length=f"{design_len:.3f}", coupler_length="0.000", src=src)
|
||||
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
||||
(pipes[0], layer(f"{PID}::철근::기초::{mark}", FT_RGB), f"{PID}-R")):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = lay
|
||||
oid = f"{prefix}-{mark}-{occ}"
|
||||
attr.Name = oid
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in common.items():
|
||||
attr.SetUserString(k, v)
|
||||
(doc.Objects.AddBrep if isinstance(geo, rg.Brep) else doc.Objects.AddCurve)(geo, attr)
|
||||
|
||||
|
||||
def bar_y(x, z, a, end, dz):
|
||||
"""Bar along Y (length a) at x, height z; end legs of length dz (+up / -down) or none."""
|
||||
h = a / 2
|
||||
if not end:
|
||||
return [(x, -h, z), (x, h, z)]
|
||||
return [(x, -h, z + dz), (x, -h, z), (x, h, z), (x, h, z + dz)]
|
||||
|
||||
|
||||
def bar_x(y, z, a, end, dz):
|
||||
h = a / 2
|
||||
if not end:
|
||||
return [(-h, y, z), (h, y, z)]
|
||||
return [(-h, y, z + dz), (-h, y, z), (h, y, z), (h, y, z + dz)]
|
||||
|
||||
|
||||
def sched(m):
|
||||
row = P["footing_rebar"]["schedule"][m]
|
||||
nums = [float(t) for t in row[3:] if t.replace(".", "", 1).isdigit()]
|
||||
return row, nums
|
||||
|
||||
|
||||
def mm(v):
|
||||
return v * 1000 if v < 100 else v
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith((f"{PID}-R-B", f"{PID}-RC-B")):
|
||||
doc.Objects.Delete(o, True)
|
||||
fr = P["footing_rebar"]
|
||||
L = fr["layers"]
|
||||
n = 0
|
||||
a_y, a_x = fr["a_y"], fr["a_x"] # D3 a of Y-bars (B1-B4, B12) / X-bars (B5-B8, B11)
|
||||
# bottom layer 1 (Y): B2 U (legs up) at the outermost positions and alternate from the next; B4 straight
|
||||
pos = L["bottom_Y1"]["pos"]
|
||||
edge = max(abs(x) for x in pos)
|
||||
inner = [x for x in pos if abs(x) != edge]
|
||||
for i, x in enumerate(pos):
|
||||
mark = "B2" if abs(x) == edge or inner.index(x) % 2 == 0 else "B4"
|
||||
z = FB + L["bottom_Y1"]["z"]
|
||||
if mark == "B2":
|
||||
c, dl = poly(bar_y(x, z, a_y, True, leg("B2")), MARK["B2"][0]), a_y + 2 * abs(leg("B2"))
|
||||
else:
|
||||
c, dl = poly(bar_y(x, z, a_y, False, 0), MARK["B4"][0]), a_y
|
||||
add_bar(c, mark, f"x{x:+.0f}", dl, "D1 정면도 하면 점 z122 / D1·D2 '70@125=8.750'")
|
||||
n += 1
|
||||
pos = L["bottom_X2"]["pos"]
|
||||
edge = max(abs(y) for y in pos)
|
||||
inner = [y for y in pos if abs(y) != edge]
|
||||
for y in pos:
|
||||
mark = "B6" if abs(y) == edge or inner.index(y) % 2 == 0 else "B8"
|
||||
z = FB + L["bottom_X2"]["z"]
|
||||
if mark == "B6":
|
||||
c, dl = poly(bar_x(y, z, a_x, True, leg("B6")), MARK["B6"][0]), a_x + 2 * abs(leg("B6"))
|
||||
else:
|
||||
c, dl = poly(bar_x(y, z, a_x, False, 0), MARK["B8"][0]), a_x
|
||||
add_bar(c, mark, f"y{y:+.0f}", dl, "D1 단면도 하면 점 z178 / D1 '58@125=7.250'")
|
||||
n += 1
|
||||
for key, mark, axis, a in (("bottom_Y3", "B3", "Y", a_y), ("bottom_X4", "B7", "X", a_x),
|
||||
("top_Y", "B1", "Y", a_y), ("top_X", "B5", "X", a_x)):
|
||||
z, dz, dia = FB + L[key]["z"], leg(mark), MARK[mark][0]
|
||||
for v in L[key]["pos"]:
|
||||
pts = bar_y(v, z, a, True, dz) if axis == "Y" else bar_x(v, z, a, True, dz)
|
||||
add_bar(poly(pts, dia), mark, f"{axis.lower()}{v:+.0f}", a + 2 * abs(dz),
|
||||
f"D1 {'정면도' if axis == 'Y' else '단면도'} 점 z{L[key]['z']}")
|
||||
n += 1
|
||||
for key, mark, axis, a in (("mid_X", "B11", "X", a_x), ("mid_Y", "B12", "Y", a_y)):
|
||||
for zs, vs in L[key]["levels"].items():
|
||||
z = FB + float(zs)
|
||||
dz = leg(mark)
|
||||
for v in vs:
|
||||
pts = bar_y(v, z, a, True, dz) if axis == "Y" else bar_x(v, z, a, True, dz)
|
||||
add_bar(poly(pts, MARK[mark][0]), mark, f"z{float(zs):.0f}{axis.lower()}{v:+.0f}", a + 2 * abs(dz),
|
||||
f"D1 {'정면도' if axis == 'Y' else '단면도'} 중앙 점 z{zs}")
|
||||
n += 1
|
||||
b10 = fr["b10"]
|
||||
hx, hy = b10["size_x"] / 2, b10["size_y"] / 2
|
||||
for k, zs in enumerate(b10["levels"]):
|
||||
z = FB + zs
|
||||
pts = [(-hx, 0, z), (-hx, -hy, z), (hx, -hy, z), (hx, hy, z), (-hx, hy, z), (-hx, 0.0, z)]
|
||||
c = poly(pts, MARK["B10"][0])
|
||||
add_bar(c, "B10", f"z{zs}", 2 * (b10["size_x"] + b10["size_y"]),
|
||||
"D1 정면도 '150','9@200'; D3 B10 9.198x7.644 (closed, start/end at -X mid side)")
|
||||
n += 1
|
||||
b9 = fr["b9"]
|
||||
rc = b9["c"] / math.pi # 180-degree hook developed length = c
|
||||
for i, x in enumerate(b9["grid_x"]):
|
||||
for j, y in enumerate(b9["grid_y"]):
|
||||
if (i + j) % 2 != b9["checker_parity"]:
|
||||
continue
|
||||
z0, z1 = FB + b9["z0"], FB + b9["z0"] + b9["a"]
|
||||
base = poly([(x + b9["b"], y, z0), (x, y, z0), (x, y, z1)], MARK["B9"][0])
|
||||
top = rg.ArcCurve(rg.Arc(rg.Point3d(x, y, z1), rg.Point3d(x + rc, y, z1 + rc),
|
||||
rg.Point3d(x + 2 * rc, y, z1)))
|
||||
c = rg.Curve.JoinCurves([base, top], TOL)[0]
|
||||
add_bar(c, "B9", f"x{x:+.0f}y{y:+.0f}", b9["a"] + b9["b"] + b9["c"],
|
||||
"D1 EREC grid @250 checker (i+j even) [추론]; count 493 [역산]")
|
||||
n += 1
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"footing_bars": n}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,138 @@
|
||||
"""G3 stage, form "mats" (Ulsan P1, 2026-10-13): footing rebar as explicit bar groups, perimeter hoops and
|
||||
vertical bars, from params `footing_rebar` (centreline + pipe solid per bar).
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after the G1 builder. z values are above the footing bottom.
|
||||
params footing_rebar:
|
||||
groups [{mark, axis "X"|"Y", z, pos [...], a, leg (signed: + up / - down / 0), lap, src}]
|
||||
a bar along `axis` of length a centred on the footing, at `pos` across, with end legs
|
||||
design length = a + 2|leg| + lap (lap only adds length)
|
||||
hoops {mark, half, levels [...], design_length}: closed square hoops of centreline half-width `half`
|
||||
verticals {mark, grid_x [...], grid_y [...], parity, z0, a, leg}: vertical bars on a checkerboard of the grid,
|
||||
legs of length `leg` towards +`leg_axis` ("X"|"Y") at both ends (shape I with b, SD400 catalogue)
|
||||
Optional (Ulsan P2, 2026-10-15; absent -> unchanged):
|
||||
footing.rot_deg all bars are built in the foundation's own axes and turned counter-clockwise about the pier centre
|
||||
hoops.half_x / half_y rectangular hoop (default: square `half`)
|
||||
verticals.points [[x, y], ...] explicit positions instead of the checkerboard; leg_bottom / leg_top leg lengths per end
|
||||
(default `leg`); design length = a + leg_bottom + leg_top
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
import System
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
FB = P["footing"]["z_bottom"]
|
||||
MARK = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items()}
|
||||
DIA = {d: float(d[1:]) for d, _ in MARK.values()}
|
||||
CL_RGB = tuple(P["display"]["centreline_colour"])
|
||||
FT_RGB = tuple(P["display"]["footing_layer_colour"])
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
ROT = math.radians(P["footing"].get("rot_deg", 0.0))
|
||||
|
||||
|
||||
def turn(pts):
|
||||
"""Foundation axes -> pier axes (counter-clockwise ROT about the origin)."""
|
||||
if not ROT:
|
||||
return pts
|
||||
c, s = math.cos(ROT), math.sin(ROT)
|
||||
return [(x * c - y * s, x * s + y * c, z) for x, y, z in pts]
|
||||
|
||||
|
||||
def min_r(dia):
|
||||
d = DIA[dia]
|
||||
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2
|
||||
|
||||
|
||||
def poly(pts, dia):
|
||||
pl = rg.PolylineCurve([rg.Point3d(*p) for p in pts])
|
||||
if len(pts) < 3:
|
||||
return pl
|
||||
return rg.Curve.CreateFilletCornersCurve(pl, min_r(dia), TOL, math.radians(1)) or pl
|
||||
|
||||
|
||||
def add_bar(curve, mark, occ, design_len, src):
|
||||
dia, shape = MARK[mark]
|
||||
pipes = rg.Brep.CreatePipe(curve, DIA[dia] / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
|
||||
if not pipes or not pipes[0].IsSolid:
|
||||
raise RuntimeError(f"{mark} {occ}: pipe failed")
|
||||
common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=f"{PID}-FT", anchor_hosts="",
|
||||
design_length=f"{design_len:.3f}", coupler_length="0.000", src=src)
|
||||
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
||||
(pipes[0], layer(f"{PID}::철근::기초::{mark}", FT_RGB), f"{PID}-R")):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = lay
|
||||
oid = f"{prefix}-{mark}-{occ}"
|
||||
attr.Name = oid
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in common.items():
|
||||
attr.SetUserString(k, v)
|
||||
gid = doc.Objects.AddBrep(geo, attr) if isinstance(geo, rg.Brep) else doc.Objects.AddCurve(geo, attr)
|
||||
if gid == System.Guid.Empty:
|
||||
raise RuntimeError(f"{oid}: add failed")
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith((f"{PID}-R-B", f"{PID}-RC-B")):
|
||||
doc.Objects.Delete(o, True)
|
||||
fr = P["footing_rebar"]
|
||||
n = 0
|
||||
for g in fr["groups"]:
|
||||
h, z, leg = g["a"] / 2, FB + g["z"], g.get("leg", 0)
|
||||
for v in g["pos"]:
|
||||
if g["axis"] == "Y":
|
||||
ends = [(v, -h, z), (v, h, z)]
|
||||
else:
|
||||
ends = [(-h, v, z), (h, v, z)]
|
||||
pts = ends if not leg else [(ends[0][0], ends[0][1], z + leg), ends[0], ends[1], (ends[1][0], ends[1][1], z + leg)]
|
||||
add_bar(poly(turn(pts), MARK[g["mark"]][0]), g["mark"], f"{g['axis'].lower()}{v:+.0f}",
|
||||
g["a"] + 2 * abs(leg) + g.get("lap", 0), g.get("src", ""))
|
||||
n += 1
|
||||
hp = fr["hoops"]
|
||||
sx, sy = hp.get("half_x", hp.get("half")), hp.get("half_y", hp.get("half"))
|
||||
for zz in hp["levels"]:
|
||||
z = FB + zz
|
||||
pts = [(-sx, 0, z), (-sx, -sy, z), (sx, -sy, z), (sx, sy, z), (-sx, sy, z), (-sx, 0.0, z)]
|
||||
add_bar(poly(turn(pts), MARK[hp["mark"]][0]), hp["mark"], f"z{zz:.0f}", hp["design_length"], hp.get("src", ""))
|
||||
n += 1
|
||||
vt = fr["verticals"]
|
||||
if "points" in vt:
|
||||
where = [tuple(q) for q in vt["points"]]
|
||||
else:
|
||||
where = [(x, y) for i, x in enumerate(vt["grid_x"]) for j, y in enumerate(vt["grid_y"]) if (i + j) % 2 == vt["parity"]]
|
||||
lb, lt = vt.get("leg_bottom", vt.get("leg", 0)), vt.get("leg_top", vt.get("leg", 0))
|
||||
ux, uy = (1, 0) if vt.get("leg_axis", "X") == "X" else (0, 1)
|
||||
for x, y in where:
|
||||
z0, z1 = FB + vt["z0"], FB + vt["z0"] + vt["a"]
|
||||
pts = [(x + ux * lb, y + uy * lb, z0), (x, y, z0), (x, y, z1), (x + ux * lt, y + uy * lt, z1)]
|
||||
add_bar(poly(turn(pts), MARK[vt["mark"]][0]), vt["mark"], f"x{x:+.0f}y{y:+.0f}", vt["a"] + lb + lt, vt.get("src", ""))
|
||||
n += 1
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"footing_bars": n}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Negative test helper: move selected objects of the open doc (inside Rhino 8, via runner).
|
||||
|
||||
Globals: __rhino_doc__, PIER_ID, MOVES = [{"id": "BS08"} | {"mark": "P2"}, "d": [dx, dy, dz]}, ...],
|
||||
optional UNDO (move back by -d). A `mark` selector moves the first occurrence (sorted) of that mark,
|
||||
centreline and pipe together. Prints what was moved; raises if a selector matches nothing.
|
||||
"""
|
||||
import json
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectEnumeratorSettings
|
||||
|
||||
doc = __rhino_doc__
|
||||
s = ObjectEnumeratorSettings()
|
||||
s.HiddenObjects = True
|
||||
s.LockedObjects = True
|
||||
objs = list(doc.Objects.GetObjectList(s))
|
||||
sign = -1 if globals().get("UNDO") else 1
|
||||
moved = []
|
||||
for mv in MOVES:
|
||||
if "id" in mv:
|
||||
sel = [o for o in objs if o.Attributes.GetUserString("id") == f"{PIER_ID}-{mv['id']}"]
|
||||
else:
|
||||
cand = [o for o in objs if o.Attributes.GetUserString("mark") == mv["mark"]
|
||||
and (o.Attributes.GetUserString("id") or "").startswith(f"{PIER_ID}-R")]
|
||||
first = sorted({o.Attributes.GetUserString("occurrence") for o in cand})[0] if cand else None
|
||||
sel = [o for o in cand if o.Attributes.GetUserString("occurrence") == first]
|
||||
if not sel:
|
||||
raise RuntimeError(f"perturb: nothing matches {mv}")
|
||||
dx, dy, dz = (sign * v for v in mv["d"])
|
||||
xf = rg.Transform.Translation(dx, dy, dz)
|
||||
for o in sel:
|
||||
doc.Objects.Transform(o.Id, xf, True)
|
||||
moved.append(o.Attributes.GetUserString("id"))
|
||||
print(json.dumps({"moved": moved, "undo": sign < 0}))
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Colour rebar pipe solids by nominal diameter (run inside Rhino 8 via runner after the rebar stages).
|
||||
|
||||
Globals: PARAMS_PATH. Ported from Code/교각모델링/apply_rebar_colors.py (Ungok P1, model v0.1.09).
|
||||
|
||||
Rule (context-migration/디자인지침_경로.md, R8 criteria 2026-10-07): same diameter -> same colour
|
||||
regardless of steel grade (S = SD500, H = SD400). Centrelines keep their layer colour.
|
||||
Prints a check: per-diameter object count and colour, and any pipe whose colour does not match.
|
||||
"""
|
||||
import json
|
||||
from collections import Counter
|
||||
|
||||
from Rhino.DocObjects import ObjectColorSource
|
||||
from System.Drawing import Color
|
||||
|
||||
doc = __rhino_doc__
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
PID = P["pier_id"]
|
||||
PALETTE = {int(k): tuple(v) for k, v in P["display"]["rebar_palette"].items()}
|
||||
|
||||
|
||||
def main():
|
||||
done, bad = Counter(), []
|
||||
for o in list(doc.Objects):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if not oid.startswith(f"{PID}-R-"):
|
||||
continue
|
||||
dia = int(o.Attributes.GetUserString("dia")[1:]) # 'S32' / 'H19' -> 32 / 19
|
||||
if dia not in PALETTE:
|
||||
bad.append((oid, "no palette entry"))
|
||||
continue
|
||||
attr = o.Attributes.Duplicate()
|
||||
attr.ObjectColor = Color.FromArgb(*PALETTE[dia])
|
||||
attr.ColorSource = ObjectColorSource.ColorFromObject
|
||||
doc.Objects.ModifyAttributes(o, attr, True)
|
||||
done[dia] += 1
|
||||
# check (re-read objects)
|
||||
seen = {}
|
||||
for o in doc.Objects:
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith(f"{PID}-R-"):
|
||||
dia = int(o.Attributes.GetUserString("dia")[1:])
|
||||
c = o.Attributes.ObjectColor
|
||||
ok = o.Attributes.ColorSource == ObjectColorSource.ColorFromObject and (c.R, c.G, c.B) == PALETTE[dia]
|
||||
if not ok:
|
||||
bad.append((oid, "colour mismatch"))
|
||||
seen.setdefault(dia, set()).add((c.R, c.G, c.B))
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps(dict(coloured=dict(done), colours_per_dia={k: list(v) for k, v in seen.items()},
|
||||
distinct_colours=len({tuple(PALETTE[k]) for k in seen}), mismatches=bad[:10],
|
||||
mismatch_count=len(bad))))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Generic rebar stage (Ulsan P1 coping, 2026-10-13): bars given as explicit centreline polylines in params.
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH). Used as the G4 builder (params["coping_rebar"]).
|
||||
params coping_rebar:
|
||||
host object id suffix of the host concrete (e.g. "CP")
|
||||
group layer group under 철근:: (e.g. "코핑")
|
||||
bars [{mark, occ, pts [[x, y, z], ...], r (corner fillet radius), design_length, src}]
|
||||
a closed bar repeats its first point at the end (start it mid-side so no corner sits at the seam)
|
||||
Ids: <pier>-R-<mark>-<occ> (pipe) and <pier>-RC-<mark>-<occ> (centreline).
|
||||
Section by gate (2026-10-14, Okseong P1): GATE "G3" -> params footing_rebar, otherwise coping_rebar.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
import System
|
||||
from Rhino.DocObjects import ObjectAttributes
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
SEC = P["footing_rebar" if globals().get("GATE") == "G3" else "coping_rebar"]
|
||||
MARK = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items()}
|
||||
CL_RGB = tuple(P["display"]["centreline_colour"])
|
||||
LAY_RGB = tuple(P["display"]["coping_layer_colour"])
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
def centreline(pts, r):
|
||||
pl = rg.PolylineCurve([rg.Point3d(*p) for p in pts])
|
||||
if len(pts) < 3 or r <= 0:
|
||||
return pl
|
||||
c = rg.Curve.CreateFilletCornersCurve(pl, r, TOL, math.radians(1))
|
||||
if c is None:
|
||||
raise RuntimeError(f"fillet failed (r {r})")
|
||||
return c
|
||||
|
||||
|
||||
def add_bar(b):
|
||||
mark, occ = b["mark"], b["occ"]
|
||||
dia, shape = MARK[mark]
|
||||
curve = centreline(b["pts"], b.get("r", 0))
|
||||
pipes = rg.Brep.CreatePipe(curve, float(dia[1:]) / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
|
||||
if not pipes or not pipes[0].IsSolid:
|
||||
raise RuntimeError(f"{mark} {occ}: pipe failed")
|
||||
common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=f"{PID}-{SEC['host']}", anchor_hosts="",
|
||||
design_length=f"{b['design_length']:.3f}", coupler_length="0.000", src=b.get("src", ""))
|
||||
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
||||
(pipes[0], layer(f"{PID}::철근::{SEC['group']}::{mark}", LAY_RGB), f"{PID}-R")):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = lay
|
||||
oid = f"{prefix}-{mark}-{occ}"
|
||||
attr.Name = oid
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in common.items():
|
||||
attr.SetUserString(k, v)
|
||||
gid = doc.Objects.AddBrep(geo, attr) if isinstance(geo, rg.Brep) else doc.Objects.AddCurve(geo, attr)
|
||||
if gid == System.Guid.Empty:
|
||||
raise RuntimeError(f"{oid}: add failed")
|
||||
|
||||
|
||||
def main():
|
||||
marks = {b["mark"] for b in SEC["bars"]}
|
||||
for o in list(doc.Objects):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith((f"{PID}-R-", f"{PID}-RC-")) and o.Attributes.GetUserString("mark") in marks:
|
||||
doc.Objects.Delete(o, True)
|
||||
for b in SEC["bars"]:
|
||||
add_bar(b)
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"bars": len(SEC["bars"]), "marks": len(marks)}))
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,180 @@
|
||||
"""G3 stage, form "shaft cage" (Ramp-A P1, 2026-10-13): drilled-shaft reinforcement cage (no footing). Straight main
|
||||
bars in two pieces joined by couplers (upper / lower mark), lapped half-ring hoops with 135-deg hooks, X / Y cross
|
||||
ties, bottom U bars in vertical planes through the centre, and the anti-uplift plate under them.
|
||||
|
||||
Run inside Rhino 8 via runner (globals PARAMS_PATH) after the G1 / G2 builders. Units mm, Z = EL x 1000.
|
||||
params shaft_rebar:
|
||||
main {count, start_deg, radius, pieces: [{mark, z0, z1}, ...] bottom to top, coupler_length}
|
||||
hoops {mark, radius, levels, a, lap, hook, join_deg [first, second]} joins alternate per level (S5)
|
||||
ties {mark, levels, straight, leg, hook_tail, y_lift} as column_rebar_straight.py (S5)
|
||||
u_bars {mark, count, z_bottom, half_bottom, half_top, leg_top_z, top_z, bend_r} one U per plane at main-bar angle
|
||||
i (i < count): bottom +-half_bottom at z_bottom, sloped legs to +-half_top at leg_top_z, vertical to top_z (S3)
|
||||
plate {size, thickness, z_top, colour} square, centred (S4)
|
||||
design_lengths {mark: schedule-style original length per piece}
|
||||
Bars below the pile segment boundary are hosted by the lower pile segment, above it by the upper one.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectAttributes, ObjectColorSource
|
||||
from System.Drawing import Color
|
||||
|
||||
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
|
||||
doc = __rhino_doc__
|
||||
TOL = 0.01
|
||||
PID = P["pier_id"]
|
||||
MARK = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items()}
|
||||
DIA = {d: float(d[1:]) for d, _ in MARK.values()}
|
||||
CL_RGB = tuple(P["display"]["centreline_colour"])
|
||||
SEGS = P["pile"]["segments"]
|
||||
|
||||
|
||||
def layer(path, color):
|
||||
parent = -1
|
||||
parts = path.split("::")
|
||||
for i in range(1, len(parts) + 1):
|
||||
idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -1)
|
||||
if idx < 0:
|
||||
lay = Rhino.DocObjects.Layer()
|
||||
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
|
||||
if parent >= 0:
|
||||
lay.ParentLayerId = doc.Layers[parent].Id
|
||||
idx = doc.Layers.Add(lay)
|
||||
parent = idx
|
||||
return parent
|
||||
|
||||
|
||||
def min_bend_radius(dia_name):
|
||||
d = DIA[dia_name]
|
||||
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2
|
||||
|
||||
|
||||
def host(z):
|
||||
for i, s in enumerate(SEGS, 1):
|
||||
if s["z0"] <= z <= s["z1"]:
|
||||
return f"{PID}-PL{i:02d}"
|
||||
return ""
|
||||
|
||||
|
||||
def add_bar(curve, mark, occ, design_len, src, coupler=0.0):
|
||||
dia, shape = MARK[mark]
|
||||
pipes = rg.Brep.CreatePipe(curve, DIA[dia] / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
|
||||
if not pipes or not pipes[0].IsSolid:
|
||||
raise RuntimeError(f"{mark} {occ}: pipe failed")
|
||||
bb = curve.GetBoundingBox(True)
|
||||
common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=host((bb.Min.Z + bb.Max.Z) / 2),
|
||||
anchor_hosts="", design_length=f"{design_len:.3f}", coupler_length=f"{coupler:.3f}", src=src)
|
||||
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
||||
(pipes[0], layer(f"{PID}::철근::말뚝::{mark}", (110, 110, 110)), f"{PID}-R")):
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = lay
|
||||
oid = f"{prefix}-{mark}-{occ}"
|
||||
attr.Name = oid
|
||||
attr.SetUserString("id", oid)
|
||||
for k, v in common.items():
|
||||
attr.SetUserString(k, v)
|
||||
gid = doc.Objects.AddBrep(geo, attr) if isinstance(geo, rg.Brep) else doc.Objects.AddCurve(geo, attr)
|
||||
if gid == __import__("System").Guid.Empty:
|
||||
raise RuntimeError(f"{oid}: add failed")
|
||||
|
||||
|
||||
def pt(r, deg, z):
|
||||
t = math.radians(deg)
|
||||
return rg.Point3d(r * math.cos(t), r * math.sin(t), z)
|
||||
|
||||
|
||||
def hooked_arc(r, a0, a1, z, hook, bend_r):
|
||||
"""Arc a0 -> a1 (deg, ccw) with inward 135-deg hooks at both ends, fillet bend_r (as column_rebar_ring.py)."""
|
||||
arc = rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, (a0 + a1) / 2, z), pt(r, a1, z)))
|
||||
pieces = []
|
||||
for at_start in (True, False):
|
||||
p = arc.PointAtStart if at_start else arc.PointAtEnd
|
||||
t = -arc.TangentAtStart if at_start else arc.TangentAtEnd
|
||||
n = rg.Vector3d(-p.X, -p.Y, 0)
|
||||
n.Unitize()
|
||||
d = t * math.cos(math.radians(135)) + n * math.sin(math.radians(135))
|
||||
d.Unitize()
|
||||
pieces.append(rg.LineCurve(p, p + d * hook))
|
||||
joined = rg.Curve.JoinCurves([pieces[0], arc, pieces[1]], TOL)[0]
|
||||
return rg.Curve.CreateFilletCornersCurve(joined, bend_r, TOL, math.radians(1)) or joined
|
||||
|
||||
|
||||
def tie_curve(axis, z, half, leg, tail, flip):
|
||||
"""As column_rebar_straight.py: straight part on the X / Y diameter, 90-deg leg down at one end, 135-deg hook."""
|
||||
u = rg.Vector3d(1, 0, 0) if axis == 0 else rg.Vector3d(0, 1, 0)
|
||||
s = -1 if flip else 1
|
||||
a, b = rg.Point3d(0, 0, z) - u * (s * half), rg.Point3d(0, 0, z) + u * (s * half)
|
||||
d = -u * s * math.cos(math.radians(45)) + rg.Vector3d(0, 0, -1) * math.sin(math.radians(45))
|
||||
return rg.PolylineCurve([a + rg.Vector3d(0, 0, -leg), a, b, b + d * tail])
|
||||
|
||||
|
||||
def main():
|
||||
for o in list(doc.Objects):
|
||||
oid = o.Attributes.GetUserString("id") or ""
|
||||
if oid.startswith((f"{PID}-PT",)) or (oid.startswith((f"{PID}-R-", f"{PID}-RC-"))
|
||||
and (o.Attributes.GetUserString("mark") or "") in SR_MARKS):
|
||||
doc.Objects.Delete(o, True)
|
||||
DL = SR["design_lengths"]
|
||||
m, n = SR["main"], 0
|
||||
for i in range(m["count"]):
|
||||
th = m["start_deg"] + i * 360.0 / m["count"]
|
||||
for pc in m["pieces"]:
|
||||
c = rg.LineCurve(pt(m["radius"], th, pc["z0"]), pt(m["radius"], th, pc["z1"]))
|
||||
add_bar(c, pc["mark"], f"{i + 1:02d}", DL[pc["mark"]],
|
||||
f"angle {th:.4f} deg; D4 단면 G-G·J-J 61개; r {m['radius']} (S2); EL {pc['z0'] / 1000:.3f}~{pc['z1'] / 1000:.3f}; "
|
||||
f"커플러 이음 (S6)", coupler=m["coupler_length"])
|
||||
n += 1
|
||||
h = SR["hoops"]
|
||||
r, ext = h["radius"], math.degrees(h["lap"] / 2 / h["radius"])
|
||||
for j, z in enumerate(h["levels"]):
|
||||
a0 = h["join_deg"][j % 2]
|
||||
for q, s in enumerate((a0, a0 + 180)):
|
||||
c = hooked_arc(r, s - ext, s + 180 + ext, z, h["hook"], min_bend_radius(MARK[h["mark"]][0]))
|
||||
add_bar(c, h["mark"], f"L{j + 1:03d}{'ab'[q]}", DL[h["mark"]],
|
||||
f"EL {z / 1000:.3f}; r {r} (S1); 겹이음 {h['lap']:.0f} at {a0}/{a0 + 180} deg (S5 [추론])")
|
||||
n += 1
|
||||
t = SR["ties"]
|
||||
rb = min_bend_radius(MARK[t["mark"]][0])
|
||||
for j, z in enumerate(t["levels"]):
|
||||
for axis, name in ((0, "X"), (1, "Y")):
|
||||
zt = z + (t["y_lift"] if axis == 1 else 0)
|
||||
pl = tie_curve(axis, zt, t["straight"] / 2, t["leg"], t["hook_tail"], flip=(j % 2 == 1))
|
||||
c = rg.Curve.CreateFilletCornersCurve(pl, rb, TOL, math.radians(1)) or pl
|
||||
add_bar(c, t["mark"], f"L{j + 1:02d}{name}", DL[t["mark"]],
|
||||
f"EL {zt / 1000:.3f}; {name} 방향 (S5 [추론])")
|
||||
n += 1
|
||||
u = SR["u_bars"]
|
||||
for i in range(u["count"]):
|
||||
th = m["start_deg"] + i * 360.0 / m["count"]
|
||||
e = rg.Vector3d(math.cos(math.radians(th)), math.sin(math.radians(th)), 0)
|
||||
P0 = lambda s, z: rg.Point3d(0, 0, z) + e * s
|
||||
pl = rg.PolylineCurve([P0(-u["half_top"], u["top_z"]), P0(-u["half_top"], u["leg_top_z"]),
|
||||
P0(-u["half_bottom"], u["z_bottom"]), P0(u["half_bottom"], u["z_bottom"]),
|
||||
P0(u["half_top"], u["leg_top_z"]), P0(u["half_top"], u["top_z"])])
|
||||
c = rg.Curve.CreateFilletCornersCurve(pl, u["bend_r"], TOL, math.radians(1)) or pl
|
||||
add_bar(c, u["mark"], f"{i + 1:02d}", DL[u["mark"]],
|
||||
f"plane {th:.4f} deg through the centre (S3 [추론]); bottom EL {u['z_bottom'] / 1000:.3f}; c {u['top_z'] - u['leg_top_z']:.0f} welded to P1-2")
|
||||
n += 1
|
||||
pt_ = SR["plate"]
|
||||
a = pt_["size"] / 2
|
||||
box = rg.Box(rg.BoundingBox(-a, -a, pt_["z_top"] - pt_["thickness"], a, a, pt_["z_top"])).ToBrep()
|
||||
attr = ObjectAttributes()
|
||||
attr.LayerIndex = layer(f"{PID}::철근::말뚝::PLATE", tuple(pt_["colour"]))
|
||||
attr.ObjectColor = Color.FromArgb(*pt_["colour"])
|
||||
attr.ColorSource = ObjectColorSource.ColorFromObject
|
||||
oid = f"{PID}-PT01"
|
||||
attr.Name = oid
|
||||
for k, v in (("id", oid), ("role", "anti-uplift plate"), ("host", host(pt_["z_top"])),
|
||||
("src", f"D4 떠오름 방지 PLATE {pt_['size']:.0f}x{pt_['size']:.0f}x{pt_['thickness']:.0f}; U형 바닥 아래 (S4 [추론])")):
|
||||
attr.SetUserString(k, v)
|
||||
if doc.Objects.AddBrep(box, attr) == __import__("System").Guid.Empty:
|
||||
raise RuntimeError("plate: add failed")
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"bars": n, "plate": 1}))
|
||||
|
||||
|
||||
SR = P["shaft_rebar"]
|
||||
SR_MARKS = {pc["mark"] for pc in SR["main"]["pieces"]} | {SR[k]["mark"] for k in ("hoops", "ties", "u_bars")}
|
||||
main()
|
||||
Reference in new issue
Block a user