CadQuery / CadQuery/cadquery

Strange behavior of offset2D on circles as inner face wires

Open
#2,046 2 comments 0 reactions 0 assignees View on GitHub
OCC kernel issue
Dominant language
Python
Stars
5.8k
Forks
541
Avg merge
3d 2h
Merged PRs (30d)
5

Description

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

```python
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.
```python
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
```python
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](https://github.com/user-attachments/files/28843266/offset_geometry.py)

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.