Introduced with notation for workplanes and points

This commit is contained in:
Roger Maitland 2022-09-15 09:28:05 -04:00
parent 81a6a2bde3
commit 69abdd9c45
19 changed files with 660 additions and 481 deletions

View file

@ -0,0 +1,9 @@
import build123d as bd
with bd.BuildPart() as bp:
bd.Box(3, 3, 3)
with bd.Workplanes(*bp.faces()):
bd.Box(1, 2, 0.1, rotation=(0, 0, 45))
if "show_object" in locals():
show_object(bp.part, name="box on faces")

View file

@ -42,15 +42,15 @@ with BuildLine() as one:
TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)
with BuildSketch() as two:
PushPoints((font_height * 0.35, 0))
Text("2", fontsize=10, valign=Valign.BOTTOM)
with Locations((font_height * 0.35, 0)):
Text("2", fontsize=10, valign=Valign.BOTTOM)
with BuildPart() as three_d:
PushPoints((font_height * 1.1, 0))
with BuildSketch():
Text("3d", fontsize=10, valign=Valign.BOTTOM)
Extrude(font_height * 0.3)
logo_width = three_d.vertices().sort_by(SortBy.X)[-1].x
with Locations((font_height * 1.1, 0)):
with BuildSketch():
Text("3d", fontsize=10, valign=Valign.BOTTOM)
Extrude(font_height * 0.3)
logo_width = three_d.vertices().sort_by(SortBy.X)[-1].x
with BuildLine() as arrow_left:
t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))
@ -64,20 +64,20 @@ with BuildLine() as extension_lines:
(logo_width, -font_height * 0.1),
(logo_width, -ext_line_length - font_height * 0.1),
)
PushPoints(l1 @ 0.5)
Add(*arrow_left.line)
PushPoints(l2 @ 0.5)
Add(*arrow_left.line, rotation=180.0)
with Locations(l1 @ 0.5):
Add(*arrow_left.line)
with Locations(l2 @ 0.5):
Add(*arrow_left.line, rotation=180.0)
Line(l1 @ 0.5, l1 @ 0.5 + cq.Vector(dim_line_length, 0))
Line(l2 @ 0.5, l2 @ 0.5 - cq.Vector(dim_line_length, 0))
# Precisely center the build Faces
with BuildSketch() as build:
PushPoints(
with Locations(
(l1 @ 0.5 + l2 @ 0.5) / 2
- cq.Vector((build_vertices[-1].x + build_vertices[0].x) / 2, 0)
)
Add(build_text.sketch)
):
Add(build_text.sketch)
logo = cq.Assembly(None, name="logo")
logo.add(one.line_as_wire, name="one")
@ -86,23 +86,24 @@ logo.add(three_d.part, name="three_d")
for line in extension_lines.line:
logo.add(line)
logo.add(build.sketch, name="build")
logo.save("logo.step")
cq.exporters.export(
logo.toCompound(),
"logo.svg",
opt={
# "width": 300,
# "height": 300,
# "marginLeft": 10,
# "marginTop": 10,
"showAxes": False,
# "projectionDir": (0.5, 0.5, 0.5),
"strokeWidth": 0.1,
# "strokeColor": (255, 0, 0),
# "hiddenColor": (0, 0, 255),
"showHidden": False,
},
)
if False:
logo.save("logo.step")
cq.exporters.export(
logo.toCompound(),
"logo.svg",
opt={
# "width": 300,
# "height": 300,
# "marginLeft": 10,
# "marginTop": 10,
"showAxes": False,
# "projectionDir": (0.5, 0.5, 0.5),
"strokeWidth": 0.1,
# "strokeColor": (255, 0, 0),
# "hiddenColor": (0, 0, 255),
"showHidden": False,
},
)
if "show_object" in locals():
show_object(one.line, name="one")

View file

@ -47,8 +47,8 @@ with BuildSketch() as leaf:
BuildFace(*leaf.pending_edges)
with BuildSketch() as west_field:
PushPoints((-1, 0))
Rectangle(0.5, 1, centered=(False, False))
with Locations((-1, 0)):
Rectangle(0.5, 1, centered=(False, False))
with BuildSketch() as east_field:
Mirror(west_field.sketch, axis=Axis.Y)

19
examples/circuit_board.py Normal file
View file

@ -0,0 +1,19 @@
from build123d import *
with BuildPart() as pcb:
with BuildSketch():
Rectangle(70, 30)
for i in range(65 // 5):
x = i * 5 - 30
with Locations((x, -15), (x, -10), (x, 10), (x, 15)):
Circle(1, mode=Mode.SUBTRACT)
for i in range(30 // 5 - 1):
y = i * 5 - 10
with Locations((30, y), (35, y)):
Circle(1, mode=Mode.SUBTRACT)
with GridLocations(60, 20, 2, 2):
Circle(2, mode=Mode.SUBTRACT)
Extrude(3)
if "show_object" in locals():
show_object(pcb.part)

View file

@ -38,19 +38,19 @@ with BuildSketch() as minute_indicator:
with BuildSketch() as clock_face:
Circle(clock_radius)
PolarArray(0, 0, 360, 60)
Add(minute_indicator.sketch, mode=Mode.SUBTRACT)
PolarArray(clock_radius * 0.875, 0, 360, 12)
SlotOverall(clock_radius * 0.05, clock_radius * 0.025, mode=Mode.SUBTRACT)
with PolarLocations(0, 0, 360, 60):
Add(minute_indicator.sketch, mode=Mode.SUBTRACT)
with PolarLocations(clock_radius * 0.875, 0, 360, 12):
SlotOverall(clock_radius * 0.05, clock_radius * 0.025, mode=Mode.SUBTRACT)
for hour in range(1, 13):
PolarArray(clock_radius * 0.75, -hour * 30 + 90, 360, 1, rotate=False)
Text(
str(hour),
fontsize=clock_radius * 0.175,
font_style=FontStyle.BOLD,
halign=Halign.CENTER,
mode=Mode.SUBTRACT,
)
with PolarLocations(clock_radius * 0.75, -hour * 30 + 90, 360, 1, rotate=False):
Text(
str(hour),
fontsize=clock_radius * 0.175,
font_style=FontStyle.BOLD,
halign=Halign.CENTER,
mode=Mode.SUBTRACT,
)
if "show_object" in locals():
show_object(clock_face.sketch, name="clock_face")

View file

@ -62,20 +62,18 @@ with BuildPart(workplane="XZ") as rail:
)
)
Fillet(*inside_vertices, radius=fillet)
outside_vertices = list(
filter(
lambda v: (v.Y == 0.0 or v.Y == height)
and -overall_width / 2 < v.X < overall_width / 2,
din.vertices(),
)
outside_vertices = filter(
lambda v: (v.Y == 0.0 or v.Y == height)
and -overall_width / 2 < v.X < overall_width / 2,
din.vertices(),
)
Fillet(*outside_vertices, radius=fillet + thickness)
Extrude(rail_length)
WorkplanesFromFaces(rail.faces().filter_by_axis(Axis.Z)[-1], replace=True)
with BuildSketch() as slots:
RectangularArray(0, slot_pitch, 1, rail_length // slot_pitch - 1)
SlotOverall(slot_length, slot_width, rotation=90)
slot_holes = Extrude(-height, mode=Mode.SUBTRACT)
with Workplanes(rail.faces().filter_by_axis(Axis.Z)[-1]):
with BuildSketch() as slots:
with GridLocations(0, slot_pitch, 1, rail_length // slot_pitch - 1):
SlotOverall(slot_length, slot_width, rotation=90)
Extrude(-height, mode=Mode.SUBTRACT)
if "show_object" in locals():
show_object(rail.part, name="rail")

View file

@ -44,20 +44,20 @@ with BuildPart() as handle:
# Create the cross sections - added to pending_faces
for i in range(segment_count + 1):
Workplanes(
with Workplanes(
Plane(
origin=handle_path @ (i / segment_count),
normal=handle_path % (i / segment_count),
)
)
with BuildSketch() as section:
if i % segment_count == 0:
Circle(1)
else:
Rectangle(1, 2)
Fillet(*section.vertices(), radius=0.2)
):
with BuildSketch() as section:
if i % segment_count == 0:
Circle(1)
else:
Rectangle(1.25, 3)
Fillet(*section.vertices(), radius=0.2)
# Record the sections for display
sections = list(*handle.pending_faces.values())
sections = handle.pending_faces
# Create the handle by sweeping along the path
Sweep(multisection=True)

View file

@ -0,0 +1,19 @@
from build123d import *
with BuildPart() as blocks:
with Locations((-1, -1, 0)):
Box(1, 2, 1, centered=(True, False, False))
Box(1, 1, 2, centered=(True, False, False))
with Locations((1, -1, 0)):
Box(1, 2, 1, centered=(True, False, False))
bottom_edges = blocks.edges().filter_by_position(
Axis.Z, 0, 1, inclusive=(True, False)
)
Chamfer(*bottom_edges, length=0.1)
top_edges = blocks.edges().filter_by_position(Axis.Z, 1, 2, inclusive=(False, True))
Chamfer(*top_edges, length=0.1)
if "show_object" in locals():
show_object(blocks.part)

View file

@ -29,15 +29,15 @@ from build123d import *
with BuildPart() as pipes:
Box(10, 10, 10, rotation=(10, 20, 30))
WorkplanesFromFaces(*pipes.faces(), replace=True)
with BuildSketch() as pipe:
Circle(4)
Extrude(-5, mode=Mode.SUBTRACT)
with BuildSketch() as pipe:
Circle(4.5)
Circle(4, mode=Mode.SUBTRACT)
Extrude(10)
Fillet(*pipes.edges(Select.LAST), radius=0.2)
with Workplanes(*pipes.faces()):
with BuildSketch() as pipe:
Circle(4)
Extrude(-5, mode=Mode.SUBTRACT)
with BuildSketch() as pipe:
Circle(4.5)
Circle(4, mode=Mode.SUBTRACT)
Extrude(10)
Fillet(*pipes.edges(Select.LAST), radius=0.2)
if "show_object" in locals():
show_object(pipes.part, name="intersecting pipes")

96
examples/lego.py Normal file
View file

@ -0,0 +1,96 @@
"""
name: lego.py
by: Gumyr
date: September 12th 2022
desc:
This example creates a model of a double wide lego block with a
parametric length (pip_count).
license:
Copyright 2022 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 under the License is 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.
"""
from build123d import *
from cadquery import Plane
pip_count = 6
lego_unit_size = 8
pip_height = 1.8
pip_diameter = 4.8
block_length = lego_unit_size * pip_count
block_width = 16
base_height = 9.6
block_height = base_height + pip_height
support_outer_diameter = 6.5
support_inner_diameter = 4.8
ridge_width = 0.6
ridge_depth = 0.3
wall_thickness = 1.2
with BuildPart() as lego:
# Draw the bottom of the block
with BuildSketch():
# Start with a Rectangle the size of the block
perimeter = Rectangle(block_length, block_width)
# Subtract an offset to create the block walls
Offset(
perimeter,
amount=-wall_thickness,
kind=Kind.INTERSECTION,
mode=Mode.SUBTRACT,
)
# Add a grid of lengthwise and widthwise bars
with GridLocations(0, lego_unit_size, 1, 2):
Rectangle(block_length, ridge_width)
with GridLocations(lego_unit_size, 0, pip_count, 1):
Rectangle(ridge_width, block_width)
# Substract a rectangle leaving ribs on the block walls
Rectangle(
block_length - 2 * (wall_thickness + ridge_depth),
block_width - 2 * (wall_thickness + ridge_depth),
mode=Mode.SUBTRACT,
)
# Add a row of hollow circles to the center
with GridLocations(lego_unit_size, 0, pip_count - 1, 1):
Circle(support_outer_diameter / 2)
Circle(support_inner_diameter / 2, mode=Mode.SUBTRACT)
# Extrude this base sketch to the height of the walls
Extrude(base_height - wall_thickness)
# Create a workplane on the top of the walls
with Workplanes(
Plane(origin=(0, 0, base_height - wall_thickness), normal=(0, 0, 1))
):
# Create the top of the block
Box(
block_length,
block_width,
wall_thickness,
centered=(True, True, False),
)
# Create a workplane on the top of the block
with Workplanes(lego.faces().sort_by(SortBy.Z)[-1]):
# Create a grid of pips
with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):
Cylinder(
radius=pip_diameter / 2, height=pip_height, centered=(True, True, False)
)
if "show_object" in locals():
show_object(lego.part, name="lego")

View file

@ -32,9 +32,9 @@ from build123d import *
with BuildPart() as art:
slice_count = 10
for i in range(slice_count + 1):
Workplanes(Plane(origin=(0, 0, i * 3), normal=(0, 0, 1)))
with BuildSketch() as slice:
Circle(10 * sin(i * pi / slice_count) + 5)
with Workplanes(Plane(origin=(0, 0, i * 3), normal=(0, 0, 1))):
with BuildSketch() as slice:
Circle(10 * sin(i * pi / slice_count) + 5)
Loft()
top_bottom = art.faces().filter_by_type(Type.PLANE)
Shell(*top_bottom, thickness=0.5)

View file

@ -2,8 +2,8 @@ from build123d import *
with BuildPart() as obj:
Box(5, 5, 1)
WorkplanesFromFaces(*obj.faces().filter_by_axis(Axis.Z))
Sphere(1.8, mode=Mode.SUBTRACT)
with Workplanes(*obj.faces().filter_by_axis(Axis.Z)):
Sphere(1.8, mode=Mode.SUBTRACT)
if "show_object" in locals():
show_object(obj.part)

View file

@ -37,10 +37,10 @@ with BuildPart() as pillow_block:
Rectangle(width, height)
Fillet(*plan.vertices(), radius=5)
Extrude(thickness)
WorkplanesFromFaces(pillow_block.faces().filter_by_axis(Axis.Z)[-1])
CounterBoreHole(bearing_axle_radius, bearing_radius, bearing_thickness)
RectangularArray(width - 2 * padding, height - 2 * padding, 2, 2)
CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height)
with Workplanes(pillow_block.faces().filter_by_axis(Axis.Z)[-1]):
CounterBoreHole(bearing_axle_radius, bearing_radius, bearing_thickness)
with GridLocations(width - 2 * padding, height - 2 * padding, 2, 2):
CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height)
# Render the part
if "show_object" in locals():

16
examples/washer.py Normal file
View file

@ -0,0 +1,16 @@
from build123d import *
with BuildPart() as simple_washer:
Cylinder(3, 2)
Hole(1)
with BuildPart() as washer:
with Locations((10, 0)):
Cylinder(3, 2)
with Workplanes(washer.faces().sort_by(SortBy.Z)[-1]):
Hole(1)
if "show_object" in locals():
show_object(simple_washer.part, name="simple_washer")
show_object(washer.part, name="washer")

View file

@ -24,11 +24,11 @@ __all__ = [
"BoundingBox",
"Chamfer",
"Fillet",
"HexArray",
"HexLocations",
"Mirror",
"PolarArray",
"PushPoints",
"RectangularArray",
"PolarLocations",
"Locations",
"GridLocations",
"BuildLine",
"CenterArc",
"Helix",
@ -52,7 +52,6 @@ __all__ = [
"Split",
"Sweep",
"Workplanes",
"WorkplanesFromFaces",
"Box",
"Cone",
"Cylinder",
@ -74,4 +73,6 @@ __all__ = [
"SlotOverall",
"Text",
"Trapezoid",
"LocationList",
"WorkplaneList",
]

View file

@ -30,6 +30,7 @@ license:
"""
import contextvars
from itertools import product
from abc import ABC, abstractmethod
from math import radians
from typing import Iterable, Union
@ -46,6 +47,7 @@ from cadquery import (
Vertex,
Plane,
)
from cadquery.occ_impl.shapes import VectorLike
from OCP.gp import gp_Pnt, gp_Ax1, gp_Dir, gp_Trsf
import cq_warehouse.extensions
import logging
@ -520,8 +522,9 @@ class Builder(ABC):
"Builder._current"
)
def __init__(self, mode: Mode = Mode.ADD):
def __init__(self, mode: Mode = Mode.ADD, initial_plane: Plane = Plane.named("XY")):
self.mode = mode
self.initial_plane = initial_plane
self._reset_tok = None
self._parent = None
self.last_vertices = []
@ -532,6 +535,10 @@ class Builder(ABC):
self._parent = Builder._get_context()
self._reset_tok = self._current.set(self)
# Push an initial plane and point
self.workplane_generator = Workplanes(self.initial_plane).__enter__()
logger.info(f"Entering {type(self).__name__} with mode={self.mode}")
return self
@ -539,6 +546,10 @@ class Builder(ABC):
"""Upon exiting restore context and send object to parent"""
self._current.reset(self._reset_tok)
logger.info(f"Exiting {type(self).__name__}")
# Pop the initial plane and point
self.workplane_generator.__exit__(None, None, None)
if self._parent is not None:
logger.debug(
f"Transferring {len([o for o in self._obj])} to {type(self._parent).__name__}"
@ -571,3 +582,252 @@ class Builder(ABC):
here to avoid having to recreate this method.
"""
return cls._current.get(None)
class LocationList:
"""Context variable used to link to LocationList instance"""
_current: contextvars.ContextVar["LocationList"] = contextvars.ContextVar(
"ContextList._current"
)
def __init__(self, locations: list):
self._reset_tok = None
self.locations = locations
def __enter__(self):
"""Upon entering create a token to restore contextvars"""
self._reset_tok = self._current.set(self)
logger.info(f"{type(self).__name__} is pushing {len(self.locations)} points")
return self
def __exit__(self, exception_type, exception_value, traceback):
"""Upon exiting restore context"""
self._current.reset(self._reset_tok)
logger.info(f"{type(self).__name__} is popping {len(self.locations)} points")
@classmethod
def _get_context(cls):
"""Return the instance of the current LocationList"""
return cls._current.get(None)
class HexLocations(LocationList):
"""Location Generator: Hex Array
Creates a context of hexagon array of points.
Args:
diagonal: tip to tip size of hexagon ( must be > 0)
xCount: number of points ( > 0 )
yCount: number of points ( > 0 )
centered: specify centering along each axis.
Raises:
ValueError: Spacing and count must be > 0
"""
def __init__(
self,
diagonal: float,
xCount: int,
yCount: int,
centered: tuple[bool, bool] = (True, True),
):
xSpacing = 3 * diagonal / 4
ySpacing = diagonal * sqrt(3) / 2
if xSpacing <= 0 or ySpacing <= 0 or xCount < 1 or yCount < 1:
raise ValueError("Spacing and count must be > 0 ")
lpoints = [] # coordinates relative to bottom left point
for x in range(0, xCount, 2):
for y in range(yCount):
lpoints.append(Vector(xSpacing * x, ySpacing * y + ySpacing / 2))
for x in range(1, xCount, 2):
for y in range(yCount):
lpoints.append(Vector(xSpacing * x, ySpacing * y + ySpacing))
# shift points down and left relative to origin if requested
offset = Vector()
if centered[0]:
offset += Vector(-xSpacing * (xCount - 1) * 0.5, 0)
if centered[1]:
offset += Vector(0, -ySpacing * yCount * 0.5)
lpoints = [x + offset for x in lpoints]
# convert to locations
self.locations = [
Location(plane) * Location(pt)
for pt in lpoints
for plane in WorkplaneList._get_context().workplanes
]
super().__init__(self.locations)
class PolarLocations(LocationList):
"""Location Generator: Polar Array
Push a polar array of locations to Part or Sketch
Args:
radius (float): array radius
start_angle (float): angle to first point from +ve X axis
stop_angle (float): angle to last point from +ve X axis
count (int): Number of points to push
rotate (bool, optional): Align locations with arc tangents. Defaults to True.
Raises:
ValueError: Count must be greater than or equal to 1
"""
def __init__(
self,
radius: float,
start_angle: float,
stop_angle: float,
count: int,
rotate: bool = True,
):
if count < 1:
raise ValueError(f"At least 1 elements required, requested {count}")
angle_step = (stop_angle - start_angle) / count
# Note: rotate==False==0 so the location orientation doesn't change
self.locations = [
Location(plane)
* Location(
Vector(radius, 0).rotateZ(start_angle + angle_step * i),
Vector(0, 0, 1),
rotate * angle_step * i,
)
for i in range(count)
for plane in WorkplaneList._get_context().workplanes
]
super().__init__(self.locations)
class Locations(LocationList):
"""Location Generator: Push Points
Push sequence of locations to Part or Sketch
Args:
pts (Union[VectorLike, Vertex, Location]): sequence of points to push
"""
def __init__(self, *pts: Union[VectorLike, Vertex, Location]):
self.locations = []
for plane in WorkplaneList._get_context().workplanes:
for pt in pts:
if isinstance(pt, Location):
self.locations.append(Location(plane) * pt)
elif isinstance(pt, Vector):
self.locations.append(Location(plane) * Location(pt))
elif isinstance(pt, Vertex):
self.locations.append(
Location(plane) * Location(Vector(pt.toTuple()))
)
elif isinstance(pt, tuple):
self.locations.append(Location(plane) * Location(Vector(pt)))
else:
raise ValueError(f"Locations doesn't accept type {type(pt)}")
super().__init__(self.locations)
class GridLocations(LocationList):
"""Location Generator: Rectangular Array
Push a rectangular array of locations to Part or Sketch
Args:
x_spacing (float): horizontal spacing
y_spacing (float): vertical spacing
x_count (int): number of horizontal points
y_count (int): number of vertical points
Raises:
ValueError: Either x or y count must be greater than or equal to one.
"""
def __init__(self, x_spacing: float, y_spacing: float, x_count: int, y_count: int):
if x_count < 1 or y_count < 1:
raise ValueError(
f"At least 1 elements required, requested {x_count}, {y_count}"
)
self.locations = []
for plane in WorkplaneList._get_context().workplanes:
offset = Vector((x_count - 1) * x_spacing, (y_count - 1) * y_spacing) * 0.5
for i, j in product(range(x_count), range(y_count)):
self.locations.append(
Location(plane)
* Location(Vector(i * x_spacing, j * y_spacing) - offset)
)
super().__init__(self.locations)
class WorkplaneList:
"""Context variable used to link to WorkplaneList instance"""
_current: contextvars.ContextVar["WorkplaneList"] = contextvars.ContextVar(
"WorkplaneList._current"
)
def __init__(self, workplanes: list):
self._reset_tok = None
self.workplanes = workplanes
def __enter__(self):
"""Upon entering create a token to restore contextvars"""
self._reset_tok = self._current.set(self)
self.point_generator = Locations((0, 0, 0)).__enter__()
logger.info(
f"{type(self).__name__} is pushing {len(self.workplanes)} workplanes"
)
return self
def __exit__(self, exception_type, exception_value, traceback):
"""Upon exiting restore context"""
self._current.reset(self._reset_tok)
self.point_generator.__exit__(None, None, None)
logger.info(
f"{type(self).__name__} is popping {len(self.workplanes)} workplanes"
)
@classmethod
def _get_context(cls):
"""Return the instance of the current ContextList"""
return cls._current.get(None)
class Workplanes(WorkplaneList):
"""Workplane Generator: Workplanes
Create workplanes from the given sequence of planes.
Args:
objs (Union[Face,PlaneLike]): sequence of faces or planes to use
as workplanes.
Raises:
ValueError: invalid input
"""
def __init__(self, *objs: Union[Face, PlaneLike]):
self.workplanes = []
for obj in objs:
if isinstance(obj, Plane):
self.workplanes.append(obj)
elif isinstance(obj, str):
self.workplanes.append(Plane.named(obj))
elif isinstance(obj, Face):
self.workplanes.append(
Plane(origin=obj.Center(), normal=obj.normalAt(obj.Center()))
)
else:
raise ValueError(f"Workplanes does not accept {type(obj)}")
super().__init__(self.workplanes)

View file

@ -1,5 +1,31 @@
from itertools import product
from math import sqrt
"""
BuildGeneric
name: build_generic.py
by: Gumyr
date: July 12th 2022
desc:
This python module is a library of generic classes used by other
build123d builders.
license:
Copyright 2022 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 under the License is 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.
"""
from typing import Union
from build123d import (
BuildLine,
@ -10,6 +36,7 @@ from build123d import (
Rotation,
Axis,
Builder,
LocationList,
)
from cadquery import (
Shape,
@ -24,6 +51,11 @@ from cadquery import (
Vector,
)
from cadquery.occ_impl.shapes import VectorLike
import logging
logging.getLogger("build123d").addHandler(logging.NullHandler())
logger = logging.getLogger("build123d")
#
# Objects
@ -82,10 +114,9 @@ class Add(Compound):
# Can't use get_and_clear_locations because the solid needs to be
# oriented to the workplane after being moved to a local location
new_objects = [
workplane.fromLocalCoords(solid.moved(location))
solid.moved(location)
for solid in new_solids
for workplane in context.workplanes
for location in context.locations
for location in LocationList._get_context().locations
]
context.locations = [Location(Vector())]
context._add_to_context(*new_objects, mode=mode)
@ -98,11 +129,10 @@ class Add(Compound):
obj.rotate(
Vector(0, 0, 0), Vector(0, 0, 1), rotation_angle
).moved(location)
for location in context.locations
for location in LocationList._get_context().locations
]
)
context._add_to_context(*new_objects, mode=mode)
context.locations = [Location(Vector())]
# elif isinstance(context, BuildLine):
# new_objects = [obj for obj in objects if isinstance(obj, Edge)]
# for new_wires in filter(lambda o: isinstance(o, Wire), objects):
@ -248,63 +278,6 @@ class Fillet(Compound):
)
class HexArray:
"""Generic Operation: Hex Array
Creates a hexagon array of points and pushes them to Part or Sketch locations.
Args:
diagonal: tip to tip size of hexagon ( must be > 0)
xCount: number of points ( > 0 )
yCount: number of points ( > 0 )
centered: specify centering along each axis.
Raises:
ValueError: Spacing and count must be > 0
"""
def __init__(
self,
diagonal: float,
xCount: int,
yCount: int,
centered: tuple[bool, bool] = (True, True),
):
context: Builder = Builder._get_context()
xSpacing = 3 * diagonal / 4
ySpacing = diagonal * sqrt(3) / 2
if xSpacing <= 0 or ySpacing <= 0 or xCount < 1 or yCount < 1:
raise ValueError("Spacing and count must be > 0 ")
lpoints = [] # coordinates relative to bottom left point
for x in range(0, xCount, 2):
for y in range(yCount):
lpoints.append(Vector(xSpacing * x, ySpacing * y + ySpacing / 2))
for x in range(1, xCount, 2):
for y in range(yCount):
lpoints.append(Vector(xSpacing * x, ySpacing * y + ySpacing))
# shift points down and left relative to origin if requested
offset = Vector()
if centered[0]:
offset += Vector(-xSpacing * (xCount - 1) * 0.5, 0)
if centered[1]:
offset += Vector(0, -ySpacing * yCount * 0.5)
lpoints = [x + offset for x in lpoints]
# convert to locations
new_locations = [Location(pt) for pt in lpoints]
context.locations = new_locations
def __iter__(self):
"""Iterate over the points typically in a for loop"""
context: Builder = Builder._get_context()
for location in context.locations:
yield location.toTuple()[0]
context.locations = [Location(Vector())]
class Mirror(Compound):
"""Generic Operation: Mirror
@ -357,118 +330,3 @@ class Mirror(Compound):
mirrored_edges + mirrored_wires + mirrored_faces
).wrapped
)
class PolarArray:
"""Generic Operation: Polar Array
Push a polar array of locations to Part or Sketch
Args:
radius (float): array radius
start_angle (float): angle to first point from +ve X axis
stop_angle (float): angle to last point from +ve X axis
count (int): Number of points to push
rotate (bool, optional): Align locations with arc tangents. Defaults to True.
Raises:
ValueError: Count must be greater than or equal to 1
"""
def __init__(
self,
radius: float,
start_angle: float,
stop_angle: float,
count: int,
rotate: bool = True,
):
context: Builder = Builder._get_context()
if count < 1:
raise ValueError(f"At least 1 elements required, requested {count}")
angle_step = (stop_angle - start_angle) / count
# Note: rotate==False==0 so the location orientation doesn't change
new_locations = [
Location(
Vector(radius, 0).rotateZ(start_angle + angle_step * i),
Vector(0, 0, 1),
rotate * angle_step * i,
)
for i in range(count)
]
context.locations = new_locations
def __iter__(self):
"""Iterate over the points typically in a for loop"""
context: Builder = Builder._get_context()
for location in context.locations:
yield location.toTuple()[0]
context.locations = [Location(Vector())]
class PushPoints:
"""Generic Operation: Push Points
Push sequence of locations to Part or Sketch
Args:
pts (Union[VectorLike, Vertex, Location]): sequence of points to push
"""
def __init__(self, *pts: Union[VectorLike, Vertex, Location]):
context: Builder = Builder._get_context()
new_locations = []
for pt in pts:
if isinstance(pt, Location):
new_locations.append(pt)
elif isinstance(pt, Vector):
new_locations.append(Location(pt))
elif isinstance(pt, Vertex):
new_locations.append(Location(Vector(pt.toTuple())))
elif isinstance(pt, tuple):
new_locations.append(Location(Vector(pt)))
else:
raise ValueError(f"PushPoints doesn't accept type {type(pt)}")
context.locations = new_locations
class RectangularArray:
"""Generic Operation: Rectangular Array
Push a rectangular array of locations to Part or Sketch
Args:
x_spacing (float): horizontal spacing
y_spacing (float): vertical spacing
x_count (int): number of horizontal points
y_count (int): number of vertical points
Raises:
ValueError: Either x or y count must be greater than or equal to one.
"""
def __init__(self, x_spacing: float, y_spacing: float, x_count: int, y_count: int):
context: Builder = Builder._get_context()
if x_count < 1 or y_count < 1:
raise ValueError(
f"At least 1 elements required, requested {x_count}, {y_count}"
)
new_locations = []
offset = Vector((x_count - 1) * x_spacing, (y_count - 1) * y_spacing) * 0.5
for i, j in product(range(x_count), range(y_count)):
new_locations.append(
Location(Vector(i * x_spacing, j * y_spacing) - offset)
)
context.locations = new_locations
def __iter__(self):
"""Iterate over the points typically in a for loop"""
context: Builder = Builder._get_context()
for location in context.locations:
yield location.toTuple()[0]
context.locations = [Location(Vector())]

View file

@ -62,11 +62,6 @@ class BuildPart(Builder):
def _obj(self):
return self.part
@property
def workplane_count(self) -> int:
"""Number of active workplanes"""
return len(self.workplanes)
@property
def pending_faces_count(self) -> int:
"""Number of pending faces"""
@ -86,12 +81,7 @@ class BuildPart(Builder):
@property
def pending_edges_as_wire(self) -> Wire:
"""Return a wire representation of the pending edges"""
return Wire.assembleEdges(list(*self.pending_edges.values()))
@property
def pending_location_count(self) -> int:
"""Number of current locations"""
return len(self.locations)
return Wire.assembleEdges(self.pending_edges)
def __init__(
self,
@ -99,18 +89,16 @@ class BuildPart(Builder):
mode: Mode = Mode.ADD,
):
self.part: Compound = None
if isinstance(workplane, Plane):
user_plane = workplane
else:
user_plane = Plane.named(workplane)
self.workplanes: list[Plane] = [user_plane]
self.locations: list[Location] = [Location(user_plane.origin)]
self.pending_faces: dict[int : list[Face]] = {0: []}
self.pending_edges: dict[int : list[Edge]] = {0: []}
initial_plane = (
workplane if isinstance(workplane, Plane) else Plane.named(workplane)
)
# self.pending_faces: dict[int : list[Face]] = {0: []}
# self.pending_edges: dict[int : list[Edge]] = {0: []}
self.pending_faces: list[Face] = []
self.pending_edges: list[Edge] = []
self.last_faces = []
self.last_solids = []
super().__init__(mode)
super().__init__(mode, initial_plane)
def vertices(self, select: Select = Select.ALL) -> ShapeList[Vertex]:
"""Return Vertices from Part
@ -203,25 +191,18 @@ class BuildPart(Builder):
objects (Union[Edge, Face]): sequence of objects to add
"""
for obj in objects:
for i, workplane in enumerate(self.workplanes):
for loc in self.locations:
localized_obj = workplane.fromLocalCoords(obj.moved(loc))
if isinstance(obj, Face):
logger.debug(
f"Adding localized Face to pending_faces at {localized_obj.location()}"
)
if i in self.pending_faces:
self.pending_faces[i].append(localized_obj)
else:
self.pending_faces[i] = [localized_obj]
else:
logger.debug(
f"Adding localized Edge to pending_edges at {localized_obj.location()}"
)
if i in self.pending_edges:
self.pending_edges[i].append(localized_obj)
else:
self.pending_edges[i] = [localized_obj]
for loc in LocationList._get_context().locations:
localized_obj = obj.moved(loc)
if isinstance(obj, Face):
logger.debug(
f"Adding localized Face to pending_faces at {localized_obj.location()}"
)
self.pending_faces.append(localized_obj)
else:
logger.debug(
f"Adding localized Edge to pending_edges at {localized_obj.location()}"
)
self.pending_edges.append(localized_obj)
def _get_and_clear_locations(self) -> list:
"""Return location and planes from current points and workplanes and clear locations."""
@ -363,19 +344,18 @@ class CounterBoreHole(Compound):
hole_depth = (
context.part.BoundingBox().DiagonalLength if depth is None else depth
)
location_planes = context._get_and_clear_locations()
new_solids = [
Solid.makeCylinder(
radius, hole_depth, loc.position(), plane.zDir * -1.0
).fuse(
Solid.makeCylinder(radius, hole_depth, (0, 0, 0), (0, 0, -1))
.fuse(
Solid.makeCylinder(
counter_bore_radius,
counter_bore_depth,
loc.position(),
plane.zDir * -1.0,
(0, 0, 0),
(0, 0, -1),
)
)
for loc, plane in location_planes
.moved(location)
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -408,20 +388,19 @@ class CounterSinkHole(Compound):
context.part.BoundingBox().DiagonalLength if depth is None else depth
)
cone_height = counter_sink_radius / tan(radians(counter_sink_angle / 2.0))
location_planes = context._get_and_clear_locations()
new_solids = [
Solid.makeCylinder(
radius, hole_depth, loc.position(), plane.zDir * -1.0
).fuse(
Solid.makeCylinder(radius, hole_depth, (0, 0, 0), (0, 0, -1))
.fuse(
Solid.makeCone(
counter_sink_radius,
0.0,
cone_height,
loc.position(),
plane.zDir * -1.0,
(0, 0, 0),
(0, 0, -1),
)
)
for loc, plane in location_planes
.moved(location)
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -448,26 +427,24 @@ class Extrude(Compound):
):
new_solids: list[Solid] = []
context: BuildPart = BuildPart._get_context()
for plane_index, faces in context.pending_faces.items():
for face in faces:
for face in context.pending_faces:
new_solids.append(
Solid.extrudeLinear(
face,
face.normalAt(face.Center()) * until,
0,
)
)
if both:
new_solids.append(
Solid.extrudeLinear(
face,
context.workplanes[plane_index].zDir * until,
face.normalAt(face.Center()) * until * -1.0,
0,
)
)
if both:
new_solids.append(
Solid.extrudeLinear(
face,
context.workplanes[plane_index].zDir * until * -1.0,
0,
)
)
context.pending_faces = {0: []}
context.pending_faces = []
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -494,12 +471,11 @@ class Hole(Compound):
hole_depth = (
context.part.BoundingBox().DiagonalLength if depth is None else depth
)
location_planes = context._get_and_clear_locations()
new_solids = [
Solid.makeCylinder(
radius, hole_depth, loc.position(), plane.zDir * -1.0, 360
Solid.makeCylinder(radius, hole_depth, (0, 0, 0), (0, 0, -1), 360).moved(
location
)
for loc, plane in location_planes
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -522,15 +498,12 @@ class Loft(Solid):
context: BuildPart = BuildPart._get_context()
if not sections:
loft_wires = []
for i in range(len(context.workplanes)):
for face in context.pending_faces[i]:
loft_wires.append(face.outerWire())
loft_wires = [face.outerWire() for face in context.pending_faces]
else:
loft_wires = [section.outerWire() for section in sections]
new_solid = Solid.makeLoft(loft_wires, ruled)
context.pending_faces = {0: []}
context.pending_faces = []
context._add_to_context(new_solid, mode=mode)
super().__init__(new_solid.wrapped)
@ -563,7 +536,8 @@ class Revolve(Compound):
angle = 360.0 if angle == 0 else angle
new_solids = []
for i, workplane in enumerate(context.workplanes):
# for i, workplane in enumerate(context.workplanes):
for location in LocationList._get_context().locations:
axis = []
if axis_start is None:
axis.append(workplane.fromLocalCoords(Vector(0, 0, 0)))
@ -578,7 +552,7 @@ class Revolve(Compound):
for face in context.pending_faces[i]:
new_solids.append(Solid.revolve(face, angle, *axis))
context.pending_faces = {0: []}
context.pending_faces = []
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -743,8 +717,8 @@ class Sweep(Compound):
if sections:
section_list = sections
else:
section_list = list(*context.pending_faces.values())
context.pending_faces = {0: []}
section_list = context.pending_faces
context.pending_faces = []
if binormal is None and normal is not None:
binormal_mode = Vector(normal)
@ -754,12 +728,12 @@ class Sweep(Compound):
binormal_mode = binormal
new_solids = []
for workplane in context.workplanes:
for location in LocationList._get_context().locations:
if multisection:
sections = [section.outerWire() for section in section_list]
new_solid = Solid.sweep_multi(
sections, path_wire, True, is_frenet, binormal_mode
)
).moved(location)
else:
for section in section_list:
new_solid = Solid.sweep(
@ -769,51 +743,13 @@ class Sweep(Compound):
is_frenet,
binormal_mode,
transition.name.lower(),
)
new_solids.append(workplane.fromLocalCoords(new_solid))
).moved(location)
new_solids.append(new_solid)
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
class Workplanes:
"""Part Operation: Workplanes
Create workplanes from the given sequence of planes, optionally replacing existing
workplanes.
Args:
planes (PlaneLike): sequence of planes to use as workplanes.
replace (bool, optional): replace existing workplanes. Defaults to True.
"""
def __init__(self, *planes: PlaneLike, replace=True):
user_planes = [
user_plane if isinstance(user_plane, Plane) else Plane.named(user_plane)
for user_plane in planes
]
BuildPart._get_context()._workplane(*user_planes, replace=replace)
class WorkplanesFromFaces:
"""Part Operation: Workplanes from Faces
Create workplanes from the given sequence of faces, optionally replacing existing
workplanes. The workplane origin is aligned to the center of the face.
Args:
faces (Face): sequence of faces to convert to workplanes.
replace (bool, optional): replace existing workplanes. Defaults to True.
"""
def __init__(self, *faces: Face, replace=True):
new_planes = [
Plane(origin=face.Center(), normal=face.normalAt(face.Center()))
for face in faces
]
BuildPart._get_context()._workplane(*new_planes, replace=replace)
#
# Objects
#
@ -828,7 +764,6 @@ class Box(Compound):
length (float): box size
width (float): box size
height (float): box size
position (VectorLike, optional): initial position. Defaults to None.
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
centered (tuple[bool, bool, bool], optional): center about axes.
Defaults to (True, True, True).
@ -840,7 +775,6 @@ class Box(Compound):
length: float,
width: float,
height: float,
position: VectorLike = None,
rotation: RotationLike = (0, 0, 0),
centered: tuple[bool, bool, bool] = (True, True, True),
mode: Mode = Mode.ADD,
@ -848,14 +782,6 @@ class Box(Compound):
context: BuildPart = BuildPart._get_context()
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
if position:
location_planes = [
(Location(plane.fromLocalCoords(Vector(position))), plane)
for plane in context.workplanes
]
else:
location_planes = context._get_and_clear_locations()
center_offset = Vector(
-length / 2 if centered[0] else 0,
-width / 2 if centered[1] else 0,
@ -866,10 +792,10 @@ class Box(Compound):
length,
width,
height,
loc.position() + plane.fromLocalCoords(center_offset) - plane.origin,
plane.zDir,
).moved(rotate)
for loc, plane in location_planes
center_offset,
Vector(0, 0, 1),
).moved(location * rotate)
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -885,7 +811,6 @@ class Cone(Compound):
top_radius (float): top size, could be zero
height (float): cone size
arc_size (float, optional): angular size of cone. Defaults to 360.
position (VectorLike, optional): initial position. Defaults to None.
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
centered (tuple[bool, bool, bool], optional): center about axes.
Defaults to (True, True, True).
@ -898,7 +823,6 @@ class Cone(Compound):
top_radius: float,
height: float,
arc_size: float = 360,
position: VectorLike = None,
rotation: RotationLike = (0, 0, 0),
centered: tuple[bool, bool, bool] = (True, True, True),
mode: Mode = Mode.ADD,
@ -906,13 +830,6 @@ class Cone(Compound):
context: BuildPart = BuildPart._get_context()
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
if position:
location_planes = [
(Location(plane.fromLocalCoords(Vector(position))), plane)
for plane in context.workplanes
]
else:
location_planes = context._get_and_clear_locations()
center_offset = Vector(
0 if centered[0] else max(bottom_radius, top_radius),
0 if centered[1] else max(bottom_radius, top_radius),
@ -923,11 +840,11 @@ class Cone(Compound):
bottom_radius,
top_radius,
height,
loc.position() + plane.fromLocalCoords(center_offset) - plane.origin,
plane.zDir,
center_offset,
Vector(0, 0, 1),
arc_size,
).moved(rotate)
for loc, plane in location_planes
).moved(location * rotate)
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -942,7 +859,6 @@ class Cylinder(Compound):
radius (float): cylinder size
height (float): cylinder size
arc_size (float, optional): angular size of cone. Defaults to 360.
position (VectorLike, optional): initial position. Defaults to None.
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
centered (tuple[bool, bool, bool], optional): center about axes.
Defaults to (True, True, True).
@ -954,20 +870,12 @@ class Cylinder(Compound):
radius: float,
height: float,
arc_size: float = 360,
position: VectorLike = None,
rotation: RotationLike = (0, 0, 0),
centered: tuple[bool, bool, bool] = (True, True, True),
mode: Mode = Mode.ADD,
):
context: BuildPart = BuildPart._get_context()
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
if position:
location_planes = [
(Location(plane.fromLocalCoords(Vector(position))), plane)
for plane in context.workplanes
]
else:
location_planes = context._get_and_clear_locations()
center_offset = Vector(
0 if centered[0] else radius,
0 if centered[1] else radius,
@ -977,11 +885,11 @@ class Cylinder(Compound):
Solid.makeCylinder(
radius,
height,
loc.position() + plane.fromLocalCoords(center_offset) - plane.origin,
plane.zDir,
center_offset,
Vector(0, 0, 1),
arc_size,
).moved(rotate)
for loc, plane in location_planes
).moved(location * rotate)
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -1018,13 +926,6 @@ class Sphere(Compound):
context: BuildPart = BuildPart._get_context()
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
if position:
location_planes = [
(Location(plane.fromLocalCoords(Vector(position))), plane)
for plane in context.workplanes
]
else:
location_planes = context._get_and_clear_locations()
center_offset = Vector(
0 if centered[0] else radius,
0 if centered[1] else radius,
@ -1033,13 +934,13 @@ class Sphere(Compound):
new_solids = [
Solid.makeSphere(
radius,
loc.position() + plane.fromLocalCoords(center_offset) - plane.origin,
plane.zDir,
center_offset,
(0, 0, 1),
arc_size1,
arc_size2,
arc_size3,
).moved(rotate)
for loc, plane in location_planes
).moved(location * rotate)
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -1093,12 +994,12 @@ class Torus(Compound):
Solid.makeTorus(
major_radius,
minor_radius,
loc.position() + plane.fromLocalCoords(center_offset) - plane.origin,
plane.zDir,
center_offset,
Vector(0, 0, 1),
major_arc_size,
minor_arc_size,
).moved(rotate)
for loc, plane in location_planes
).moved(location * rotate)
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)
@ -1117,7 +1018,6 @@ class Wedge(Compound):
zmin (float): minimum Z location
xmax (float): maximum X location
zmax (float): maximum Z location
position (VectorLike, optional): initial position. Defaults to None.
rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).
mode (Mode, optional): combine mode. Defaults to Mode.ADD.
"""
@ -1131,25 +1031,15 @@ class Wedge(Compound):
zmin: float,
xmax: float,
zmax: float,
position: VectorLike = None,
rotation: RotationLike = (0, 0, 0),
mode: Mode = Mode.ADD,
):
context: BuildPart = BuildPart._get_context()
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
if position:
location_planes = [
(Location(plane.fromLocalCoords(Vector(position))), plane)
for plane in context.workplanes
]
else:
location_planes = context._get_and_clear_locations()
new_solids = [
Solid.makeWedge(
dx, dy, dz, xmin, zmin, xmax, zmax, loc.position(), plane.zDir
).moved(rotate)
for loc, plane in location_planes
Solid.makeWedge(dx, dy, dz, xmin, zmin, xmax, zmax).moved(location * rotate)
for location in LocationList._get_context().locations
]
context._add_to_context(*new_solids, mode=mode)
super().__init__(Compound.makeCompound(new_solids).wrapped)

View file

@ -78,7 +78,7 @@ class BuildSketch(Builder):
def __init__(self, mode: Mode = Mode.ADD):
self.sketch: Compound = None
self.pending_edges: ShapeList[Edge] = ShapeList()
self.locations: list[Location] = [Location(Vector())]
# self.locations: list[Location] = [Location(Vector())]
self.last_faces = []
super().__init__(mode)
@ -335,10 +335,11 @@ class Circle(Compound):
face = Face.makeFromWires(Wire.makeCircle(radius, *z_axis)).moved(
Location(center_offset)
)
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -381,10 +382,11 @@ class Ellipse(Compound):
)
face = face.moved(Location(center_offset))
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -416,10 +418,11 @@ class Polygon(Compound):
0 if centered[1] else bounding_box.ylen / 2,
)
face = face.moved(Location(center_offset))
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -453,10 +456,11 @@ class Rectangle(Compound):
)
face = face.moved(Location(center_offset))
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -497,10 +501,11 @@ class RegularPolygon(Compound):
)
face = face.moved(Location(center_offset))
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -531,10 +536,11 @@ class SlotArc(Compound):
raise ValueError("Bug - Edges aren't supported by offset")
# arc_wire = arc if isinstance(arc, Wire) else Wire.assembleEdges([arc])
face = Face.makeFromWires(arc.offset2D(height / 2)[0]).rotate(*z_axis, rotation)
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -573,10 +579,11 @@ class SlotCenterPoint(Compound):
]
)[0].offset2D(height / 2)[0]
).rotate(*z_axis, rotation)
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -609,10 +616,11 @@ class SlotCenterToCenter(Compound):
]
).offset2D(height / 2)[0]
).rotate(*z_axis, rotation)
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -644,10 +652,11 @@ class SlotOverall(Compound):
]
).offset2D(height / 2)[0]
).rotate(*z_axis, rotation)
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -697,11 +706,13 @@ class Text(Compound):
position_on_path,
path,
).rotate(Vector(), Vector(0, 0, 1), rotation)
new_compounds = [text_string.moved(location) for location in context.locations]
new_compounds = [
text_string.moved(location)
for location in LocationList._get_context().locations
]
new_faces = [face for compound in new_compounds for face in compound]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)
@ -758,8 +769,9 @@ class Trapezoid(Compound):
0 if centered[1] else bounding_box.ylen / 2,
)
face = face.moved(Location(center_offset))
new_faces = [face.moved(location) for location in context.locations]
new_faces = [
face.moved(location) for location in LocationList._get_context().locations
]
for face in new_faces:
context._add_to_context(face, mode=mode)
context.locations = [Location(Vector())]
super().__init__(Compound.makeCompound(new_faces).wrapped)