Revolving a 1D Outer Mold Line

15 views
Skip to first unread message

Patrick Dees

unread,
May 29, 2024, 4:01:55 PMMay 29
to CadQuery
Good afternoon all;

This is my first time posting to this forum so if I am not doing something correctly please let me know and I will update!

I am trying to put together a consistent method for generating a tank, composed of a central cylindrical section and elliptical domes on each end. My most straight-forward approach has been to simply define the 1D perimeter of the shape, and then revolve that. However, this comes up with an error every time.

python Version: 3.12.2
CadQuery Version: 2.4.0

Code:
---------------------------------------------------------------
import cadquery as cq

# dimensions of the tank
thickness = 0.1
dt = 10 - 2*thickness    # diameter of tank
rt = dt/2  # radius of tank
ht = 20    # total height of tank cylindrical section
hht = ht / 2 # half the tank height
hd = 3 - thickness     # height of dome


# define workplane
wp = cq.Workplane("XZ")

# trying to give the entire perimeter then rotate
# start at bottom right corner above dome
tank = wp.move(xDist=rt, yDist=-hht)
# right-side vertical line of cylindrical section
tank = tank.line(xDist=0, yDist=ht)
# top dome
tank = tank.ellipseArc(
    x_radius=rt,
    y_radius=hd,
    angle1=0.,
    angle2=180.,
    sense=1, #  clockwise (-1) or counter clockwise (1)
)
# left-side vertical line of cylindrical section
tank = tank.line(xDist=0, yDist=-ht)
# bottom dome
tank = tank.ellipseArc(
    x_radius=rt,
    y_radius=hd,
    angle1=180,
    angle2=0.,
    sense=1, #  clockwise (-1) or counter clockwise (1)
)
# close the Wire
tank = tank.close()
# revolve to make a solid
tank = tank.revolve(180)
# finally, apply a thin shell
tank = tank.shell(thickness=-thickness)


---------------------------------------------------------------
The stack trace I get is:


StdFail_NotDone                           Traceback (most recent call last)
Cell In[86], line 20
     12 tank = tank.ellipseArc(
     13     x_radius=rt,
     14     y_radius=hd,
   (...)
     17     sense=1, #  clockwise (-1) or counter clockwise (1)
     18 )
     19 tank = tank.close()
---> 20 tank = tank.revolve(180)
     21 # finally, apply a thin shell
     22 tank = tank.shell(thickness=-thickness)

File ...\envs\parametric-cad-update\Lib\site-packages\cadquery\cq.py:3152, in Workplane.revolve(self, angleDegrees, axisStart, axisEnd, combine, clean)
   3149     axisEnd = self.plane.toWorldCoords(axisEnd).toTuple()
   3151 # returns a Solid (or a compound if there were multiple)
-> 3152 r = self._revolve(angleDegrees, axisStart, axisEnd)
   3154 return self._combineWithBase(r, combine, clean)

File ...\envs\parametric-cad-update\Lib\site-packages\cadquery\cq.py:3757, in Workplane._revolve(self, angleDegrees, axisStart, axisEnd)
   3755 toFuse = []
   3756 for f in self._getFaces():
-> 3757     thisObj = Solid.revolve(f, angleDegrees, Vector(axisStart), Vector(axisEnd))
   3758     toFuse.append(thisObj)
   3760 return Compound.makeCompound(toFuse)

File ...\envs\parametric-cad-update\Lib\site-packages\cadquery\utils.py:50, in cqmultimethod.__call__(self, *args, **kwargs)
     47 def __call__(self, *args, **kwargs):
     49     try:
---> 50         return super().__call__(*args, **kwargs)
     51     except DispatchError:
     52         return next(iter(self.values()))(*args, **kwargs)

File ...\envs\parametric-cad-update\Lib\site-packages\multimethod\__init__.py:315, in multimethod.__call__(self, *args, **kwargs)
    313 func = self[tuple(func(arg) for func, arg in zip(self.type_checkers, args))]
    314 try:
--> 315     return func(*args, **kwargs)
    316 except TypeError as ex:
    317     raise DispatchError(f"Function {func.__code__}") from ex

File ...\envs\parametric-cad-update\Lib\site-packages\cadquery\occ_impl\shapes.py:3432, in Solid.revolve(cls, face, angleDegrees, axisStart, axisEnd)
   3427 v2 = v2 - v1
   3428 revol_builder = BRepPrimAPI_MakeRevol(
   3429     face.wrapped, gp_Ax1(v1.toPnt(), v2.toDir()), radians(angleDegrees), True
   3430 )
-> 3432 return cls(revol_builder.Shape())

StdFail_NotDone: BRep_API: command not done

---------------------------------------------------------------

Can anyone see what I'm doing wrong? This seems like a very straight-forward implementation to me.

Thank you!
Pat

Jeremy Wright

unread,
May 29, 2024, 6:12:03 PMMay 29
to Patrick Dees, CadQuery
This modified version seems to work. When you revolve around an axis with a profile that crosses the axis, you can get self-intersecting geometry which breaks the CAD kernel.

import cadquery as cq

# dimensions of the tank
thickness = 0.1
dt = 10 - 2*thickness # diameter of tank
rt = dt/2 # radius of tank
ht = 20 # total height of tank cylindrical section
hht = ht / 2 # half the tank height
hd = 3 - thickness # height of dome


# define workplane
wp = cq.Workplane("XZ")

# trying to give the entire perimeter then rotate
# start at bottom right corner above dome
tank = wp.move(xDist=rt, yDist=-hht)
# right-side vertical line of cylindrical section
tank = tank.line(xDist=0, yDist=ht)
# top dome
tank = tank.ellipseArc(
x_radius=rt,
y_radius=hd,
angle1=0.,
angle2=90.,
sense=1, # clockwise (-1) or counter clockwise (1)
)

# # left-side vertical line of cylindrical section
tank = tank.line(xDist=0, yDist=-ht-rt)
# bottom dome
tank = tank.ellipseArc(
x_radius=rt,
y_radius=hd,
angle1=-90,
angle2=0.,
sense=1, # clockwise (-1) or counter clockwise (1)
)
# close the Wire
tank = tank.close()
# revolve to make a solid
tank = tank.revolve(360)
# finally, apply a thin shell
tank = tank.shell(thickness=-thickness)
--
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 the Google Groups "CadQuery" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cadquery+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cadquery/28d7dc3d-f7c9-4665-8df4-0fd6c113bc08n%40googlegroups.com.

Jeremy Wright

unread,
May 30, 2024, 10:52:50 AMMay 30
to Patrick Dees, CadQuery
I'm not very familiar with gmsh, but I know that some community members use it with CadQuery.

I can import the step file of the tank without errors in FreeCAD. I can also check that the shape is valid before exporting with `tank.val().isValid()`, and that shows True. I have also tried exporting to STL and do not see any errors when importing and slicing in Cura. You might want to try different deviation settings for gmsh mesh generation. You could also try using one of the mesh based formats that CadQuery exports (like STL).

On Thu, May 30, 2024 at 10:29 AM Patrick Dees <patrick...@gmail.com> wrote:
Thank you very much for your reply! That does work great, even when I do reversed domes. I am able to visualize this in a Jupyter notebook to confirm the shape. However, when I move this over to gmsh, I am getting an error claiming that the shape is not closed.

Specifically, I have the following helper function:

---------------------------------------------------------------------------
def visualize_geometry(
    cq_obj: Assembly | Workplane, fname: Optional[str] = None
) -> None:
    if fname is None:
        # get a temporary file name in the temp directory
        fname = join(gettempdir(), next(_get_candidate_names()))

    if isinstance(cq_obj, Assembly):
        cq_obj.save(f"{fname}.step")
    elif isinstance(cq_obj, Workplane):
        exporters.export(cq_obj, f"{fname}.step")
    else:
        raise ValueError(
            f"Passed object is of type {(type(cq_obj))} which is not "
            "supported. Pass either a cadquery Assembly or Workplane"
        )

    gmsh.initialize()
    # don't display messages on the terminal
    gmsh.option.setNumber('General.Terminal', 0)
    gmsh.open(f"{fname}.step")
    gmsh.option.setNumber("Mesh.MeshSizeMin", 0.001)
    gmsh.option.setNumber("Mesh.MeshSizeMax", 1.0)
    gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 10)
    gmsh.model.mesh.generate(dim=2)
    # gmsh.model.mesh.refine()
    gmsh.fltk.run()
    gmsh.finalize()

---------------------------------------------------------------------------
Which gives me the following error:

---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
~\AppData\Local\Temp\2\ipykernel_15740\1258170212.py in ?()
      4 path.append(join(getcwd(), '..'))
      5 from util.visualization import visualize_geometry
      6
      7
----> 8 visualize_geometry(tank)

c:\dev\parametric-geometry\examples\..\util\visualization.py in ?(cq_obj, fname)
     28     gmsh.open(f"{fname}.step")
     29     gmsh.option.setNumber("Mesh.MeshSizeMin", 0.001)
     30     gmsh.option.setNumber("Mesh.MeshSizeMax", 1.0)
     31     gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 10)
---> 32     gmsh.model.mesh.generate(dim=2)
     33     # gmsh.model.mesh.refine()
     34     gmsh.fltk.run()
     35     gmsh.finalize()

...\envs\parametric-cad-update\Lib\site-packages\gmsh.py in ?(dim)
   2042             lib.gmshModelMeshGenerate(
   2043                 c_int(dim),
   2044                 byref(ierr))
   2045             if ierr.value != 0:
-> 2046                 raise Exception(logger.getLastError())

Exception: The 1D mesh seems not to be forming a closed loop

---------------------------------------------------------------------------

Any thoughts on this?

Patrick Dees

unread,
May 30, 2024, 3:37:59 PMMay 30
to Jeremy Wright, CadQuery

Adam Urbanczyk

unread,
May 30, 2024, 3:40:36 PMMay 30
to CadQuery
Nothing CQ specific here. Maybe try NETGEN or open an issue on their tracker?
Reply all
Reply to author
Forward
0 new messages