mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Most functionality working
This commit is contained in:
parent
94d0d2a868
commit
2efa2a3a09
3 changed files with 172 additions and 56 deletions
|
|
@ -1272,7 +1272,7 @@ class PointArcTangentArc(BaseEdgeObject):
|
||||||
|
|
||||||
# Confirm new tangent point is colinear with point tangent on arc
|
# Confirm new tangent point is colinear with point tangent on arc
|
||||||
arc_dir = arc.tangent_at(tangent_point)
|
arc_dir = arc.tangent_at(tangent_point)
|
||||||
if tangent_dir.cross(arc_dir).length > TOLERANCE:
|
if tangent_dir.cross(arc_dir).length > TOLERANCE * 10:
|
||||||
raise RuntimeError("No tangent arc found, found tangent out of tolerance.")
|
raise RuntimeError("No tangent arc found, found tangent out of tolerance.")
|
||||||
|
|
||||||
arc = TangentArc(arc_point, tangent_point, tangent=arc_tangent)
|
arc = TangentArc(arc_point, tangent_point, tangent=arc_tangent)
|
||||||
|
|
|
||||||
|
|
@ -484,6 +484,52 @@ class Mixin1D(Shape):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def derivative_at(
|
||||||
|
self,
|
||||||
|
position: float | VectorLike,
|
||||||
|
order: int = 2,
|
||||||
|
position_mode: PositionMode = PositionMode.PARAMETER,
|
||||||
|
) -> Vector:
|
||||||
|
"""Derivative At
|
||||||
|
|
||||||
|
Generate a derivative along the underlying curve.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position (float | VectorLike): distance, parameter value or point
|
||||||
|
order (int): derivative order. Defaults to 2
|
||||||
|
position_mode (PositionMode, optional): position calculation mode. Defaults to
|
||||||
|
PositionMode.PARAMETER.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: position must be a float or a point
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Vector: position on the underlying curve
|
||||||
|
"""
|
||||||
|
if isinstance(position, (float, int)):
|
||||||
|
comp_curve, occt_param = self._occt_param_at(position, position_mode)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
point_on_curve = Vector(position)
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValueError("position must be a float or a point") from exc
|
||||||
|
if isinstance(self, Wire):
|
||||||
|
closest_edge = min(
|
||||||
|
self.edges(), key=lambda e: e.distance_to(point_on_curve)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
closest_edge = self
|
||||||
|
u_value = closest_edge.param_at_point(point_on_curve)
|
||||||
|
comp_curve, occt_param = closest_edge._occt_param_at(u_value)
|
||||||
|
|
||||||
|
derivative_gp_vec = comp_curve.DN(occt_param, order)
|
||||||
|
if derivative_gp_vec.Magnitude() == 0:
|
||||||
|
return Vector(0, 0, 0)
|
||||||
|
|
||||||
|
if self.is_forward:
|
||||||
|
return Vector(derivative_gp_vec)
|
||||||
|
return Vector(derivative_gp_vec) * -1
|
||||||
|
|
||||||
def edge(self) -> Edge | None:
|
def edge(self) -> Edge | None:
|
||||||
"""Return the Edge"""
|
"""Return the Edge"""
|
||||||
return Shape.get_single_shape(self, "Edge")
|
return Shape.get_single_shape(self, "Edge")
|
||||||
|
|
@ -822,11 +868,11 @@ class Mixin1D(Shape):
|
||||||
return line
|
return line
|
||||||
|
|
||||||
def position_at(
|
def position_at(
|
||||||
self, distance: float, position_mode: PositionMode = PositionMode.PARAMETER
|
self, position: float, position_mode: PositionMode = PositionMode.PARAMETER
|
||||||
) -> Vector:
|
) -> Vector:
|
||||||
"""Position At
|
"""Position At
|
||||||
|
|
||||||
Generate a position along the underlying curve.
|
Generate a position along the underlying Wire.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
distance (float): distance or parameter value
|
distance (float): distance or parameter value
|
||||||
|
|
@ -836,18 +882,12 @@ class Mixin1D(Shape):
|
||||||
Returns:
|
Returns:
|
||||||
Vector: position on the underlying curve
|
Vector: position on the underlying curve
|
||||||
"""
|
"""
|
||||||
curve = self.geom_adaptor()
|
# Find the TopoDS_Edge and parameter on that edge at given position
|
||||||
|
edge_curve_adaptor, occt_edge_param = self._occt_param_at(
|
||||||
|
position, position_mode
|
||||||
|
)
|
||||||
|
|
||||||
if position_mode == PositionMode.PARAMETER:
|
return Vector(edge_curve_adaptor.Value(occt_edge_param))
|
||||||
if not self.is_forward:
|
|
||||||
distance = 1 - distance
|
|
||||||
param = self.param_at(distance)
|
|
||||||
else:
|
|
||||||
if not self.is_forward:
|
|
||||||
distance = self.length - distance
|
|
||||||
param = self.param_at(distance / self.length)
|
|
||||||
|
|
||||||
return Vector(curve.Value(param))
|
|
||||||
|
|
||||||
def positions(
|
def positions(
|
||||||
self,
|
self,
|
||||||
|
|
@ -1191,51 +1231,10 @@ class Mixin1D(Shape):
|
||||||
position_mode (PositionMode, optional): position calculation mode.
|
position_mode (PositionMode, optional): position calculation mode.
|
||||||
Defaults to PositionMode.PARAMETER.
|
Defaults to PositionMode.PARAMETER.
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: invalid position
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Vector: tangent value
|
Vector: tangent value
|
||||||
"""
|
"""
|
||||||
|
return self.derivative_at(position, 1, position_mode).normalized()
|
||||||
if isinstance(position, (float, int)):
|
|
||||||
if not self.is_forward:
|
|
||||||
if position_mode == PositionMode.PARAMETER:
|
|
||||||
position = 1 - position
|
|
||||||
else:
|
|
||||||
position = self.length - position
|
|
||||||
|
|
||||||
curve = self.geom_adaptor()
|
|
||||||
if position_mode == PositionMode.PARAMETER:
|
|
||||||
parameter = self.param_at(position)
|
|
||||||
else:
|
|
||||||
parameter = self.param_at(position / self.length)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
pnt = Vector(position)
|
|
||||||
except Exception as exc:
|
|
||||||
raise ValueError("position must be a float or a point") from exc
|
|
||||||
# GeomAPI_ProjectPointOnCurve only works with Edges so find
|
|
||||||
# the closest Edge if the shape has multiple Edges.
|
|
||||||
my_edges: list[Edge] = self.edges()
|
|
||||||
distances = [(e.distance_to(pnt), i) for i, e in enumerate(my_edges)]
|
|
||||||
sorted_distances = sorted(distances, key=lambda x: x[0])
|
|
||||||
closest_edge = my_edges[sorted_distances[0][1]]
|
|
||||||
# Get the extreme of the parameter values for this Edge
|
|
||||||
first: float = closest_edge.param_at(0)
|
|
||||||
last: float = closest_edge.param_at(1)
|
|
||||||
# Extract the Geom_Curve from the Shape
|
|
||||||
curve = BRep_Tool.Curve_s(closest_edge.wrapped, first, last)
|
|
||||||
projector = GeomAPI_ProjectPointOnCurve(pnt.to_pnt(), curve)
|
|
||||||
parameter = projector.LowerDistanceParameter()
|
|
||||||
|
|
||||||
tmp = gp_Pnt()
|
|
||||||
res = gp_Vec()
|
|
||||||
curve.D1(parameter, tmp, res)
|
|
||||||
|
|
||||||
if self.is_forward:
|
|
||||||
return Vector(gp_Dir(res))
|
|
||||||
return Vector(gp_Dir(res)) * -1
|
|
||||||
|
|
||||||
def vertex(self) -> Vertex | None:
|
def vertex(self) -> Vertex | None:
|
||||||
"""Return the Vertex"""
|
"""Return the Vertex"""
|
||||||
|
|
@ -1789,6 +1788,36 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
|
|
||||||
return return_value
|
return return_value
|
||||||
|
|
||||||
|
# def derivative_at(
|
||||||
|
# self,
|
||||||
|
# position: float,
|
||||||
|
# order: int = 2,
|
||||||
|
# position_mode: PositionMode = PositionMode.PARAMETER,
|
||||||
|
# ) -> Vector:
|
||||||
|
# """Derivative At
|
||||||
|
|
||||||
|
# Generate a derivative along the underlying curve.
|
||||||
|
|
||||||
|
# Args:
|
||||||
|
# position (float): distance or parameter value
|
||||||
|
# order (int): derivative order. Defaults to 2
|
||||||
|
# position_mode (PositionMode, optional): position calculation mode. Defaults to
|
||||||
|
# PositionMode.PARAMETER.
|
||||||
|
|
||||||
|
# Returns:
|
||||||
|
# Vector: position on the underlying curve
|
||||||
|
# """
|
||||||
|
# comp_curve, occt_param = self._occt_param_at(position, position_mode)
|
||||||
|
# derivative_gp_vec = comp_curve.DN(occt_param, order)
|
||||||
|
# if derivative_gp_vec.Magnitude() == 0:
|
||||||
|
# return Vector(0, 0, 0)
|
||||||
|
# else:
|
||||||
|
# gp_dir = gp_Dir(derivative_gp_vec)
|
||||||
|
|
||||||
|
# if self.is_forward:
|
||||||
|
# return Vector(gp_dir)
|
||||||
|
# return Vector(gp_dir) * -1
|
||||||
|
|
||||||
def distribute_locations(
|
def distribute_locations(
|
||||||
self: Wire | Edge,
|
self: Wire | Edge,
|
||||||
count: int,
|
count: int,
|
||||||
|
|
@ -2092,6 +2121,26 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
return ShapeList(common_vertices + common_edges)
|
return ShapeList(common_vertices + common_edges)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _occt_param_at(
|
||||||
|
self, position: float, position_mode: PositionMode = PositionMode.PARAMETER
|
||||||
|
) -> tuple[BRepAdaptor_CompCurve, float]:
|
||||||
|
comp_curve = self.geom_adaptor()
|
||||||
|
length = GCPnts_AbscissaPoint.Length_s(comp_curve)
|
||||||
|
|
||||||
|
if position_mode == PositionMode.PARAMETER:
|
||||||
|
if not self.is_forward:
|
||||||
|
position = 1 - position
|
||||||
|
value = position
|
||||||
|
else:
|
||||||
|
if not self.is_forward:
|
||||||
|
position = self.length - position
|
||||||
|
value = position / self.length
|
||||||
|
|
||||||
|
occt_param = GCPnts_AbscissaPoint(
|
||||||
|
comp_curve, length * value, comp_curve.FirstParameter()
|
||||||
|
).Parameter()
|
||||||
|
return comp_curve, occt_param
|
||||||
|
|
||||||
def param_at_point(self, point: VectorLike) -> float:
|
def param_at_point(self, point: VectorLike) -> float:
|
||||||
"""param_at_point
|
"""param_at_point
|
||||||
|
|
||||||
|
|
@ -2163,6 +2212,24 @@ class Edge(Mixin1D, Shape[TopoDS_Edge]):
|
||||||
|
|
||||||
raise RuntimeError("Unable to find parameter, Edge is too complex")
|
raise RuntimeError("Unable to find parameter, Edge is too complex")
|
||||||
|
|
||||||
|
# def position_at(
|
||||||
|
# self, position: float, position_mode: PositionMode = PositionMode.PARAMETER
|
||||||
|
# ) -> Vector:
|
||||||
|
# """Position At
|
||||||
|
|
||||||
|
# Generate a position along the underlying curve.
|
||||||
|
|
||||||
|
# Args:
|
||||||
|
# position (float): distance or parameter value
|
||||||
|
# position_mode (PositionMode, optional): position calculation mode. Defaults to
|
||||||
|
# PositionMode.PARAMETER.
|
||||||
|
|
||||||
|
# Returns:
|
||||||
|
# Vector: position on the underlying curve
|
||||||
|
# """
|
||||||
|
# comp_curve, occt_param = self._occt_param_at(position, position_mode)
|
||||||
|
# return Vector(comp_curve.Value(occt_param))
|
||||||
|
|
||||||
def project_to_shape(
|
def project_to_shape(
|
||||||
self,
|
self,
|
||||||
target_object: Shape,
|
target_object: Shape,
|
||||||
|
|
@ -3000,6 +3067,50 @@ class Wire(Mixin1D, Shape[TopoDS_Wire]):
|
||||||
|
|
||||||
return distance / wire_length
|
return distance / wire_length
|
||||||
|
|
||||||
|
def _occt_param_at(
|
||||||
|
self, position: float, position_mode: PositionMode = PositionMode.PARAMETER
|
||||||
|
) -> tuple[BRepAdaptor_CompCurve, float]:
|
||||||
|
wire_curve_adaptor = self.geom_adaptor()
|
||||||
|
|
||||||
|
if position_mode == PositionMode.PARAMETER:
|
||||||
|
if not self.is_forward:
|
||||||
|
position = 1 - position
|
||||||
|
occt_wire_param = self.param_at(position)
|
||||||
|
else:
|
||||||
|
if not self.is_forward:
|
||||||
|
position = self.length - position
|
||||||
|
occt_wire_param = self.param_at(position / self.length)
|
||||||
|
|
||||||
|
topods_edge_at_position = TopoDS_Edge()
|
||||||
|
occt_edge_params = wire_curve_adaptor.Edge(
|
||||||
|
occt_wire_param, topods_edge_at_position
|
||||||
|
)
|
||||||
|
edge_curve_adaptor = BRepAdaptor_Curve(topods_edge_at_position)
|
||||||
|
|
||||||
|
return edge_curve_adaptor, occt_edge_params[0]
|
||||||
|
|
||||||
|
# def position_at(
|
||||||
|
# self, position: float, position_mode: PositionMode = PositionMode.PARAMETER
|
||||||
|
# ) -> Vector:
|
||||||
|
# """Position At
|
||||||
|
|
||||||
|
# Generate a position along the underlying Wire.
|
||||||
|
|
||||||
|
# Args:
|
||||||
|
# distance (float): distance or parameter value
|
||||||
|
# position_mode (PositionMode, optional): position calculation mode. Defaults to
|
||||||
|
# PositionMode.PARAMETER.
|
||||||
|
|
||||||
|
# Returns:
|
||||||
|
# Vector: position on the underlying curve
|
||||||
|
# """
|
||||||
|
# # Find the TopoDS_Edge and parameter on that edge at given position
|
||||||
|
# edge_curve_adaptor, occt_edge_param = self._occt_param_at(
|
||||||
|
# position, position_mode
|
||||||
|
# )
|
||||||
|
|
||||||
|
# return Vector(edge_curve_adaptor.Value(occt_edge_param))
|
||||||
|
|
||||||
def project_to_shape(
|
def project_to_shape(
|
||||||
self,
|
self,
|
||||||
target_object: Shape,
|
target_object: Shape,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ import unittest
|
||||||
from math import sqrt, pi
|
from math import sqrt, pi
|
||||||
from build123d import *
|
from build123d import *
|
||||||
|
|
||||||
|
from ocp_vscode import show
|
||||||
|
|
||||||
|
|
||||||
def _assertTupleAlmostEquals(self, expected, actual, places, msg=None):
|
def _assertTupleAlmostEquals(self, expected, actual, places, msg=None):
|
||||||
"""Check Tuples"""
|
"""Check Tuples"""
|
||||||
|
|
@ -673,6 +675,9 @@ class BuildLineTests(unittest.TestCase):
|
||||||
|
|
||||||
# Check coincidence, tangency with each arc
|
# Check coincidence, tangency with each arc
|
||||||
_, p1, p2 = start_arc.distance_to_with_closest_points(l1)
|
_, p1, p2 = start_arc.distance_to_with_closest_points(l1)
|
||||||
|
a1 = Axis(p1, start_arc.tangent_at(p1))
|
||||||
|
a2 = Axis(p2, l1.tangent_at(p2))
|
||||||
|
show(start_arc, l1, p1, p2, a1, a2)
|
||||||
self.assertTupleAlmostEquals(tuple(p1), tuple(p2), 5)
|
self.assertTupleAlmostEquals(tuple(p1), tuple(p2), 5)
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
start_arc.tangent_at(p1).cross(l1.tangent_at(p2)).length, 0, 5
|
start_arc.tangent_at(p1).cross(l1.tangent_at(p2)).length, 0, 5
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue