diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 2709dd3..6d347f8 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -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: diff --git a/examples/lego.py b/examples/lego.py index 56a62bb..4408417 100644 --- a/examples/lego.py +++ b/examples/lego.py @@ -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, diff --git a/src/build123d/build_line.py b/src/build123d/build_line.py index 8b2e032..d84a5d1 100644 --- a/src/build123d/build_line.py +++ b/src/build123d/build_line.py @@ -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 diff --git a/src/build123d/build_part.py b/src/build123d/build_part.py index 80dbd2e..b99b73c 100644 --- a/src/build123d/build_part.py +++ b/src/build123d/build_part.py @@ -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