mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-15 15:20:37 -08:00
99% coverage on constrained lines
Some checks failed
benchmarks / benchmarks (macos-14, 3.12) (push) Has been cancelled
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Has been cancelled
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Has been cancelled
benchmarks / benchmarks (windows-latest, 3.12) (push) Has been cancelled
Upload coverage reports to Codecov / run (push) Has been cancelled
pylint / lint (3.10) (push) Has been cancelled
Run type checker / typecheck (3.10) (push) Has been cancelled
Run type checker / typecheck (3.13) (push) Has been cancelled
Wheel building and publishing / Build wheel on ubuntu-latest (push) Has been cancelled
tests / tests (macos-14, 3.10) (push) Has been cancelled
tests / tests (macos-14, 3.13) (push) Has been cancelled
tests / tests (macos-15-intel, 3.10) (push) Has been cancelled
tests / tests (macos-15-intel, 3.13) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
tests / tests (windows-latest, 3.10) (push) Has been cancelled
tests / tests (windows-latest, 3.13) (push) Has been cancelled
Wheel building and publishing / upload_pypi (push) Has been cancelled
Some checks failed
benchmarks / benchmarks (macos-14, 3.12) (push) Has been cancelled
benchmarks / benchmarks (macos-15-intel, 3.12) (push) Has been cancelled
benchmarks / benchmarks (ubuntu-latest, 3.12) (push) Has been cancelled
benchmarks / benchmarks (windows-latest, 3.12) (push) Has been cancelled
Upload coverage reports to Codecov / run (push) Has been cancelled
pylint / lint (3.10) (push) Has been cancelled
Run type checker / typecheck (3.10) (push) Has been cancelled
Run type checker / typecheck (3.13) (push) Has been cancelled
Wheel building and publishing / Build wheel on ubuntu-latest (push) Has been cancelled
tests / tests (macos-14, 3.10) (push) Has been cancelled
tests / tests (macos-14, 3.13) (push) Has been cancelled
tests / tests (macos-15-intel, 3.10) (push) Has been cancelled
tests / tests (macos-15-intel, 3.13) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
tests / tests (windows-latest, 3.10) (push) Has been cancelled
tests / tests (windows-latest, 3.13) (push) Has been cancelled
Wheel building and publishing / upload_pypi (push) Has been cancelled
This commit is contained in:
parent
a12961cfff
commit
32c1322370
2 changed files with 234 additions and 12 deletions
201
tests/test_direct_api/test_constrained_lines.py
Normal file
201
tests/test_direct_api/test_constrained_lines.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
"""
|
||||
build123d tests
|
||||
|
||||
name: test_constrained_lines.py
|
||||
by: Gumyr
|
||||
date: October 8, 2025
|
||||
|
||||
desc:
|
||||
This python module contains tests for the build123d project.
|
||||
|
||||
license:
|
||||
|
||||
Copyright 2025 Gumyr
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
"""
|
||||
|
||||
import math
|
||||
import pytest
|
||||
from OCP.gp import gp_Pnt2d, gp_Dir2d, gp_Lin2d
|
||||
from build123d import Edge, Axis, Vector, Tangency, Plane
|
||||
from build123d.topology.constrained_lines import (
|
||||
_make_2tan_lines,
|
||||
_make_tan_oriented_lines,
|
||||
_edge_from_line,
|
||||
)
|
||||
from build123d.geometry import TOLERANCE
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unit_circle() -> Edge:
|
||||
"""A simple unit circle centered at the origin on XY."""
|
||||
return Edge.make_circle(1.0, Plane.XY)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# utility tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_edge_from_line():
|
||||
line = _edge_from_line(gp_Pnt2d(0, 0), gp_Pnt2d(1, 0))
|
||||
assert Edge(line).length == 1
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
_edge_from_line(gp_Pnt2d(0, 0), gp_Pnt2d(0, 0))
|
||||
assert "Failed to build edge from line contacts" in str(excinfo.value)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _make_2tan_lines tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_two_circles_tangents(unit_circle):
|
||||
"""Tangent lines between two separated circles should yield four results."""
|
||||
c1 = unit_circle
|
||||
c2 = unit_circle.translate((3, 0, 0)) # displaced along X
|
||||
lines = _make_2tan_lines(c1, c2, edge_factory=Edge)
|
||||
# There should be 4 external/internal tangents
|
||||
assert len(lines) in (4, 2)
|
||||
for ln in lines:
|
||||
assert isinstance(ln, Edge)
|
||||
# Tangent lines should not intersect the circle interior
|
||||
dmin = c1.distance_to(ln)
|
||||
assert dmin >= -1e-6
|
||||
|
||||
|
||||
def test_two_constrained_circles_tangents1(unit_circle):
|
||||
"""Tangent lines between two separated circles should yield four results."""
|
||||
c1 = unit_circle
|
||||
c2 = unit_circle.translate((3, 0, 0)) # displaced along X
|
||||
lines = _make_2tan_lines((c1, Tangency.ENCLOSING), c2, edge_factory=Edge)
|
||||
# There should be 2 external/internal tangents
|
||||
assert len(lines) == 2
|
||||
for ln in lines:
|
||||
assert isinstance(ln, Edge)
|
||||
# Tangent lines should not intersect the circle interior
|
||||
dmin = c1.distance_to(ln)
|
||||
assert dmin >= -1e-6
|
||||
|
||||
|
||||
def test_two_constrained_circles_tangents2(unit_circle):
|
||||
"""Tangent lines between two separated circles should yield four results."""
|
||||
c1 = unit_circle
|
||||
c2 = unit_circle.translate((3, 0, 0)) # displaced along X
|
||||
lines = _make_2tan_lines(
|
||||
(c1, Tangency.ENCLOSING), (c2, Tangency.ENCLOSING), edge_factory=Edge
|
||||
)
|
||||
# There should be 1 external/external tangents
|
||||
assert len(lines) == 1
|
||||
for ln in lines:
|
||||
assert isinstance(ln, Edge)
|
||||
# Tangent lines should not intersect the circle interior
|
||||
dmin = c1.distance_to(ln)
|
||||
assert dmin >= -1e-6
|
||||
|
||||
|
||||
def test_curve_and_point_tangent(unit_circle):
|
||||
"""A line tangent to a circle and passing through a point should exist."""
|
||||
pt = Vector(2.0, 0.0)
|
||||
lines = _make_2tan_lines(unit_circle, pt, edge_factory=Edge)
|
||||
assert len(lines) == 2
|
||||
for ln in lines:
|
||||
# The line must pass through the given point (approximately)
|
||||
dist_to_point = ln.distance_to(pt)
|
||||
assert math.isclose(dist_to_point, 0.0, abs_tol=1e-6)
|
||||
# It should also touch the circle at exactly one point
|
||||
dist_to_circle = unit_circle.distance_to(ln)
|
||||
assert math.isclose(dist_to_circle, 0.0, abs_tol=TOLERANCE)
|
||||
|
||||
|
||||
def test_invalid_tangent_raises(unit_circle):
|
||||
"""Non-intersecting degenerate input result in no output."""
|
||||
lines = _make_2tan_lines(unit_circle, unit_circle, edge_factory=Edge)
|
||||
assert len(lines) == 0
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
_make_2tan_lines(unit_circle, Vector(0, 0), edge_factory=Edge)
|
||||
assert "Unable to find common tangent line(s)" in str(excinfo.value)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _make_tan_oriented_lines tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize("angle_deg", [math.radians(30), -math.radians(30)])
|
||||
def test_oriented_tangents_with_x_axis(unit_circle, angle_deg):
|
||||
"""Lines tangent to a circle at ±30° from the X-axis."""
|
||||
lines = _make_tan_oriented_lines(unit_circle, Axis.X, angle_deg, edge_factory=Edge)
|
||||
assert all(isinstance(e, Edge) for e in lines)
|
||||
# The tangent lines should all intersect the X axis (red line)
|
||||
for ln in lines:
|
||||
p = ln.position_at(0.5)
|
||||
assert abs(p.Z) < 1e-9
|
||||
|
||||
lines = _make_tan_oriented_lines(unit_circle, Axis.X, 0, edge_factory=Edge)
|
||||
assert len(lines) == 0
|
||||
|
||||
lines = _make_tan_oriented_lines(
|
||||
unit_circle, Axis((0, -2), (1, 0)), 0, edge_factory=Edge
|
||||
)
|
||||
assert len(lines) == 0
|
||||
|
||||
|
||||
def test_oriented_tangents_with_y_axis(unit_circle):
|
||||
"""Lines tangent to a circle and 30° from Y-axis should exist."""
|
||||
angle = math.radians(30)
|
||||
lines = _make_tan_oriented_lines(unit_circle, Axis.Y, angle, edge_factory=Edge)
|
||||
assert len(lines) >= 1
|
||||
# They should roughly touch the circle (tangent distance ≈ 0)
|
||||
for ln in lines:
|
||||
assert unit_circle.distance_to(ln) < 1e-6
|
||||
|
||||
|
||||
def test_oriented_constrained_tangents_with_y_axis(unit_circle):
|
||||
angle = math.radians(30)
|
||||
lines = _make_tan_oriented_lines(
|
||||
(unit_circle, Tangency.ENCLOSING), Axis.Y, angle, edge_factory=Edge
|
||||
)
|
||||
assert len(lines) == 1
|
||||
for ln in lines:
|
||||
assert unit_circle.distance_to(ln) < 1e-6
|
||||
|
||||
|
||||
def test_invalid_oriented_tangent_raises(unit_circle):
|
||||
"""Non-intersecting degenerate input result in no output."""
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
_make_tan_oriented_lines(unit_circle, Axis.Z, 1, edge_factory=Edge)
|
||||
assert "reference Axis can't be perpendicular to Plane.XY" in str(excinfo.value)
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
_make_tan_oriented_lines(
|
||||
unit_circle, Axis((1, 2, 3), (0, 0, -1)), 1, edge_factory=Edge
|
||||
)
|
||||
assert "reference Axis can't be perpendicular to Plane.XY" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_invalid_oriented_tangent(unit_circle):
|
||||
lines = _make_tan_oriented_lines(
|
||||
unit_circle, Axis((1, 0), (0, 1)), 0, edge_factory=Edge
|
||||
)
|
||||
assert len(lines) == 0
|
||||
|
||||
lines = _make_tan_oriented_lines(
|
||||
unit_circle.translate((0, 1 + 1e-7)), Axis.X, 0, edge_factory=Edge
|
||||
)
|
||||
assert len(lines) == 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue