Skip to content

Commit

Permalink
Added method extents and projected hull to Part classes
Browse files Browse the repository at this point in the history
  • Loading branch information
drlukeparry committed Apr 10, 2021
1 parent 147e3f9 commit 4d8747f
Showing 1 changed file with 87 additions and 18 deletions.
105 changes: 87 additions & 18 deletions pyslm/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from typing import Any, List, Optional, Tuple

from shapely.geometry import Polygon
from shapely.ops import unary_union

from scipy.spatial.qhull import ConvexHull


class DocumentObject(ABC):
Expand Down Expand Up @@ -41,6 +44,8 @@ def setName(self, name):
def boundingBox(self): # const
raise NotImplementedError('Abstract method should be implemented in derived class')

def extents(self):
raise NotImplementedError('Abstract method should be implemented in derived class')

class Document:

Expand Down Expand Up @@ -267,7 +272,7 @@ def getTransform(self) -> np.ndarray:
(:attr:`~Part.origin`), a :attr:`~Part.rotation` and a :attr:`~Part.scaleFactor`
"""

Sx = trimesh.transformations.scale_matrix(factor = self._scaleFactor[0], direction=[1,0,0])
Sx = trimesh.transformations.scale_matrix(factor=self._scaleFactor[0], direction=[1,0,0])
Sy = trimesh.transformations.scale_matrix(factor=self._scaleFactor[1] , direction=[0,1,0])
Sz = trimesh.transformations.scale_matrix(factor=self._scaleFactor[2], direction=[0,0,1])
S = Sx*Sy*Sz
Expand Down Expand Up @@ -305,15 +310,86 @@ def setGeometryByMesh(self, mesh: trimesh.Trimesh) -> None:
self._geometry = mesh
self._dirty = True

def getProjectedHull(self, returnPoly: bool = False):
"""
The convex hull of the part projected in the Z-direction. This is for convenience when trying to find the
approximate boundary of the part when used for optimising the layout of parts.
:return: The convex hull of the part
"""

coords = self.geometry.vertices[:,:2]

chull = ConvexHull(coords)

hullCoords = coords[chull.vertices]

if returnPoly:
hullCoords = np.append(hullCoords, hullCoords[0,:].reshape(-1,2), axis=0)
return Polygon(hullCoords)
else:
return hullCoords

def getProjectedArea(self) :
"""
The resultant projected area of the part projected on the z-axis.
:return: A Shapely Polygon representing the projected area of the part
"""

facesCpy = self.geometry.faces

shapes = self.geometry.vertices[facesCpy, :2]

triPolys = []

for face in shapes:
faceCpy= np.append(face, face[0,:].reshape(-1,2), axis=0)
triPolys.append(Polygon(faceCpy))

return unary_union(triPolys)

@property
def boundingBox(self) -> np.ndarray: # const
"""
The bounding box of the geometry transformed in the global coordinate frame :math:`(X,Y,Z)`. The bounding
box is a 1x6 array consisting of the minimum coordinates followed by the maximum coordinates for the corners of
the bounding box.
"""

if not self.geometry:
raise ValueError('Geometry was not set')
else:
return self.geometry.bounds.flatten()

@property
def extents(self) -> np.ndarray: # const
"""
The extents the geometry transformed in the global coordinate frame :math:`(X,Y,Z)`. The extents is a 1x3 array
consisting of the linear dimensions of the part.
"""

if not self.geometry:
raise ValueError('Geometry was not set')

bbox = self.boundingBox

return np.array([bbox[3] - bbox[0],
bbox[4] - bbox[1],
bbox[5] - bbox[2]])


@property
def volume(self) -> float:
if not self.geometry.is_volume:
raise ValueError('Part is not a valid volume')

return self.geometry.volume


@property
def area(self) -> float: # const
def surfaceArea(self) -> float: # const
""" Surface area of the part geometry"""
return self.geometry.area

@property
Expand All @@ -325,36 +401,29 @@ def geometry(self) -> trimesh.Trimesh:
return None

if self.isDirty():
print('Updating {:s} Geometry Representation'.format(self.label))
self._geometryCache = self._geometry.copy()
self._geometryCache.apply_transform(self.getTransform())
self._dirty = False
self.regenerate()

return self._geometryCache

@property
def boundingBox(self) -> np.ndarray: # const
def regenerate(self):
"""
The bounding box of the geometry transformed in the global coordinate frame :math:`(X,Y,Z)`. The bounding
box is a 1x6 array consisting of the minimum coordinates followed by the maximum coordinates for the corners of
the bounding box.
Regenerate the geometry
"""
print('Updating {:s} Geometry Representation'.format(self.label))
self._geometryCache = self._geometry.copy()
self._geometryCache.apply_transform(self.getTransform())
self._dirty = False

if not self.geometry:
raise ValueError('Geometry was not set')
else:
return self.geometry.bounds.flatten()

@property
def partType(self) -> str:
"""
Returns the Part type. This will be used in future for the document tree.
:return: The part type
The Part type. This will be used in future for the document tree.
"""

return self._partType


def getTrimeshSlice(self, z: float) -> trimesh.path.Path2D:
"""
The vector slice is created by using `trimesh` to slice the mesh into a polygon - returns a shapely polygon.
Expand Down

0 comments on commit 4d8747f

Please sign in to comment.