Filling a closed surface

871 views
Skip to first unread message

CONNOR MORENO

unread,
Mar 15, 2022, 6:03:01 PM3/15/22
to CadQuery
Hello!

I'm trying to build a solid of what's a relatively complicated shape (see attached images). Due to the complex nature of this shape, it may be easiest to use CQ's parametricSurface method. However, parametricSurface returns only a surface (understandably) and I'm having a hard time finding tools in the documentation to fill the resulting surface to create a solid object. Any thoughts? Please let me know if any more detail is needed on my end.

Thanks!
Screen Shot 2022-03-15 at 4.48.04 PM.png
Screen Shot 2022-03-15 at 4.47.48 PM.png

CONNOR MORENO

unread,
Mar 15, 2022, 6:05:22 PM3/15/22
to CadQuery
Note: the images shared are the result of parametricSurface

Roger Maitland

unread,
Mar 19, 2022, 5:09:53 PM3/19/22
to CadQuery
Interesting project.  The parametricSurface() method creates an object with the inherent type of Face (use the .val() to convert a Workplane object into its base type). In general, to convert Faces into a Solid object this works:

solid_object = cq.Solid.makeSolid(cq.Shell.makeShell(faces))

where faces is a list of type Face (e.g. 6 faces for a cube).

I've successfully done this many times but not with a single Face defining the Solid so I don't know if it will work.  Let us know.

Cheers,
Roger

CONNOR MORENO

unread,
Mar 19, 2022, 5:36:24 PM3/19/22
to CadQuery
Thanks for the response! I've tried playing around with makeSolid, though I wasn't including .val(). Is this roughly what you mean?

face = cq.Workplane('XY').parametricSurface(function).val()
solid = cq.Solid.makeSolid(cq.Shell.makeShell([face]))

where function is that which gives the (x, y, z) coordinates of the surface. Doing so gives the following error:

TypeError: SolidFromShell(): incompatible function arguments. The following argument types are supported: 
1. (self: OCP.ShapeFix.ShapeFix_Solid, shell: OCP.TopoDS.TopoDS_Shell) -> OCP.TopoDS.TopoDS_Solid
Invoked with: <OCP.ShapeFix.ShapeFix_Solid object at 0x18debbdf0>, <OCP.TopoDS.TopoDS_Face object at 0x18de0f2f0>

Not really sure what's going on here; I'm not very familiar with the OCP backside of CadQuery.

Roger Maitland

unread,
Mar 19, 2022, 5:52:16 PM3/19/22
to CadQuery
Yes. It seems like the Shell isn't valid. You could try this to get some more info:

face = cq.Workplane('XY').parametricSurface(function).val()
print(f"{face.isValid()=}")
shell = cq.Shell.makeShell([face])
print(f"{shell.isValid()=}")
solid = cq.Solid.makeSolid(shell)
print(f"{solid.isValid()=}")

Roger Maitland

unread,
Mar 19, 2022, 6:13:23 PM3/19/22
to CadQuery
I'm guessing this isn't going to work as a simple sphere fails in the same way as your example:

import cadquery as cq

sphere = cq.Solid.makeSphere(50, angleDegrees1=-90)
sphere_faces = sphere.Faces()
print(len(sphere_faces)) # 1
print(f"{sphere_faces[0].isValid()=}") # True
sphere_shell = cq.Shell.makeShell(sphere_faces)
print(f"{sphere_shell.isValid()=}") # True
print(f"{sphere_shell.Closed()=}") # False
sphere_from_shell = cq.Solid.makeSolid(sphere_shell)
print(f"{sphere_from_shell.isValid()=}")

if "show_object" in locals():
show_object(sphere, name="solid")
show_object(sphere_shell, name="sphere_shell")
show_object(sphere_from_shell, name="sphere_from_shell")

Even though this shell is created from a face extracted from the sphere, it's Valid but not Closed. I'm guessing this is why makeSolid fails. Not sure what to suggest next...

CONNOR MORENO

unread,
Mar 21, 2022, 1:24:55 PM3/21/22
to CadQuery
No worries. Thanks for the help!

I am able to make the volume by lofting over cross-sections but some strange things happen near the region where the volume "closes," not to mention some general lumpiness:
Screen Shot 2022-03-21 at 11.39.14 AM.pngScreen Shot 2022-03-21 at 11.39.32 AM.png
I can include more cross-sections along the lofting path to smooth but doing so has its own issues:
Screen Shot 2022-03-21 at 11.45.18 AM.pngScreen Shot 2022-03-21 at 11.50.10 AM.png
For whatever reason, the loft is including only the surfaces where the volume closes (confirmed that there are indeed two surfaces there). This seems to occur only when the revolution exceeds 90º (keeping the proportion of cross-sections constant):

90º revolution, N/4 cross-sections along path:
Screen Shot 2022-03-21 at 11.53.36 AM.pngScreen Shot 2022-03-21 at 11.54.29 AM.png
135º revolution, 3N/4 cross-sections along path:
Screen Shot 2022-03-21 at 11.59.52 AM.pngScreen Shot 2022-03-21 at 12.00.14 PM.png
180º revolution, N/2 cross-sections along path:
Screen Shot 2022-03-21 at 11.57.30 AM.pngScreen Shot 2022-03-21 at 11.57.56 AM.png

Sorry if this was a massive information dump but any suggestions?

Connor

Roger Maitland

unread,
Mar 21, 2022, 7:41:30 PM3/21/22
to CONNOR MORENO, CadQuery
I haven't had much success with this kind of lofting.  However, segmenting your design seems like it might be a good idea so I tried it with a torus built the same way:
import cadquery as cq
import cq_warehouse.extensions

torus_major_radius = 50
torus_minor_radius = 10


def surface(u, v):
"""Calculate the surface displacement of the flag at a given position"""
return (
cq.Vector(torus_minor_radius, 0, 0).rotateY(360 * u)
+ cq.Vector(torus_major_radius, 0, 0)
).rotateZ(180 * v)


torus_face = cq.Workplane("XY").parametricSurface(lambda u, v: surface(u, v), N=10)
torus_end_edges = [
torus_face.edges(">X").val(),
torus_face.edges("<X").val(),
]
torus_end_wires = [cq.Wire.assembleEdges([e]) for e in torus_end_edges]
torus_end_faces = [cq.Face.makeFromWires(w, []) for w in torus_end_wires]
torus_faces = [torus_face.val()] + torus_end_faces
torus_shell = cq.Shell.makeShell(torus_faces).fix()
torus_solid = cq.Solid.makeSolid(torus_shell)

for i, f in enumerate(torus_faces):
print(f"face{i}.isValid()={f.isValid()}")
print(f"{torus_shell.isValid()=}")
print(f"{torus_shell.Closed()=}")
print(f"{torus_solid.isValid()=}")

test = cq.Solid.makeBox(50, 50, 50).fuse(torus_solid)
print(f"{test.isValid()=}")
if "show_object" in locals():
# show_object(torus_solid, name="torus_solid")
# show_object(torus_face, name="torus_face")
# show_object(torus_end_edges, name="torus_end_edges")
# show_object(torus_end_wires, name="torus_end_wires")
# show_object(torus_end_faces, name="torus_end_faces")
# show_object(torus_shell, name="torus_shell")
show_object(torus_solid, name="torus_solid")
# show_object(test, name="test")
which outputs:
face0.isValid()=True
face1.isValid()=False
face2.isValid()=False
torus_shell.isValid()=True
torus_shell.Closed()=True
torus_solid.isValid()=True
test.isValid()=True
half_torus.png
Even though the end faces aren't valid - don't know why - the Shell is valid, at least after fix(). The two halves can be combined with a fuse/union.

Maybe not a great solution but something to try.

Cheers,
Roger

--
cadquery home: https://github.com/CadQuery/cadquery
post issues at https://github.com/CadQuery/cadquery/issues
run it at home at : https://github.com/CadQuery/CQ-editor
---
You received this message because you are subscribed to a topic in the Google Groups "CadQuery" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/cadquery/MoOVlSTSeec/unsubscribe.
To unsubscribe from this group and all its topics, send an email to cadquery+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cadquery/c8d3c009-7c5c-4011-87e2-2b308b927fd7n%40googlegroups.com.

Adam Urbanczyk

unread,
Mar 22, 2022, 3:43:19 AM3/22/22
to CadQuery
It would be easier if you could share your code. As things are now, you'll likely need to use OCCT / OCP directly ("approximate" code):

import OCP

shell = OCP.TopoDS.TopoDS_Shell()
bldr = OCP.BRep.BRep_Builder()
bldr.MakeShell(shell)
bldr.Add(shell, yourFace.wrapped)

s = cq.Solid.makeSolid(cq.Shell(shell))

NB: I'm not sure if this will work as-is and if your face is actually closed.

P.S. You mentioned lofting, for this kind of geometries multi-section sweep will definitely be better.

sriramakrishna turaga

unread,
Jun 14, 2022, 3:14:17 AM6/14/22
to CadQuery
hi  CONNOR MORENO, and all

I am also looking at a similar kind of need. I wanted to generate surfaces from the equation. please look into the link.

Request you to guide me with sample code on how the function should look like in perametricsurface method. Once the surface generated i need to convert that as a solid and then a step file.

Please help.

Thanks & Regards
Sriramakrishna Turaga

CONNOR MORENO

unread,
Jul 8, 2022, 4:37:17 PM7/8/22
to CadQuery
Hi all,

Sorry it's been a while since I followed up on this but Adam, your suggestion did indeed make the CadQuery shape a solid. Executing the attached script outputs print(solid) as <cadquery.occ_impl.shapes.Solid object at 0x7f48cc1f6eb0>. However, it is not a filled solid but just a surface still (see attached image. I have confirmed that the .step is indeed hollow and this is not a product of the visualization software). Is there as way to modify this script to ensure the surface is filled? By the way, don't worry too much about the "vmec" stuff; it's just the module that returns the Cartesian coordinates describing this shape.

Thanks!

Sriramakrishna, let us know if you still need help. I'm not sure how much this script will assist but the function just needs to be any that returns the Cartesian coordinates describing your surface.
Screen Shot 2022-07-08 at 3.31.33 PM.png
parametric_test.py

CONNOR MORENO

unread,
Jul 8, 2022, 6:02:28 PM7/8/22
to CadQuery
Also, I tried using the Face.thicken() functionality on the master branch and it created the monstrosity attached. Perhaps I used it wrong
Screen Shot 2022-07-08 at 4.36.45 PM.png
Screen Shot 2022-07-08 at 4.37.04 PM.png

Laurent Alebarde

unread,
Apr 21, 2023, 10:13:05 AM4/21/23
to CadQuery
I have the same need, here are successive lofts of parametrized surfaces. The bottom looks surprisingly opened. Sweep is not an option forme.

Capture d’écran_2023-04-21_16-10-36.png

@Adam, would it be possible to have a "solid = True/False" option in loft please?

Laurent Alebarde

unread,
Apr 21, 2023, 10:39:47 AM4/21/23
to CadQuery
Of course, the openness of the shell was a mistake of me !

Capture d’écran_2023-04-21_16-21-10.png

Laurent Alebarde

unread,
Apr 21, 2023, 10:58:58 AM4/21/23
to CadQuery
From the documentation basic example, if I split it, it looks closed. May I assume it is solid?

```
import cadquery as cq
import OCP
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").circle(1.5) \
    .workplane(offset=3.0).rect(0.75, 0.5).loft(combine=True)
result = result.faces(">Z").workplane(offset = -1).split(keepBottom = True)
```
Capture d’écran_2023-04-21_16-56-23.png
Reply all
Reply to author
Forward
0 new messages