CadQuery / cadquery

A python parametric CAD scripting framework based on OCCT
https://cadquery.readthedocs.io
Other
2.93k stars 276 forks source link

STL (tesselation) quality #367

Closed adam-urbanczyk closed 3 years ago

adam-urbanczyk commented 4 years ago

It is quite common to get non-manifold meshes out of the kernel (example below). Investigate possible solutions

import math
import cadquery as cq

from cadquery import Workplane as WP
from cadquery import exporters

from cadquery import Edge
from cadquery import Wire
from cadquery import Face
from cadquery import Shell
from cadquery import Solid
from cadquery import Vector

def Tetra(s=1):
    c0 = s*math.sqrt(2) / 4
    vertices = [[c0, -c0, c0], [c0, c0, -c0], [-c0, c0, c0], [-c0, -c0, -c0]]
    faces_ixs = [[0, 1, 2, 0], [1, 0, 3, 1], [2, 3, 0, 2], [3, 2, 1, 3]]

    faces = []
    for ixs in faces_ixs:
        lines = []
        for v1, v2 in zip(ixs, ixs[1:]):
            lines.append(
                Edge.makeLine(Vector(*vertices[v1]), Vector(*vertices[v2]))
            )
        wire = Wire.combine(lines)
        faces.append(Face.makeFromWires(wire))

    shell = Shell.makeShell(faces)
    solid = Solid.makeSolid(shell)
    return solid

def save( self, fname, tol = 1):
    with open(fname, 'w') as f:
        exporters.exportShape(self, "STL", f, tol)

WP.save = save

s = WP("XY").sphere(radius=1).union(Tetra(s=3))
michaelgale commented 4 years ago

The problem might not be the mesh itself. The resulting solid before exporting to mesh is not valid.

s.solids().val().isValid()

is False after union. The resulting solid has 12 faces and 12 edges. This invalid BRep doesn't help the mesher make a good mesh. The resulting mesh has dozens of errors including flipped normals, duplicate faces, non-manifolds, etc.

Just out of curiosity, I tried this:

xr = -90
yr = -60
zr = -180
s = WP("XY").sphere(radius=1).rotate((0,0,0),(1,0,0),xr) \
    .rotate((0,0,0),(0,1,0),yr) \
    .rotate((0,0,0),(0,0,1),zr) \
    .union(Tetra(s=3))

This produces a valid solid with 10 faces and 10 edges. The meshed solid is not perfect, but it is manifold. It only complains about 4 flipped normals.

I think each intermediate rotation allows the OCCT fuse operation to "clean" redundant edges so that the "edge seam" in the sphere's equator eventually optimizes away. The resulting solid is valid from a BRep point of view but the geometry is still challenging from a meshing perspective. However it is a better place for it to start!

FYI, changing the tolerance of the mesher does not help, it still yields 2-4 flipped normals.

michaelgale commented 4 years ago

@adam-urbanczyk As a follow up, the following mutual orientations of the sphere and tetra solid result in both a valid BRep and valid mesh:

xr = 91
yr = -27
zr = 0
s = WP("XY").sphere(radius=1).rotate((0,0,0),(1,0,0),xr) \
    .rotate((0,0,0),(0,1,0),yr) \
    .rotate((0,0,0),(0,0,1),zr) \
    .union(Tetra(s=3).rotate((0,0,0),(0,1,0),33))

I guess it shows the fragility of both the BRep kernel and the mesher in certain edge case geometries. There are operations which are at the boundaries of tolerances and floating point residues which result in one bad normal estimation and the solid falls apart!

michaelgale commented 4 years ago

Sorry @adam-urbanczyk, one last follow-up. I tried your code using the OCP branch (OCCT 7.4 vs. 6.9 in oce) and everything works. The BRep solid is valid and the resulting mesh has no errors. Perhaps this is just an artifact of the older 6.9 OCCT kernel and the transition to OCP using 7.4 will save unnecessary effort on fixing/improving meshing?

jmwright commented 4 years ago

Thanks for the research @michaelgale

adam-urbanczyk commented 4 years ago

Thanks for the detailed analysis @michaelgale ! I'm did try with OCP and used FreeCAD mesh analysis to find the issues. I don't have much experience with this kind of issues, maybe FreeCAD is giving false positives. Do you happen to know about a tool/library that would give a trustworthy assessment of mesh validity?

michaelgale commented 4 years ago

@adam-urbanczyk Is meshing still a problem with the OCP based CQ kernel? Generally, I have not had much trouble with exported STL meshes from CQ. If the open cascade solid is valid prior to meshing, then the resulting meshes have been ok.

I did import a few complex meshes into FreeCAD and although every imported shape looks fine, the analysis tool in the Mesh Workbench did sometimes find a few issues. Most relate to self intersections of edges/vertices etc. Out of interest, I also imported the meshes into Blender. If you install the "3D Print Toolbox" add-on for Blender, you can also perform mesh analysis. On problematic meshes, Blender reports different issues, but always the shapes were manifold. Lastly, I used the Blender mesh repair and re-exported the mesh, imported into FreeCAD, and FreeCAD detected no problems with the mesh. Incidentally, a "bad" mesh STL file size of 2.1 MB shrank to 460 kB after repair/re-export from Blender.

The source code for the 3D print toolbox (and almost everything else in the Blender ecosystem) is python and open source. In fact, I have integrated Blender into my CQ workflows since it is inherently scriptable with python. I use Blender to perform high quality renders and animations of CQ generated models. Blender's mesh tools are far superior to anything in FreeCAD.

I think STL meshing is probably ok in the OCP / CQ stack -- but if its still problematic, there is possibility of borrowing some code/insights from the Blender ecosystem into CQ? Do you have any particular CQ test cases of meshing that are problematic that I could look at?

adam-urbanczyk commented 4 years ago

I was triggered by two different posts on our group. One of them is in the issue description. To some degree I could reproduce the issues. Good to know that Blender has such a workbench. I think that they are on GPL so not really compatible with CQ. I think that for mesh repair they are using their own implementation of the PolyMender algorithm (see below).

https://github.com/CadQuery/cadquery/wiki/Meshing-and-mesh-repair

adam-urbanczyk commented 4 years ago

BTW: this is the problematic model rednered as a pointcloud. This picture reveals the issue obraz

adam-urbanczyk commented 4 years ago

I tried the MEFISTO mesher in FreeCAD and NETGEN. The latter produces meshes without self-intersections:

import netgen.occ as nocc

s = nocc.OCCGeometry('model.step')
m = s.GenerateMesh(perfstepsend=meshing.MeshingStep.MESHSURFACE,maxh=.1,optsteps2d=0)
m.Export('model.stl', 'STL Format')
michaelgale commented 4 years ago

Interesting result.

adam-urbanczyk commented 3 years ago

Regarding mesh repair, this library looks promising: https://github.com/alicevision/geogram In the end I did not try it though because Netgen seems to just work.