Skip to content

Commit

Permalink
Initial commit of PySLM to use PyCork Library
Browse files Browse the repository at this point in the history
  • Loading branch information
drlukeparry committed Jan 21, 2022
1 parent 8cb7e6e commit e438bef
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 69 deletions.
2 changes: 1 addition & 1 deletion pyslm/analysis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ def getLayerGeometryTime(layerGeometry: LayerGeometry, models: List[Model]) -> f
:param models: A list of :class:`~pyslm.geometry.Model` which is used by the :class:`geometry.LayerGeometry`
:return: The time taken to scan across the :class:`~pyslm.geometry.LayerGeometry`
"""
# Find the build style

# Find the build style
bstyle = utils.getBuildStyleById(models, layerGeometry.mid, layerGeometry.bid)
return getLayerGeometryPathLength(layerGeometry) / getEffectiveLaserSpeed(bstyle)

Expand Down
3 changes: 2 additions & 1 deletion pyslm/support/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

from .geometry import extrudeFace
from .utils import *
from .support import *
from .tri2img import *
#from .tri2img import *
from .render import *
39 changes: 18 additions & 21 deletions pyslm/support/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from mapbox_earcut import triangulate_float64, triangulate_float32
from triangle import triangulate

import pycork

from pyslm import pyclipper
from line_profiler_pycharm import profile

Expand Down Expand Up @@ -104,39 +106,34 @@ def extrudeFace(extrudeMesh: trimesh.Trimesh,
return extMesh


def boolUnion(meshA, meshB, CORK_PATH):
def boolUnion(meshA, meshB):

vertsOut, facesOut = pycork.union(meshA.vertices, meshA.faces, meshB.vertices, meshB.faces)

return trimesh.Trimesh(vertices=vertsOut, faces=facesOut, process=True)

if isinstance(meshA, trimesh.Trimesh):
meshA.export('a.off')

if isinstance(meshB, trimesh.Trimesh):
meshA.export('b.off')
def boolIntersect(meshA, meshB):

subprocess.call([CORK_PATH, '-union', 'b.off', 'a.off', 'c.off'])
return trimesh.load_mesh('c.off')
vertsOut, facesOut = pycork.intersection(meshA.vertices, meshA.faces, meshB.vertices, meshB.faces)

return trimesh.Trimesh(vertices=vertsOut, faces=facesOut, process=True)

def boolIntersect(meshA, meshB, CORK_PATH):
#subprocess.call([CORK_PATH, '-isct', 'a.off', 'b.off', 'c.off'])
#return trimesh.load_mesh('c.off')

if isinstance(meshA, trimesh.Trimesh):
meshA.export('a.off')
def boolDiff(meshA, meshB):

if isinstance(meshB, trimesh.Trimesh):
meshA.export('b.off')
vertsOut, facesOut = pycork.difference(meshA.vertices, meshA.faces, meshB.vertices, meshB.faces)

subprocess.call([CORK_PATH, '-isct', 'a.off', 'b.off', 'c.off'])
return trimesh.load_mesh('c.off')
return trimesh.Trimesh(vertices=vertsOut, faces=facesOut, process=True)

def boolDiff(meshA, meshB, CORK_PATH):
def resolveIntersection(meshA, meshB):

if isinstance(meshA, trimesh.Trimesh):
meshA.export('a.off')
vertsOut, facesOut = pycork.resolveIntersection(meshA.vertices, meshA.faces, meshB.vertices, meshB.faces)

if isinstance(meshB, trimesh.Trimesh):
meshA.export('b.off')
return trimesh.Trimesh(vertices=vertsOut, faces=facesOut, process=True)

subprocess.call([CORK_PATH, '-diff', 'a.off', 'b.off', 'c.off'])
return trimesh.load_mesh('c.off')

def createPath2DfromPaths(paths: List[np.ndarray]) -> trimesh.path.Path2D:
"""
Expand Down
110 changes: 65 additions & 45 deletions pyslm/support/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
except BaseException as E:
raise BaseException("Vispy is required to use the support.geometry submodule")

try:
import pycork
except BaseException as E:
raise BaseException("Pycork is required for the support submodule")

import abc
from builtins import staticmethod
from typing import Any, Optional, List, Tuple, Union
Expand Down Expand Up @@ -194,7 +199,7 @@ def geometry(self) -> trimesh.Trimesh:
return self._supportVolume

@property
def volume(self):
def volume(self) -> float:
""" The calculated volume of the support volume region """
return self._supportVolume.volume

Expand Down Expand Up @@ -408,14 +413,6 @@ class BlockSupportGenerator(BaseSupportGenerator):
:class:`BlockSupportBase`.
"""

CORK_PATH = ''
"""
The Path to the Cork Boolean Library as a static attribute. This should be specified when initialising the scripts.
.. note::
In future this may be included as a separate Python module for ease of installation.
"""

_supportSkinSideTolerance = 1.0 - 1e-3
"""
The support skin side tolerance is used for masking the extrusions side faces when generating the polygon region
Expand Down Expand Up @@ -468,17 +465,17 @@ def gradThreshold(rayProjectionDistance: float, overhangAngle: float) -> float:
@property
def splineSimplificationFactor(self) -> float:
"""
The simplification factor using a spline aproxixmation approach for smoothening the support volume boundary
The simplification factor using a spline approximation approach for smoothening the support volume boundary
"""
return self._splineSimplificationFactor

@splineSimplificationFactor.setter
def splineSimplificationFactor(self, value: float):
self._splineSimplificationFactor = value
self._splineSimplificationFactor = value

@property
def overhangAngle(self) -> float:
""" The overhang angle (degrees) used for identifying support surfaces on the Part. """
""" The overhang angle (degrees) used for identifying support surfaces on the :class:`Part` """
return self._overhangAngle

@overhangAngle.setter
Expand Down Expand Up @@ -521,7 +518,7 @@ def outerSupportEdgeGap(self, spacing: float):
@property
def innerSupportEdgeGap(self) -> float:
"""
The inner support gap is the distance between supports regions that are identified as separated by a
The inner support gap is the distance between supports regions that are identified as separated by a
significant vertical extent.
"""
return self._innerSupportEdgeGap
Expand All @@ -547,7 +544,7 @@ def simplifyPolygonFactor(self) -> float:
"""
The simplification factor used for simplifying the boundary polygon generated from the rasterisation process.
This has the effect of reducing the complexity of the extruded support volume generated that is intersected with
the Part.
the :class:`Part`.
"""
return self._simplifyPolygonFactor

Expand Down Expand Up @@ -603,6 +600,7 @@ def _identifySelfIntersectionHeightMap(self, subregion: trimesh.Trimesh,
# upper surface
#bbox = np.hstack([offsetPoly.bounds, subregion.bounds[:,2].reshape(2,1)])

# Extend the bounding box extents in the Z direction
bbox2 = bbox.copy()
bbox2[0,2] -= 1
bbox2[1,2] += 1
Expand All @@ -618,6 +616,8 @@ def _identifySelfIntersectionHeightMap(self, subregion: trimesh.Trimesh,
mask = lowerImg > 1.01
heightMap2[mask] = lowerImg[mask]

#import matplotlib.pyplot as plt
#plt.imshow(heightMap2)
return heightMap2.T, upperImg, lowerImg

def _identifySelfIntersectionHeightMapRayTracing(self, subregion: trimesh.Trimesh,
Expand Down Expand Up @@ -725,7 +725,7 @@ 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')
#part.geometry.export('part.off')

supportBlockRegions = []

Expand Down Expand Up @@ -772,19 +772,25 @@ def identifySupportRegions(self, part: Part, overhangAngle: float,
timeIntersect = time.time()

logging.info('\t - start intersecting mesh')
# Intersect the projection of the support face with the original part using the Cork Library
extruMesh.export('downProjExtr.off')
subprocess.call([self.CORK_PATH, '-isct', 'part.off', 'downProjExtr.off', 'c.off'])
logging.info('\t - finished intersecting mesh')
if False:

"""
Note the cutMesh is the project down from the support surface with the original mesh
"""
bbox = extruMesh.bounds
cutMesh = trimesh.load_mesh('c.off', process=True, validate=True)
logging.info('\t\t - mesh intersection time using Cork: '.format(time.time() - timeIntersect))
# Intersect the projection of the support face with the original part using the Cork Library
extruMesh.export('downProjExtr.off')
subprocess.call([self.CORK_PATH, '-isct', 'part.off', 'downProjExtr.off', 'c.off'])
logging.info('\t - finished intersecting mesh')

"""
Note the cutMesh is the project down from the support surface with the original mesh
"""

cutMesh = trimesh.load_mesh('c.off', process=True, validate=True)

bbox = extruMesh.bounds
cutMesh = boolIntersect(part.geometry, extruMesh)
logging.info('\t\t - Mesh intersection time using Cork: {:.3f}s'.format(time.time() - timeIntersect))
logging.info('\t - intersecting mesh')
totalBooleanTime += time.time() - timeIntersect

# Note this a hard tolerance
if cutMesh.volume < -1: # 50

Expand Down Expand Up @@ -828,6 +834,9 @@ def identifySupportRegions(self, part: Part, overhangAngle: float,
logging.info('\t - finished generated support height map')

heightMap = np.pad(heightMap, ((2, 2), (2,2)), 'constant', constant_values=((1, 1), (1,1)))

import matplotlib.pyplot as plt

vx, vy = np.gradient(heightMap)
grads = np.sqrt(vx ** 2 + vy ** 2)

Expand Down Expand Up @@ -889,7 +898,7 @@ def identifySupportRegions(self, part: Part, overhangAngle: float,
continue

if len(outPolygons) > 1:
raise ValueError('multi polygons')
raise Exception('Multi polygons - error please submit a bug report')

bufferPolyA = mergedPoly.polygons_full[0].simplify(self.simplifyPolygonFactor*self.rayProjectionResolution)

Expand Down Expand Up @@ -950,7 +959,7 @@ def identifySupportRegions(self, part: Part, overhangAngle: float,
hitLoc2 = coords2.copy()
hitLoc2[:, 2] = 0.0

logging.info('Creating Base-plate support')
logging.info('\tCreating Base-plate support')
else:
logging.warning('PROJECTIONS NOT MATCHING - skipping support generation')
continue
Expand All @@ -960,20 +969,27 @@ def identifySupportRegions(self, part: Part, overhangAngle: float,

# Extrude the surface based on the heights from the second ray cast
extrudedBlock = extrudeFace(surf2, None, hitLoc2[:, 2] - self.lowerProjectionOffset)
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
"""
import time
timeDiff = time.time()
subprocess.call([self.CORK_PATH, '-diff', 'b.off', 'part.off', 'c.off'])
blockSupportMesh = trimesh.load_mesh('c.off', process=True, validate=True)
timeDiff = time.time()

if False:

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)

blockSupportMesh = boolDiff(extrudedBlock, part.geometry)
blockSupportMesh.fix_normals()
blockSupportMesh.remove_degenerate_faces()

#print('timeDiff ', time.time() - timeDiff)
logging.info('\t\t Boolean Difference Time: {:.3f}\n'.format(time.time() - timeDiff))

totalBooleanTime += time.time() - timeDiff

Expand All @@ -988,9 +1004,9 @@ def identifySupportRegions(self, part: Part, overhangAngle: float,

supportBlockRegions.append(baseSupportBlock)

logging.info('\t - processed support face')
logging.info('\t - processed support face\n')

logging.info('Total boolean time: {:.3f}'.format(totalBooleanTime))
logging.info('Total boolean time: {:.3f}\n'.format(totalBooleanTime))
print('Total Boolean Time', totalBooleanTime)
return supportBlockRegions

Expand Down Expand Up @@ -1020,7 +1036,7 @@ class GridBlockSupport(BlockSupportBase):
_pairTolerance = 1e-1
"""
Pair tolerance used for matching upper and lower paths of the support boundary. This is an internal tolerance
used but may be re-define."""
used but can be re-define."""


def __init__(self, supportObject: Part = None,
Expand Down Expand Up @@ -1062,12 +1078,12 @@ def useSupportSkin(self, value):
self._useSupportSkin = value

@property
def useSupportBorder(self):
def useSupportBorder(self) -> bool:
""" Generates a border around the each truss grid """
return self._useSupportBorder

@useSupportBorder.setter
def useSupportBorder(self, value):
def useSupportBorder(self, value: bool):
self._useSupportBorder = value

@property
Expand Down Expand Up @@ -1223,11 +1239,15 @@ def geometry(self) -> trimesh.Trimesh:
if self._mergeMesh:
logging.info('\t - Performing Boolean Union of all Intersection Meshes')
# Intersect the projection of the support face with the original part using the Cork Library
slicesX.export('secX.off')
slicesY.export('secY.off')

subprocess.call([BlockSupportGenerator.CORK_PATH, '-resolve', 'secY.off', 'secX.off', 'merge.off'])
isectMesh = trimesh.load_mesh('merge.off')
if False:
slicesX.export('secX.off')
slicesY.export('secY.off')

subprocess.call([BlockSupportGenerator.CORK_PATH, '-resolve', 'secY.off', 'secX.off', 'merge.off'])
isectMesh = trimesh.load_mesh('merge.off')

isectMesh = resolveIntersection(slicesX, slicesY)
isectMesh += supportSkins
else:
logging.info('\t Concatenating Support Geometry Meshes Together')
Expand Down
17 changes: 16 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@
'triangle',
'colorlog']) # log in pretty colors


requirements_supports = set([
'setuptools', # do setuptools stuff
'shapely',
'rtree',
'scikit-image',
'networkx',
'trimesh', # Required for meshing geometry
'triangle',
'vispy',
'pycork'
'mapbox-earcut'
'colorlog']) # log in pretty colors

# requirements for building documentation
# Note API is only read from pyclipper in external project
requirements_docs = set([
Expand Down Expand Up @@ -113,6 +127,7 @@
packages=find_packages(exclude=('tests', 'docs', 'examples')),
install_requires=list(requirements_default),
extras_require={'easy': list(requirements_easy),
'support': list(requirements_supports),
'docs': list(requirements_docs)},

project_urls = {
Expand All @@ -121,4 +136,4 @@
'Tracker': 'https://github.com/drlukeparry/pyslm/issues'
}

)
)

0 comments on commit e438bef

Please sign in to comment.