[docs]classLayer(object):""" A Layer stores a group of ordered paths that are generated when a geometry is sliced. Layers are typically organized horizontally, but can also be organized vertically (see VerticalLayer). A Layer consists of one, or multiple Paths (depending on the geometry). Attributes ---------- paths: list :class:`compas_slicer.geometry.Path` is_brim: bool True if this layer is a brim layer. number_of_brim_offsets: int The number of brim offsets this layer has (None if no brim). is_raft: bool True if this layer is a raft layer. """
[docs]def__init__(self,paths):# check inputifpathsisNone:paths=[]iflen(paths)>0:assertisinstance(paths[0],compas_slicer.geometry.Path)self.paths=pathsself.min_max_z_height=(None,None)# Tuple containing the min and max z height of the layer.ifpaths:self.calculate_z_bounds()# brimself.is_brim=Falseself.number_of_brim_offsets=None# raftself.is_raft=False
def__repr__(self):no_of_paths=len(self.paths)ifself.pathselse0return"<Layer object with %i paths>"%no_of_paths@propertydeftotal_number_of_points(self):"""Returns the total number of points within the layer."""num=0forpathinself.paths:num+=len(path.printpoints)returnnum
[docs]defcalculate_z_bounds(self):""" Fills in the attribute self.min_max_z_height. """assertlen(self.paths)>0,"You cannot calculate z_bounds because the list of paths is empty."z_min=2**32# very big numberz_max=-2**32# very small numberforpathinself.paths:forptinpath.points:z_min=min(z_min,pt[2])z_max=max(z_max,pt[2])self.min_max_z_height=(z_min,z_max)
[docs]@classmethoddeffrom_data(cls,data):"""Construct a layer from its data representation. Parameters ---------- data: dict The data dictionary. Returns ------- layer The constructed layer. """paths_data=data['paths']paths=[Path.from_data(paths_data[key])forkeyinpaths_data]layer=cls(paths=paths)layer.is_brim=data['is_brim']layer.number_of_brim_offsets=data['number_of_brim_offsets']layer.min_max_z_height=data['min_max_z_height']returnlayer
[docs]defto_data(self):"""Returns a dictionary of structured data representing the data structure. Returns ------- dict The layer's data. """data={'paths':{i:[]foriinrange(len(self.paths))},'layer_type':'horizontal_layer','is_brim':self.is_brim,'number_of_brim_offsets':self.number_of_brim_offsets,'min_max_z_height':self.min_max_z_height}fori,pathinenumerate(self.paths):data['paths'][i]=path.to_data()returndata
classVerticalLayer(Layer):""" Vertical ordering. A VerticalLayer stores the print paths sorted in vertical groups. It is created with an empty list of paths that is filled in afterwards. Attributes ---------- id: int Identifier of vertical layer. """def__init__(self,id=0,paths=None):Layer.__init__(self,paths=paths)self.id=idself.head_centroid=Nonedef__repr__(self):no_of_paths=len(self.paths)ifself.pathselse0return"<Vertical Layer object with id : %d and %d paths>"%(self.id,no_of_paths)defappend_(self,path):""" Add path to self.paths list. """self.paths.append(path)self.compute_head_centroid()self.calculate_z_bounds()defcompute_head_centroid(self):""" Find the centroid of all the points of the last path in the self.paths list"""pts=np.array(self.paths[-1].points)self.head_centroid=np.mean(pts,axis=0)defprintout_details(self):""" Prints the details of the class. """logger.info("VerticalLayer id : %d"%self.id)logger.info("Total number of paths : %d"%len(self.paths))defto_data(self):"""Returns a dictionary of structured data representing the data structure. Returns ------- dict The vertical layer's data. """data={'paths':{i:[]foriinrange(len(self.paths))},'min_max_z_height':self.min_max_z_height,'layer_type':'vertical_layer'}fori,pathinenumerate(self.paths):data['paths'][i]=path.to_data()returndata@classmethoddeffrom_data(cls,data):"""Construct a vertical layer from its data representation. Parameters ---------- data: dict The data dictionary. Returns ------- layer The constructed vertical layer. """paths_data=data['paths']paths=[Path.from_data(paths_data[key])forkeyinpaths_data]layer=cls(id=None)layer.paths=pathslayer.min_max_z_height=data['min_max_z_height']returnlayerclassVerticalLayersManager:""" Creates empty vertical layers and assigns to the input paths to the fitting vertical layer using the add() function. The criterion for grouping paths to VerticalLayers is based on the proximity of the centroids of the paths. If the input paths don't fit in any vertical layer, then new vertical layer is created with that path. Attributes ---------- threshold_max_centroid_dist: float. The maximum get_distance that the centroids of two successive paths can have to belong in the same VerticalLayer. max_paths_per_layer: int Maximum number of layers that a vertical layer can consist of. If None, then the vertical layer has an unlimited number of layers. """def__init__(self,threshold_max_centroid_dist=25.0,max_paths_per_layer=None):self.layers=[VerticalLayer(id=0)]# vertical_layers_print_data that contain isocurves (compas_slicer.Path)self.threshold_max_centroid_dist=threshold_max_centroid_distself.max_paths_per_layer=max_paths_per_layerdefadd(self,path):selected_layer=None# Find an eligible layer for path (called selected_layer)iflen(self.layers[0].paths)==0:# first path goes to first layerselected_layer=self.layers[0]else:# find the candidate segment for new isocurvecentroid=np.mean(np.array(path.points),axis=0)other_centroids=get_vertical_layers_centroids_list(self.layers)candidate_layer=self.layers[utils.get_closest_pt_index(centroid,other_centroids)]ifnp.linalg.norm(candidate_layer.head_centroid-centroid)<self.threshold_max_centroid_dist:ifself.max_paths_per_layer:iflen(candidate_layer.paths)<self.max_paths_per_layer:selected_layer=candidate_layerelse:selected_layer=candidate_layerifnotselected_layer:# then create new layerselected_layer=VerticalLayer(id=self.layers[-1].id+1)self.layers.append(selected_layer)selected_layer.append_(path)defget_vertical_layers_centroids_list(vert_layers):""" Returns a list with points that are the centroids of the heads of all vertical_layers_print_data. The head of a vertical_layer is its last path. """head_centroids=[]forvert_layerinvert_layers:head_centroids.append(vert_layer.head_centroid)returnhead_centroidsif__name__=="__main__":pass