Added instance variables to BuildPart, updated tutorial

This commit is contained in:
Roger Maitland 2022-09-28 14:40:47 -04:00
parent 91aa315bae
commit e991ba05a0
4 changed files with 117 additions and 23 deletions

View file

@ -12,8 +12,7 @@ Step 1: Setup
Before getting to the CAD operations, this Lego script needs to import the build123d
environment. There are over 100 python classes in build123d so we'll just import them
all with a ``from build123d import *`` but there are other options that we won't explore
here. In addition, the ``Plane`` object from ``cadquery`` will be used so we'll import
that class as well.
here.
The dimensions of the Lego block follow. A key parameter is ``pip_count``, the length
of the Lego blocks in pips. This parameter must be at least 2.
@ -184,14 +183,18 @@ could be done with another sketch, we'll add a box to the top of the walls.
:lines: 46-76
:emphasize-lines: 23-31
To position the top, we'll create a new location context ``Locations`` at the center
and at the height of the walls. To determine the height we'll extract that from the
To position the top, we'll create a new location context ``Workplanes`` at the center
and at the height of the walls. Much like the location contexts, the ``Workplanes`` context
creates one or more planes that
can be used to position further features. To determine the height we'll extract that from the
``lego.part`` by using the ``vertices()`` method which returns a list of the positions
of all of the vertices of the Lego block so far. Since we're interested in the top,
we'll sort by the vertical (Z) axis and take the top of the list (index -1). Finally,
the ``z`` property of this vertex will return just the height of the top.
we'll sort by the vertical (Z) axis and take the top of the list ``>> Axis.Z``. Finally,
the ``Z`` property of this vertex will return just the height of the top. Note that
the ``X`` and ``Y`` values are not used from the selected vertex as there are no
vertices in the center of the block.
Within the scope of this ``Locations`` context, a ``Box`` is created, centered at
Within the scope of this ``Workplanes`` context, a ``Box`` is created, centered at
the intersection of the x and y axis but not in the z thus aligning with the top of the walls.
The base is closed now as shown here:
@ -210,9 +213,8 @@ a new workplane on top of the block where we can position the pips.
:lines: 46-81
:emphasize-lines: 32-36
Much like the location contexts, the ``Workplanes`` context creates one or more planes that
can be used to position further features. In this case, the workplane is created from the
top Face of the Lego block by using the ``faces`` method and then sorted vertically.
In this case, the workplane is created from the top Face of the Lego block by using the
``faces`` method and then sorted vertically and taking the top one ``>> Axis.Z``.
On the new workplane, a grid of locations is created and a number of ``Cylinder``'s are positioned
at each location.
@ -220,7 +222,6 @@ at each location.
.. image:: tutorial_step11.svg
:align: center
This completes the Lego block. To access the finished product, refer to the builder's internal
object as shown here:

View file

@ -66,7 +66,7 @@ with BuildPart() as lego:
Circle(support_inner_diameter / 2, mode=Mode.SUBTRACT)
Extrude(amount=base_height - wall_thickness)
with Workplanes(
Plane(origin=(0, 0, base_height - wall_thickness), normal=(0, 0, 1))
Plane(origin=(0, 0, (lego.vertices() >> Axis.Z).Z), normal=(0, 0, 1))
):
Box(
block_length,

View file

@ -67,6 +67,10 @@ class BuildLine(Builder):
self.locations: list[Location] = [Location(Vector())]
super().__init__(mode)
def faces(self):
"""Override the base Builder class definition of faces()"""
return NotImplementedError("faces() doesn't apply to BuildLine")
def wires(self, select: Select = Select.ALL) -> ShapeList[Wire]:
"""Return Wires from Line

View file

@ -279,9 +279,14 @@ class CounterBoreHole(Compound):
mode: Mode = Mode.SUBTRACT,
):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context)
self.radius = radius
self.counter_bore_radius = counter_bore_radius
self.counter_bore_depth = counter_bore_depth
self.depth = depth
self.mode = mode
new_solids = []
for location in LocationList._get_context().locations:
hole_depth = (
@ -329,9 +334,14 @@ class CounterSinkHole(Compound):
mode: Mode = Mode.SUBTRACT,
):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context)
self.radius = radius
self.counter_sink_radius = counter_sink_radius
self.depth = depth
self.counter_sink_angle = counter_sink_angle
self.mode = mode
for location in LocationList._get_context().locations:
hole_depth = (
context.part.fuse(Solid.makeBox(1, 1, 1).locate(location))
@ -388,11 +398,17 @@ class Extrude(Compound):
taper: float = 0.0,
mode: Mode = Mode.ADD,
):
new_solids: list[Solid] = []
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context, [to_extrude])
self.to_extrude = to_extrude
self.amount = amount
self.until = until
self.both = both
self.taper = taper
self.mode = mode
new_solids: list[Solid] = []
if not to_extrude and not context.pending_faces:
raise ValueError("Either a face or a pending face must be provided")
@ -518,9 +534,12 @@ class Hole(Compound):
mode: Mode = Mode.SUBTRACT,
):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context)
self.radius = radius
self.depth = depth
self.mode = mode
# To ensure the hole will go all the way through the part when
# no depth is specified, calculate depth based on the part and
# hole location. In this case start the hole above the part
@ -560,9 +579,12 @@ class Loft(Solid):
def __init__(self, *sections: Face, ruled: bool = False, mode: Mode = Mode.ADD):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context, sections)
self.sections = sections
self.ruled = ruled
self.mode = mode
if not sections:
loft_wires = [face.outerWire() for face in context.pending_faces]
context.pending_faces = []
@ -605,9 +627,13 @@ class Revolve(Compound):
mode: Mode = Mode.ADD,
):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context, profiles)
self.profiles = profiles
self.axis = axis
self.revolution_arc = revolution_arc
self.mode = mode
# Make sure we account for users specifying angles larger than 360 degrees, and
# for OCCT not assuming that a 0 degree revolve means a 360 degree revolve
angle = revolution_arc % 360.0
@ -675,9 +701,12 @@ class Section(Compound):
mode: Mode = Mode.INTERSECT,
):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context)
self.section_by = section_by
self.height = height
self.mode = mode
max_size = context.part.BoundingBox().DiagonalLength
section_planes = (
@ -737,9 +766,17 @@ class Sweep(Compound):
mode: Mode = Mode.ADD,
):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context, sections)
self.sections = sections
self.path = path
self.multisection = multisection
self.is_frenet = is_frenet
self.transition = transition
self.normal = normal
self.binormal = binormal
self.mode = mode
if path is None:
path_wire = context.pending_edges_as_wire
else:
@ -811,10 +848,17 @@ class Box(Compound):
mode: Mode = Mode.ADD,
):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context)
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
self.length = length
self.width = width
self.height = height
self.rotation = rotate
self.centered = centered
self.mode = mode
center_offset = Vector(
-length / 2 if centered[0] else 0,
-width / 2 if centered[1] else 0,
@ -861,10 +905,18 @@ class Cone(Compound):
mode: Mode = Mode.ADD,
):
context: BuildPart = BuildPart._get_context()
validate_inputs(self, context)
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
self.bottom_radius = bottom_radius
self.top_radius = top_radius
self.height = height
self.arc_size = arc_size
self.rotation = rotate
self.centered = centered
self.mode = mode
center_offset = Vector(
0 if centered[0] else max(bottom_radius, top_radius),
0 if centered[1] else max(bottom_radius, top_radius),
@ -913,6 +965,14 @@ class Cylinder(Compound):
validate_inputs(self, context)
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
self.radius = radius
self.height = height
self.arc_size = arc_size
self.rotation = rotate
self.centered = centered
self.mode = mode
center_offset = Vector(
0 if centered[0] else radius,
0 if centered[1] else radius,
@ -962,6 +1022,15 @@ class Sphere(Compound):
validate_inputs(self, context)
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
self.radius = radius
self.arc_size1 = arc_size1
self.arc_size2 = arc_size2
self.arc_size3 = arc_size3
self.rotation = rotate
self.centered = centered
self.mode = mode
center_offset = Vector(
0 if centered[0] else radius,
0 if centered[1] else radius,
@ -1013,6 +1082,15 @@ class Torus(Compound):
validate_inputs(self, context)
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
self.major_radius = major_radius
self.minor_radius = minor_radius
self.major_arc_size = major_arc_size
self.minor_arc_size = minor_arc_size
self.rotation = rotate
self.centered = centered
self.mode = mode
center_offset = Vector(
0 if centered[0] else major_radius,
0 if centered[1] else major_radius,
@ -1066,6 +1144,17 @@ class Wedge(Compound):
validate_inputs(self, context)
rotate = Rotation(*rotation) if isinstance(rotation, tuple) else rotation
self.dx = dx
self.dy = dy
self.dz = dz
self.xmin = xmin
self.zmin = zmin
self.xmax = xmax
self.zmax = zmax
self.rotation = rotate
self.mode = mode
new_solids = [
Solid.makeWedge(dx, dy, dz, xmin, zmin, xmax, zmax).moved(location * rotate)
for location in LocationList._get_context().locations