Open jdegenstein opened 10 months ago
object.orientation += (10, 20, 30))
, with Locations(Rotation((10, 20, 30))):
, add(something, rotation=
)Algebra mode also
There are many ways to move an object. One of the lesser known ways is to just change it's position: box.position = (1,2,3)
box.position += (1,2,3)
is a relative move
The two main issues I've bumped into both depend on placing a work plane in an arbitrary location out in 3D space, and controlling its orientation. For example, using a sweep() to connect two different rectangles via a spline's path, but both rectangles are set at specific points, and are unrelated to each other.
With the ability to place a workplane in an arbitrary spot, it's easy enough to place a workplane at a variable spot; I arbitrarily choose this box's corner over at min-X/max-Y/mid-Z.
(EDIT: One of my examples is actually about locating a sketch at a specific position using coordinates from an existing sketch, but this is already documented. The matter still gets complicated when you stack rotating the second sketch's workplane.)
An issue I might run into: Placing a workplane at the result of projecting a line/shape onto another face. So, if you have two unrelated boxes (A and B), and A has a small hole in the center, projecting that hole's center point onto B, and then dropping a workplane on B's face, centered where that projected centerpoint lands.
How about this example for arbitrary placement of sketches (@jdegenstein IIRC you have an similar example that might be better)?
with BuildPart() as twist:
with BuildSketch() as x_section:
Circle(1)
with GridLocations(0, 2, 1, 2):
Circle(0.75, mode=Mode.SUBTRACT)
for i in range(1, 11):
with Locations(Location((0, 0, i), (0, 0, i * 36))):
add(x_section.sketch)
x_sections = twist.pending_faces # for display
loft()
@gumyr For an example, I'd rather see as little going on as possible. Either just extrude a Rectangle into a box, or make a cone point at the plane's origin.
Second, how does this work when you rotate the plane?
Because of my prior experience with OpenSCAD, one of my instincts (good, bad, or otherwise) is to make global translations work in a predictable way independent of what the rotation is doing. This is partially because I have a hard time rotating coordinate systems in my head.
Suppose I'm making a simple rectangular duct. The base is centered at global (0, 0, 0), and the top needs to be 15mm above (+Z) and 7mm in front (+Y) of the centerpoint, oriented 34 degrees off from vertical (XZ plane, rotated). The easiest way (in my head) to set this up is one work plane at the origin, and another at (0, 7, 15), rotated one of (34, 124, 214, 304) degrees. Set up those two workplanes, draw your rectangles, use a curve in the YZ plane to set up a sweep between the two. Just need a clean way to set up that second work plane.
@proegssilb you were too fast - this might address your previous question about projection:
Here a mounting plate for a flange is created (in Algebra mode) with the sketch relative to the flange. Key features are projected from the flange to the plate.
from build123d import *
from ocp_vscode import *
from bd_warehouse.flange import WeldNeckFlange
flange = Pos(1 * IN, 2 * IN, 3 * IN) * WeldNeckFlange(
nps="12", flange_class=300, face_type="Ring"
)
# Initial sketch plane to be repositioned relative to center of flange
plate_sketch_plane = Plane.XY
pipe_hole = (
flange.edges()
.filter_by(GeomType.CIRCLE)
.group_by(SortBy.RADIUS)[3]
.group_by(Axis.Z)[0]
)
# Project the pipe hole from the flange onto the plate sketch plane
pipe_hole_projection = project(pipe_hole, workplane=plate_sketch_plane)
# Relocate the plate sketch plane to the flange center and rotate it
plate_sketch_plane = plate_sketch_plane.shift_origin(
pipe_hole_projection.edge().arc_center
).rotated((0, 0, 15))
# Create the sketch for the plate with dimensions relative to the flange
plate_plan = (
plate_sketch_plane * Pos(-8 * IN, 0, 0) * RectangleRounded(40 * IN, 30 * IN, 1 * IN)
)
plate = extrude(plate_plan, amount=-1 * IN)
# Project the flange's bolt circle to the plate sketch
flange_bolt_circle = (
flange.edges()
.filter_by(GeomType.CIRCLE)
.group_by(SortBy.RADIUS)[2]
.group_by(Axis.Z)[0]
)
bolt_circle_projection = project(flange_bolt_circle, workplane=plate_sketch_plane)
# Create some mounting slots relative to the flange
slots = (
plate_sketch_plane
* Pos(-18 * IN, 0, 0)
* GridLocations(10 * IN, 20 * IN, 2, 2)
* SlotOverall(4 * IN, 1 * IN)
)
# Make faces out of the projected edges
holes = [
make_face(e) for e in bolt_circle_projection.edges() + pipe_hole_projection.edges()
]
# Cut holes in the plate
for face in slots + holes:
plate -= extrude(face, amount=-1 * IN)
I can't read Algebra mode, but here's a distilled thing from an actual model I had to fumble my way through:
base_width = 40
base_height = 30
base_extr = 15
targ_loc = (15,0,25)
targ_rot = (0,34,0)
targ_width = 20
targ_height = 10
targ_extr = 5
with BuildPart() as plane_place_demo:
with BuildSketch() as base_sketch:
Rectangle(base_width, base_height)
extrude(amount=base_extr)
targ_plane = Plane(targ_loc).rotated(targ_rot)
with BuildSketch(targ_plane):
Rectangle(targ_width, targ_height)
extrude(amount=targ_extr)
That should be pretty close to an intro example. Remove the extrusions, add an offset or two (does offsetting in negative mode work?), add a line & sweep, and you have a tutorial for ducts that I could have used while working on my 3D printer.
Hope this helps.
How is this for an example of building a duct with three different operations for creating the Solid, extrude, sweep and loft?
from build123d import *
from ocp_vscode import *
with BuildPart() as duct:
# Straight vertical section
with BuildSketch() as duct_plan:
Rectangle(30, 20)
inside = offset(amount=-1, mode=Mode.SUBTRACT)
extrude(amount=10)
top = duct.faces().sort_by(Axis.Z)[-1]
# Swept section
with BuildLine(Plane.XZ):
path1 = JernArc(
start=top.center(),
tangent=(0, 1),
radius=50,
arc_size=-45,
)
sweep(top, path=path1)
# Lofted section
with BuildLine(Plane.XZ):
path2 = JernArc(
start=path1 @ 1,
tangent=path1 % 1,
radius=50,
arc_size=-45,
)
with BuildSketch(path2 ^ 0.0): # Position & Orientation at beginning
add(duct_plan.sketch)
with BuildSketch(path2 ^ 0.5): # Position & Orientation at middle
add(duct_plan.sketch)
with BuildSketch(path2 ^ 1.0): # Position & Orientation at end
add(duct_plan.sketch)
loft(ruled=True)
# loft doesn't support a hole, so make one
with BuildSketch(path2 ^ 0.0):
add(inside)
with BuildSketch(path2 ^ 0.5):
add(inside)
with BuildSketch(path2 ^ 1.0):
add(inside)
loft(ruled=True, mode=Mode.SUBTRACT)
show_all()
The more efficient way to create is a duct with a single sweep like this:
with BuildPart() as duct2:
with BuildLine(Plane.XZ):
l1 = Line((0, 0), (0, 10))
l2 = JernArc(
start=l1 @ 1,
tangent=l1 % 1,
radius=50,
arc_size=-90,
)
l3 = Line(l2 @ 1, l2 @ 1 + (10, 0))
with BuildSketch() as duct_plan:
Rectangle(30, 20)
inside = offset(amount=-1, mode=Mode.SUBTRACT)
sweep()
but it doesn't show the other operations.
# Suppose I'm making a simple rectangular duct. The base is centered at
# global (0, 0, 0), and the top needs to be 15mm above (+Z) and 7mm in front (+Y)
# of the centerpoint, oriented 34 degrees off from vertical (XZ plane, rotated).
with BuildPart() as duct3:
with BuildSketch() as duct_plan1:
Rectangle(30, 20)
with BuildSketch(
Plane(origin=(0, 7, 15), x_dir=(1, 0, 0), z_dir=(0, 1, 0)).rotated((34, 0, 0))
) as duct_plan2:
add(duct_plan1.sketch)
loft()
Here is the flange example in Builder mode:
flange = WeldNeckFlange(nps="12", flange_class=300, face_type="Ring", mode=Mode.PRIVATE)
flange_projection = project(flange.edges(), workplane=Plane.XY)
pipe_edge: Edge = (
flange_projection.edges().filter_by(GeomType.CIRCLE).group_by(SortBy.RADIUS)[3][0]
)
with BuildPart() as plate:
with BuildSketch(
Plane(origin=pipe_edge.arc_center, x_dir=Vector(1, 0, 0).rotate(Axis.Z, 15))
) as plate_plan:
with Locations((-8 * IN, 0, 0)):
RectangleRounded(40 * IN, 30 * IN, 1 * IN)
make_face(pipe_edge, mode=Mode.SUBTRACT)
for hole_edge in (
flange_projection.edges()
.filter_by(GeomType.CIRCLE)
.group_by(SortBy.RADIUS)[0]
):
make_face(hole_edge, mode=Mode.SUBTRACT)
with Locations((-18 * IN, 0, 0)):
with GridLocations(10 * IN, 20 * IN, 2, 2):
SlotOverall(4 * IN, 1 * IN, mode=Mode.SUBTRACT)
extrude(amount=1 * IN)
show(plate, Pos(0, 0, 4 * IN) * flange)
This is the example that I'd consider "simple enough" for documentation purposes. If the two boxes I showed are too simple or are visually confusing, then the comment I linked to is what I'd suggest using.
With tutorials & introductory examples, there's a balance between "having an interesting part" and "showing a specific idea without minimal noise". The idea that I had a hard time finding in the documentation was "how do I position a workplane at a specific location in 3D space, with a particular orientation". So I picked the most boring example that demonstrated that. Ducts are what I was working on when I discovered I had no idea how to do that thing, so they're a "complete part" that can still show the same concept.
With the flange in Builder mode, I now see where it constructs a plane at a variable spot. If, however, I was completely brand new to build123d, there would be a lot going on in that example, and it'd take me a while to sift through everything. Therefore, in my opinion, it makes a better tutorial than an Introductory Example. Whereas my boxes code was aiming for closer to an introductory example, with the assumption that a duct could be a tutorial.
Currently the intro examples touch on object placement, but there are more scenarios that need to be covered. I propose that another object placement examples section is added to the docs.
Some ideas:
part
I am looking for more input here, so please suggest more examples.