def __init__(self, shape, options): self.solid = Wrappers.Solid(shape) self.slices = [] #a list of slices self.sliceMap = {} #distinct slices. different because some slices are re-used instead of calculated again self.options = options self.__setDefaultOptions() self.extruder = Extruder(options) #note: make sure to fix the shape before translating! if options.fixShape: self.solid.fixDefects() if options.translateToPositiveCenter: self.solid = self.solid.translateToPositiveCenter( options.tableCenterX, options.tableCenterY) #now apply slicing limits, if the user specified them #warning, it is confusing if the user specifies these, and specifies translate=true, how do they know where zMin and zMax will be? if options.zMin == None: options.zMin = self.solid.zMin if options.zMax == None: options.zMax = self.solid.zMax
def __init__(self, shape, options): self.solid = Wrappers.Solid(shape) self.slices = [] # a list of slices self.sliceMap = {} # distinct slices. different because some slices are re-used instead of calculated again self.options = options self.__setDefaultOptions() self.extruder = Extruder(options) # note: make sure to fix the shape before translating! if options.fixShape: self.solid.fixDefects() if options.translateToPositiveCenter: self.solid = self.solid.translateToPositiveCenter(options.tableCenterX, options.tableCenterY) # now apply slicing limits, if the user specified them # warning, it is confusing if the user specifies these, and specifies translate=true, how do they know where zMin and zMax will be? if options.zMin == None: options.zMin = self.solid.zMin if options.zMax == None: options.zMax = self.solid.zMax
class Slicer: """ Load the shape, and compute slicing options """ def __init__(self, shape, options): self.solid = Wrappers.Solid(shape) self.slices = [] #a list of slices self.sliceMap = {} #distinct slices. different because some slices are re-used instead of calculated again self.options = options self.__setDefaultOptions() self.extruder = Extruder(options) #note: make sure to fix the shape before translating! if options.fixShape: self.solid.fixDefects() if options.translateToPositiveCenter: self.solid = self.solid.translateToPositiveCenter( options.tableCenterX, options.tableCenterY) #now apply slicing limits, if the user specified them #warning, it is confusing if the user specifies these, and specifies translate=true, how do they know where zMin and zMax will be? if options.zMin == None: options.zMin = self.solid.zMin if options.zMax == None: options.zMax = self.solid.zMax def __setDefaultOptions(self): """ sets defaults that may not have been set by the user. This is done here rather than inside of Slicing Defaults because in several cases, knowledge of the part and the options is required to do a good job. """ options = self.options errs = options.errors() if len(errs) > 0: raise Exception("Problem with Options: " + str(errs)) #does user want to translate to positive space? if options.translateToPositiveCenter == None: options.translateToPositiveCenter = True #guess unit of measure if options.units == None: options.units = self.solid.guessUnitOfMeasure() #guess sane values for options not supplied if options.trackWidth == None: options.trackWidth = options.nozzleDiameter * 1.05 if options.layerHeight == None: options.layerHeight = 0.1 if options.fillingWallThickness == None: options.fillingWallThickness = 1.0 #must normalize to units #TODO: fix this later with smarter computations #hack-- we can do better than this later options.generateSupports = False options.machineMaxFeedRate = 200 options.fixShape = True if options.fillingEnabled is None: options.fillingEnabled = True options.gcodeNumberFormat = "%0.3f" options.gcodeVerboseComments = True options.gcodeUseArcs = True options.gcodeAxisName = "E" options.gcodeCurveApproxTolerance = 0.001 options.gcodeFileExtension = ".gcode" options.svgFileExtension = ".svg" """ Slices the object, after which slices will contain a list of slices for printing A slice is a horizontal section of the part. TODO: add 'skeinning'-- more perimeters than fill lines """ @Util.printTiming def execute(self): #TODO: use parallel cores to speed this up, see #http://code.google.com/p/pythonocc/source/browse/trunk/src/examples/Level2/Concurrency/parallel_slicer.py #at this point we have a solid ready to execute zLevel = self.options.zMin + TOLERANCE layerNo = 1 fillAngle = 0 while zLevel < self.options.zMax: print "Slicing At ZLevel=%0.3f" % zLevel #compute the faces for the slice. It may be a transformed #copy of another slice, or a new slice cSlice = self._computeSlice(zLevel, layerNo, fillAngle) self.slices.append(cSlice) zLevel += self.options.layerHeight #return (resultShape,outerWire,otherWires) @Util.printTiming def makeSection(self, cuttingPlane, shapeToSection, zLevel): """ Uses halfspaces to make a cut. """ bff = BRepBuilderAPI.BRepBuilderAPI_MakeFace(cuttingPlane) face = bff.Face() origin = gp.gp_Pnt(0, 0, zLevel - 1) #odd, a halfspace is faster than a box? hs = BRepPrimAPI.BRepPrimAPI_MakeHalfSpace(face, origin) hs.Build() halfspace = hs.Solid() #make the cut bc = BRepAlgoAPI.BRepAlgoAPI_Cut(self.solid.shape, halfspace) cutShape = bc.Shape() ff = [] for face in Topo(cutShape).faces(): if OCCUtil.isFaceAtZLevel(zLevel, face): ff.append(face) return ff @Util.printTiming def makeSection2(self, cuttingPlane, shapeToSection, zLevel): """ Uses BrepSection Algo. this generally returns a list of wires, not a face """ #section is certainly faster, but produces only edges. #those have to be re-organized into wires, probably #using ShapeAnalysis_WireOrder face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(cuttingPlane).Shape() # Computes Shape/Plane intersection section = BRepAlgoAPI.BRepAlgoAPI_Section(self.solid.shape, face) #section = BRepAlgo.BRepAlgo_Section(self.solid.shape,face); section.Build() if section.IsDone(): #Topology.dumpTopology(section.Shape()); #what we got back was a compound of edges t = Topo(section.Shape()) wb = OCCUtil.MultiWireBuilder() for e in t.edges(): wb.addEdge(e) wires = wb.getWires() print wires for w in wires: Topology.dumpTopology(w) return wires else: raise Exception("Could not compute Section!") "computes the boundaries for the given slice" #@Util.printTiming def _computeSlice(self, zLevel, layerNo, fillAngle): cSlice = Slice() cSlice.zLevel = zLevel cSlice.layerNo = layerNo cSlice.fillAngle = fillAngle cSlice.thickness = self.options.layerHeight #make a cutting plane p = gp.gp_Pnt(0, 0, zLevel) csys = gp.gp_Ax3(p, gp.gp().DZ()) cuttingPlane = gp.gp_Pln(csys) #makeSection will use the old reliable way that will get a face from each cut. #makeSection2 will use BRepAlgoaPI_Section, but has problems ordering the edges correctly. newFaces = self.makeSection(cuttingPlane, self.solid.shape, zLevel) for f in newFaces: cSlice.addFace(Face(f)) #bff = BRepBuilderAPI.BRepBuilderAPI_MakeFace(cuttingPlane); #face = bff.Face(); #odd, a halfspace is faster than a box? #hs = BRepPrimAPI.BRepPrimAPI_MakeHalfSpace(face,origin); #hs.Build(); #halfspace = hs.Solid(); #make the cut #bc = BRepAlgoAPI.BRepAlgoAPI_Cut(self.solid.shape,halfspace); #cutShape = bc.Shape(); #for face in Topo(cutShape).faces(): # if OCCUtil.isFaceAtZLevel(zLevel,face): # cSlice.addFace(Face(face)); # break; mySum = cSlice.computeFingerPrint() # #uncomment to enable layer copying. # #check for identical slices. return matching ones if found. if self.sliceMap.has_key(mySum): #print "i can copy this layer!" return self.sliceMap[mySum].copyToZ(zLevel, layerNo) self.sliceMap[mySum] = cSlice #print "This slice has %d faces." % len(cSlice.faces) if self.options.fillingEnabled: self._fillSlice(cSlice) return cSlice """ Fills a slice with vector paths for FDM """ def _fillSlice(self, slice): #how many shells do we need? #attempt to create the number of shells requested by the user, plus one additional for infill for f in slice.faces: numShells = (int)(self.options.fillingWallThickness / self.extruder.trackWidth()) + 1 numShells = max(2, numShells) faceWires = OCCUtil.wireListFromFace(f.face) #regardless, the last successfully created offset is used for hatch infill shells = [] for i in range(1, numShells): #compute offset inwards by one track width offset = (-1) * i * self.extruder.trackWidth() innerEdge = OCCUtil.offsetWireList(faceWires, offset) if len(innerEdge) == 0: #performance: dont offset if there were already no wires continue pathCenter = OCCUtil.offsetWireList( innerEdge, self.extruder.trackWidth() / 2) if len(pathCenter) > 0: shells.append(pathCenter) if len(shells) > 1: #use last one for filling. print "%d shells were available for Hatching" % len(shells) lastShell = shells.pop() s = self.solid h = hatchlib.Hatcher(lastShell, zLevel, (s.xMin, s.yMin, s.xMax, s.yMax), self.extruder.trackWidth(), cSlice.fillAngle) h.hatch() ww = h.getWires() print "Hatching complete: %d fillWires created" % len(ww) f.fillWires = ww else: print "WARNING: not filling this layer, too few shells were computable" #add shells for s in shells: for ss in s: f.shellWires.append(ss) def display(self): """ for debugging purposes, display the result """ for slice in self.slices: for f in slice.faces: for w in f.fillWires: debugdisplay.DisplayColoredShape(w, 'BLUE', False) for w in f.shellWires: debugdisplay.DisplayColoredShape(w, 'GREEN', False) debugdisplay.FitAll() debugdisplay.start_display()
class Slicer: """ Load the shape, and compute slicing options """ def __init__(self, shape, options): self.solid = Wrappers.Solid(shape) self.slices = [] # a list of slices self.sliceMap = {} # distinct slices. different because some slices are re-used instead of calculated again self.options = options self.__setDefaultOptions() self.extruder = Extruder(options) # note: make sure to fix the shape before translating! if options.fixShape: self.solid.fixDefects() if options.translateToPositiveCenter: self.solid = self.solid.translateToPositiveCenter(options.tableCenterX, options.tableCenterY) # now apply slicing limits, if the user specified them # warning, it is confusing if the user specifies these, and specifies translate=true, how do they know where zMin and zMax will be? if options.zMin == None: options.zMin = self.solid.zMin if options.zMax == None: options.zMax = self.solid.zMax def __setDefaultOptions(self): """ sets defaults that may not have been set by the user. This is done here rather than inside of Slicing Defaults because in several cases, knowledge of the part and the options is required to do a good job. """ options = self.options errs = options.errors() if len(errs) > 0: raise Exception("Problem with Options: " + str(errs)) # does user want to translate to positive space? if options.translateToPositiveCenter == None: options.translateToPositiveCenter = True # guess unit of measure if options.units == None: options.units = self.solid.guessUnitOfMeasure() # guess sane values for options not supplied if options.trackWidth == None: options.trackWidth = options.nozzleDiameter * 1.05 if options.layerHeight == None: options.layerHeight = 0.1 if options.fillingWallThickness == None: options.fillingWallThickness = 1.0 # must normalize to units # TODO: fix this later with smarter computations # hack-- we can do better than this later options.generateSupports = False options.machineMaxFeedRate = 200 options.fixShape = True if options.fillingEnabled is None: options.fillingEnabled = True options.gcodeNumberFormat = "%0.3f" options.gcodeVerboseComments = True options.gcodeUseArcs = True options.gcodeAxisName = "E" options.gcodeCurveApproxTolerance = 0.001 options.gcodeFileExtension = ".gcode" options.svgFileExtension = ".svg" """ Slices the object, after which slices will contain a list of slices for printing A slice is a horizontal section of the part. TODO: add 'skeinning'-- more perimeters than fill lines """ @Util.printTiming def execute(self): # TODO: use parallel cores to speed this up, see # http://code.google.com/p/pythonocc/source/browse/trunk/src/examples/Level2/Concurrency/parallel_slicer.py # at this point we have a solid ready to execute zLevel = self.options.zMin + TOLERANCE layerNo = 1 fillAngle = 0 while zLevel < self.options.zMax: print "Slicing At ZLevel=%0.3f" % zLevel # compute the faces for the slice. It may be a transformed # copy of another slice, or a new slice cSlice = self._computeSlice(zLevel, layerNo, fillAngle) self.slices.append(cSlice) zLevel += self.options.layerHeight # return (resultShape,outerWire,otherWires) @Util.printTiming def makeSection(self, cuttingPlane, shapeToSection, zLevel): """ Uses halfspaces to make a cut. """ bff = BRepBuilderAPI.BRepBuilderAPI_MakeFace(cuttingPlane) face = bff.Face() origin = gp.gp_Pnt(0, 0, zLevel - 1) # odd, a halfspace is faster than a box? hs = BRepPrimAPI.BRepPrimAPI_MakeHalfSpace(face, origin) hs.Build() halfspace = hs.Solid() # make the cut bc = BRepAlgoAPI.BRepAlgoAPI_Cut(self.solid.shape, halfspace) cutShape = bc.Shape() ff = [] for face in Topo(cutShape).faces(): if OCCUtil.isFaceAtZLevel(zLevel, face): ff.append(face) return ff @Util.printTiming def makeSection2(self, cuttingPlane, shapeToSection, zLevel): """ Uses BrepSection Algo. this generally returns a list of wires, not a face """ # section is certainly faster, but produces only edges. # those have to be re-organized into wires, probably # using ShapeAnalysis_WireOrder face = BRepBuilderAPI.BRepBuilderAPI_MakeFace(cuttingPlane).Shape() # Computes Shape/Plane intersection section = BRepAlgoAPI.BRepAlgoAPI_Section(self.solid.shape, face) # section = BRepAlgo.BRepAlgo_Section(self.solid.shape,face); section.Build() if section.IsDone(): # Topology.dumpTopology(section.Shape()); # what we got back was a compound of edges t = Topo(section.Shape()) wb = OCCUtil.MultiWireBuilder() for e in t.edges(): wb.addEdge(e) wires = wb.getWires() print wires for w in wires: Topology.dumpTopology(w) return wires else: raise Exception("Could not compute Section!") "computes the boundaries for the given slice" # @Util.printTiming def _computeSlice(self, zLevel, layerNo, fillAngle): cSlice = Slice() cSlice.zLevel = zLevel cSlice.layerNo = layerNo cSlice.fillAngle = fillAngle cSlice.thickness = self.options.layerHeight # make a cutting plane p = gp.gp_Pnt(0, 0, zLevel) csys = gp.gp_Ax3(p, gp.gp().DZ()) cuttingPlane = gp.gp_Pln(csys) # makeSection will use the old reliable way that will get a face from each cut. # makeSection2 will use BRepAlgoaPI_Section, but has problems ordering the edges correctly. newFaces = self.makeSection(cuttingPlane, self.solid.shape, zLevel) for f in newFaces: cSlice.addFace(Face(f)) # bff = BRepBuilderAPI.BRepBuilderAPI_MakeFace(cuttingPlane); # face = bff.Face(); # odd, a halfspace is faster than a box? # hs = BRepPrimAPI.BRepPrimAPI_MakeHalfSpace(face,origin); # hs.Build(); # halfspace = hs.Solid(); # make the cut # bc = BRepAlgoAPI.BRepAlgoAPI_Cut(self.solid.shape,halfspace); # cutShape = bc.Shape(); # for face in Topo(cutShape).faces(): # if OCCUtil.isFaceAtZLevel(zLevel,face): # cSlice.addFace(Face(face)); # break; mySum = cSlice.computeFingerPrint() # # uncomment to enable layer copying. # # check for identical slices. return matching ones if found. if self.sliceMap.has_key(mySum): # print "i can copy this layer!" return self.sliceMap[mySum].copyToZ(zLevel, layerNo) self.sliceMap[mySum] = cSlice # print "This slice has %d faces." % len(cSlice.faces) if self.options.fillingEnabled: self._fillSlice(cSlice) return cSlice """ Fills a slice with vector paths for FDM """ def _fillSlice(self, slice): # how many shells do we need? # attempt to create the number of shells requested by the user, plus one additional for infill for f in slice.faces: numShells = (int)(self.options.fillingWallThickness / self.extruder.trackWidth()) + 1 numShells = max(2, numShells) faceWires = OCCUtil.wireListFromFace(f.face) # regardless, the last successfully created offset is used for hatch infill shells = [] for i in range(1, numShells): # compute offset inwards by one track width offset = (-1) * i * self.extruder.trackWidth() innerEdge = OCCUtil.offsetWireList(faceWires, offset) if len(innerEdge) == 0: # performance: dont offset if there were already no wires continue pathCenter = OCCUtil.offsetWireList(innerEdge, self.extruder.trackWidth() / 2) if len(pathCenter) > 0: shells.append(pathCenter) if len(shells) > 1: # use last one for filling. print "%d shells were available for Hatching" % len(shells) lastShell = shells.pop() s = self.solid h = hatchlib.Hatcher( lastShell, zLevel, (s.xMin, s.yMin, s.xMax, s.yMax), self.extruder.trackWidth(), cSlice.fillAngle ) h.hatch() ww = h.getWires() print "Hatching complete: %d fillWires created" % len(ww) f.fillWires = ww else: print "WARNING: not filling this layer, too few shells were computable" # add shells for s in shells: for ss in s: f.shellWires.append(ss) def display(self): """ for debugging purposes, display the result """ for slice in self.slices: for f in slice.faces: for w in f.fillWires: debugdisplay.DisplayColoredShape(w, "BLUE", False) for w in f.shellWires: debugdisplay.DisplayColoredShape(w, "GREEN", False) debugdisplay.FitAll() debugdisplay.start_display()