Skip to content

CadQuery Workplane.cut() produces degenerate results for overlapping spheres #2020

Open

Description

@shimwell

Description

I've run into an issue where a boolean cut between two overlapping spheres using Workplane.cut() appears to produce incorrect geometry. The resulting solid retains the volume of the original uncut sphere, and the cut face has zero area. I noticed that performing the same operation with OCP.BRepAlgoAPI.BRepAlgoAPI_Cut directly gives the expected result, so I wanted to flag this in case it's helpful.

It's possible I'm using the API incorrectly happy to be corrected if so!

Reproduction

import cadquery as cq                                                                                          
from OCP.BRepPrimAPI import BRepPrimAPI_MakeSphere                                                             
from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut                                                                    
from OCP.gp import gp_Pnt                                                                                      
 
d, r = 3, 5        5                                                                                    
                                                                   
# Via CadQuery
sphere1 = cq.Workplane().transformed(offset=(-d, 0, 0)).sphere(r)
sphere2 = cq.Workplane().transformed(offset=(d, 0, 0)).sphere(r)                                               
crescent_cq = sphere2.cut(sphere1)
                                                                                                               
print(f"CadQuery volume: {crescent_cq.val().Volume():.2f}")                                                    
# 523.60 — same as the original sphere, expected ~414.69
                                                                                                               
for f in crescent_cq.val().Faces():                                
    print(f"  Face area: {f.Area():.4f}")                                                                      
# Face 0: 314.1593 (full sphere surface area)                                                                  
# Face 1: 0.0000   (cut face appears degenerate)
                                                                                                               
# Via OCC directly                                                                                             
s1 = BRepPrimAPI_MakeSphere(gp_Pnt(-d, 0, 0), r).Shape()
s2 = BRepPrimAPI_MakeSphere(gp_Pnt(d, 0, 0), r).Shape()                                                        
crescent_occ = BRepAlgoAPI_Cut(s2, s1).Shape()                                                                 
 
print(f"OCC volume: {cq.Solid(crescent_occ).Volume():.2f}")                                                    
# 414.69 — expected crescent volume                                
                                                                                                               
for f in cq.Shape(crescent_occ).Faces():
    print(f"  Face area: {f.Area():.4f}")                                                                      
# Three faces, all with non-zero area                              

Expected behaviour

sphere2.cut(sphere1) should produce a crescent-shaped solid with volume ~414.69 and faces with non-zero area at the cut boundary.

Actual behaviour

The result has the same volume as the uncut sphere (523.60). The cut boundary face has zero area and receives no mesh triangles from BRepMesh_IncrementalMesh, which leaves the solid effectively unclosed.

Context

@jmwright and I came across this while working on CAD-to-DAGMC neutronics geometry conversion. The degenerate face prevents the mesh from being watertight, which causes particle transport failures. Using BRepAlgoAPI_Cut directly works around the issue for now. I think this is related to the errors we saw on the model-benchmark-zoo PR today

Environment

Tested on both CadQuery 2.7.0 and 2.8.0.dev0 (installed from main today) — same behaviour on both.

Activity

  1. adam-urbanczyk commented on Mar 28, 2026

    @adam-urbanczyk
    Member

    This is related to failed clean. As a workaround try: crescent_cq = sphere2.cut(sphere1, clean=False)

  2. amaelFr commented on Apr 14, 2026

    @amaelFr

    Got the same kind of issue with this code

    
    import cadquery as cq
    from cadquery.vis import show
    
    diameter_in = 8.0
    diameter_out = 12.0
    pitch = 6
    length = 20.0
    
    # Create helix path (compatible with CadQuery versions that don't expose Workplane.helix)
    helix = cq.Wire.makeHelix(
        pitch=pitch,
        height=length+pitch,
        radius=diameter_out / 2,
    )
    
    # Thread profile (triangle)
    profile = (
        cq.Workplane("XZ")
        .polyline([
            (diameter_in/2, -pitch/2,),
            (diameter_out/2,0),
            (diameter_in/2, pitch/2)
        ])
        .close()
    )
    
    core = cq.Workplane("XY").circle(diameter_in/2).extrude(length)
    
    # Sweep profile along helix
    thread = profile.sweep(path=helix, isFrenet=True).translate((0, 0, -pitch/2))
    
    excess_helix = cq.Workplane("XY").circle(diameter_out/2).extrude(pitch)
    
    thread = thread.cut(
        excess_helix.translate((0, 0, -pitch))
    )
    thread = thread.cut(
        excess_helix.translate((0, 0, length))
    )
    
    thread  = thread.union(core)
    cq.exporters.export(thread, "screw.step")
    cq.exporters.export(thread, "model.stl")
    
    show(thread)
    

    Is there a way to correct it ?

    One error on the cuts and one on the union of the tread and core

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