cadquery icon indicating copy to clipboard operation
cadquery copied to clipboard

Thickening surface works with CQ2.1 but crashes with master

Open bragostin opened this issue 9 months ago • 10 comments

This code used to work with Cadquery 2.2.0b2 + Python 3.8 (under Debian Bookworm). It was convenient to sew surfaces together and thicken them all at once so that the seams would remain continuous.

import cadquery as cq
from OCP.BRepOffset import BRepOffset_MakeOffset, BRepOffset_Skin, BRepOffset_RectoVerso
from OCP.GeomAbs import GeomAbs_Intersection, GeomAbs_Arc, GeomAbs_Tangent

def _thicken(self, thickness, join=GeomAbs_Arc):
    solid = BRepOffset_MakeOffset()
    solid.Initialize(self.wrapped, thickness, 1e-5, BRepOffset_Skin, False, False, join, True)#, True) #The last True is important to make solid
    solid.MakeOffsetShape()
    return cq.Shape.cast(solid.Shape())
cq.Shell.thicken = _thicken


pts = [ (0, 0, 0), (1, 0, 0), (1, 1, 0) , (0, 1, 0), (0, 0, 0) ]
wires = cq.Wire.makePolygon(listOfVertices=pts, forConstruction=False)
wires = cq.Workplane().newObject([wires])
face_1 = cq.Workplane('XY').interpPlate(wires, [(0.5,0.5,0.5)], 0, degree=2, nbPtsOnCur=15,  nbIter=2, anisotropy=False, tol2d=0.00001, tol3d=0.0001, tolAng=0.01, tolCurv=0.1, maxDeg=7, maxSegments=9).val()
face_1.exportStep("face_1.stp")

pts = [ (1, 0, 0), (2, 0, 0), (2, 1, 0) , (1, 1, 0), (1, 0, 0) ]
wires = cq.Wire.makePolygon(listOfVertices=pts, forConstruction=False)
wires = cq.Workplane().newObject([wires])
face_2 = cq.Workplane('XY').interpPlate(wires, [(1.5,0.5,-0.5)], 0, degree=2, nbPtsOnCur=15,  nbIter=2, anisotropy=False, tol2d=0.00001, tol3d=0.0001, tolAng=0.01, tolCurv=0.1, maxDeg=7, maxSegments=9).val()

shells = cq.Shell.makeShell([face_1, face_2])

skin = shells.thicken(0.1)

Now with CQ master and pyton 3.11, it now yields:

  File "/home/issue_thicken.py", line 28, in <module>
    skin = shells.thicken(0.1)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/issue_thicken.py", line 11, in _thicken
    return cq.Shape.cast(solid.Shape())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/cadquery/lib/python3.11/site-packages/cadquery/occ_impl/shapes.py", line 445, in cast
    t = shapetype(obj)
        ^^^^^^^^^^^^^^
  File "/home/cadquery/lib/python3.11/site-packages/cadquery/occ_impl/shapes.py", line 367, in shapetype
    raise ValueError("Null TopoDS_Shape object")
ValueError: Null TopoDS_Shape object

What might have caused this change?

This is the expected result:

Image

bragostin avatar Feb 14 '25 14:02 bragostin

@bragostin It looks like you might have installed via pip. Can you also try with conda to see if you get the same result?

jmwright avatar Feb 14 '25 14:02 jmwright

@jmwright yes that's right. Will try with conda.

bragostin avatar Feb 14 '25 15:02 bragostin

@jmwright same issue with conda install

bragostin avatar Feb 14 '25 15:02 bragostin

@bragostin Thanks for confirming.

jmwright avatar Feb 14 '25 15:02 jmwright

I reproduced the error with master and OCP 7.8.1.1.

As a workaround you might try the following. Note also that the Solid.interpPlate is deprecated.

from cadquery.func import *

pts1 = [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 0)]
wire1 = polyline(*pts1)
face1 = Face.makeNSidedSurface(wire1, [(0.5, 0.5, 0.5)])

pts2 = [(1, 0, 0), (2, 0, 0), (2, 1, 0), (1, 1, 0), (1, 0, 0)]
wire2 = polyline(*pts2)
face2 = Face.makeNSidedSurface(wire2, [(1.5, 0.5, -0.5)])

shell1 = shell(face1, face2)
skin1 = offset(shell1, 0.1)

print(check(skin1))

lorenzncode avatar Feb 14 '25 15:02 lorenzncode

Note also that the Solid.interpPlate is deprecated.

Nevermind, you're calling the Workplane method which uses Face.makeNSidedSurface.

lorenzncode avatar Feb 14 '25 18:02 lorenzncode

The original code works after changing with degree=2 to degree=3 (degree=2 also results in error in the free func version).
I don't think it's a CQ issue.

lorenzncode avatar Feb 14 '25 18:02 lorenzncode

@lorenzncode maybe something changed in BRepOffset_MakeOffset() on the OCC side. Thank you for checking, I really love the Free API!

bragostin avatar Feb 14 '25 20:02 bragostin

A more representative example, for reference, creating thick Schwarz-D surfaces with degree=3, nbPtsOnCur=20, that used to work with CQ2.1 but not anymore with Master:

# Works with Cadquery 2.2.0b2 + Python 3.8 but not with master
# Already not working with Cadquery 2.3.1 + Python 3.10
import cadquery as cq
from OCP.BRepOffset import BRepOffset_MakeOffset, BRepOffset_Skin, BRepOffset_RectoVerso
from OCP.GeomAbs import GeomAbs_Intersection, GeomAbs_Arc, GeomAbs_Tangent

def _thicken(self, thickness, join=GeomAbs_Arc):
    solid = BRepOffset_MakeOffset()
    solid.Initialize(self.wrapped, thickness, 1e-5, BRepOffset_Skin, False, False, join, True)#, True) #The last True is important to make solid
    solid.MakeOffsetShape()
    return cq.Shape.cast(solid.Shape())
cq.Shell.thicken = _thicken


pts =  (
[[-5.    ,  4.6875,  4.5   ], [-5.    ,  4.6875, -4.5   ], [ 5.    ,  4.6875, -4.5   ], 
[ 5.    , -4.6875, -4.5   ], [ 5.    , -4.6875,  4.5   ], [-5.    , -4.6875,  4.5   ],  
[-5.    ,  4.6875,  4.5   ]]
)
wires = cq.Workplane().polyline(pts)

face_0 = cq.Workplane('XY').interpPlate(wires, [(0,0,0)], 0, degree=3, nbPtsOnCur=20,  nbIter=2, anisotropy=False, tol2d=0.00001, tol3d=0.0001, tolAng=0.01, tolCurv=0.1, maxDeg=8, maxSegments=9)

bb = face_0.val().BoundingBox()
xmin, xmax, ymin, ymax, zmin, zmax = bb.xmin, bb.xmax, bb.ymin, bb.ymax, bb.zmin, bb.zmax
face_1 = face_0.translate((0,0,0))
face_1 = face_1.add( face_0.translate((-(xmax-xmin), -(ymax-ymin), 0)).rotate((0,0,0),(0,0,1), 180) )
face_1 = face_1.add( face_0.translate((-(xmax-xmin), 0, (zmax-zmin))).rotate((0,0,0),(0,1,0), 180) )
face_1 = face_1.add( face_0.translate((0, -(ymax-ymin), (zmax-zmin))).rotate((0,0,0),(1,0,0), 180) )

shell = cq.Shell.makeShell(face_1.vals())
shell.exportStep("shell_CQ_2.1.stp")

skin = shell.thicken(1)
skin.exportStep("skin_CQ_2.1.stp")

# Re-written with Free Function API : does not work with Cadquery Master + Python 3.11
import cadquery as cq
from cadquery.func import *
from OCP.GeomAbs import GeomAbs_C0

pts =  (
[[-5.    ,  4.6875,  4.5   ], [-5.    ,  4.6875, -4.5   ], [ 5.    ,  4.6875, -4.5   ], 
[ 5.    , -4.6875, -4.5   ], [ 5.    , -4.6875,  4.5   ], [-5.    , -4.6875,  4.5   ],  
[-5.    ,  4.6875,  4.5   ]]
)
wires = polyline(*pts)
face_0 = Face.makeNSidedSurface(edges=wires, constraints=[(0,0,0)], continuity=GeomAbs_C0, degree=3, nbPtsOnCur=20, nbIter=2, anisotropy=False, tol2d=1e-05, tol3d=0.0001, tolAng=0.01, tolCurv=0.1, maxDeg=8, maxSegments=9)

bb = face_0.BoundingBox()
xmin, xmax, ymin, ymax, zmin, zmax = bb.xmin, bb.xmax, bb.ymin, bb.ymax, bb.zmin, bb.zmax

face_1 = face_0.moved((0,0,0))
face_1 = face_1 + face_0.moved((-(xmax-xmin), -(ymax-ymin), 0)).rotate((0,0,0),(0,0,1), 180)
face_1 = face_1 + face_0.moved((-(xmax-xmin), 0, (zmax-zmin))).rotate((0,0,0),(0,1,0), 180)
face_1 = face_1 + face_0.moved((0, -(ymax-ymin), (zmax-zmin))).rotate((0,0,0),(1,0,0), 180)

shell_1 = shell(face_1)
shell_1.exportStep("shell_CQ_Master.stp")

skin_1 = offset(shell_1, 1)
print(check(skin_1))
skin_1.exportStep("skin_CQ_Master.stp")

Expected result:

Image

bragostin avatar Feb 15 '25 12:02 bragostin

Maybe linked to this OCC bug: https://tracker.dev.opencascade.org/view.php?id=33166

bragostin avatar Feb 17 '25 17:02 bragostin