- tech drawing and render improv
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""Smoke test: layout optimizer fills the page, no overlaps, title block clear."""
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
sys.path.insert(0, "/Volumes/Data_drive/Programming/fluency/src")
|
||||
|
||||
from fluency.models.data_model import DrawingView
|
||||
from fluency.technical_drawing import (
|
||||
_layout_views_on_sheet,
|
||||
build_manual_candidates,
|
||||
)
|
||||
|
||||
A3W, A3H = 420.0, 297.0
|
||||
TB = (235.0, 0.0, 420.0, 62.0) # title block zone incl. clearance
|
||||
|
||||
|
||||
def b(x, y, w, h):
|
||||
return (x, y, x + w, y + h)
|
||||
|
||||
|
||||
def overlaps(r1, r2, clear=0.0):
|
||||
x0, y0, w, h = r1
|
||||
x1, y1, w2, h2 = r2
|
||||
return not (x0 + w <= x1 + clear or x1 + w2 <= x0 + clear
|
||||
or y0 + h <= y1 + clear or y1 + h2 <= y0 + clear)
|
||||
|
||||
|
||||
def tb_overlap(r, clear=5.0):
|
||||
x0, y0, w, h = r
|
||||
x1, y1, w2, h2 = TB
|
||||
return not (x0 + w <= x1 + clear or x1 + w2 <= x0 + clear
|
||||
or y0 + h <= y1 + clear or y1 + h2 <= y0 + clear)
|
||||
|
||||
|
||||
def union_rect(rects):
|
||||
x0 = min(r[0] for r in rects)
|
||||
y0 = min(r[1] for r in rects)
|
||||
x1 = max(r[0] + r[2] for r in rects)
|
||||
y1 = max(r[1] + r[3] for r in rects)
|
||||
return (x0, y0, x1 - x0, y1 - y0)
|
||||
|
||||
|
||||
def check(name, kinds, boxes, expect_rot=None):
|
||||
views = [DrawingView(kind=k) for k in kinds]
|
||||
bboxes = {k: boxes[k] for k in kinds}
|
||||
slots, scale, rots = _layout_views_on_sheet(views, bboxes)
|
||||
print(f"--- {name}: scale={scale:.4f} rots={rots}")
|
||||
# all slots within sheet
|
||||
for k, s in slots.items():
|
||||
assert 10 - 1e-6 <= s[0] and 10 - 1e-6 <= s[1], f"{k} outside sheet {s}"
|
||||
assert s[0] + s[2] <= A3W - 10 + 1e-6, f"{k} beyond right {s}"
|
||||
assert s[1] + s[3] <= A3H - 10 + 1e-6, f"{k} beyond top {s}"
|
||||
# no overlaps between slots
|
||||
ks = list(slots)
|
||||
for i in range(len(ks)):
|
||||
for j in range(i + 1, len(ks)):
|
||||
assert not overlaps(slots[ks[i]], slots[ks[j]], 11.9), \
|
||||
f"{ks[i]} overlaps {ks[j]}: {slots[ks[i]]} / {slots[ks[j]]}"
|
||||
# title block clear
|
||||
for k, s in slots.items():
|
||||
assert not tb_overlap(s), f"{k} intrudes title block {s}"
|
||||
# fill report
|
||||
u = union_rect(list(slots.values()))
|
||||
area = u[2] * u[3]
|
||||
print(f" union: x0={u[0]:.1f} y0={u[1]:.1f} w={u[2]:.1f} h={u[3]:.1f} "
|
||||
f"area={area:.0f}mm^2 ({100*area/(A3W*A3H):.0f}% of sheet)")
|
||||
for k in ks:
|
||||
print(f" {k}: {tuple(round(v,1) for v in slots[k])}")
|
||||
if expect_rot is not None:
|
||||
assert rots == expect_rot, f"expected {expect_rot}, got {rots}"
|
||||
return slots, scale, rots
|
||||
|
||||
|
||||
# 1. Two square views (front+top): should fill the page, no rotation.
|
||||
check("two square", ["front", "top"],
|
||||
{"front": b(0, 0, 40, 40), "top": b(0, 0, 40, 20)})
|
||||
|
||||
# 2. Wide bar, 4 views (old screenshot case): front+back wide, top+right.
|
||||
check("wide bar 4", ["front", "top", "right", "back"],
|
||||
{"front": b(0, 0, 120, 25), "top": b(0, 0, 25, 40),
|
||||
"right": b(0, 0, 25, 40), "back": b(0, 0, 120, 25)})
|
||||
|
||||
# 3. Six views of a long thin part: rotation should kick in.
|
||||
check("thin part 6", ["front", "top", "right", "left", "back", "bottom"],
|
||||
{"front": b(0, 0, 200, 30), "top": b(0, 0, 30, 50),
|
||||
"right": b(0, 0, 50, 30), "left": b(0, 0, 50, 30),
|
||||
"back": b(0, 0, 200, 30), "bottom": b(0, 0, 30, 50)})
|
||||
|
||||
# 4. Single front view: fills the whole page.
|
||||
check("single", ["front"], {"front": b(0, 0, 10, 20)})
|
||||
|
||||
# 5. All 6 + isometric + custom.
|
||||
check("everything",
|
||||
["front", "top", "right", "left", "back", "bottom", "isometric"],
|
||||
{"front": b(0, 0, 80, 40), "top": b(0, 0, 80, 30),
|
||||
"right": b(0, 0, 30, 40), "left": b(0, 0, 30, 40),
|
||||
"back": b(0, 0, 80, 40), "bottom": b(0, 0, 80, 30),
|
||||
"isometric": b(0, 0, 60, 60)})
|
||||
|
||||
# ── Inverse-transform roundtrip ─────────────────────────────────────────
|
||||
# A 90°-rotated view: forward via _assemble_view's recorded 6-tuple,
|
||||
# inverse via the widget's formula.
|
||||
t = (2.0, 150.0, 80.0, 90.0, 10.0, 5.0) # s, o_x, o_y, deg, cx, cy
|
||||
scale, ox, oy, deg, cx, cy = t
|
||||
th = math.radians(deg)
|
||||
cos_t, sin_t = math.cos(th), math.sin(th)
|
||||
|
||||
|
||||
def fwd(p):
|
||||
dx, dy = p[0] - cx, p[1] - cy
|
||||
return ((dx * cos_t - dy * sin_t) * scale + ox,
|
||||
(dx * sin_t + dy * cos_t) * scale + oy)
|
||||
|
||||
|
||||
def inv(p):
|
||||
sx, sy = (p[0] - ox) / scale, (p[1] - oy) / scale
|
||||
return (sx * cos_t + sy * sin_t + cx, -sx * sin_t + sy * cos_t + cy)
|
||||
|
||||
|
||||
for p in [(0, 0), (10, 5), (3, -7), (42.5, 11.25)]:
|
||||
rt = inv(fwd(p))
|
||||
assert abs(rt[0] - p[0]) < 1e-9 and abs(rt[1] - p[1]) < 1e-9, (p, rt)
|
||||
print("inverse roundtrip OK")
|
||||
|
||||
# Legacy 3-tuple still works through build_manual_candidates.
|
||||
from fluency.models.data_model import DrawingAnnotation, TechnicalDrawing
|
||||
d = TechnicalDrawing(source_kind="component", source_id="c")
|
||||
ann = DrawingAnnotation(kind="dimension", dimension_kind="length",
|
||||
view_id="front", anchors=[(0.0, 0.0), (0.0, 12.5)],
|
||||
direction=(0.0, 1.0))
|
||||
d.annotations.append(ann)
|
||||
cands, res, unres = build_manual_candidates(d, {"front": (2.0, 10.0, 20.0)})
|
||||
assert unres == [] and cands[0].anchor_points[1] == (10.0, 45.0)
|
||||
print("legacy 3-tuple OK")
|
||||
|
||||
# 6-tuple manual: rotated length direction must rotate too.
|
||||
ann2 = DrawingAnnotation(kind="dimension", dimension_kind="length",
|
||||
view_id="front",
|
||||
anchors=[(0.0, 0.0), (0.0, 10.0)],
|
||||
direction=(0.0, 1.0))
|
||||
d2 = TechnicalDrawing(source_kind="component", source_id="c")
|
||||
d2.annotations.append(ann2)
|
||||
# 90° rotation about centre c=(5,5), scale 2, o=(100,80)
|
||||
cands, res, unres = build_manual_candidates(
|
||||
d2, {"front": (2.0, 100.0, 80.0, 90.0, 5.0, 5.0)}
|
||||
)
|
||||
c = cands[0]
|
||||
# anchors: (0,0)->rot90 about (5,5) = (5-(0-5)*0 - ... compute: dx=-5,dy=-5
|
||||
# fwd: (dx*cos - dy*sin)*2+100 = (0 - (-5))*2+100 = 110 ; (dx*sin+dy*cos)*2+80 = (-5)*2+80=70
|
||||
# (0,10): dx=-5, dy=5 -> (0-5)*2+100=90 ; (−5*1+0)*2+80=70
|
||||
assert c.anchor_points[0] == (110.0, 70.0), c.anchor_points
|
||||
assert c.anchor_points[1] == (90.0, 70.0), c.anchor_points
|
||||
# direction (0,1) rotated 90° CCW -> (-1, 0)
|
||||
assert c.direction[0] == -1.0 and abs(c.direction[1]) < 1e-9, c.direction
|
||||
print("6-tuple manual (rotated) OK")
|
||||
|
||||
print("ALL SMOKE CHECKS PASSED")
|
||||
Reference in New Issue
Block a user