Skip to content

Strange behavior of offset2D on circles as inner face wires #2046

Description

@sxhulzenstein

I have programmed this function which generates the offset wires of a face. In a nutshell, it is performing the following tasks:

  • Generating the offset wires of the outer face wire by performing offset2D(offset_distance)
  • Generating the offset wires of all inner face wires by performing offset2D(-offset_distance)
  • Creating a face from all outer offset wires
  • Creating a face from all inner offset wires
  • Subtracting the inner face from the outer face and return the resulting wires of the face
def offset_wires(face: Face, offset_distance: float) -> list[Wire]:
    outer_wire_offsets: list[Wire] = face.outerWire().offset2D(offset_distance)
    inner_wires_offsets: list[Wire] = [offset_wire
                                       for inner_wire in face.innerWires()
                                       for offset_wire in inner_wire.offset2D(-offset_distance)]

    outer_faces = [Face.makeFromWires(outer_wire_offset) for outer_wire_offset in outer_wire_offsets]
    if not outer_faces:
        raise Exception("No outer wires found")

    outer_face = outer_faces[0] if len(outer_faces) == 1 else outer_faces[0].fuse(*outer_faces[1:]).clean()

    inner_faces = [Face.makeFromWires(inner_wires_offset) for inner_wires_offset in inner_wires_offsets]

    if not inner_faces:
        resulting_offset_wires: list[Wire] = outer_face.Wires()
    else:
        inner_face = inner_faces[0] if len(inner_faces) == 1 else inner_faces[0].fuse(*inner_faces[1:]).clean()
        resulting_offset_wires: list[Wire] = (outer_face - inner_face).clean().Wires()

    return resulting_offset_wires

Creating the offset for the outer wire works fine, but having a circle as the inner wire seems to cause some problems. Not only is it depending on the chosen value for offset_value, it also depends on how the circle is generated. I am using the following shape for testing:

Image

Case 1 - Circle using Edge classmethod

First I am creating the pill shaped outer wire and after that creating a circle using the Edge.makeCircle() method.

def make_geometry() -> Face:
    width: float = 4.0
    height: float = 10.0
    outer_wire: Wire = (Workplane()
                        .moveTo(width/2, -height/2 + width/ 2)
                        .vLine(height - width)
                        .tangentArcPoint((-width, 0.0))
                        .vLine(-height + width)
                        .tangentArcPoint((width, 0.0))
                        .consolidateWires()
                        ).wires().val()
    inner_edge: Edge = Edge.makeCircle(width/4, pnt=Vector(0.0, height/2 - width/2, 0), dir=Vector(0, 0, -1), orientation=True)
    inner_wire: Wire = Wire.assembleEdges([inner_edge])

    return Face.makeFromWires(outer_wire, [inner_wire])

If I apply the offset_wires function with a value of offset_distance < 0 it works as expected. But for offset_distance >=0 a ValueError with the message "Null TopoDS_Shape object" is thrown.

Image

Case 2 - Circle using the Workplane method

In this case, the outer wire is generated in the same way. The inner wire is created by doing

    inner_wire: Wire = (Workplane(origin=(0, height/2 - width/2, 0))
                        .circle(width/4)
                        ).wires().val()

By using a non-negative value for offset_distance CADQuery does not crash but the result mismatches my expectation. The location of the offset circle seems to be changed.

Image

And, for values offset_distance < 0 a ValueError exception is thrown.
If I instead use a rectangle or rectangle with rounded corners for the inner wire, the behavior is as expected again.

Setup

I tried this using both the CQ-Editor (Version 0.6.dev0, Python 3.13.11) and the cadquery package (2.7.0, Python 3.13.13).
Full script for testing:
offset_geometry.py

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions