- tech draw draft v2

This commit is contained in:
bklronin
2026-08-17 21:39:20 +02:00
parent 37e5335446
commit 813ddc3596
3 changed files with 573 additions and 188 deletions
+87 -26
View File
@@ -243,7 +243,16 @@ class DrawingCanvas(QWidget):
t = self._render_result.view_transforms.get(view_id)
if t is None or t[0] <= 0:
return None
scale, ox, oy = t
scale, ox, oy = t[0], t[1], t[2]
if len(t) >= 6:
# sheet(p) = s·R(θ)(p c) + o → invert around the centre.
th = math.radians(t[3])
cos_t, sin_t = math.cos(th), math.sin(th)
sx, sy = (pt[0] - ox) / scale, (pt[1] - oy) / scale
return (
sx * cos_t + sy * sin_t + t[4],
-sx * sin_t + sy * cos_t + t[5],
)
return ((pt[0] - ox) / scale, (pt[1] - oy) / scale)
def _pick_feature(self, pos: QPointF) -> Optional[dict]:
@@ -283,12 +292,19 @@ class DrawingCanvas(QWidget):
"p2": m2,
}
elif prim.kind == "circle" and prim.center and prim.radius:
d = abs(
math.hypot(
pt[0] - prim.center[0], pt[1] - prim.center[1]
)
- prim.radius
)
dc = math.hypot(pt[0] - prim.center[0], pt[1] - prim.center[1])
if dc <= tol:
# Hit the circle's centre mark: a point feature — circle
# centres are first-class dimension references.
mc = self._sheet_to_model(prim.view_id, prim.center)
if mc:
return {
"kind": "point",
"view_id": prim.view_id,
"point": mc,
"radius": prim.radius / scale,
}
d = abs(dc - prim.radius)
if d < best_d:
best_d = d
mc = self._sheet_to_model(prim.view_id, prim.center)
@@ -506,19 +522,11 @@ class TechnicalDrawingWidget(QWidget):
(
"distance",
"Distance",
"Pick two edges — the dimension line is placed "
"perpendicular to them, measuring the distance between",
),
(
"diameter",
"Diameter",
"Pick a circle — its diameter is added",
),
(
"angle",
"Angle",
"Pick two edges — the angle between them is added",
"Pick two edges, an edge and a circle centre, or two "
"circle centres — measures the distance between them",
),
("diameter", "Diameter", "Pick a circle — its diameter is added"),
("angle", "Angle", "Pick two edges — the angle between them is added"),
):
btn = QPushButton(label)
btn.setCheckable(True)
@@ -818,7 +826,7 @@ class TechnicalDrawingWidget(QWidget):
self._first_pick = None
self._canvas.set_pick_mode(tool)
prompts = {
"distance": "Distance: click the first edge",
"distance": "Distance: click an edge or a circle centre",
"diameter": "Diameter: click a circle",
"angle": "Angle: click the first edge",
}
@@ -883,6 +891,40 @@ class TechnicalDrawingWidget(QWidget):
elif mode in ("distance", "angle"):
self._on_edge_pick(info, mode)
@staticmethod
def _pick_point(info: dict) -> Optional[Tuple[float, float]]:
"""The measurable point of a pick: a point pick is its point, a
circle pick counts as its centre; a bare segment is None."""
if info.get("kind") == "point":
return info["point"]
if info.get("kind") == "circle":
return info["center"]
return None
@classmethod
def _distance_anchors(
cls, first: dict, second: dict
) -> Tuple[Tuple[float, float], Tuple[float, float]]:
"""Anchor pair for a distance between two picks (model coords).
A pick may be a segment (edge), a circle (measured at its centre)
or a point (a picked circle centre). Point/segment mixes use the
closest point on the segment so the dimension lands perpendicular
to the edge, ISO style.
"""
fp = cls._pick_point(first)
sp = cls._pick_point(second)
if fp is not None and sp is not None:
return fp, sp
if fp is not None:
return fp, _closest_point_on_segment(fp, second["p1"], second["p2"])
if sp is not None:
return sp, _closest_point_on_segment(sp, first["p1"], first["p2"])
q1, q2, _d = _closest_points_on_segments(
first["p1"], first["p2"], second["p1"], second["p2"]
)
return q1, q2
def _on_edge_pick(self, info: dict, mode: str) -> None:
if (
self._first_pick is None
@@ -891,16 +933,16 @@ class TechnicalDrawingWidget(QWidget):
# First edge (or picked in a different view: restart there).
self._first_pick = info
self._status_label.setText(
"Select the second edge in the same view (Esc cancels)"
"Select the second feature in the same view (Esc cancels)"
)
return
a1, a2 = self._first_pick["p1"], self._first_pick["p2"]
b1, b2 = info["p1"], info["p2"]
first = self._first_pick
view_id = info["view_id"]
if mode == "distance":
q1, q2, dist = _closest_points_on_segments(a1, a2, b1, b2)
p1, p2 = self._distance_anchors(first, info)
dist = math.hypot(p2[0] - p1[0], p2[1] - p1[1])
if dist < 0.01:
self._status_label.setText(
"The two edges coincide — no distance to measure"
@@ -908,12 +950,20 @@ class TechnicalDrawingWidget(QWidget):
return
self._add_manual_dimension(
"length",
anchors=(q1, q2),
anchors=(p1, p2),
view_id=view_id,
direction=((q2[0] - q1[0]) / dist, (q2[1] - q1[1]) / dist),
direction=((p2[0] - p1[0]) / dist, (p2[1] - p1[1]) / dist),
done_msg=f"Distance dimension added: {dist:.2f}",
)
else: # angle
if first.get("kind") != "segment" or info.get("kind") != "segment":
self._status_label.setText(
"Angle needs two edges — cancel and pick edge lines"
)
self._first_pick = None
return
a1, a2 = first["p1"], first["p2"]
b1, b2 = info["p1"], info["p2"]
vertex = _line_intersection(a1, a2, b1, b2)
if vertex is None:
self._status_label.setText(
@@ -937,6 +987,17 @@ class TechnicalDrawingWidget(QWidget):
)
def _on_diameter_pick(self, info: dict) -> None:
if info.get("kind") == "point" and info.get("radius"):
# Picked the centre mark of a circle.
cx, cy = info["point"]
r = info["radius"]
self._add_manual_dimension(
"diameter",
anchors=((cx - r, cy), (cx + r, cy)),
view_id=info["view_id"],
done_msg=f"Diameter dimension added: Ø{2 * r:.2f}",
)
return
if info.get("kind") != "circle":
return
cx, cy = info["center"]