def _extractPathWire(self, obj, base, flatWire, cutShp): PathLog.debug('_extractPathWire()') subLoops = list() rtnWIRES = list() osWrIdxs = list() subDistFactor = 1.0 # Raise to include sub wires at greater distance from original fdv = obj.FinalDepth.Value wire = flatWire lstVrtIdx = len(wire.Vertexes) - 1 lstVrt = wire.Vertexes[lstVrtIdx] frstVrt = wire.Vertexes[0] cent0 = FreeCAD.Vector(frstVrt.X, frstVrt.Y, fdv) cent1 = FreeCAD.Vector(lstVrt.X, lstVrt.Y, fdv) pl = FreeCAD.Placement() pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) pl.Base = FreeCAD.Vector(0, 0, 0) # Calculate offset shape, containing cut region ofstShp = self._extractFaceOffset(obj, cutShp, False) # CHECK for ZERO area of offset shape try: osArea = ofstShp.Area except Exception as ee: PathLog.error('No area to offset shape returned.') return False if PathLog.getLevel(PathLog.thisModule()) == 4: os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpOffsetShape') os.Shape = ofstShp os.recompute() os.purgeTouched() self.tmpGrp.addObject(os) numOSWires = len(ofstShp.Wires) for w in range(0, numOSWires): osWrIdxs.append(w) # Identify two vertexes for dividing offset loop NEAR0 = self._findNearestVertex(ofstShp, cent0) min0i = 0 min0 = NEAR0[0][4] for n in range(0, len(NEAR0)): N = NEAR0[n] if N[4] < min0: min0 = N[4] min0i = n (w0, vi0, pnt0, vrt0, d0) = NEAR0[0] # min0i if PathLog.getLevel(PathLog.thisModule()) == 4: near0 = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpNear0') near0.Shape = Part.makeLine(cent0, pnt0) near0.recompute() near0.purgeTouched() self.tmpGrp.addObject(near0) NEAR1 = self._findNearestVertex(ofstShp, cent1) min1i = 0 min1 = NEAR1[0][4] for n in range(0, len(NEAR1)): N = NEAR1[n] if N[4] < min1: min1 = N[4] min1i = n (w1, vi1, pnt1, vrt1, d1) = NEAR1[0] # min1i if PathLog.getLevel(PathLog.thisModule()) == 4: near1 = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpNear1') near1.Shape = Part.makeLine(cent1, pnt1) near1.recompute() near1.purgeTouched() self.tmpGrp.addObject(near1) if w0 != w1: PathLog.warning( 'Offset wire endpoint indexes are not equal - w0, w1: {}, {}'. format(w0, w1)) if PathLog.getLevel(PathLog.thisModule()) == 4: PathLog.debug('min0i is {}.'.format(min0i)) PathLog.debug('min1i is {}.'.format(min1i)) PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0])) PathLog.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1])) PathLog.debug('NEAR0 is {}.'.format(NEAR0)) PathLog.debug('NEAR1 is {}.'.format(NEAR1)) mainWire = ofstShp.Wires[w0] # Check for additional closed loops in offset wire by checking distance to iTAG or eTAG elements if numOSWires > 1: # check all wires for proximity(children) to intersection tags tagsComList = list() for T in self.cutSideTags.Faces: tcom = T.CenterOfMass tv = FreeCAD.Vector(tcom.x, tcom.y, 0.0) tagsComList.append(tv) subDist = self.ofstRadius * subDistFactor for w in osWrIdxs: if w != w0: cutSub = False VTXS = ofstShp.Wires[w].Vertexes for V in VTXS: v = FreeCAD.Vector(V.X, V.Y, 0.0) for t in tagsComList: if t.sub(v).Length < subDist: cutSub = True break if cutSub is True: break if cutSub is True: sub = Part.Wire( Part.__sortEdges__(ofstShp.Wires[w].Edges)) subLoops.append(sub) # Eif # Break offset loop into two wires - one of which is the desired profile path wire. (edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1]) edgs0 = list() edgs1 = list() for e in edgeIdxs0: edgs0.append(mainWire.Edges[e]) for e in edgeIdxs1: edgs1.append(mainWire.Edges[e]) part0 = Part.Wire(Part.__sortEdges__(edgs0)) part1 = Part.Wire(Part.__sortEdges__(edgs1)) # Determine which part is nearest original edge(s) distToPart0 = self._distMidToMid(wire.Wires[0], part0.Wires[0]) distToPart1 = self._distMidToMid(wire.Wires[0], part1.Wires[0]) if distToPart0 < distToPart1: rtnWIRES.append(part0) else: rtnWIRES.append(part1) rtnWIRES.extend(subLoops) return rtnWIRES
def debugMarker(vector, label, color=None, radius=0.5): if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: obj = FreeCAD.ActiveDocument.addObject("Part::Sphere", label) obj.Label = label obj.Radius = radius obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)) if color: obj.ViewObject.ShapeColor = color
def debugEdge(edge, prefix, force=False): if force or PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: pf = edge.valueAt(edge.FirstParameter) pl = edge.valueAt(edge.LastParameter) if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) else: pm = edge.valueAt((edge.FirstParameter+edge.LastParameter)/2) print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pm.x, pm.y, pm.z, pl.x, pl.y, pl.z))
def debugCylinder(vector, r, height, label, color=None): if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: obj = FreeCAD.ActiveDocument.addObject("Part::Cylinder", label) obj.Label = label obj.Radius = r obj.Height = height obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)) obj.ViewObject.Transparency = 90 if color: obj.ViewObject.ShapeColor = color
def getEnvelope(partshape, subshape=None, depthparams=None): ''' getEnvelope(partshape, stockheight=None) returns a shape corresponding to the partshape silhouette extruded to height. if stockheight is given, the returned shape is extruded to that height otherwise the returned shape is the height of the original shape boundbox partshape = solid object stockheight = float - Absolute Z height of the top of material before cutting. ''' PathLog.track(partshape, subshape, depthparams) # if partshape.Volume == 0.0: #Not a 3D object # return None zShift = 0 if subshape is not None: if isinstance(subshape, Part.Face): PathLog.debug('processing a face') sec = Part.makeCompound([subshape]) else: area = Path.Area(Fill=2, Coplanar=0).add(subshape) area.setPlane(makeWorkplane(partshape)) PathLog.debug("About to section with params: {}".format(area.getParams())) sec = area.makeSections(heights=[0.0], project=True)[0].getShape() # zShift = partshape.BoundBox.ZMin - subshape.BoundBox.ZMin PathLog.debug('partshapeZmin: {}, subshapeZMin: {}, zShift: {}'.format(partshape.BoundBox.ZMin, subshape.BoundBox.ZMin, zShift)) else: area = Path.Area(Fill=2, Coplanar=0).add(partshape) area.setPlane(makeWorkplane(partshape)) sec = area.makeSections(heights=[0.0], project=True)[0].getShape() # If depthparams are passed, use it to calculate bottom and height of # envelope if depthparams is not None: # eLength = float(stockheight)-partshape.BoundBox.ZMin eLength = depthparams.safe_height - depthparams.final_depth #envelopeshape = sec.extrude(FreeCAD.Vector(0, 0, eLength)) zShift = depthparams.final_depth - sec.BoundBox.ZMin PathLog.debug('boundbox zMIN: {} elength: {} zShift {}'.format(partshape.BoundBox.ZMin, eLength, zShift)) else: eLength = partshape.BoundBox.ZLength - sec.BoundBox.ZMin # Shift the section based on selection and depthparams. newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), sec.Placement.Rotation) sec.Placement = newPlace # Extrude the section to top of Boundbox or desired height envelopeshape = sec.extrude(FreeCAD.Vector(0, 0, eLength)) if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: removalshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "Envelope") removalshape.Shape = envelopeshape return envelopeshape
def processTags(self, obj): tagID = 0 if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: for tag in self.tags: tagID += 1 if tag.enabled: PathLog.debug("x=%s, y=%s, z=%s" % (tag.x, tag.y, self.pathData.minZ)) # debugMarker(FreeCAD.Vector(tag.x, tag.y, self.pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) # if tag.angle != 90: # debugCone(tag.originAt(self.pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) # else: # debugCylinder(tag.originAt(self.pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) obj.Path = self.createPath(obj, self.pathData, self.tags)
import FreeCAD import Path import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils from PathScripts.PathUtils import waiting_effects from PySide import QtCore __title__ = "Base class for PathArea based operations." __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Base class and properties for Path.Area based operations." if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule() else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class ObjectOp(PathOp.ObjectOp): '''Base class for all Path.Area based operations. Provides standard features including debugging properties AreaParams, PathParams and removalshape, all hidden. The main reason for existence is to implement the standard interface to Path.Area so subclasses only have to provide the shapes for the operations.'''
import Path import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils from PySide import QtCore, QtGui # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader('Part', globals(), 'Part') """Z Depth Correction Dressup. This dressup takes a probe file as input and does bilinear interpolation of the Zdepths to correct for a surface which is not parallel to the milling table/bed. The probe file should conform to the format specified by the linuxcnc G38 probe logging: 9-number coordinate consisting of XYZABCUVW http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38 """ LOGLEVEL = False LOG_MODULE = PathLog.thisModule() if LOGLEVEL: PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) PathLog.trackModule(PathLog.thisModule()) else: PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03'] rapidcommands = ['G0', 'G00']
# * USA * # * * # *************************************************************************** import FreeCAD import Part import Path import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathProfileBase as PathProfileBase import PathScripts.PathUtils as PathUtils from DraftGeomUtils import findWires from PySide import QtCore PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) #PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) __title__ = "Path Profile Edges Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Path Profile operation based on edges."
import Part import Path import PathScripts import PathScripts.PathLog as PathLog import PathScripts.PathToolEdit as PathToolEdit import PathScripts.PathUtil as PathUtil from FreeCAD import Units from PySide import QtCore if FreeCAD.GuiUp: import FreeCADGui import PathScripts.PathGui as PathGui if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class ToolControllerTemplate: '''Attribute and sub element strings for template export/import.''' Expressions = 'xengine' ExprExpr = 'expr' ExprProp = 'prop' HorizFeed = 'hfeed' HorizRapid = 'hrapid'
import FreeCAD import FreeCADGui import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathDrilling as PathDrilling import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui __title__ = "Path Drilling Operation UI." __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "UI and Command for Path Drilling Operation." if True: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule()) class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): '''Controller for the drilling operation's page''' def getForm(self): '''getForm() ... return UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpDrillingEdit.ui") def getFields(self, obj): '''setFields(obj) ... update obj's properties with values from the UI''' PathLog.track() self.updateInputField(obj, 'PeckDepth', self.form.peckDepth)
import PathScripts.PathLog as PathLog import traceback from MKObserverable import * from MKService import * from machinetalk.protobuf.status_pb2 import * from machinetalk.protobuf.types_pb2 import MT_EMCSTAT_FULL_UPDATE PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule()) #PathLog.trackModule(PathLog.thisModule()) class MKServiceContainer(MKObserverable): def __init__(self, valid): super().__init__() self.valid = valid def __getitem__(self, path): if self.valid: try: attr = self.__getattribute__(path[0]) return returnAttribute(attr, path[1:]) except: PathLog.error("unknown container path = %s (%s)" % (path, self)) traceback.print_stack() raise return None
def addDebugDisplay(): return PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.''' PathLog.track() inaccessible = translate( 'PathProfileEdges', 'The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.' ) if PathLog.getLevel(PathLog.thisModule()) == 4: self.tmpGrp = FreeCAD.ActiveDocument.addObject( 'App::DocumentObjectGroup', 'tmpDebugGrp') tmpGrpNm = self.tmpGrp.Name self.JOB = PathUtils.findParentJob(obj) self.offsetExtra = abs(obj.OffsetExtra.Value) if obj.UseComp: self.useComp = True self.ofstRadius = self.radius + self.offsetExtra self.commandlist.append( Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: self.useComp = False self.ofstRadius = self.offsetExtra self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) shapes = [] if obj.Base: basewires = [] zMin = None for b in obj.Base: edgelist = [] for sub in b[1]: edgelist.append(getattr(b[0].Shape, sub)) basewires.append((b[0], DraftGeomUtils.findWires(edgelist))) if zMin is None or b[0].Shape.BoundBox.ZMin < zMin: zMin = b[0].Shape.BoundBox.ZMin PathLog.debug( 'PathProfileEdges areaOpShapes():: len(basewires) is {}'. format(len(basewires))) for base, wires in basewires: for wire in wires: if wire.isClosed() is True: # f = Part.makeFace(wire, 'Part::FaceMakerSimple') # if planar error, Comment out previous line, uncomment the next two (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) f = origWire.Wires[0] if f is not False: # shift the compound to the bottom of the base object for proper sectioning zShift = zMin - f.BoundBox.ZMin newPlace = FreeCAD.Placement( FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation) f.Placement = newPlace env = PathUtils.getEnvelope( base.Shape, subshape=f, depthparams=self.depthparams) shapes.append((env, False)) else: PathLog.error(inaccessible) else: if self.JOB.GeometryTolerance.Value == 0.0: msg = self.JOB.Label + '.GeometryTolerance = 0.0.' msg += translate( 'PathProfileEdges', 'Please set to an acceptable value greater than zero.' ) PathLog.error(msg) else: cutWireObjs = False flattened = self._flattenWire( obj, wire, obj.FinalDepth.Value) if flattened: (origWire, flatWire) = flattened if PathLog.getLevel(PathLog.thisModule()) == 4: os = FreeCAD.ActiveDocument.addObject( 'Part::Feature', 'tmpFlatWire') os.Shape = flatWire os.purgeTouched() self.tmpGrp.addObject(os) cutShp = self._getCutAreaCrossSection( obj, base, origWire, flatWire) if cutShp is not False: cutWireObjs = self._extractPathWire( obj, base, flatWire, cutShp) if cutWireObjs is not False: for cW in cutWireObjs: shapes.append((cW, False)) self.profileEdgesIsOpen = True else: PathLog.error(inaccessible) else: PathLog.error(inaccessible) # Delete the temporary objects if PathLog.getLevel(PathLog.thisModule()) == 4: if FreeCAD.GuiUp: import FreeCADGui FreeCADGui.ActiveDocument.getObject( tmpGrpNm).Visibility = False self.tmpGrp.purgeTouched() return shapes
def opExecute(self, obj): '''opExecute(obj) ... processes all Base features and Locations and collects them in a list of positions and radii which is then passed to circularHoleExecute(obj, holes). If no Base geometries and no Locations are present, the job's Base is inspected and all drillable features are added to Base. In this case appropriate values for depths are also calculated and assigned. Do not overwrite, implement circularHoleExecute(obj, holes) instead.''' PathLog.track() holes = [] baseSubsTuples = [] subCount = 0 allTuples = [] self.cloneNames = [] # pylint: disable=attribute-defined-outside-init self.guiMsgs = [] # pylint: disable=attribute-defined-outside-init self.rotateFlag = False # pylint: disable=attribute-defined-outside-init self.useTempJobClones('Delete') # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.clearHeight = obj.ClearanceHeight.Value # pylint: disable=attribute-defined-outside-init self.safeHeight = obj.SafeHeight.Value # pylint: disable=attribute-defined-outside-init self.axialFeed = 0.0 # pylint: disable=attribute-defined-outside-init self.axialRapid = 0.0 # pylint: disable=attribute-defined-outside-init trgtDep = None def haveLocations(self, obj): if PathOp.FeatureLocations & self.opFeatures(obj): return len(obj.Locations) != 0 return False if obj.EnableRotation == 'Off': strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value else: # Calculate operation heights based upon rotation radii opHeights = self.opDetermineRotationRadii(obj) (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0] # pylint: disable=attribute-defined-outside-init (clrOfset, safOfst) = opHeights[1] PathLog.debug("Exec. opHeights[0]: " + str(opHeights[0])) PathLog.debug("Exec. opHeights[1]: " + str(opHeights[1])) # Set clearance and safe heights based upon rotation radii if obj.EnableRotation == 'A(x)': strDep = self.xRotRad elif obj.EnableRotation == 'B(y)': strDep = self.yRotRad else: strDep = max(self.xRotRad, self.yRotRad) finDep = -1 * strDep obj.ClearanceHeight.Value = strDep + clrOfset obj.SafeHeight.Value = strDep + safOfst # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() # Set axial feed rates based upon horizontal feed rates safeCircum = 2 * math.pi * obj.SafeHeight.Value self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init # Complete rotational analysis and temp clone creation as needed if obj.EnableRotation == 'Off': PathLog.debug("Enable Rotation setting is 'Off' for {}.".format( obj.Name)) stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: baseSubsTuples.append((base, subList, 0.0, 'A', stock)) else: for p in range(0, len(obj.Base)): (base, subsList) = obj.Base[p] for sub in subsList: if self.isHoleEnabled(obj, base, sub): shape = getattr(base.Shape, sub) rtn = False (norm, surf) = self.getFaceNormAndSurf(shape) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable if rtn is True: (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis( obj, base, angle, axis, subCount) # Verify faces are correctly oriented - InverseAngle might be necessary PathLog.debug( "Verifying {} orientation: running faceRotationAnalysis() again." .format(sub)) faceIA = getattr(clnBase.Shape, sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis( obj, norm, surf) # pylint: disable=unused-variable if rtn is True: msg = obj.Name + ":: " msg += translate( "Path", "{} might be misaligned after initial rotation." .format(sub)) + " " if obj.AttemptInverseAngle is True and obj.InverseAngle is False: (clnBase, clnStock, angle) = self.applyInverseAngle( obj, clnBase, clnStock, axis, angle) msg += translate( "Path", "Rotated to 'InverseAngle' to attempt access." ) else: if len(subsList) == 1: msg += translate( "Path", "Consider toggling the 'InverseAngle' property and recomputing." ) else: msg += translate( "Path", "Consider transferring '{}' to independent operation." .format(sub)) PathLog.warning(msg) # title = translate("Path", 'Rotation Warning') # self.guiMessage(title, msg, False) else: PathLog.debug( "Face appears to be oriented correctly.") cmnt = "{}: {} @ {}; ".format( sub, axis, str(round(angle, 5))) if cmnt not in obj.Comment: obj.Comment += cmnt tup = clnBase, sub, tag, angle, axis, clnStock allTuples.append(tup) else: if self.warnDisabledAxis(obj, axis, sub) is True: pass # Skip drill feature due to access issue else: PathLog.debug(str(sub) + ": No rotation used") axis = 'X' angle = 0.0 tag = base.Name + '_' + axis + str( angle).replace('.', '_') stock = PathUtils.findParentJob(obj).Stock tup = base, sub, tag, angle, axis, stock allTuples.append(tup) # Eif # Eif subCount += 1 # Efor # Efor (Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList) subList = [] for o in range(0, len(Tags)): PathLog.debug('hTag: {}'.format(Tags[o])) subList = [] for (base, sub, tag, angle, axis, stock) in Grps[o]: subList.append(sub) pair = base, subList, angle, axis, stock baseSubsTuples.append(pair) # Efor for base, subs, angle, axis, stock in baseSubsTuples: for sub in subs: if self.isHoleEnabled(obj, base, sub): pos = self.holePosition(obj, base, sub) if pos: # Default is treat selection as 'Face' shape finDep = base.Shape.getElement(sub).BoundBox.ZMin if base.Shape.getElement(sub).ShapeType == 'Edge': msg = translate( "Path", "Verify Final Depth of holes based on edges. {} depth is: {} mm" .format(sub, round(finDep, 4))) + " " msg += translate( "Path", "Always select the bottom edge of the hole when using an edge." ) PathLog.warning(msg) # If user has not adjusted Final Depth value, attempt to determine from sub trgtDep = obj.FinalDepth.Value if obj.OpFinalDepth.Value == obj.FinalDepth.Value: trgtDep = finDep if obj.FinalDepth.Value < finDep: msg = translate( "Path", "Final Depth setting is below the hole bottom for {}." .format(sub)) PathLog.warning(msg) holes.append({ 'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub), 'angle': angle, 'axis': axis, 'trgtDep': trgtDep, 'stkTop': stock.Shape.BoundBox.ZMax }) if haveLocations(self, obj): for location in obj.Locations: # holes.append({'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'finDep': obj.FinalDepth.Value}) trgtDep = obj.FinalDepth.Value holes.append({ 'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'trgtDep': trgtDep, 'stkTop': PathUtils.findParentJob(obj).stock.Shape.BoundBox.ZMax }) # If all holes based upon edges, set post-operation Final Depth to highest edge height if obj.OpFinalDepth.Value == obj.FinalDepth.Value: if len(holes) == 1: PathLog.info( translate( 'Path', "Single-hole operation. Saving Final Depth determined from hole base." )) obj.FinalDepth.Value = trgtDep if len(holes) > 0: self.circularHoleExecute( obj, holes) # circularHoleExecute() located in PathDrilling.py self.useTempJobClones( 'Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user PathLog.debug("obj.Name: " + str(obj.Name))
def _getCutAreaCrossSection(self, obj, base, origWire, flatWire): PathLog.debug('_getCutAreaCrossSection()') FCAD = FreeCAD.ActiveDocument tolerance = self.JOB.GeometryTolerance.Value toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules minBfr = toolDiam * 1.25 bbBfr = (self.ofstRadius * 2) * 1.25 if bbBfr < minBfr: bbBfr = minBfr fwBB = flatWire.BoundBox wBB = origWire.BoundBox minArea = (self.ofstRadius - tolerance)**2 * math.pi useWire = origWire.Wires[0] numOrigEdges = len(useWire.Edges) sdv = wBB.ZMax fdv = obj.FinalDepth.Value extLenFwd = sdv - fdv WIRE = flatWire.Wires[0] numEdges = len(WIRE.Edges) # Identify first/last edges and first/last vertex on wire begE = WIRE.Edges[0] # beginning edge endE = WIRE.Edges[numEdges - 1] # ending edge blen = begE.Length elen = endE.Length Vb = begE.Vertexes[0] # first vertex of wire Ve = endE.Vertexes[1] # last vertex of wire pb = FreeCAD.Vector(Vb.X, Vb.Y, fdv) pe = FreeCAD.Vector(Ve.X, Ve.Y, fdv) # Identify endpoints connecting circle center and diameter vectDist = pe.sub(pb) diam = vectDist.Length cntr = vectDist.multiply(0.5).add(pb) R = diam / 2 pl = FreeCAD.Placement() pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) pl.Base = FreeCAD.Vector(0, 0, 0) # Obtain beginning point perpendicular points if blen > 0.1: bcp = begE.valueAt(begE.getParameterByLength( 0.1)) # point returned 0.1 mm along edge else: bcp = FreeCAD.Vector(begE.Vertexes[1].X, begE.Vertexes[1].Y, fdv) if elen > 0.1: ecp = endE.valueAt(endE.getParameterByLength( elen - 0.1)) # point returned 0.1 mm along edge else: ecp = FreeCAD.Vector(endE.Vertexes[1].X, endE.Vertexes[1].Y, fdv) # Create intersection tags for determining which side of wire to cut (begInt, begExt, iTAG, eTAG) = self._makeIntersectionTags(useWire, numOrigEdges, fdv) if not begInt or not begExt: return False self.iTAG = iTAG self.eTAG = eTAG # Create extended wire boundbox, and extrude extBndbox = self._makeExtendedBoundBox(wBB, bbBfr, fdv) extBndboxEXT = extBndbox.extrude(FreeCAD.Vector(0, 0, extLenFwd)) # Cut model(selected edges) from extended edges boundbox cutArea = extBndboxEXT.cut(base.Shape) if PathLog.getLevel(PathLog.thisModule()) == 4: CA = FCAD.addObject('Part::Feature', 'tmpCutArea') CA.Shape = cutArea CA.recompute() CA.purgeTouched() self.tmpGrp.addObject(CA) # Get top and bottom faces of cut area (CA), and combine faces when necessary topFc = list() botFc = list() bbZMax = cutArea.BoundBox.ZMax bbZMin = cutArea.BoundBox.ZMin for f in range(0, len(cutArea.Faces)): FcBB = cutArea.Faces[f].BoundBox if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance: topFc.append(f) if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance: botFc.append(f) if len(topFc) == 0: PathLog.error('Failed to identify top faces of cut area.') return False topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc]) topComp.translate(FreeCAD.Vector( 0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth if len(botFc) > 1: PathLog.debug('len(botFc) > 1') bndboxFace = Part.Face(extBndbox.Wires[0]) tmpFace = Part.Face(extBndbox.Wires[0]) for f in botFc: Q = tmpFace.cut(cutArea.Faces[f]) tmpFace = Q botComp = bndboxFace.cut(tmpFace) else: botComp = Part.makeCompound([ cutArea.Faces[f] for f in botFc ]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) botComp.translate(FreeCAD.Vector( 0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth # Make common of the two comFC = topComp.common(botComp) # Determine with which set of intersection tags the model intersects (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, 'QRY', comFC) if cmnExtArea > cmnIntArea: PathLog.debug('Cutting on Ext side.') self.cutSide = 'E' self.cutSideTags = eTAG tagCOM = begExt.CenterOfMass else: PathLog.debug('Cutting on Int side.') self.cutSide = 'I' self.cutSideTags = iTAG tagCOM = begInt.CenterOfMass # Make two beginning style(oriented) 'L' shape stops begStop = self._makeStop('BEG', bcp, pb, 'BegStop') altBegStop = self._makeStop('END', bcp, pb, 'BegStop') # Identify to which style 'L' stop the beginning intersection tag is closest, # and create partner end 'L' stop geometry, and save for application later lenBS_extETag = begStop.CenterOfMass.sub(tagCOM).Length lenABS_extETag = altBegStop.CenterOfMass.sub(tagCOM).Length if lenBS_extETag < lenABS_extETag: endStop = self._makeStop('END', ecp, pe, 'EndStop') pathStops = Part.makeCompound([begStop, endStop]) else: altEndStop = self._makeStop('BEG', ecp, pe, 'EndStop') pathStops = Part.makeCompound([altBegStop, altEndStop]) pathStops.translate(FreeCAD.Vector(0, 0, fdv - pathStops.BoundBox.ZMin)) # Identify closed wire in cross-section that corresponds to user-selected edge(s) workShp = comFC fcShp = workShp wire = origWire WS = workShp.Wires lenWS = len(WS) if lenWS < 3: wi = 0 else: wi = None for wvt in wire.Vertexes: for w in range(0, lenWS): twr = WS[w] for v in range(0, len(twr.Vertexes)): V = twr.Vertexes[v] if abs(V.X - wvt.X) < tolerance: if abs(V.Y - wvt.Y) < tolerance: # Same vertex found. This wire to be used for offset wi = w break # Efor if wi is None: PathLog.error( 'The cut area cross-section wire does not coincide with selected edge. Wires[] index is None.' ) return False else: PathLog.debug('Cross-section Wires[] index is {}.'.format(wi)) nWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi].Edges)) fcShp = Part.Face(nWire) fcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) # Eif # verify that wire chosen is not inside the physical model if wi > 0: # and isInterior is False: PathLog.debug( 'Multiple wires in cut area. First choice is not 0. Testing.') testArea = fcShp.cut(base.Shape) isReady = self._checkTagIntersection(iTAG, eTAG, self.cutSide, testArea) PathLog.debug('isReady {}.'.format(isReady)) if isReady is False: PathLog.debug('Using wire index {}.'.format(wi - 1)) pWire = Part.Wire( Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) pfcShp = Part.Face(pWire) pfcShp.translate( FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) workShp = pfcShp.cut(fcShp) if testArea.Area < minArea: PathLog.debug( 'offset area is less than minArea of {}.'.format(minArea)) PathLog.debug('Using wire index {}.'.format(wi - 1)) pWire = Part.Wire( Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) pfcShp = Part.Face(pWire) pfcShp.translate( FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) workShp = pfcShp.cut(fcShp) # Eif # Add path stops at ends of wire cutShp = workShp.cut(pathStops) return cutShp
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * # * GNU Library General Public License for more details. * # * * # * You should have received a copy of the GNU Library General Public * # * License along with this program; if not, write to the Free Software * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * USA * # * * # *************************************************************************** import FreeCAD import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathLog as PathLog import sys PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) from PathTests.PathTestUtils import PathTestBase def refstring(string): if sys.version_info.major < 3: return string return string.replace(" u'", " '") class TestPathSetupSheet(PathTestBase): def setUp(self): self.doc = FreeCAD.newDocument("TestPathSetupSheet") def tearDown(self):
def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection instead.''' PathLog.track() # Instantiate class variables for operation reference self.endVector = None # pylint: disable=attribute-defined-outside-init self.rotateFlag = False # pylint: disable=attribute-defined-outside-init self.leadIn = 2.0 # pylint: disable=attribute-defined-outside-init self.cloneNames = [] # pylint: disable=attribute-defined-outside-init self.guiMsgs = [] # pylint: disable=attribute-defined-outside-init self.tempObjectNames = [] # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.useTempJobClones('Delete') # Clear temporary group and recreate for temp job clones self.rotStartDepth = None # pylint: disable=attribute-defined-outside-init if obj.EnableRotation != 'Off': # Calculate operation heights based upon rotation radii opHeights = self.opDetermineRotationRadii(obj) (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0] # pylint: disable=attribute-defined-outside-init (self.clrOfset, self.safOfst) = opHeights[1] # pylint: disable=attribute-defined-outside-init # Set clearance and safe heights based upon rotation radii if obj.EnableRotation == 'A(x)': strDep = self.xRotRad elif obj.EnableRotation == 'B(y)': strDep = self.yRotRad else: strDep = max(self.xRotRad, self.yRotRad) finDep = -1 * strDep self.rotStartDepth = strDep obj.ClearanceHeight.Value = strDep + self.clrOfset obj.SafeHeight.Value = strDep + self.safOfst # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() else: strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value # Set axial feed rates based upon horizontal feed rates safeCircum = 2 * math.pi * obj.SafeHeight.Value self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init # Initiate depthparams and calculate operation heights for rotational operation self.depthparams = self._customDepthParams(obj, obj.StartDepth.Value, obj.FinalDepth.Value) # Set start point if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: start = obj.StartPoint else: start = None aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return # Adjust tuples length received from other PathWB tools/operations beside PathPocketShape shapes = [] for shp in aOS: if len(shp) == 2: (fc, iH) = shp # fc, iH, sub, angle, axis, strtDep, finDep tup = fc, iH, 'otherOp', 0.0, 'S', obj.StartDepth.Value, obj.FinalDepth.Value shapes.append(tup) else: shapes.append(shp) if len(shapes) > 1: jobs = list() for s in shapes: if s[2] == 'OpenEdge': shp = Part.makeCompound(s[0]) else: shp = s[0] jobs.append({ 'x': shp.BoundBox.XMax, 'y': shp.BoundBox.YMax, 'shape': s }) jobs = PathUtils.sort_jobs(jobs, ['x', 'y']) shapes = [j['shape'] for j in jobs] sims = [] numShapes = len(shapes) for ns in range(0, numShapes): profileEdgesIsOpen = False (shape, isHole, sub, angle, axis, strDep, finDep) = shapes[ns] # pylint: disable=unused-variable if sub == 'OpenEdge': profileEdgesIsOpen = True if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: osp = obj.StartPoint self.commandlist.append(Path.Command('G0', {'X': osp.x, 'Y': osp.y, 'F': self.horizRapid})) if ns < numShapes - 1: nextAxis = shapes[ns + 1][4] else: nextAxis = 'L' self.depthparams = self._customDepthParams(obj, strDep, finDep) try: if profileEdgesIsOpen: (pp, sim) = self._buildProfileOpenEdges(obj, shape, isHole, start, getsim) else: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) except Exception as e: # pylint: disable=broad-except FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.") else: if profileEdgesIsOpen: ppCmds = pp else: ppCmds = pp.Commands if obj.EnableRotation != 'Off' and self.rotateFlag is True: # Rotate model to index for cut if axis == 'X': axisOfRot = 'A' elif axis == 'Y': axisOfRot = 'B' elif axis == 'Z': axisOfRot = 'C' else: axisOfRot = 'A' # Rotate Model to correct angle ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid})) # Raise cutter to safe height ppCmds.insert(0, Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Return index to starting position if axis of rotation changes. if numShapes > 1: if ns != numShapes - 1: if axis != nextAxis: ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) # Eif # Save gcode commands to object command list self.commandlist.extend(ppCmds) sims.append(sim) # Eif if self.areaOpRetractTool(obj) and self.endVector is not None: self.endVector[2] = obj.ClearanceHeight.Value self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) # Raise cutter to safe height and rotate back to original orientation if self.rotateFlag is True: resetAxis = False lastJobOp = None nextJobOp = None opIdx = 0 JOB = PathUtils.findParentJob(obj) jobOps = JOB.Operations.Group numJobOps = len(jobOps) for joi in range(0, numJobOps): jo = jobOps[joi] if jo.Name == obj.Name: opIdx = joi lastOpIdx = opIdx - 1 nextOpIdx = opIdx + 1 if lastOpIdx > -1: lastJobOp = jobOps[lastOpIdx] if nextOpIdx < numJobOps: nextJobOp = jobOps[nextOpIdx] if lastJobOp is not None: if hasattr(lastJobOp, 'EnableRotation'): PathLog.debug('Last Op, {}, has `EnableRotation` set to {}'.format(lastJobOp.Label, lastJobOp.EnableRotation)) if lastJobOp.EnableRotation != obj.EnableRotation: resetAxis = True if ns == numShapes - 1: # If last shape, check next op EnableRotation setting if nextJobOp is not None: if hasattr(nextJobOp, 'EnableRotation'): PathLog.debug('Next Op, {}, has `EnableRotation` set to {}'.format(nextJobOp.Label, nextJobOp.EnableRotation)) if nextJobOp.EnableRotation != obj.EnableRotation: resetAxis = True # Raise to safe height if rotation activated self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # reset rotational axes if necessary if resetAxis is True: self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) self.useTempJobClones('Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user for ton in self.tempObjectNames: # remove temporary objects by name FreeCAD.ActiveDocument.removeObject(ton) PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims
open a gcode file, parse its contents, and create the appropriate objects in FreeCAD. This preprocessor will not add imported gcode to an existing job. For a more useful preprocessor, look at the gcode_pre.py file Read the Path Workbench documentation to know how to create Path objects from GCode. ''' import os import Path import FreeCAD import PathScripts.PathLog as PathLog # LEVEL = PathLog.Level.DEBUG LEVEL = PathLog.Level.INFO PathLog.setLevel(LEVEL, PathLog.thisModule()) if LEVEL == PathLog.Level.DEBUG: PathLog.trackModule(PathLog.thisModule()) # to distinguish python built-in open function from the one declared below if open.__module__ in ['__builtin__', 'io']: pythonopen = open def open(filename): "called when freecad opens a file." PathLog.track(filename) docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname)
def execute(self, obj, getsim=False): PathLog.track() commandlist = [] simlist = [] commandlist.append(Path.Command("(" + obj.Label + ")")) if not obj.Active: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False return parentJob = PathUtils.findParentJob(obj) if parentJob is None: return baseobject = parentJob.Base if baseobject is None: return self.depthparams = depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=0.0, final_depth=obj.FinalDepth.Value, user_depths=None) toolLoad = obj.ToolController if toolLoad is None or toolLoad.ToolNumber == 0: FreeCAD.Console.PrintError( "No Tool Controller is selected. We need a tool to build a Path." ) else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value self.vertRapid = toolLoad.VertRapid.Value self.horizRapid = toolLoad.HorizRapid.Value tool = toolLoad.Proxy.getTool(toolLoad) if not tool or tool.Diameter == 0: FreeCAD.Console.PrintError( "No Tool found or diameter is zero. We need a tool to build a Path." ) return else: self.radius = tool.Diameter / 2 if obj.Base: PathLog.debug("base items exist. Processing...") for b in obj.Base: PathLog.debug("Base item: {}".format(b)) for sub in b[1]: if "Face" in sub: shape = Part.makeCompound([getattr(b[0].Shape, sub)]) #shape = getattr(b[0].Shape, sub) else: edges = [getattr(b[0].Shape, sub) for sub in b[1]] shape = Part.makeFace(edges, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope(baseobject.Shape, subshape=shape, depthparams=self.depthparams) removal = env.cut(baseobject.Shape) if PathLog.getLevel( PathLog.thisModule()) == PathLog.Level.DEBUG: removalshape = FreeCAD.ActiveDocument.addObject( "Part::Feature", "removalshape") removalshape.Shape = removal try: (pp, sim) = self._buildPathArea(obj, removal, getsim=getsim) if sim is not None: simlist.append(sim) commandlist.extend(pp.Commands) except Exception as e: FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError( "Something unexpected happened. Unable to generate a pocket path. Check project and tool config." ) else: # process the job base object as a whole PathLog.debug("processing the whole job base object") env = PathUtils.getEnvelope(baseobject.Shape, subshape=None, depthparams=self.depthparams) removal = env.cut(baseobject.Shape) if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: removalshape = FreeCAD.ActiveDocument.addObject( "Part::Feature", "removalshape") removalshape.Shape = removal try: (pp, sim) = self._buildPathArea(obj, removal, getsim=getsim) commandlist.extend(pp.Commands) if sim is not None: simlist.append(sim) #commandlist.extend(self._buildPathArea(obj, env.cut(baseobject.Shape)).Commands) except Exception as e: FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError( "Something unexpected happened. Unable to generate a pocket path. Check project and tool config." ) # Let's finish by rapid to clearance...just for safety commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) path = Path.Path(commandlist) obj.Path = path obj.ViewObject.Visibility = True PathLog.debug(simlist) simshape = None if len(simlist) > 1: simshape = simlist[0].fuse(simlist[1:]) elif len(simlist) == 1: simshape = simlist[0] if simshape is not None and PathLog.getLevel( PathLog.thisModule()) == PathLog.Level.DEBUG: sim = FreeCAD.ActiveDocument.addObject("Part::Feature", "simshape") sim.Shape = simshape return simshape
def _makeStop(self, sType, pA, pB, lbl): rad = self.radius ofstRad = self.ofstRadius extra = self.radius / 10 pl = FreeCAD.Placement() pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) pl.Base = FreeCAD.Vector(0, 0, 0) E = FreeCAD.Vector(pB.x, pB.y, 0) # endpoint C = FreeCAD.Vector(pA.x, pA.y, 0) # checkpoint lenEC = E.sub(C).Length if self.useComp is True or (self.useComp is False and self.offsetExtra != 0): # 'L' stop shape and edge legend # --1-- # | | # 2 6 # | | # | ----5----| # | 4 # -----3-------| # positive dist in _makePerp2DVector() is CCW rotation p1 = E if sType == 'BEG': p2 = self._makePerp2DVector(C, E, -0.25) # E1 p3 = self._makePerp2DVector(p1, p2, ofstRad + 1 + extra) # E2 p4 = self._makePerp2DVector(p2, p3, 0.25 + ofstRad + extra) # E3 p5 = self._makePerp2DVector(p3, p4, 1 + extra) # E4 p6 = self._makePerp2DVector(p4, p5, ofstRad + extra) # E5 elif sType == 'END': p2 = self._makePerp2DVector(C, E, 0.25) # E1 p3 = self._makePerp2DVector(p1, p2, -1 * (ofstRad + 1 + extra)) # E2 p4 = self._makePerp2DVector(p2, p3, -1 * (0.25 + ofstRad + extra)) # E3 p5 = self._makePerp2DVector(p3, p4, -1 * (1 + extra)) # E4 p6 = self._makePerp2DVector(p4, p5, -1 * (ofstRad + extra)) # E5 p7 = E # E6 L1 = Part.makeLine(p1, p2) L2 = Part.makeLine(p2, p3) L3 = Part.makeLine(p3, p4) L4 = Part.makeLine(p4, p5) L5 = Part.makeLine(p5, p6) L6 = Part.makeLine(p6, p7) wire = Part.Wire([L1, L2, L3, L4, L5, L6]) else: # 'L' stop shape and edge legend # : # |----2-------| # 3 1 # |-----4------| # positive dist in _makePerp2DVector() is CCW rotation p1 = E if sType == 'BEG': p2 = self._makePerp2DVector( C, E, -1 * (0.25 + abs(self.offsetExtra))) # left, 0.25 p3 = self._makePerp2DVector(p1, p2, 0.25 + abs(self.offsetExtra)) p4 = self._makePerp2DVector( p2, p3, (0.5 + abs(self.offsetExtra))) # FIRST POINT p5 = self._makePerp2DVector( p3, p4, 0.25 + abs(self.offsetExtra)) # E1 SECOND elif sType == 'END': p2 = self._makePerp2DVector( C, E, (0.25 + abs(self.offsetExtra))) # left, 0.25 p3 = self._makePerp2DVector( p1, p2, -1 * (0.25 + abs(self.offsetExtra))) p4 = self._makePerp2DVector( p2, p3, -1 * (0.5 + abs(self.offsetExtra))) # FIRST POINT p5 = self._makePerp2DVector( p3, p4, -1 * (0.25 + abs(self.offsetExtra))) # E1 SECOND p6 = p1 # E4 L1 = Part.makeLine(p1, p2) L2 = Part.makeLine(p2, p3) L3 = Part.makeLine(p3, p4) L4 = Part.makeLine(p4, p5) L5 = Part.makeLine(p5, p6) wire = Part.Wire([L1, L2, L3, L4, L5]) # Eif face = Part.Face(wire) if PathLog.getLevel(PathLog.thisModule()) == 4: os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp' + lbl) os.Shape = face os.recompute() os.purgeTouched() self.tmpGrp.addObject(os) return face
import Draft import math import Part # from PathScripts.PathUtils import waiting_effects from PySide import QtCore if FreeCAD.GuiUp: import FreeCADGui __title__ = "Base class for PathArea based operations." __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Base class and properties for Path.Area based operations." LOGLEVEL = PathLog.Level.INFO PathLog.setLevel(LOGLEVEL, PathLog.thisModule()) if LOGLEVEL is PathLog.Level.DEBUG: PathLog.trackModule() # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class ObjectOp(PathOp.ObjectOp): '''Base class for all Path.Area based operations. Provides standard features including debugging properties AreaParams, PathParams and removalshape, all hidden. The main reason for existence is to implement the standard interface to Path.Area so subclasses only have to provide the shapes for the
def _separateWireAtVertexes(self, wire, VV1, VV2): PathLog.debug('_separateWireAtVertexes()') tolerance = self.JOB.GeometryTolerance.Value grps = [[], []] wireIdxs = [[], []] V1 = FreeCAD.Vector(VV1.X, VV1.Y, VV1.Z) V2 = FreeCAD.Vector(VV2.X, VV2.Y, VV2.Z) lenE = len(wire.Edges) FLGS = list() for e in range(0, lenE): FLGS.append(0) chk4 = False for e in range(0, lenE): v = 0 E = wire.Edges[e] fv0 = FreeCAD.Vector(E.Vertexes[0].X, E.Vertexes[0].Y, E.Vertexes[0].Z) fv1 = FreeCAD.Vector(E.Vertexes[1].X, E.Vertexes[1].Y, E.Vertexes[1].Z) if fv0.sub(V1).Length < tolerance: v = 1 if fv1.sub(V2).Length < tolerance: v += 3 chk4 = True elif fv1.sub(V1).Length < tolerance: v = 1 if fv0.sub(V2).Length < tolerance: v += 3 chk4 = True if fv0.sub(V2).Length < tolerance: v = 3 if fv1.sub(V1).Length < tolerance: v += 1 chk4 = True elif fv1.sub(V2).Length < tolerance: v = 3 if fv0.sub(V1).Length < tolerance: v += 1 chk4 = True FLGS[e] += v # Efor PathLog.debug('_separateWireAtVertexes() FLGS: \n{}'.format(FLGS)) PRE = list() POST = list() IDXS = list() IDX1 = list() IDX2 = list() for e in range(0, lenE): f = FLGS[e] PRE.append(f) POST.append(f) IDXS.append(e) IDX1.append(e) IDX2.append(e) PRE.extend(FLGS) PRE.extend(POST) lenFULL = len(PRE) IDXS.extend(IDX1) IDXS.extend(IDX2) if chk4 is True: # find beginning 1 edge begIdx = None begFlg = False for e in range(0, lenFULL): f = PRE[e] i = IDXS[e] if f == 4: begIdx = e grps[0].append(f) wireIdxs[0].append(i) break # find first 3 edge endIdx = None for e in range(begIdx + 1, lenE + begIdx): f = PRE[e] i = IDXS[e] grps[1].append(f) wireIdxs[1].append(i) else: # find beginning 1 edge begIdx = None begFlg = False for e in range(0, lenFULL): f = PRE[e] if f == 1: if begFlg is False: begFlg = True else: begIdx = e break # find first 3 edge and group all first wire edges endIdx = None for e in range(begIdx, lenE + begIdx): f = PRE[e] i = IDXS[e] if f == 3: grps[0].append(f) wireIdxs[0].append(i) endIdx = e break else: grps[0].append(f) wireIdxs[0].append(i) # Collect remaining edges for e in range(endIdx + 1, lenFULL): f = PRE[e] i = IDXS[e] if f == 1: grps[1].append(f) wireIdxs[1].append(i) break else: wireIdxs[1].append(i) grps[1].append(f) # Efor # Eif if PathLog.getLevel(PathLog.thisModule()) != 4: PathLog.debug('grps[0]: {}'.format(grps[0])) PathLog.debug('grps[1]: {}'.format(grps[1])) PathLog.debug('wireIdxs[0]: {}'.format(wireIdxs[0])) PathLog.debug('wireIdxs[1]: {}'.format(wireIdxs[1])) PathLog.debug('PRE: {}'.format(PRE)) PathLog.debug('IDXS: {}'.format(IDXS)) return (wireIdxs[0], wireIdxs[1])
def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection instead.''' PathLog.track() # Instantiate class variables for operation reference self.endVector = None # pylint: disable=attribute-defined-outside-init self.rotateFlag = False # pylint: disable=attribute-defined-outside-init self.leadIn = 2.0 # pylint: disable=attribute-defined-outside-init self.cloneNames = [] # pylint: disable=attribute-defined-outside-init self.guiMsgs = [] # pylint: disable=attribute-defined-outside-init self.tempObjectNames = [] # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.useTempJobClones('Delete') # Clear temporary group and recreate for temp job clones self.profileEdgesIsOpen = False if obj.EnableRotation != 'Off': # Calculate operation heights based upon rotation radii opHeights = self.opDetermineRotationRadii(obj) (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0] # pylint: disable=attribute-defined-outside-init (self.clrOfset, self.safOfst) = opHeights[1] # pylint: disable=attribute-defined-outside-init # Set clearance and safe heights based upon rotation radii if obj.EnableRotation == 'A(x)': strDep = self.xRotRad elif obj.EnableRotation == 'B(y)': strDep = self.yRotRad else: strDep = max(self.xRotRad, self.yRotRad) finDep = -1 * strDep obj.ClearanceHeight.Value = strDep + self.clrOfset obj.SafeHeight.Value = strDep + self.safOfst #if self.initWithRotation is False: # if obj.FinalDepth.Value == obj.OpFinalDepth.Value: # obj.FinalDepth.Value = finDep # if obj.StartDepth.Value == obj.OpStartDepth.Value: # obj.StartDepth.Value = strDep # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() else: strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value # Set axial feed rates based upon horizontal feed rates safeCircum = 2 * math.pi * obj.SafeHeight.Value self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init # Initiate depthparams and calculate operation heights for rotational operation finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0 self.depthparams = PathUtils.depth_params( # pylint: disable=attribute-defined-outside-init clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=obj.FinalDepth.Value, user_depths=None) # Set start point if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: start = obj.StartPoint else: start = None aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return # Adjust tuples length received from other PathWB tools/operations beside PathPocketShape shapes = [] for shp in aOS: if len(shp) == 2: (fc, iH) = shp # fc, iH, sub, angle, axis, strtDep, finDep tup = fc, iH, 'otherOp', 0.0, 'S', obj.StartDepth.Value, obj.FinalDepth.Value shapes.append(tup) else: shapes.append(shp) if len(shapes) > 1: jobs = [{ 'x': s[0].BoundBox.XMax, 'y': s[0].BoundBox.YMax, 'shape': s } for s in shapes] jobs = PathUtils.sort_jobs(jobs, ['x', 'y']) shapes = [j['shape'] for j in jobs] if self.profileEdgesIsOpen is True: if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: osp = obj.StartPoint self.commandlist.append(Path.Command('G0', {'X': osp.x, 'Y': osp.y, 'F': self.horizRapid})) sims = [] numShapes = len(shapes) for ns in range(0, numShapes): (shape, isHole, sub, angle, axis, strDep, finDep) = shapes[ns] # pylint: disable=unused-variable if ns < numShapes - 1: nextAxis = shapes[ns + 1][4] else: nextAxis = 'L' finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0 self.depthparams = PathUtils.depth_params( # pylint: disable=attribute-defined-outside-init clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=strDep, # obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep, # obj.FinalDepth.Value, user_depths=None) try: if self.profileEdgesIsOpen is True: (pp, sim) = self._buildProfileOpenEdges(obj, shape, isHole, start, getsim) else: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) except Exception as e: # pylint: disable=broad-except FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.") else: if self.profileEdgesIsOpen is True: ppCmds = pp else: ppCmds = pp.Commands if obj.EnableRotation != 'Off' and self.rotateFlag is True: # Rotate model to index for cut if axis == 'X': axisOfRot = 'A' elif axis == 'Y': axisOfRot = 'B' # Reverse angle temporarily to match model. Error in FreeCAD render of B axis rotations if obj.B_AxisErrorOverride is True: angle = -1 * angle elif axis == 'Z': axisOfRot = 'C' else: axisOfRot = 'A' # Rotate Model to correct angle ppCmds.insert(0, Path.Command('G1', {axisOfRot: angle, 'F': self.axialFeed})) ppCmds.insert(0, Path.Command('N100', {})) # Raise cutter to safe depth and return index to starting position ppCmds.append(Path.Command('N200', {})) ppCmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) if axis != nextAxis: ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) # Eif # Save gcode commands to object command list self.commandlist.extend(ppCmds) sims.append(sim) # Eif if self.areaOpRetractTool(obj): self.endVector = None # pylint: disable=attribute-defined-outside-init # Raise cutter to safe height and rotate back to original orientation if self.rotateFlag is True: self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) self.useTempJobClones('Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user for ton in self.tempObjectNames: # remove temporary objects by name FreeCAD.ActiveDocument.removeObject(ton) PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() PathLog.debug("----- areaOpShapes() in PathPocketShape.py") self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False baseSubsTuples = [] allTuples = [] subCount = 0 if obj.Base: PathLog.debug('Processing obj.Base') self.removalshapes = [] # pylint: disable=attribute-defined-outside-init if obj.EnableRotation == 'Off': stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: tup = (base, subList, 0.0, 'X', stock) baseSubsTuples.append(tup) else: PathLog.debug('... Rotation is active') # method call here for p in range(0, len(obj.Base)): (bst, at) = self.process_base_geometry_with_rotation(obj, p, subCount) allTuples.extend(at) baseSubsTuples.extend(bst) for o in baseSubsTuples: self.horiz = [] # pylint: disable=attribute-defined-outside-init self.vert = [] # pylint: disable=attribute-defined-outside-init subBase = o[0] subsList = o[1] angle = o[2] axis = o[3] # stock = o[4] for sub in subsList: if 'Face' in sub: if not self.clasifySub(subBase, sub): PathLog.error(translate('PathPocket', 'Pocket does not support shape %s.%s') % (subBase.Label, sub)) if obj.EnableRotation != 'Off': PathLog.warning(translate('PathPocket', 'Face might not be within rotation accessibility limits.')) # Determine final depth as highest value of bottom boundbox of vertical face, # in case of uneven faces on bottom if len(self.vert) > 0: vFinDep = self.vert[0].BoundBox.ZMin for vFace in self.vert: if vFace.BoundBox.ZMin > vFinDep: vFinDep = vFace.BoundBox.ZMin # Determine if vertical faces for a loop: Extract planar loop wire as new horizontal face. self.vertical = PathGeom.combineConnectedShapes(self.vert) # pylint: disable=attribute-defined-outside-init self.vWires = [TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical] # pylint: disable=attribute-defined-outside-init for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) # face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): msg = translate('PathPocket', 'Vertical faces do not form a loop - ignoring') PathLog.error(msg) else: face.translate(FreeCAD.Vector(0, 0, vFinDep - face.BoundBox.ZMin)) self.horiz.append(face) # add faces for extensions self.exts = [] # pylint: disable=attribute-defined-outside-init for ext in self.getExtensions(obj): wire = ext.getWire() if wire: face = Part.Face(wire) self.horiz.append(face) self.exts.append(face) # check all faces and see if they are touching/overlapping and combine those into a compound self.horizontal = [] # pylint: disable=attribute-defined-outside-init for shape in PathGeom.combineConnectedShapes(self.horiz): shape.sewShape() # shape.tessellate(0.1) shpZMin = shape.BoundBox.ZMin PathLog.debug('PathGeom.combineConnectedShapes shape.BoundBox.ZMin: {}'.format(shape.BoundBox.ZMin)) if obj.UseOutline: wire = TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) wFace = Part.Face(wire) if wFace.BoundBox.ZMin != shpZMin: wFace.translate(FreeCAD.Vector(0, 0, shpZMin - wFace.BoundBox.ZMin)) self.horizontal.append(wFace) PathLog.debug('PathGeom.combineConnectedShapes shape.BoundBox.ZMin: {}'.format(wFace.BoundBox.ZMin)) else: self.horizontal.append(shape) # move all horizontal faces to FinalDepth # extrude all faces up to StartDepth and those are the removal shapes start_dep = obj.StartDepth.Value clrnc = 0.5 # self._addDebugObject('subBase', subBase.Shape) for face in self.horizontal: isFaceUp = True invZ = 0.0 useAngle = angle faceZMin = face.BoundBox.ZMin adj_final_dep = obj.FinalDepth.Value trans = obj.FinalDepth.Value - face.BoundBox.ZMin PathLog.debug('face.BoundBox.ZMin: {}'.format(face.BoundBox.ZMin)) if obj.EnableRotation != 'Off': PathLog.debug('... running isFaceUp()') isFaceUp = self.isFaceUp(subBase, face) # Determine if face is really oriented toward Z+ (rotational purposes) # ignore for cylindrical faces if not isFaceUp: PathLog.debug('... NOT isFaceUp') useAngle += 180.0 invZ = (-2 * face.BoundBox.ZMin) face.translate(FreeCAD.Vector(0.0, 0.0, invZ)) faceZMin = face.BoundBox.ZMin # reset faceZMin PathLog.debug('... face.BoundBox.ZMin: {}'.format(face.BoundBox.ZMin)) else: PathLog.debug('... isFaceUp') if useAngle > 180.0: useAngle -= 360.0 # Apply LimitDepthToFace property for rotational operations if obj.LimitDepthToFace: if obj.FinalDepth.Value < face.BoundBox.ZMin: PathLog.debug('obj.FinalDepth.Value < face.BoundBox.ZMin') # Raise FinalDepth to face depth adj_final_dep = faceZMin # face.BoundBox.ZMin # faceZMin # Ensure StartDepth is above FinalDepth if start_dep <= adj_final_dep: start_dep = adj_final_dep + 1.0 msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to:') PathLog.warning(msg + ' {} mm.'.format(start_dep)) PathLog.debug('LimitDepthToFace adj_final_dep: {}'.format(adj_final_dep)) # Eif face.translate(FreeCAD.Vector(0.0, 0.0, adj_final_dep - faceZMin - clrnc)) zExtVal = start_dep - adj_final_dep + (2 * clrnc) extShp = face.removeSplitter().extrude(FreeCAD.Vector(0, 0, zExtVal)) self.removalshapes.append((extShp, False, 'pathPocketShape', useAngle, axis, start_dep, adj_final_dep)) PathLog.debug("Extent values are strDep: {}, finDep: {}, extrd: {}".format(start_dep, adj_final_dep, zExtVal)) # Efor face # Efor else: # process the job base object as a whole PathLog.debug(translate("Path", 'Processing model as a whole ...')) finDep = obj.FinalDepth.Value strDep = obj.StartDepth.Value self.outlines = [Part.Face(TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model] # pylint: disable=attribute-defined-outside-init stockBB = self.stock.Shape.BoundBox self.removalshapes = [] # pylint: disable=attribute-defined-outside-init self.bodies = [] # pylint: disable=attribute-defined-outside-init for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append((self.stock.Shape.cut(body), False, 'pathPocketShape', 0.0, 'X', strDep, finDep)) for (shape, hole, sub, angle, axis, strDep, finDep) in self.removalshapes: # pylint: disable=unused-variable shape.tessellate(0.05) # originally 0.1 if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
import FreeCAD import FreeCADGui import math import Part import Path import PathScripts.PathDressup as PathDressup import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils from PathScripts.PathGeom import PathGeom from PySide import QtCore, QtGui """Dogbone Dressup object and FreeCAD command""" LOG_MODULE = PathLog.thisModule() if False: PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] movestraight = ['G1', 'G01'] movecw = ['G2', 'G02'] moveccw = ['G3', 'G03'] movearc = movecw + moveccw
import FreeCAD import FreeCADGui import Path import PathScripts.PathJob as PathJob import PathScripts.PathLog as PathLog import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import os from PathScripts.PathPostProcessor import PostProcessor from PySide import QtCore, QtGui from datetime import datetime LOG_MODULE = PathLog.thisModule() PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class _TempObject: # pylint: disable=no-init Path = None Name = "Fixture" InList = [] Label = "Fixture"
# * * # *************************************************************************** import FreeCAD import FreeCADGui import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathHelix as PathHelix import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui from PySide import QtCore, QtGui __doc__ = "Helix operation page controller and command implementation." if True: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule()) class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): '''Page controller class for Helix operations.''' def getForm(self): '''getForm() ... return UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpHelixEdit.ui") def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' PathLog.track()
def opExecute(self, obj): '''opExecute(obj) ... processes all Base features and Locations and collects them in a list of positions and radii which is then passed to circularHoleExecute(obj, holes). If no Base geometries and no Locations are present, the job's Base is inspected and all drillable features are added to Base. In this case appropriate values for depths are also calculated and assigned. Do not overwrite, implement circularHoleExecute(obj, holes) instead.''' PathLog.track() holes = [] baseSubsTuples = [] subCount = 0 allTuples = [] self.cloneNames = [] # pylint: disable=attribute-defined-outside-init self.guiMsgs = [] # pylint: disable=attribute-defined-outside-init self.rotateFlag = False # pylint: disable=attribute-defined-outside-init self.useTempJobClones('Delete') # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.clearHeight = obj.ClearanceHeight.Value # pylint: disable=attribute-defined-outside-init self.safeHeight = obj.SafeHeight.Value # pylint: disable=attribute-defined-outside-init self.axialFeed = 0.0 # pylint: disable=attribute-defined-outside-init self.axialRapid = 0.0 # pylint: disable=attribute-defined-outside-init def haveLocations(self, obj): if PathOp.FeatureLocations & self.opFeatures(obj): return len(obj.Locations) != 0 return False if obj.EnableRotation == 'Off': strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value else: # Calculate operation heights based upon rotation radii opHeights = self.opDetermineRotationRadii(obj) (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0] # pylint: disable=attribute-defined-outside-init (clrOfset, safOfst) = opHeights[1] PathLog.debug("Exec. opHeights[0]: " + str(opHeights[0])) PathLog.debug("Exec. opHeights[1]: " + str(opHeights[1])) # Set clearance and safe heights based upon rotation radii if obj.EnableRotation == 'A(x)': strDep = self.xRotRad elif obj.EnableRotation == 'B(y)': strDep = self.yRotRad else: strDep = max(self.xRotRad, self.yRotRad) finDep = -1 * strDep obj.ClearanceHeight.Value = strDep + clrOfset obj.SafeHeight.Value = strDep + safOfst # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() # Set axial feed rates based upon horizontal feed rates safeCircum = 2 * math.pi * obj.SafeHeight.Value self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init # Complete rotational analysis and temp clone creation as needed if obj.EnableRotation == 'Off': PathLog.debug("Enable Rotation setting is 'Off' for {}.".format( obj.Name)) stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: baseSubsTuples.append((base, subList, 0.0, 'A', stock)) else: for p in range(0, len(obj.Base)): (bst, at) = self.process_base_geometry_with_rotation( obj, p, subCount) allTuples.extend(at) baseSubsTuples.extend(bst) for base, subs, angle, axis, stock in baseSubsTuples: # rotate shorter angle in opposite direction if angle > 180: angle -= 360 elif angle < -180: angle += 360 # Re-analyze rotated model for drillable holes if obj.EnableRotation != 'Off': rotated_features = self.findHoles(obj, base) for sub in subs: PathLog.debug('sub, angle, axis: {}, {}, {}'.format( sub, angle, axis)) if self.isHoleEnabled(obj, base, sub): pos = self.holePosition(obj, base, sub) if pos: # Identify face to which edge belongs sub_shape = base.Shape.getElement(sub) # Default is to treat selection as 'Face' shape holeBtm = sub_shape.BoundBox.ZMin if obj.EnableRotation != 'Off': # Update Start and Final depths due to rotation, if auto defaults are active parent_face = self._find_parent_face_of_edge( rotated_features, sub_shape) if parent_face: PathLog.debug('parent_face found') holeBtm = parent_face.BoundBox.ZMin if obj.OpStartDepth == obj.StartDepth: obj.StartDepth.Value = parent_face.BoundBox.ZMax PathLog.debug('new StartDepth: {}'.format( obj.StartDepth.Value)) if obj.OpFinalDepth == obj.FinalDepth: obj.FinalDepth.Value = holeBtm PathLog.debug( 'new FinalDepth: {}'.format(holeBtm)) else: PathLog.debug('NO parent_face identified') if base.Shape.getElement(sub).ShapeType == 'Edge': msg = translate( "Path", "Verify Final Depth of holes based on edges. {} depth is: {} mm" .format(sub, round(holeBtm, 4))) + " " msg += translate( "Path", "Always select the bottom edge of the hole when using an edge." ) PathLog.warning(msg) # Warn user if Final Depth set lower than bottom of hole if obj.FinalDepth.Value < holeBtm: msg = translate( "Path", "Final Depth setting is below the hole bottom for {}." .format(sub)) + ' ' msg += translate( "Path", "{} depth is calculated at {} mm".format( sub, round(holeBtm, 4))) PathLog.warning(msg) holes.append({ 'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub), 'angle': angle, 'axis': axis, 'trgtDep': obj.FinalDepth.Value, 'stkTop': stock.Shape.BoundBox.ZMax }) # haveLocations are populated from user-provided (x, y) coordinates # provided by the user in the Base Locations tab of the Task Editor window if haveLocations(self, obj): for location in obj.Locations: # holes.append({'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'holeBtm': obj.FinalDepth.Value}) holes.append({ 'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'trgtDep': obj.FinalDepth.Value, 'stkTop': PathUtils.findParentJob(obj).Stock.Shape.BoundBox.ZMax }) if len(holes) > 0: self.circularHoleExecute( obj, holes) # circularHoleExecute() located in PathDrilling.py self.useTempJobClones( 'Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user PathLog.debug("obj.Name: " + str(obj.Name))