gumyr / build123d

A python CAD programming library
Apache License 2.0
395 stars 72 forks source link

Offset should behave the same for 2d and 3d #74

Closed bernhard-42 closed 1 year ago

bernhard-42 commented 1 year ago

Just playing around with offset. I find it a bit confusing when I go through all combinations:

Faces:

with BuildSketch() as s:
    Rectangle(1, 2)
    # good:
    Offset(amount=-0.1, mode=bd.Mode.REPLACE)  # ok, smaller face, as expected
    Offset(amount=-0.1, mode=bd.Mode.SUBTRACT) # ok, face with hole, as expected
    Offset(amount=0.1, mode=bd.Mode.REPLACE)   # ok, larger face, as expected (same as mode=ADD)
    # not needed:
    Offset(amount=0.1, mode=bd.Mode.ADD)       # ok, larger face (same as mode=REPLACE)
    Offset(amount=-0.1, mode=bd.Mode.ADD)      # same as original rectangle
    Offset(amount=0.1, mode=bd.Mode.SUBTRACT)  # Fails with s.sketch.faces == []

Solids:

with BuildPart() as p:
    b = Box(1, 2, 3)
    # good:
    Offset(amount=-0.1, mode=bd.Mode.REPLACE)  # same box, but hollow => would expect smaller box, filled
    Offset(amount=0.1, mode=bd.Mode.REPLACE)   # larger hollow box => would expect larger box filled
    Offset(amount=-0.1, mode=bd.Mode.SUBTRACT) # smaller box => would expect original box, but hollow
    Offset(amount=0.1, mode=bd.Mode.ADD)       # ok, larger box
    # not needed:
    Offset(amount=-0.1, mode=bd.Mode.ADD)      # same as original box
    Offset(amount=0.1, mode=bd.Mode.SUBTRACT)  # same as original box

Not all combinations make sense, some are redundant and the behaviour of the same param combinations between face and solid are different. And, I would have searched Shell and not thought about trying Offset and its parameters.

bernhard-42 commented 1 year ago

a workaround for single objects could be something along

    if <is 3d> and <single obj>:
        if amount > 0:
            return result.fuse(objects)
        else:
            return objects.cut(result)
    else:
        return result
gumyr commented 1 year ago

The problem was a misunderstanding of what Shape.shell does - as implemented it creates hollow shapes which when combined with the original make for the problems seen above. To fix this a new Shape.offset_3d method was created that doesn't create a hollow object. The results are shown below:

from build123d import *

with BuildPart() as o1:
    b = Box(4, 4, 4)
    o = Offset(amount=-1, kind=Kind.INTERSECTION, mode=Mode.REPLACE)
    print(f"{o.volume=:5.1f}, {o1.part.volume=:5.1f}, target: {2**3}")

with BuildPart() as o2:
    b = Box(4, 4, 4)
    o = Offset(amount=1, kind=Kind.INTERSECTION, mode=Mode.REPLACE)
    print(f"{o.volume=:5.1f}, {o2.part.volume=:5.1f}, target: {6**3}")

with BuildPart() as o3:
    b = Box(4, 4, 4)
    o = Offset(amount=-1, kind=Kind.INTERSECTION, mode=Mode.SUBTRACT)
    print(f"{o.volume=:5.1f}, {o3.part.volume=:5.1f}, target: {4**3-2**3}")

with BuildPart() as o4:
    b = Box(4, 4, 4)
    o = Offset(amount=1, kind=Kind.INTERSECTION, mode=Mode.ADD)
    print(f"{o.volume=:5.1f}, {o4.part.volume=:5.1f}, target: {6**3}")

with BuildPart() as o5:
    b = Box(4, 4, 4)
    o = Offset(amount=-1, kind=Kind.INTERSECTION, mode=Mode.ADD)
    print(f"{o.volume=:5.1f}, {o5.part.volume=:5.1f}, target: {4**3}")

with BuildPart() as o6:
    b = Box(4, 4, 4)
    o = Offset(amount=1, kind=Kind.INTERSECTION, mode=Mode.SUBTRACT)
    print(f"{o.volume=}, {o6.part.volume=:5.1f}, target: {0**3}")

if "show_object" in locals():
    show_object(
        Compound.make_compound(b.edges()),
        name="box",
        options={"alpha": 0.8, "color": "green"},
    )
    show_object(o1, name="negative replace", options={"alpha": 0.8})
    show_object(o2, name="positive replace", options={"alpha": 0.8})
    show_object(o3, name="negative subtract", options={"alpha": 0.8})
    show_object(o4, name="positive add", options={"alpha": 0.8})
    show_object(o5, name="negative add", options={"alpha": 0.8})
    show_object(o6, name="positive subtract", options={"alpha": 0.8})
o.volume=  8.0, o1.part.volume=  8.0, target: 8
o.volume=216.0, o2.part.volume=216.0, target: 216
o.volume=  8.0, o3.part.volume= 56.0, target: 56
o.volume=216.0, o4.part.volume=216.0, target: 216
o.volume=  8.0, o5.part.volume= 64.0, target: 64
o.volume=216.0, o6.part.volume=  0.0, target: 0

Each of these show the original object's Edges in red: image image image image image image

gumyr commented 1 year ago

755994f8c52dba1cdde6b4c38402feeb26712d9f fixed this