Skip to content

Commit

Permalink
Update Geometry for libSLM and Added Updates to Validate Buildfiles
Browse files Browse the repository at this point in the history
Provided an Enum for laser mode available in libSLM
Updated the classes to represent changes to libSLM (jumpDelay, jumpSpeed)  and file position
Added
Added a utility class pyslm/geometry/utils.py to verify and validate Models and Layers defined before exporting to a Machine Build File
  • Loading branch information
drlukeparry committed Feb 1, 2021
1 parent 84c7547 commit e75b486
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 3 deletions.
6 changes: 4 additions & 2 deletions pyslm/geometry/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@

try:
from libSLM import Header, BuildStyle, Model, Layer, LayerGeometry, ContourGeometry, HatchGeometry, PointsGeometry
from libSLM import Header, BuildStyle, Model, Layer, LayerGeometry, ContourGeometry, HatchGeometry, PointsGeometry, LaserMode

except BaseException as E:
"""
The libSLM library is not available so instead use the fallback python equivalent in order to store the layer and
geometry information for use later. This removes the capability to export to machine build file format
"""
from .geometry import Header, BuildStyle, Model, Layer, LayerGeometry, ContourGeometry, HatchGeometry, PointsGeometry
from .geometry import Header, BuildStyle, Model, Layer, LayerGeometry, ContourGeometry, HatchGeometry, PointsGeometry, LaserMode

from .utils import *

49 changes: 48 additions & 1 deletion pyslm/geometry/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
from typing import Any, List, Optional, Tuple


class LaserMode(Enum):
CW = 0
""" Continious Wave """

PULSE = 1
""" Pulsed mode (Default option) """

class Header:
"""
The Header provides basic information about the machine build file, such as the name of the file
Expand Down Expand Up @@ -37,6 +44,8 @@ def __init__(self):
self._laserMode = 1
self._pointDistance = 0
self._pointExposureTime = 0
self._jumpDelay = 0
self._jumpSpeed = 0

@property
def bid(self) -> int:
Expand Down Expand Up @@ -76,6 +85,10 @@ def laserId(self, value: int):

@property
def laserMode(self) -> int:
"""
Determines the laser mode to use via :class:`LaserMode` which is either continious wave (CW) or
pulsed (Pulsed) laser operation
"""
return self._laserMode

@laserMode.setter
Expand Down Expand Up @@ -129,6 +142,31 @@ def pointDistance(self) -> int:
def pointDistance(self, pointDistance: int):
self._pointDistance = pointDistance

@property
def jumpDelay(self) ->int:
""" The jump delay time (usually expressed as an integer :math:`\\mu m`) """
return self._jumpDelay

@jumpDelay.setter
def jumpDelay(self, delay: int):
"""
The jump speed between scan vectors (usually expressed as an integer :math:`mm/s`). This must be set to
zero (default) if it is not explicitly used.
"""
self._jumpDelay = delay

@property
def jumpSpeed(self) -> int:
"""
The jump speed between scan vectors (usually expressed as an integer :math:`mm/s`). This must be set to
zero (default) if it is not explicitly used.
"""
return self._jumpSpeed

@jumpSpeed.setter
def jumpSpeed(self, speed: int):
self._jumpSpeed = speed

def setStyle(self, bid: int, focus: int, power: float,
pointExposureTime: int, pointExposureDistance: int, laserSpeed: Optional[float] = 0.0,
laserId: Optional[int] = 1, laserMode: Optional[int] = 1,
Expand Down Expand Up @@ -209,7 +247,7 @@ def buildStyleDescription(self, description: str):

@property
def buildStyleName(self) -> str:
""" The BuildStyle applied to the Model"""
""" The BuildStyle name applied to the Model"""
return self._buildStyleName

@buildStyleName.setter
Expand Down Expand Up @@ -398,6 +436,15 @@ def __init__(self, z: Optional[int] = 0, id: Optional[int] = 0):
self._id = id
self._geometry = []
self._name = ""
self._layerFilePosition = 0

@property
def layerFilePosition(self):
""" The position of the layer in the build file, when available. """
return self._layerFilePosition

def isLoaded(self) -> bool:
return True

@property
def name(self) -> str:
Expand Down
154 changes: 154 additions & 0 deletions pyslm/geometry/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from typing import List, Optional, Tuple, Union
import numpy as np
from warnings import warn

from . import Layer, LayerGeometry, HatchGeometry, ContourGeometry, PointsGeometry, BuildStyle, Model

def getBuildStyleById(models: List[Model], mid: int, bid: int) -> Union[BuildStyle, None]:
"""
Returns the Buildstyle found from a list of :class:`Model` given a model id and build id.
:param models: A list of models
:param mid: The selected model id
:param bid: The selected buildstyle id
:return: The BuildStyle if found or `None`
"""
model = next(x for x in models if x.mid == mid)

if model:
bstyle = next(x for x in model.buildStyles if x.bid == bid)

return bstyle

return None


def getModel(models: List[Model], mid: int) -> Union[BuildStyle, None]:
"""
Returns the Model found from a list of :class:`Model` given a model id and build id.
:param models: A list of models
:param mid: The selected model id
:return: The BuildStyle if found or `None`
"""
model = next(x for x in models if x.mid == mid)

return model if model else None


class ModelValidator:

@staticmethod
def _buildStyleIndex(models):

index = {}
for model in models:
for bstyle in model.buildStyles:
index[model.mid, bstyle.bid] = bstyle

return index

@staticmethod
def _modelIndex(models):

index = {}
for model in models:
index[model.mid] = model

return index

@staticmethod
def validateBuildStyle(bstyle: BuildStyle):
if bstyle.bid < 1 or not isinstance(bstyle.bid, int):
raise Exception("BuildStyle ({:d}) should have a positive integer id".format(bstyle.bid))

if bstyle.laserPower < 0 or not isinstance(bstyle.laserPower, float):
raise Exception("BuildStyle({:d}).laserPower must be a positive integer".format(bstyle.bid))

if bstyle.laserSpeed < 0 or not isinstance(bstyle.laserSpeed, float):
raise Exception("BuildStyle({:d}).laserPower must be a positive integer".format(bstyle.bid))

if bstyle.pointDistance < 1 or not isinstance(bstyle.pointDistance, int):
raise Exception("BuildStyle({:d}).pointDistance must be a positive integer (>0)".format(bstyle.bid))

if bstyle.pointExposureTime < 1 or not isinstance(bstyle.pointExposureTime, int):
raise Exception("BuildStyle({:d}).pointExposureTime must be a positive integer (>0)".format(bstyle.bid))

if bstyle.jumpDelay < 0 or not isinstance(bstyle.jumpDelay, int):
raise Exception("BuildStyle({:d}).jumpDelay must be a positive integer ".format(bstyle.bid))

if bstyle.jumpSpeed < 0 or not isinstance(bstyle.jumpSpeed, int):
raise Exception("BuildStyle({:d}).jumpSpeed must be a positive integer (>0)".format(bstyle.bid))

if bstyle.laserId < 1 or not isinstance(bstyle.laserId, int):
raise Exception("BuildStyle({:d}).laserId must be a positive integer (>0)".format(bstyle.bid))

if bstyle.laserMode < 0 or not isinstance(bstyle.laserMode, int):
raise Exception("BuildStyle({:d}).laserMode must be a positive integer (0)".format(bstyle.bid))

if bstyle.laserId < 1 or not isinstance(bstyle.laserId, int):
raise Exception("BuildStyle({:d}).laserId must be a positive integer (>0)".format(bstyle.bid))

@staticmethod
def validateModel(model: Model):

bstyleList = []

if model.topLayerId == 0:
raise Exception('The top layer id of Model ({:s}) has not been set'.format(model.name))

if len(model.buildStyles) == 0:
raise Exception('Model ({:s} does not contain any build styles'.format(model.name))

for bstyle in model.buildStyles:

if bstyle in bstyleList:
raise Exception('Model ({:s} does not contain build styles with unique id'.format(model.name))
else:
bstyleList.append(bstyle.bid)

ModelValidator.validateBuildStyle(bstyle)

@staticmethod
def validateBuild(models: List[Model], layers: List[Layer]):

# Build the indices for the models and the build styles
modelIdx = ModelValidator._modelIndex(models)
bstyleIdx = ModelValidator._buildStyleIndex(models)
modelTopLayerIdx = dict()
layerDelta = np.array([layer.z for layer in layers])

for model in models:
ModelValidator.validateModel(model)

""" Iterate across each layer and validate the input """
for layer in layers:

if len(layer.geometry) == 0:
warn("Warning: Layer ({:d}) does not contain any layer geometry. It is advised to check this is valid".format(layer.layerId))

for layerGeom in layer.geometry:
model = modelIdx.get(model.mid, None)

if not model:
raise Exception("Layer Geometry in layer ({:d} @ {:.3f}) has not been assigned a model".format(layer.layerId, layer.z))

bstyle = bstyleIdx.get((layerGeom.mid, layerGeom.bid), None)

if not bstyle:
raise Exception("Layer Geometry in layer ({:d} @ {:.3f}) has not been assigned a buildstyle".format(layer.layerId, layer.z))

modelTopLayerIdx[model.mid] = layer.layerId

""" Check to see if all models were assigned to a layer geometry"""
for model in models:
if modelTopLayerIdx.get(model.mid, False):
warn("Warning: Model({:s} was not used in any layer)".format(model.name))

if model.topLayerId != modelTopLayerIdx[model.mid]:
raise Exception("Top Layer Id of Model ({:d}) differs in the layers used ({:d})".format(model.topLayerId,
modelTopLayerIdx[model.mid]))

return True

0 comments on commit e75b486

Please sign in to comment.