Skip to content

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

@sxhulzenstein

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions