Skip to content

Commit

Permalink
Refactored BuildLine to use a single workplane
Browse files Browse the repository at this point in the history
  • Loading branch information
gumyr committed Jan 29, 2023
1 parent 755994f commit b9e0fc4
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 69 deletions.
9 changes: 9 additions & 0 deletions src/build123d/build_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,15 @@ def _get_context(cls):
"""Return the instance of the current ContextList"""
return cls._current.get(None)

@classmethod
def localize(cls, *points: VectorLike) -> tuple(Vector):
workplane = WorkplaneList._get_context().workplanes[0]
localized_pts = [
workplane.from_local_coords(pt) if isinstance(pt, tuple) else pt
for pt in points
]
return localized_pts


class Workplanes(WorkplaneList):
"""Workplane Context: Workplanes
Expand Down
115 changes: 65 additions & 50 deletions src/build123d/build_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
limitations under the License.
"""
import copy
import inspect
from math import sin, cos, radians, degrees, atan2, sqrt, copysign
from typing import Union, Iterable
Expand All @@ -42,15 +43,31 @@
Face,
Plane,
)
from build123d.build_common import Builder, logger
from build123d.build_common import Builder, WorkplaneList, logger


class BuildLine(Builder):
"""BuildLine
Create lines (objects with length but not area or volume) from edges or wires.
BuildLine only works with a single workplane which is used to convert tuples
as inputs to global coordinates. For example:
.. code::
with BuildLine(Plane.YZ) as radius_arc:
RadiusArc((1, 2), (2, 1), 1)
creates an arc from global points (0, 1, 2) to (0, 2, 1). Note that points
entered as Vector(x, y, z) are considered global and are not localized.
The workplane is also used to define planes parallel to the workplane that
arcs are created on.
Args:
workplane (Union[Face, Plane, Location], optional): plane used when local
coordinates are used and when creating arcs. Defaults to Plane.XY.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""

Expand All @@ -68,13 +85,13 @@ def _obj_name(self):

def __init__(
self,
*workplanes: Union[Face, Plane, Location],
workplane: Union[Face, Plane, Location] = Plane.XY,
mode: Mode = Mode.ADD,
):
self.initial_planes = workplanes
self.initial_plane = workplane
self.mode = mode
self.line: Compound = None
super().__init__(*workplanes, mode=mode)
super().__init__(workplane, mode=mode)

def faces(self):
"""faces() not implemented"""
Expand Down Expand Up @@ -193,7 +210,7 @@ def __init__(
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

polls = [Vector(p) for p in cntl_pnts]
polls = WorkplaneList.localize(*cntl_pnts)
curve = Edge.make_bezier(*polls, weights=weights)

context._add_to_context(curve, mode=mode)
Expand Down Expand Up @@ -226,43 +243,45 @@ def __init__(
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

center_point = WorkplaneList.localize(center)[0]
circle_workplane = copy.copy(WorkplaneList._get_context().workplanes[0])
circle_workplane.origin = center_point
arc_direction = (
AngularDirection.COUNTER_CLOCKWISE
if arc_size > 0
else AngularDirection.CLOCKWISE
)
points = []
if abs(arc_size) >= 360:
arc = Edge.make_circle(
radius,
Plane(center),
circle_workplane,
start_angle=start_angle,
end_angle=start_angle,
angular_direction=arc_direction,
)
else:

center_point = Vector(center)
points = []
points.append(
center_point
Vector(center)
+ radius * Vector(cos(radians(start_angle)), sin(radians(start_angle)))
)
points.append(
center_point
Vector(center)
+ radius
* Vector(
cos(radians(start_angle + arc_size / 2)),
sin(radians(start_angle + arc_size / 2)),
)
)
points.append(
center_point
Vector(center)
+ radius
* Vector(
cos(radians(start_angle + arc_size)),
sin(radians(start_angle + arc_size)),
)
)
points = WorkplaneList.localize(*points)
arc = Edge.make_three_point_arc(*points)

context._add_to_context(arc, mode=mode)
Expand Down Expand Up @@ -404,22 +423,24 @@ def __init__(
end_angle: float = 90.0,
rotation: float = 0.0,
angular_direction: AngularDirection = AngularDirection.COUNTER_CLOCKWISE,
plane: Plane = Plane.XY,
mode: Mode = Mode.ADD,
):
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

center_pnt = Vector(center)
plane.set_origin2d(center_pnt.X, center_pnt.Y)
center_pnt = WorkplaneList.localize(center)[0]
ellipse_workplane = copy.copy(WorkplaneList._get_context().workplanes[0])
ellipse_workplane.origin = center_pnt
curve = Edge.make_ellipse(
x_radius=x_radius,
y_radius=y_radius,
plane=plane,
plane=ellipse_workplane,
start_angle=start_angle,
end_angle=end_angle,
angular_direction=angular_direction,
).rotate(Axis(plane.origin, plane.z_dir.to_dir()), rotation)
).rotate(
Axis(ellipse_workplane.origin, ellipse_workplane.z_dir.to_dir()), rotation
)

context._add_to_context(curve, mode=mode)
super().__init__(curve.wrapped)
Expand Down Expand Up @@ -457,8 +478,9 @@ def __init__(
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

center_pnt = WorkplaneList.localize(center)[0]
helix = Wire.make_helix(
pitch, height, radius, center, direction, cone_angle, lefhand
pitch, height, radius, center_pnt, direction, cone_angle, lefhand
)
context._add_to_context(*helix.edges(), mode=mode)
super().__init__(helix.wrapped)
Expand All @@ -474,7 +496,6 @@ class JernArc(Edge):
tangent (VectorLike): tangent at start point
radius (float): arc radius
arc_size (float): arc size in degrees (negative to change direction)
plane (Plane, optional): plane containing arc. Defaults to Plane.XY.
mode (Mode, optional): combination mode. Defaults to Mode.ADD.
"""

Expand All @@ -486,23 +507,23 @@ def __init__(
tangent: VectorLike,
radius: float,
arc_size: float,
plane: Plane = Plane.XY,
mode: Mode = Mode.ADD,
):
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

self.start = Vector(start)
start = WorkplaneList.localize(start)[0]
self.start = start
start_tangent = Vector(tangent).normalized()
if not plane.contains(Axis(start, start_tangent)):
raise ValueError("plane must contain start point and tangent direction")
jern_workplane = copy.copy(WorkplaneList._get_context().workplanes[0])
jern_workplane.origin = start

arc_direction = copysign(1.0, arc_size)
self.center_point = self.start + start_tangent.rotate(
Axis(self.start, plane.z_dir), arc_direction * 90
Axis(self.start, jern_workplane.z_dir), arc_direction * 90
) * abs(radius)
self.end_of_arc = self.center_point + (self.start - self.center_point).rotate(
Axis(self.start, plane.z_dir), arc_size
Axis(self.start, jern_workplane.z_dir), arc_size
)
arc = Edge.make_tangent_arc(self.start, start_tangent, self.end_of_arc)

Expand All @@ -529,6 +550,7 @@ def __init__(self, *pts: VectorLike, mode: Mode = Mode.ADD):
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

pts = WorkplaneList.localize(*pts)
if len(pts) != 2:
raise ValueError("Line requires two pts")

Expand Down Expand Up @@ -568,15 +590,16 @@ def __init__(
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

start = WorkplaneList.localize(start)[0]

if angle is not None:
x_val = cos(radians(angle)) * length
y_val = sin(radians(angle)) * length
new_edge = Edge.make_line(
Vector(start), Vector(start) + Vector(x_val, y_val, 0)
)
end = WorkplaneList.localize((x_val, y_val))[0]
new_edge = Edge.make_line(start, start + end)
elif direction is not None:
new_edge = Edge.make_line(
Vector(start), Vector(start) + Vector(direction).normalized() * length
start, start + Vector(direction).normalized() * length
)
else:
raise ValueError("Either angle or direction must be provided")
Expand Down Expand Up @@ -608,7 +631,7 @@ def __init__(self, *pts: VectorLike, close: bool = False, mode: Mode = Mode.ADD)
if len(pts) < 3:
raise ValueError("polyline requires three or more pts")

lines_pts = [Vector(p) for p in pts]
lines_pts = WorkplaneList.localize(*pts)

new_edges = [
Edge.make_line(lines_pts[i], lines_pts[i + 1])
Expand Down Expand Up @@ -648,9 +671,7 @@ def __init__(
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

start = Vector(start_point)
end = Vector(end_point)

start, end = WorkplaneList.localize(start_point, end_point)
# Calculate the sagitta from the radius
length = end.sub(start).length / 2.0
try:
Expand Down Expand Up @@ -694,21 +715,14 @@ def __init__(
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

start = Vector(start_point)
end = Vector(end_point)
start, end = WorkplaneList.localize(start_point, end_point)
mid_point = (end + start) * 0.5

sagitta_vector = (end - start).normalized() * abs(sagitta)
if sagitta > 0:
sagitta_vector.X, sagitta_vector.Y = (
-sagitta_vector.Y,
sagitta_vector.X,
) # Rotate sagitta_vector +90 deg
else:
sagitta_vector.X, sagitta_vector.Y = (
sagitta_vector.Y,
-sagitta_vector.X,
) # Rotate sagitta_vector -90 deg
sagitta_workplane = copy.copy(WorkplaneList._get_context().workplanes[0])
sagitta_vector: Vector = (end - start).normalized() * abs(sagitta)
sagitta_vector = sagitta_vector.rotate(
Axis(sagitta_workplane.origin, sagitta_workplane.z_dir),
90 if sagitta > 0 else -90,
)

sag_point = mid_point + sagitta_vector

Expand Down Expand Up @@ -744,7 +758,8 @@ def __init__(
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

spline_pts = [Vector(pt) for pt in pts]
spline_pts = WorkplaneList.localize(*pts)

if tangents:
spline_tangents = [Vector(tangent) for tangent in tangents]
else:
Expand Down Expand Up @@ -798,7 +813,7 @@ def __init__(
context: BuildLine = BuildLine._get_context(self)
context.validate_inputs(self)

arc_pts = [Vector(p) for p in pts]
arc_pts = WorkplaneList.localize(*pts)
if len(arc_pts) != 2:
raise ValueError("tangent_arc requires two points")
arc_tangent = Vector(tangent)
Expand Down Expand Up @@ -833,7 +848,7 @@ def __init__(self, *pts: VectorLike, mode: Mode = Mode.ADD):

if len(pts) != 3:
raise ValueError("ThreePointArc requires three points")
points = [Vector(p) for p in pts]
points = WorkplaneList.localize(*pts)
arc = Edge.make_three_point_arc(*points)
context._add_to_context(arc, mode=mode)
super().__init__(arc.wrapped)
6 changes: 3 additions & 3 deletions src/build123d/build_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def _obj_name(self):
@property
def pending_edges_as_wire(self) -> Wire:
"""Return a wire representation of the pending edges"""
return Wire.make_wire(self.pending_edges)
return Wire.combine(self.pending_edges)[0]

def __init__(
self,
Expand Down Expand Up @@ -221,10 +221,9 @@ def _add_to_context(
self.last_faces = list(post_faces - pre_faces)
self.last_solids = list(post_solids - pre_solids)

self._add_to_pending(*new_edges)
for plane in WorkplaneList._get_context().workplanes:
global_faces = [plane.from_local_coords(face) for face in new_faces]
global_edges = [plane.from_local_coords(edge) for edge in new_edges]
self._add_to_pending(*global_edges)
self._add_to_pending(*global_faces, face_plane=plane)

@classmethod
Expand Down Expand Up @@ -726,6 +725,7 @@ def __init__(

if path is None:
path_wire = context.pending_edges_as_wire
context.pending_edges = []
else:
path_wire = Wire.make_wire([path]) if isinstance(path, Edge) else path

Expand Down
17 changes: 8 additions & 9 deletions src/build123d/direct_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3180,18 +3180,17 @@ def cut(self, *to_cut: Shape) -> Shape:
return self._bool_op((self,), to_cut, cut_op)

def fuse(self, *to_fuse: Shape, glue: bool = False, tol: float = None) -> Shape:
"""Fuse the positional arguments with this Shape.
"""fuse
Fuse a sequence of shapes into a single shape.
Args:
glue: Sets the glue option for the algorithm, which allows
increasing performance of the intersection of the input shapes
tol: Additional tolerance
*toFuse: Shape:
glue: bool: (Default value = False)
tol: float: (Default value = None)
to_fuse (sequence Shape): shapes to fuse
glue (bool, optional): performance improvement for some shapes. Defaults to False.
tol (float, optional): tolerarance. Defaults to None.
Returns:
Shape: fused shape
"""

fuse_op = BRepAlgoAPI_Fuse()
Expand All @@ -3208,7 +3207,7 @@ def intersect(self, *to_intersect: Shape) -> Shape:
"""Intersection of the positional arguments and this Shape.
Args:
*toIntersect: Shape:
toIntersect (sequence of Shape): shape to intersect
Returns:
Expand Down
Loading

0 comments on commit b9e0fc4

Please sign in to comment.