diff --git a/README.rst b/README.rst index 177a178..6e3b798 100644 --- a/README.rst +++ b/README.rst @@ -49,9 +49,9 @@ using a single/multi point exposure by generating a series of scan vectors in a **Support Structure Generation** * Projection based block and truss support structure generation - * 3D intersected support volumes are generated from overhang regions using OpenGL ray-tracing approach * Generate a truss grid using support volumes suitable for Metal AM processes + * Exact support volume generation using the `pycork `_ library * Extracting overhang surfaces from meshes **Slicing:** diff --git a/pyslm/support/geometry.py b/pyslm/support/geometry.py index ea43043..e410d68 100644 --- a/pyslm/support/geometry.py +++ b/pyslm/support/geometry.py @@ -22,7 +22,7 @@ import pycork from pyslm import pyclipper -from line_profiler_pycharm import profile + def extrudeFace(extrudeMesh: trimesh.Trimesh, height: Optional[float] = None, @@ -106,29 +106,28 @@ def extrudeFace(extrudeMesh: trimesh.Trimesh, return extMesh -def boolUnion(meshA, meshB): +def boolUnion(meshA: trimesh.Trimesh, meshB: trimesh.Trimesh): vertsOut, facesOut = pycork.union(meshA.vertices, meshA.faces, meshB.vertices, meshB.faces) return trimesh.Trimesh(vertices=vertsOut, faces=facesOut, process=True) -def boolIntersect(meshA, meshB): +def boolIntersect(meshA: trimesh.Trimesh, meshB: trimesh.Trimesh): vertsOut, facesOut = pycork.intersection(meshA.vertices, meshA.faces, meshB.vertices, meshB.faces) return trimesh.Trimesh(vertices=vertsOut, faces=facesOut, process=True) - #subprocess.call([CORK_PATH, '-isct', 'a.off', 'b.off', 'c.off']) - #return trimesh.load_mesh('c.off') -def boolDiff(meshA, meshB): +def boolDiff(meshA: trimesh.Trimesh, meshB: trimesh.Trimesh): vertsOut, facesOut = pycork.difference(meshA.vertices, meshA.faces, meshB.vertices, meshB.faces) return trimesh.Trimesh(vertices=vertsOut, faces=facesOut, process=True) -def resolveIntersection(meshA, meshB): + +def resolveIntersection(meshA: trimesh.Trimesh, meshB: trimesh.Trimesh): vertsOut, facesOut = pycork.resolveIntersection(meshA.vertices, meshA.faces, meshB.vertices, meshB.faces) @@ -172,6 +171,7 @@ def path2DToPathList(shapes: List[shapely.geometry.polygon.Polygon]) -> List[np. return paths + def sortExteriorInteriorRings(polyNode, closePolygon: Optional[bool] = False) -> Tuple[List[np.ndarray], List[np.ndarray]]: """ @@ -240,23 +240,23 @@ def triangulateShapelyPolygon(polygon: shapely.geometry.Polygon, return result['vertices'], result['triangles'] def triangulatePolygonFromPaths(exterior: np.ndarray, interiors: List[np.ndarray], - triangle_args:str = None, + triangle_args: str = None, **kwargs): """ - Given a list of exteiror and interiors triangulation using a + Given a list of exterior and interiors triangulation using a python interface to `triangle.c` .. code-block:: bash pip install triangle - :param polygon: Polygon object to be triangulated + :param exterior: Exterior boundaries of polygon + :param interiors: Interior boundaries of polygon :param triangle_args: Passed to triangle.triangulate i.e: 'p', 'pq30' :param kwargs: :return: Returns a tuple of vertices and faces """ # set default triangulation arguments if not specified - if triangle_args is None: triangle_args = 'p' @@ -268,7 +268,8 @@ def triangulatePolygonFromPaths(exterior: np.ndarray, interiors: List[np.ndarray return result['vertices'], result['triangles'] -def _polygon_to_kwargs2(exterior, interiors): + +def _polygon_to_kwargs2(exterior: np.ndarray, interiors: List[np.ndarray]): """ Given both exterior and interior boundaries, create input for the the triangle mesh generator. This is version #2 which has been adapted from @@ -462,6 +463,7 @@ def add_boundary(boundary, start, clean=True): return result + def triangulatePolygon(section, closed: Optional[bool] = False) -> Tuple[np.ndarray, np.ndarray]: """ @@ -552,4 +554,4 @@ def generatePolygonBoundingBox(bbox: np.ndarray) -> shapely.geometry.Polygon: [a[1], b[0]], [a[0], b[0]]]) - return bboxPoly \ No newline at end of file + return bboxPoly diff --git a/pyslm/support/render.py b/pyslm/support/render.py index 5fc11a2..177cddd 100644 --- a/pyslm/support/render.py +++ b/pyslm/support/render.py @@ -54,11 +54,11 @@ def visSize(self): return self._visSize @property - def mesh(self): + def mesh(self) -> trimesh.Trimesh: return self._mesh @property - def bbox(self): + def bbox(self) -> np.ndarray: return self._bbox def setMesh(self, mesh: trimesh.Trimesh): diff --git a/pyslm/support/support.py b/pyslm/support/support.py index 10bf54e..844c32f 100644 --- a/pyslm/support/support.py +++ b/pyslm/support/support.py @@ -419,6 +419,11 @@ class BlockSupportGenerator(BaseSupportGenerator): for creating the surrounding support skin. """ + _intersectionVolumeTolerance = 50 + """ + An internal tolerancces used to determine if the projected volume intersects with the part + """ + _gausian_blur_sigma = 1.0 """ The internal parameter is used for blurring the calculated depth field to smooth out the boundaries. Care should @@ -725,7 +730,6 @@ def identifySupportRegions(self, part: Part, overhangAngle: float, """ The geometry of the part requires exporting as a '.off' file to be correctly used with the Cork Library """ - #part.geometry.export('part.off') supportBlockRegions = [] @@ -772,6 +776,7 @@ def identifySupportRegions(self, part: Part, overhangAngle: float, timeIntersect = time.time() logging.info('\t - start intersecting mesh') + if False: # Intersect the projection of the support face with the original part using the Cork Library @@ -792,9 +797,12 @@ def identifySupportRegions(self, part: Part, overhangAngle: float, totalBooleanTime += time.time() - timeIntersect # Note this a hard tolerance - if cutMesh.volume < -1: # 50 + if cutMesh.volume < BlockSupportGenerator._intersectionVolumeTolerance: # 50 """ + + Create a support structure that extends to the base plate (z=0) + NOTE - not currently used - edge smoothing cannot be performed despite this being a quicker methods, it suffer sever quality issues with jagged edges so should be avoided. """ @@ -808,7 +816,7 @@ def identifySupportRegions(self, part: Part, overhangAngle: float, supportBlockRegions.append(baseSupportBlock) - continue # No intersection had taken place + continue # No self intersection with the part has taken place with the support elif not findSelfIntersectingSupport: continue @@ -941,8 +949,8 @@ def identifySupportRegions(self, part: Part, overhangAngle: float, """ Intersecting with cutmesh is more efficient when projecting downwards """ - - if cutMesh.volume > 50: + # + if cutMesh.volume > BlockSupportGenerator._intersectionVolumeTolerance: hitLoc2, index_ray2, index_tri2 = cutMeshUpper.ray.intersects_location(ray_origins=coords2, ray_directions=ray_dir, @@ -977,14 +985,14 @@ def identifySupportRegions(self, part: Part, overhangAngle: float, extrudedBlock.export('b.off') - """ - Take the near net-shape support and obtain the difference with the original part to get clean - boundaries for the support - """ - subprocess.call([self.CORK_PATH, '-diff', 'b.off', 'part.off', 'c.off']) blockSupportMesh = trimesh.load_mesh('c.off', process=True, validate=True) + """ + Take the near net-shape support and obtain the difference with the original part to get clean + boundaries for the support + """ + blockSupportMesh = boolDiff(extrudedBlock, part.geometry) blockSupportMesh.fix_normals() blockSupportMesh.remove_degenerate_faces() @@ -1007,7 +1015,7 @@ def identifySupportRegions(self, part: Part, overhangAngle: float, logging.info('\t - processed support face\n') logging.info('Total boolean time: {:.3f}\n'.format(totalBooleanTime)) - print('Total Boolean Time', totalBooleanTime) + return supportBlockRegions @@ -1069,7 +1077,7 @@ def mergeMesh(self, state: bool): self._mergeMesh = state @property - def useSupportSkin(self) -> True: + def useSupportSkin(self) -> bool: """ Generates a support skin around the extruded boundary of the support""" return self._useSupportSkin @@ -1124,7 +1132,7 @@ def gridSpacing(self) -> List[float]: @gridSpacing.setter def gridSpacing(self, spacing: List[float]): - """ The Grid Spacing used for the support structure """ + """ The Grid spacing used for the support structure """ self._gridSpacing = spacing @staticmethod