fix: adjust tangency parameters for enclosed circles in tangent arc

Fixes [#1294](https://github.com/gumyr/build123d/issues/1294).
When generating constrained tangent arcs, the tangency parameter on the solution circle must be shifted by `pi` if the generated circle is fully enclosed within one of the target tangent circular edges. 

* Added `_enclosed_circ_param_offset` to automatically detect when a solution circle is enclosed by a circular tangency target and apply the necessary `pi` offset to its parameters.
* Applied the offset correction to the `_make_2tan_rad_arcs` and `_make_3tan_arcs` solver functions.
* Added a guard in `_make_3tan_arcs` to raise a `RuntimeError` if no common tangent arcs can be generated after evaluating all GCC solutions.
* Imported `pi` from `math` and `GeomAbs_CurveType` from `OCP.GeomAbs` to support the new geometric checks.
This commit is contained in:
javimixet 2026-05-04 12:39:44 +02:00 committed by GitHub
parent 633200b964
commit de041b6cbc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -29,7 +29,7 @@ license:
from __future__ import annotations
from math import atan2, cos, isnan, sin
from math import atan2, cos, isnan, sin, pi
from typing import overload, TYPE_CHECKING, Callable, TypeVar
from typing import cast as tcast
@ -59,6 +59,7 @@ from OCP.Geom2dGcc import (
Geom2dGcc_Lin2d2Tan,
Geom2dGcc_QualifiedCurve,
)
from OCP.GeomAbs import GeomAbs_CurveType
from OCP.GeomAPI import GeomAPI
from OCP.gp import (
gp_Ax2d,
@ -270,6 +271,30 @@ def _qstr(q) -> str: # pragma: no cover
return str(int(q))
def _enclosed_circ_param_offset(
tangent_tuples: list[tuple[Edge, Tangency]],
circ: gp_Circ2d,
params: list[float],
) -> list[float]:
"""
Adjusts the circle parameters by adding pi if the solution circle is
enclosed within a tangent circular edge.
"""
center_pnt = circ.Location()
center_vrt = Vector(center_pnt.X(), center_pnt.Y(), 0)
pars = list(params)
for i, par in enumerate(params):
edg = tangent_tuples[i][0]
if isinstance(edg.wrapped, TopoDS_Edge):
adapt = BRepAdaptor_Curve(edg.wrapped)
if adapt.GetType() == GeomAbs_CurveType.GeomAbs_Circle:
if (center_vrt - edg.arc_center).length < edg.radius:
pars[i] = par + pi
return pars
def _make_2tan_rad_arcs(
*tangencies: tuple[Edge, Tangency] | Edge | Vector, # 2
radius: float,
@ -431,6 +456,8 @@ def _make_2tan_on_arcs(
if not _ok(1, u_arg2):
continue
u_circ1, u_circ2 = _enclosed_circ_param_offset(tangent_tuples, circ, [u_circ1, u_circ2])
# Build sagitta arc(s) and select by LengthConstraint
if sagitta == Sagitta.BOTH:
solutions.extend(_two_arc_edges_from_params(circ, u_circ1, u_circ2))
@ -521,6 +548,8 @@ def _make_3tan_arcs(
if not _ok(2, u_arg3):
continue
u_circ1, u_circ2, _u_circ3 = _enclosed_circ_param_offset(tangent_tuples, circ, [u_circ1, u_circ2, _u_circ3])
# Build arc(s) between u_circ1 and u_circ2 per LengthConstraint
if sagitta == Sagitta.BOTH:
out_topos.extend(_two_arc_edges_from_params(circ, u_circ1, u_circ2))
@ -532,6 +561,9 @@ def _make_3tan_arcs(
)
out_topos.append(arcs[sagitta.value])
if len(out_topos) == 0:
raise RuntimeError("Unable to find common tangent arc(s)")
return ShapeList([edge_factory(e) for e in out_topos])