gumyr / build123d

A python CAD programming library
Apache License 2.0
431 stars 80 forks source link

Add section to docs with further object placement examples #450

Open jdegenstein opened 8 months ago

jdegenstein commented 8 months ago

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:

  1. Reusing faces of an existing part
  2. Further example with PolarLocations illustrating the interaction between object position and radius
  3. More "3D" positioning examples with e.g. CounterBoreHoles
  4. Precursory knowledge building examples for a few joint types
  5. TTT T2-17-CARRIER_BASE example

I am looking for more input here, so please suggest more examples.

jdegenstein commented 8 months ago
  1. locate, located, move, moved, translate example(s)
jdegenstein commented 7 months ago
  1. illustrate several examples of applying rotation (object.orientation += (10, 20, 30)), with Locations(Rotation((10, 20, 30))):, add(something, rotation=)
CePeU commented 7 months ago

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

proegssilb commented 5 months ago

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.)

proegssilb commented 5 months ago

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.

gumyr commented 5 months ago

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()

image

proegssilb commented 5 months ago

@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.

gumyr commented 5 months ago

@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)

image

proegssilb commented 5 months ago

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)

image

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.

gumyr commented 5 months ago

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()

image

gumyr commented 5 months ago

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()

image but it doesn't show the other operations.

gumyr commented 5 months ago
# 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()

image

gumyr commented 5 months ago

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)

image

proegssilb commented 5 months ago

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.