- arc improvements, fillets, operations, bodys
This commit is contained in:
@@ -952,7 +952,17 @@ class OCCSketch(SketchInterface):
|
||||
elif ctype == "distance":
|
||||
if h(ids[0]) is None or h(ids[1]) is None:
|
||||
return False
|
||||
self._solver.distance(h(ids[0]), h(ids[1]), params[0], self._wp)
|
||||
ent0 = self._entities.get(ids[0])
|
||||
ent1 = self._entities.get(ids[1])
|
||||
# Normalise (line, point) -> (point, line) like constrain_distance
|
||||
# does, and drop legacy line-line entries the solver can't hold.
|
||||
if ent0 is not None and ent1 is not None:
|
||||
if ent0.entity_type == "line" and ent1.entity_type == "line":
|
||||
return False
|
||||
if ent0.entity_type == "line" and ent1.entity_type == "point":
|
||||
self._solver.distance(h(ids[1]), h(ids[0]), params[0], self._wp)
|
||||
else:
|
||||
self._solver.distance(h(ids[0]), h(ids[1]), params[0], self._wp)
|
||||
elif ctype == "angle":
|
||||
if h(ids[0]) is None or h(ids[1]) is None:
|
||||
return False
|
||||
@@ -1175,16 +1185,92 @@ class OCCSketch(SketchInterface):
|
||||
entity.constraints.append("vrt")
|
||||
return True
|
||||
|
||||
def _point_line_signed_offset(
|
||||
self, point_ent: OCCSketchEntity, line_ent: OCCSketchEntity
|
||||
) -> float:
|
||||
"""Signed perpendicular offset of *point_ent* from *line_ent*, in the
|
||||
solver's sign convention — i.e. the ``valA`` that pins the point on
|
||||
its current side of the line.
|
||||
|
||||
Mirrors SolveSpace's ``PT_LINE_DISTANCE`` equation exactly
|
||||
(a = line start, b = line end, d = a - b):
|
||||
|
||||
proj = dv·(ua − u) − du·(va − v), m = |d|
|
||||
offset = proj / m
|
||||
|
||||
Returns 0.0 when the line is degenerate or the point lies on it
|
||||
(the side is then undefined — callers fall back to the unsigned
|
||||
value).
|
||||
"""
|
||||
if line_ent.id not in self._lines:
|
||||
return 0.0
|
||||
sid, eid2 = self._lines[line_ent.id]
|
||||
s_ent = self._entities.get(sid)
|
||||
e_ent = self._entities.get(eid2)
|
||||
if s_ent is None or e_ent is None or not s_ent.geometry or not e_ent.geometry:
|
||||
return 0.0
|
||||
ua, va = s_ent.geometry
|
||||
ub, vb = e_ent.geometry
|
||||
u, v = point_ent.geometry
|
||||
du = ua - ub
|
||||
dv = va - vb
|
||||
m = math.hypot(du, dv)
|
||||
if m < 1e-12:
|
||||
return 0.0
|
||||
return (dv * (ua - u) - du * (va - v)) / m
|
||||
|
||||
def constrain_distance(
|
||||
self, entity1: SketchEntity, entity2: SketchEntity, distance: float
|
||||
) -> bool:
|
||||
"""Constrain distance between two entities."""
|
||||
"""Constrain distance between two entities.
|
||||
|
||||
python-solvespace's ``distance`` accepts point-point and point-line
|
||||
(in that order) only, so line-point pairs are normalised and line-line
|
||||
pairs are rejected with a warning instead of raising TypeError.
|
||||
|
||||
A point constrained to itself with a non-zero value is always
|
||||
inconsistent — reject it up front so the UI never creates a constraint
|
||||
the solver cannot satisfy.
|
||||
"""
|
||||
e1 = self._entities.get(entity1.id)
|
||||
e2 = self._entities.get(entity2.id)
|
||||
if e1 is None or e2 is None or e1.handle is None or e2.handle is None:
|
||||
return False
|
||||
self._solver.distance(e1.handle, e2.handle, distance, self._wp)
|
||||
self._record_constraint("distance", (entity1.id, entity2.id), (distance,))
|
||||
if e1 is e2 and e1.entity_type == "point" and distance != 0.0:
|
||||
logger.warning("distance: refusing point-to-itself constraint")
|
||||
return False
|
||||
|
||||
t1, t2 = e1.entity_type, e2.entity_type
|
||||
if t1 == "line" and t2 == "line":
|
||||
logger.warning(
|
||||
"distance: line-to-line distance is not supported by the solver "
|
||||
"(select a point and a line instead)"
|
||||
)
|
||||
return False
|
||||
|
||||
# python-solvespace only accepts (point, line) ordering.
|
||||
if t1 == "line" and t2 == "point":
|
||||
e1, e2 = e2, e1
|
||||
entity1, entity2 = entity2, entity1
|
||||
|
||||
# Point-to-line distance is SIGNED in SolveSpace: the constraint
|
||||
# equation is (signed perpendicular offset) = valA, so a positive
|
||||
# valA always drives the point onto one fixed side of the line
|
||||
# (for a vertical line, always +x). Pass a value whose sign
|
||||
# matches the side the point is currently on so the constraint
|
||||
# pins it there instead of flipping it across the line. The sign
|
||||
# is recorded with the value so a solver rebuild reproduces the
|
||||
# same side.
|
||||
solver_value = distance
|
||||
if e2.entity_type == "line" and e1.entity_type == "point" and distance != 0.0:
|
||||
signed = self._point_line_signed_offset(e1, e2)
|
||||
if abs(signed) > 1e-9:
|
||||
solver_value = math.copysign(distance, signed)
|
||||
|
||||
self._solver.distance(e1.handle, e2.handle, solver_value, self._wp)
|
||||
# Record in the normalised (point-first) order so replayed / legacy
|
||||
# constraint logs are consistent.
|
||||
self._record_constraint("distance", (entity1.id, entity2.id), (solver_value,))
|
||||
return True
|
||||
|
||||
def constrain_angle(self, line1: SketchEntity, line2: SketchEntity, angle: float) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user