mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-05 18:20:46 -08:00
Add DoubleTangentArc
This commit is contained in:
parent
ec495d5aa6
commit
18aafed8e6
7 changed files with 180 additions and 3 deletions
13
docs/assets/double_tangent_line_example.svg
Normal file
13
docs/assets/double_tangent_line_example.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<svg width="40.09mm" height="101.09mm" viewBox="-0.01125 -25.01125 10.0225 25.2725" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="scale(1,-1)" stroke-linecap="round">
|
||||
<g fill="none" stroke="rgb(0,0,0)" stroke-width="0.0225">
|
||||
<path d="M 6.0,0.0 A 47.000000000000085,47.000000000000085 180.0 0,0 9.615385,18.076923" />
|
||||
<circle cx="6.0" cy="0.0" r="0.25" />
|
||||
</g>
|
||||
<g fill="none" stroke="rgb(0,0,0)" stroke-width="0.0225" id="dashed" stroke-dasharray="0.27 0.405">
|
||||
<path d="M 0.0,20.0 A 5.0,5.0 180.0 1,0 2.5,15.669873" />
|
||||
<line x1="6.0" y1="0.0" x2="6.0" y2="5.0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 680 B |
|
|
@ -17,6 +17,7 @@ Cheat Sheet
|
|||
|
||||
| :class:`~objects_curve.Bezier`
|
||||
| :class:`~objects_curve.CenterArc`
|
||||
| :class:`~objects_curve.DoubleTangentArc`
|
||||
| :class:`~objects_curve.EllipticalCenterArc`
|
||||
| :class:`~objects_curve.FilletPolyline`
|
||||
| :class:`~objects_curve.Helix`
|
||||
|
|
|
|||
|
|
@ -90,6 +90,13 @@ The following objects all can be used in BuildLine contexts. Note that
|
|||
+++
|
||||
Arc defined by center, radius, & angles
|
||||
|
||||
.. grid-item-card:: :class:`~objects_curve.DoubleTangentArc`
|
||||
|
||||
.. image:: assets/double_tangent_line_example.svg
|
||||
|
||||
+++
|
||||
Arc defined by point/tangent pair & other curve
|
||||
|
||||
.. grid-item-card:: :class:`~objects_curve.EllipticalCenterArc`
|
||||
|
||||
.. image:: assets/elliptical_center_arc_example.svg
|
||||
|
|
@ -189,6 +196,7 @@ Reference
|
|||
.. autoclass:: BaseLineObject
|
||||
.. autoclass:: Bezier
|
||||
.. autoclass:: CenterArc
|
||||
.. autoclass:: DoubleTangentArc
|
||||
.. autoclass:: EllipticalCenterArc
|
||||
.. autoclass:: FilletPolyline
|
||||
.. autoclass:: Helix
|
||||
|
|
|
|||
|
|
@ -246,7 +246,19 @@ svg.add_shape(other, "dashed")
|
|||
svg.add_shape(intersecting_line.line)
|
||||
svg.add_shape(dot.moved(Location(Vector((1, 0)))))
|
||||
svg.write("assets/intersecting_line_example.svg")
|
||||
show(other, intersecting_line)
|
||||
|
||||
with BuildLine() as double_tangent:
|
||||
l2 = JernArc(start=(0, 20), tangent=(0, 1), radius=5, arc_size=-300)
|
||||
l3 = DoubleTangentArc((6, 0), tangent=(0, 1), other=l2)
|
||||
s = 100 / max(*double_tangent.line.bounding_box().size)
|
||||
svg = ExportSVG(scale=s)
|
||||
svg.add_layer("dashed", line_type=LineType.ISO_DASH_SPACE)
|
||||
svg.add_shape(l2, "dashed")
|
||||
svg.add_shape(l3)
|
||||
svg.add_shape(dot.scale(5).moved(Pos(6, 0)))
|
||||
svg.add_shape(Edge.make_line((6, 0), (6, 5)), "dashed")
|
||||
svg.write("assets/double_tangent_line_example.svg")
|
||||
|
||||
# show_object(example_1.line, name="Ex. 1")
|
||||
# show_object(example_2.line, name="Ex. 2")
|
||||
# show_object(example_3.line, name="Ex. 3")
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ __all__ = [
|
|||
"BaseLineObject",
|
||||
"Bezier",
|
||||
"CenterArc",
|
||||
"DoubleTangentArc",
|
||||
"EllipticalCenterArc",
|
||||
"EllipticalStartArc",
|
||||
"FilletPolyline",
|
||||
|
|
|
|||
|
|
@ -29,13 +29,15 @@ license:
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import warnings
|
||||
from math import copysign, cos, radians, sin, sqrt
|
||||
from scipy.optimize import minimize
|
||||
from typing import Iterable, Union
|
||||
|
||||
from build123d.build_common import WorkplaneList, flatten_sequence, validate_inputs
|
||||
from build123d.build_enums import AngularDirection, GeomType, LengthMode, Mode
|
||||
from build123d.build_enums import AngularDirection, GeomType, Keep, LengthMode, Mode
|
||||
from build123d.build_line import BuildLine
|
||||
from build123d.geometry import Axis, Plane, Vector, VectorLike
|
||||
from build123d.geometry import Axis, Plane, Vector, VectorLike, TOLERANCE
|
||||
from build123d.topology import Edge, Face, Wire, Curve
|
||||
|
||||
|
||||
|
|
@ -149,6 +151,105 @@ class CenterArc(BaseLineObject):
|
|||
super().__init__(arc, mode=mode)
|
||||
|
||||
|
||||
class DoubleTangentArc(BaseLineObject):
|
||||
"""Line Object: Double Tangent Arc
|
||||
|
||||
Create an arc defined by a point/tangent pair and another line which the other end
|
||||
is tangent to.
|
||||
|
||||
Contains a solver.
|
||||
|
||||
Args:
|
||||
pnt (VectorLike): starting point of tangent arc
|
||||
tangent (VectorLike): tangent at starting point of tangent arc
|
||||
other (Union[Curve, Edge, Wire]): reference line
|
||||
keep (Keep, optional): selector for which arc to keep when two arcs are
|
||||
possible. The arc generated with TOP or BOTTOM depends on the geometry
|
||||
and isn't necessarily easy to predict. Defaults to Keep.TOP.
|
||||
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
|
||||
|
||||
Raises:
|
||||
RunTimeError: no double tangent arcs found
|
||||
"""
|
||||
|
||||
_applies_to = [BuildLine._tag]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pnt: VectorLike,
|
||||
tangent: VectorLike,
|
||||
other: Union[Curve, Edge, Wire],
|
||||
keep: Keep = Keep.TOP,
|
||||
mode: Mode = Mode.ADD,
|
||||
):
|
||||
context: BuildLine = BuildLine._get_context(self)
|
||||
validate_inputs(context, self)
|
||||
|
||||
arc_pt = WorkplaneList.localize(pnt)
|
||||
arc_tangent = WorkplaneList.localize(tangent)
|
||||
if WorkplaneList._get_context() is not None:
|
||||
workplane = WorkplaneList._get_context().workplanes[0]
|
||||
else:
|
||||
workplane = Edge.make_line(arc_pt, arc_pt + arc_tangent).common_plane(
|
||||
*other.edges()
|
||||
)
|
||||
if workplane is None:
|
||||
raise ValueError("DoubleTangentArc only works on a single plane")
|
||||
workplane = -workplane # Flip to help with TOP/BOTTOM
|
||||
rotation_axis = Axis((0, 0, 0), workplane.z_dir)
|
||||
# Protect against massive circles that are effectively straight lines
|
||||
max_size = 2 * other.bounding_box().add(arc_pt).diagonal
|
||||
|
||||
# Function to be minimized
|
||||
def func(radius, perpendicular_bisector):
|
||||
center = arc_pt + perpendicular_bisector * radius
|
||||
separation = other.distance_to(center)
|
||||
return abs(separation - radius)
|
||||
|
||||
# Minimize the function using bounds and the tolerance value
|
||||
arc_centers = []
|
||||
for angle in [90, -90]:
|
||||
perpendicular_bisector = arc_tangent.rotate(rotation_axis, angle)
|
||||
result = minimize(
|
||||
func,
|
||||
x0=0.0,
|
||||
args=perpendicular_bisector,
|
||||
method="Nelder-Mead",
|
||||
bounds=[(0.0, max_size)],
|
||||
tol=TOLERANCE,
|
||||
)
|
||||
arc_radius = result.x[0]
|
||||
arc_center = arc_pt + perpendicular_bisector * arc_radius
|
||||
|
||||
# Check for matching tangents
|
||||
circle = Edge.make_circle(
|
||||
arc_radius, Plane(arc_center, z_dir=rotation_axis.direction)
|
||||
)
|
||||
dist, p1, p2 = other.distance_to_with_closest_points(circle)
|
||||
if dist > TOLERANCE: # If they aren't touching
|
||||
continue
|
||||
other_axis = Axis(p1, other.tangent_at(p1))
|
||||
circle_axis = Axis(p2, circle.tangent_at(p2))
|
||||
if other_axis.is_parallel(circle_axis):
|
||||
arc_centers.append(arc_center)
|
||||
|
||||
if len(arc_centers) == 0:
|
||||
raise RuntimeError("No double tangent arcs found")
|
||||
|
||||
# If there are multiple solutions, select the desired one
|
||||
if keep == Keep.TOP:
|
||||
arc_centers = arc_centers[0:1]
|
||||
elif keep == Keep.BOTTOM:
|
||||
arc_centers = arc_centers[-1:]
|
||||
|
||||
with BuildLine() as double:
|
||||
for center in arc_centers:
|
||||
_, p1, _ = other.distance_to_with_closest_points(center)
|
||||
TangentArc(arc_pt, p1, tangent=arc_tangent)
|
||||
|
||||
super().__init__(double.line, mode=mode)
|
||||
|
||||
|
||||
class EllipticalStartArc(BaseLineObject):
|
||||
"""Line Object: Elliptical Start Arc
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,47 @@ class BuildLineTests(unittest.TestCase):
|
|||
Bezier(*pts, weights=wts)
|
||||
self.assertAlmostEqual(bz.wires()[0].length, 225.86389406824566, 5)
|
||||
|
||||
def test_double_tangent_arc(self):
|
||||
l1 = Line((10, 0), (30, 20))
|
||||
l2 = DoubleTangentArc((0, 5), (1, 0), l1)
|
||||
_, p1, p2 = l1.distance_to_with_closest_points(l2)
|
||||
self.assertTupleAlmostEquals(tuple(p1), tuple(p2), 5)
|
||||
self.assertTupleAlmostEquals(
|
||||
tuple(l1.tangent_at(p1)), tuple(l2.tangent_at(p2)), 5
|
||||
)
|
||||
|
||||
l3 = Line((10, 0), (20, -10))
|
||||
l4 = DoubleTangentArc((0, 0), (1, 0), l3)
|
||||
_, p1, p2 = l3.distance_to_with_closest_points(l4)
|
||||
self.assertTupleAlmostEquals(tuple(p1), tuple(p2), 5)
|
||||
self.assertTupleAlmostEquals(
|
||||
tuple(l3.tangent_at(p1)), tuple(l4.tangent_at(p2)), 5
|
||||
)
|
||||
|
||||
with BuildLine() as test:
|
||||
l5 = Polyline((20, -10), (10, 0), (20, 10))
|
||||
l6 = DoubleTangentArc((0, 0), (1, 0), l5, keep=Keep.BOTTOM)
|
||||
_, p1, p2 = l5.distance_to_with_closest_points(l6)
|
||||
self.assertTupleAlmostEquals(tuple(p1), tuple(p2), 5)
|
||||
self.assertTupleAlmostEquals(
|
||||
tuple(l5.tangent_at(p1)), tuple(l6.tangent_at(p2) * -1), 5
|
||||
)
|
||||
|
||||
l7 = Spline((15, 5), (5, 0), (15, -5), tangents=[(-1, 0), (1, 0)])
|
||||
l8 = DoubleTangentArc((0, 0, 0), (1, 0, 0), l7, keep=Keep.BOTH)
|
||||
self.assertEqual(len(l8.edges()), 2)
|
||||
|
||||
l9 = EllipticalCenterArc((15, 0), 10, 5, start_angle=90, end_angle=270)
|
||||
l10 = DoubleTangentArc((0, 0, 0), (1, 0, 0), l9, keep=Keep.BOTH)
|
||||
self.assertEqual(len(l10.edges()), 2)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
DoubleTangentArc((0, 0, 0), (0, 0, 1), l9)
|
||||
|
||||
l11 = Line((10, 0), (20, 0))
|
||||
with self.assertRaises(RuntimeError):
|
||||
DoubleTangentArc((0, 0, 0), (1, 0, 0), l11)
|
||||
|
||||
def test_elliptical_start_arc(self):
|
||||
with self.assertRaises(RuntimeError):
|
||||
with BuildLine():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue