Beispiel #1
0
 def onDelete(self, obj, arg2=None):
     '''Called by the view provider, there doesn't seem to be a callback on the obj itself.'''
     PathLog.track(obj.Label, arg2)
     doc = obj.Document
     # the first to tear down are the ops, they depend on other resources
     PathLog.debug('taking down ops: %s' % [o.Name for o in self.allOperations()])
     while obj.Operations.Group:
         op = obj.Operations.Group[0]
         if not op.ViewObject or not hasattr(op.ViewObject.Proxy, 'onDelete') or op.ViewObject.Proxy.onDelete(op.ViewObject, ()):
             doc.removeObject(op.Name)
     obj.Operations.Group = []
     doc.removeObject(obj.Operations.Name)
     obj.Operations = None
     # stock could depend on Base
     if obj.Stock:
         PathLog.debug('taking down stock')
         doc.removeObject(obj.Stock.Name)
         obj.Stock = None
     # base doesn't depend on anything inside job
     if obj.Base:
         PathLog.debug('taking down base')
         doc.removeObject(obj.Base.Name)
         obj.Base = None
     # Tool controllers don't depend on anything
     PathLog.debug('taking down tool controller')
     for tc in obj.ToolController:
         doc.removeObject(tc.Name)
     obj.ToolController = []
    def setup(self, obj, initial):
        PathLog.info("Here we go ... ")
        if initial:
            if hasattr(obj.Base, "BoneBlacklist"):
                # dressing up a bone dressup
                obj.Side = obj.Base.Side
            else:
                # otherwise dogbones are opposite of the base path's side
                side = Side.Right
                if hasattr(obj.Base, 'Side') and obj.Base.Side == 'Inside':
                    side = Side.Left
                if hasattr(obj.Base, 'Directin') and obj.Base.Direction == 'CCW':
                    side = Side.oppositeOf(side)
                obj.Side = side

        self.toolRadius = 5
        tc = PathDressup.toolController(obj.Base)
        if tc is None or tc.ToolNumber == 0:
            self.toolRadius = 5
        else:
            tool = tc.Proxy.getTool(tc)  # PathUtils.getTool(obj, tc.ToolNumber)
            if not tool or tool.Diameter == 0:
                self.toolRadius = 5
            else:
                self.toolRadius = tool.Diameter / 2

        self.shapes = {}
        self.dbg = []
Beispiel #3
0
 def deleteObjectsOnReject(self):
     '''deleteObjectsOnReject() ... return true if all objects should
     be created if the user hits cancel. This is used during the initial
     edit session, if the user does not press OK, it is assumed they've
     changed their mind about creating the operation.'''
     PathLog.track()
     return hasattr(self, 'deleteOnReject') and self.deleteOnReject
Beispiel #4
0
 def assignTemplate(self, obj, template):
     '''assignTemplate(obj, template) ... extract the properties from the given template file and assign to receiver.
     This will also create any TCs stored in the template.'''
     tcs = []
     if template:
         tree = xml.parse(template)
         for job in tree.getroot().iter(JobTemplate.Job):
             if job.get(JobTemplate.GeometryTolerance):
                 obj.GeometryTolerance = float(job.get(JobTemplate.GeometryTolerance))
             if job.get(JobTemplate.PostProcessor):
                 obj.PostProcessor = job.get(JobTemplate.PostProcessor)
                 if job.get(JobTemplate.PostProcessorArgs):
                     obj.PostProcessorArgs = job.get(JobTemplate.PostProcessorArgs)
                 else:
                     obj.PostProcessorArgs = ''
             if job.get(JobTemplate.PostProcessorOutputFile):
                 obj.PostProcessorOutputFile = job.get(JobTemplate.PostProcessorOutputFile)
             if job.get(JobTemplate.Description):
                 obj.Description = job.get(JobTemplate.Description)
         for tc in tree.getroot().iter(JobTemplate.ToolController):
             tcs.append(PathToolController.FromTemplate(tc))
         for stock in tree.getroot().iter(JobTemplate.Stock):
             obj.Stock = PathStock.CreateFromTemplate(self, stock)
     else:
         tcs.append(PathToolController.Create())
     PathLog.debug("setting tool controllers (%d)" % len(tcs))
     obj.ToolController = tcs
    def inOutBoneCommands(self, bone, boneAngle, fixedLength):
        corner = bone.corner(self.toolRadius)

        bone.tip = bone.inChord.End  # in case there is no bone

        PathLog.debug("corner = (%.2f, %.2f)" % (corner.x, corner.y))
        # debugMarker(corner, 'corner', (1., 0., 1.), self.toolRadius)

        length = fixedLength
        if bone.obj.Incision == Incision.Custom:
            length = bone.obj.Custom
        if bone.obj.Incision == Incision.Adaptive:
            length = bone.adaptiveLength(boneAngle, self.toolRadius)

        if length == 0:
            PathLog.info("no bone after all ..")
            return [bone.lastCommand, bone.outChord.g1Command(bone.F)]

        boneInChord = bone.inChord.move(length, boneAngle)
        boneOutChord = boneInChord.moveTo(bone.outChord.Start)

        # debugCircle(boneInChord.Start, self.toolRadius, 'boneStart')
        # debugCircle(boneInChord.End, self.toolRadius, 'boneEnd')

        bone.tip = boneInChord.End

        if bone.smooth == 0:
            return [bone.lastCommand, boneInChord.g1Command(bone.F), boneOutChord.g1Command(bone.F), bone.outChord.g1Command(bone.F)]

        # reconstruct the corner and convert to an edge
        offset = corner - bone.inChord.End
        iChord = Chord(bone.inChord.Start + offset, bone.inChord.End + offset)
        oChord = Chord(bone.outChord.Start + offset, bone.outChord.End + offset)
        iLine = iChord.asLine()
        oLine = oChord.asLine()
        cornerShape = Part.Shape([iLine, oLine])

        # construct a shape representing the cut made by the bone
        vt0 = FreeCAD.Vector(0, self.toolRadius, 0)
        vt1 = FreeCAD.Vector(length,  self.toolRadius, 0)
        vb0 = FreeCAD.Vector(0, -self.toolRadius, 0)
        vb1 = FreeCAD.Vector(length, -self.toolRadius, 0)
        vm2 = FreeCAD.Vector(length + self.toolRadius, 0, 0)

        boneBot = Part.LineSegment(vb1, vb0)
        boneLid = Part.LineSegment(vb0, vt0)
        boneTop = Part.LineSegment(vt0, vt1)

        # what we actually want is an Arc - but findIntersect only returns the coincident if one exists
        # which really sucks because that's the one we're probably not interested in ....
        boneArc = Part.Arc(vt1, vm2, vb1)
        # boneArc = Part.Circle(FreeCAD.Vector(length, 0, 0), FreeCAD.Vector(0,0,1), self.toolRadius)
        boneWire = Part.Shape([boneTop, boneArc, boneBot, boneLid])
        boneWire.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), boneAngle * 180 / math.pi)
        boneWire.translate(bone.inChord.End)
        self.boneShapes = [cornerShape, boneWire]

        bone.inCommands = self.smoothChordCommands(bone, bone.inChord, boneInChord, Part.Edge(iLine), boneWire, corner, bone.smooth & Smooth.In,  (1., 0., 0.))
        bone.outCommands = self.smoothChordCommands(bone, boneOutChord, bone.outChord, Part.Edge(oLine), boneWire, corner, bone.smooth & Smooth.Out, (0., 1., 0.))
        return bone.inCommands + bone.outCommands
Beispiel #6
0
 def selectionSupportedAsBaseGeometry(self, selection, ignoreErrors):
     if len(selection) != 1:
         if not ignoreErrors:
             PathLog.error(translate("PathProject", "Please select %s from a single solid" % self.featureName()))
         return False
     sel = selection[0]
     if sel.HasSubObjects:
         if not self.supportsVertexes() and selection[0].SubObjects[0].ShapeType == "Vertex":
             if not ignoreErrors:
                 PathLog.error(translate("PathProject", "Vertexes are not supported"))
             return False
         if not self.supportsEdges() and selection[0].SubObjects[0].ShapeType == "Edge":
             if not ignoreErrors:
                 PathLog.error(translate("PathProject", "Edges are not supported"))
             return False
         if not self.supportsFaces() and selection[0].SubObjects[0].ShapeType == "Face":
             if not ignoreErrors:
                 PathLog.error(translate("PathProject", "Faces are not supported"))
             return False
     else:
         if not self.supportsPanels() or not 'Panel' in sel.Object.Name:
             if not ignoreErrors:
                 PathLog.error(translate("PathProject", "Please select %s of a solid" % self.featureName()))
             return False
     return True
Beispiel #7
0
 def deleteBase(self):
     PathLog.track()
     selected = self.form.baseList.selectedItems()
     for item in selected:
         self.form.baseList.takeItem(self.form.baseList.row(item))
         self.setDirty()
     self.updateBase()
Beispiel #8
0
 def rotateSel(sel, n):
     p = sel.Object.Placement
     loc = sel.Object.Placement.Base
     r = axis.cross(n) # rotation axis
     a = DraftVecUtils.angle(n, axis, r) * 180 / math.pi
     PathLog.debug("oh boy: (%.2f, %.2f, %.2f) -> %.2f" % (r.x, r.y, r.z, a))
     Draft.rotate(sel.Object, a, axis=r)
Beispiel #9
0
 def setupContextMenu(self, vobj, menu):
     PathLog.track()
     for action in menu.actions():
         menu.removeAction(action)
     action = QtGui.QAction(translate('Path', 'Edit'), menu)
     action.triggered.connect(self.setEdit)
     menu.addAction(action)
Beispiel #10
0
 def updateData(self, obj, prop):
     PathLog.track(obj.Label, prop)
     # make sure the resource view providers are setup properly
     if prop == 'Base' and self.obj.Base and self.obj.Base.ViewObject and self.obj.Base.ViewObject.Proxy:
         self.obj.Base.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor)
     if prop == 'Stock' and self.obj.Stock and self.obj.Stock.ViewObject and self.obj.Stock.ViewObject.Proxy:
         self.obj.Stock.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor)
Beispiel #11
0
 def cleanup(self, resetEdit):
     PathLog.track()
     self.vobj.Proxy.resetTaskPanel()
     FreeCADGui.Control.closeDialog()
     if resetEdit:
         FreeCADGui.ActiveDocument.resetEdit()
     FreeCAD.ActiveDocument.recompute()
def RegisterViewProvider(name, provider):
    '''RegisterViewProvider(name, provider) ... if an IconViewProvider is created for an object with the given name
    an instance of provider is used instead.'''

    PathLog.track(name)
    global _factory
    _factory[name] = provider
 def setupContextMenu(self, vobj, menu):
     PathLog.track()
     from PySide import QtCore, QtGui
     edit = QtCore.QCoreApplication.translate('Path', 'Edit', None)
     action = QtGui.QAction(edit, menu)
     action.triggered.connect(self.setEdit)
     menu.addAction(action)
Beispiel #14
0
def addToJob(obj, jobname=None):
    '''adds a path object to a job
    obj = obj
    jobname = None'''
    PathLog.track(jobname)
    if jobname is not None:
        jobs = GetJobs(jobname)
        if len(jobs) == 1:
            job = jobs[0]
        else:
            PathLog.error(translate("Path", "Didn't find job %s") % jobname)
            return None
    else:
        jobs = GetJobs()
        if len(jobs) == 0:
            job = PathJobCmd.CommandJobCreate().Activated()
        elif len(jobs) == 1:
            job = jobs[0]
        else:
            # form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/DlgJobChooser.ui")
            form = FreeCADGui.PySideUic.loadUi(":/panels/DlgJobChooser.ui")
            mylist = [i.Label for i in jobs]
            form.cboProject.addItems(mylist)
            r = form.exec_()
            if r is False:
                return None
            else:
                print(form.cboProject.currentText())
                job = [i for i in jobs if i.Label == form.cboProject.currentText()][0]

    if obj and job:
        job.Proxy.addOperation(obj)
    return job
Beispiel #15
0
def cleanedges(splines, precision):
    '''cleanedges([splines],precision). Convert BSpline curves, Beziers, to arcs that can be used for cnc paths.
    Returns Lines as is. Filters Circle and Arcs for over 180 degrees. Discretizes Ellipses. Ignores other geometry. '''
    PathLog.track()
    edges = []
    for spline in splines:
        if geomType(spline) == "BSplineCurve":
            arcs = spline.Curve.toBiArcs(precision)
            for i in arcs:
                edges.append(Part.Edge(i))

        elif geomType(spline) == "BezierCurve":
            newspline = spline.Curve.toBSpline()
            arcs = newspline.toBiArcs(precision)
            for i in arcs:
                edges.append(Part.Edge(i))

        elif geomType(spline) == "Ellipse":
            edges = curvetowire(spline, 1.0)  # fixme hardcoded value

        elif geomType(spline) == "Circle":
            arcs = filterArcs(spline)
            for i in arcs:
                edges.append(Part.Edge(i))

        elif geomType(spline) == "Line":
            edges.append(spline)

        elif geomType(spline) == "LineSegment":
            edges.append(spline)

        else:
            pass

    return edges
Beispiel #16
0
    def setup(self, obj):
        PathLog.info("Here we go ... ")
        if hasattr(obj.Base, "BoneBlacklist"):
            # dressing up a bone dressup
            obj.Side = obj.Base.Side
        else:
            # otherwise dogbones are opposite of the base path's side
            if obj.Base.Side == Side.Left:
                obj.Side = Side.Right
            elif obj.Base.Side == Side.Right:
                obj.Side = Side.Left
            else:
                # This will cause an error, which is fine for now 'cause I don't know what to do here
                obj.Side = 'On'

        self.toolRadius = 5
        toolLoad = obj.ToolController
        if toolLoad is None or toolLoad.ToolNumber == 0:
            self.toolRadius = 5
        else:
            tool = toolLoad.Proxy.getTool(toolLoad) #PathUtils.getTool(obj, toolLoad.ToolNumber)
            if not tool or tool.Diameter == 0:
                self.toolRadius = 5
            else:
                self.toolRadius = tool.Diameter / 2

        self.shapes = {}
        self.dbg = []
Beispiel #17
0
 def setFromTemplate(self, obj, template):
     '''setFromTemplate(obj, xmlItem) ... extract properties from xmlItem and assign to receiver.'''
     PathLog.track(obj.Name, template)
     if template.get(ToolControllerTemplate.Version) and 1 == int(template.get(ToolControllerTemplate.Version)):
         if template.get(ToolControllerTemplate.Label):
             obj.Label = template.get(ToolControllerTemplate.Label)
         if template.get(ToolControllerTemplate.VertFeed):
             obj.VertFeed = template.get(ToolControllerTemplate.VertFeed)
         if template.get(ToolControllerTemplate.HorizFeed):
             obj.HorizFeed = template.get(ToolControllerTemplate.HorizFeed)
         if template.get(ToolControllerTemplate.VertRapid):
             obj.VertRapid = template.get(ToolControllerTemplate.VertRapid)
         if template.get(ToolControllerTemplate.HorizRapid):
             obj.HorizRapid = template.get(ToolControllerTemplate.HorizRapid)
         if template.get(ToolControllerTemplate.SpindleSpeed):
             obj.SpindleSpeed = float(template.get(ToolControllerTemplate.SpindleSpeed))
         if template.get(ToolControllerTemplate.SpindleDir):
             obj.SpindleDir = template.get(ToolControllerTemplate.SpindleDir)
         if template.get(ToolControllerTemplate.ToolNumber):
             obj.ToolNumber = int(template.get(ToolControllerTemplate.ToolNumber))
         if template.get(ToolControllerTemplate.Tool):
             obj.Tool.setFromTemplate(template.get(ToolControllerTemplate.Tool))
         if template.get(ToolControllerTemplate.Expressions):
             for exprDef in template.get(ToolControllerTemplate.Expressions):
                 obj.setExpression(exprDef[ToolControllerTemplate.ExprProp], exprDef[ToolControllerTemplate.ExprExpr])
     else:
         PathLog.error(translate('PathToolController', "Unsupported PathToolController template version %s") % template.get(ToolControllerTemplate.Version))
Beispiel #18
0
def isHorizontal(obj):
    '''isHorizontal(obj) ... answer True if obj points into X or Y'''
    if type(obj) == FreeCAD.Vector:
        return isRoughly(obj.z, 0)

    if obj.ShapeType == 'Face':
        if type(obj.Surface) == Part.Plane:
            return isVertical(obj.Surface.Axis)
        if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone:
            return isHorizontal(obj.Surface.Axis)
        if type(obj.Surface) == Part.Sphere:
            return True
        if type(obj.Surface) == Part.SurfaceOfExtrusion:
            return isHorizontal(obj.Surface.Direction)
        if type(obj.Surface) == Part.SurfaceOfRevolution:
            return isVertical(obj.Surface.Direction)
        return isRoughly(obj.BoundBox.ZLength, 0.0)

    if obj.ShapeType == 'Edge':
        if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment:
            return isHorizontal(obj.Vertexes[1].Point - obj.Vertexes[0].Point)
        if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve:
            return isVertical(obj.Curve.Axis)
        return isRoughly(obj.BoundBox.ZLength, 0.0)

    PathLog.error(translate('PathGeom', "isHorizontal(%s) not supported") % obj)
    return None
Beispiel #19
0
    def setupTemplate(self):
        templateFiles = []
        for path in PathPreferences.searchPaths():
            templateFiles.extend(self.templateFilesIn(path))

        template = {}
        for tFile in templateFiles:
            name = os.path.split(os.path.splitext(tFile)[0])[1][4:]
            if name in template:
                basename = name
                i = 0
                while name in template:
                    i = i + 1
                    name = basename + " (%s)" % i
            PathLog.track(name, tFile)
            template[name] = tFile
        selectTemplate = PathPreferences.defaultJobTemplate()
        index = 0
        self.dialog.jobTemplate.addItem('<none>', '')
        for name in sorted(template.keys()):
            if template[name] == selectTemplate:
                index = self.dialog.jobTemplate.count()
            self.dialog.jobTemplate.addItem(name, template[name])
        self.dialog.jobTemplate.setCurrentIndex(index)
        self.dialog.templateGroup.show()
    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()

        def haveLocations(self, obj):
            if PathOp.FeatureLocations & self.opFeatures(obj):
                return len(obj.Locations) != 0
            return False

        holes = []

        for base, subs in obj.Base:
            for sub in subs:
                if self.isHoleEnabled(obj, base, sub):
                    pos = self.holePosition(obj, base, sub)
                    if pos:
                        holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub)})
        if haveLocations(self, obj):
            for location in obj.Locations:
                holes.append({'x': location.x, 'y': location.y, 'r': 0})

        if len(holes) > 0:
            self.circularHoleExecute(obj, holes)
    def areaOpShapes(self, obj):
        '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.'''
        PathLog.track()

        if obj.UseComp:
            self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")"))
        else:
            self.commandlist.append(Path.Command("(Uncompensated Tool Path)"))

        shapes = []
        if obj.Base:
            basewires = []

            for b in obj.Base:
                edgelist = []
                for sub in b[1]:
                    edgelist.append(getattr(b[0].Shape, sub))
                basewires.append((b[0], findWires(edgelist)))

            for base,wires in basewires:
                for wire in wires:
                    f = Part.makeFace(wire, 'Part::FaceMakerSimple')

                    # shift the compound to the bottom of the base object for
                    # proper sectioning
                    zShift = b[0].Shape.BoundBox.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))
        return shapes
Beispiel #22
0
    def areaOpOnChanged(self, obj, prop):
        '''areaOpOnChanged(obj, prop) ... facing specific depths calculation.'''
        PathLog.track(prop)
        if prop == "StepOver" and obj.StepOver == 0:
            obj.StepOver = 1

        # default depths calculation not correct for facing
        if prop == "Base":
            job = PathUtils.findParentJob(obj)
            obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax

            if len(obj.Base) >= 1:
                print('processing')
                sublist = []
                for i in obj.Base:
                    o = i[0]
                    for s in i[1]:
                        sublist.append(o.Shape.getElement(s))

            # If the operation has a geometry identified the Finaldepth
            # is the top of the bboundbox which includes all features.
            # Otherwise, top of part.

                obj.OpFinalDepth = Part.makeCompound(sublist).BoundBox.ZMax
            else:
                obj.OpFinalDepth = job.Base.Shape.BoundBox.ZMax
 def commandsForEdges(self):
     global failures
     if self.edges:
         try:
             shape = self.shell().common(self.tag.solid)
             commands = []
             rapid = None
             for e, flip in self.orderAndFlipEdges(self.cleanupEdges(shape.Edges)):
                 debugEdge(e, '++++++++ %s' % ('<' if flip else '>'), False)
                 p1 = e.valueAt(e.FirstParameter)
                 p2 = e.valueAt(e.LastParameter)
                 if self.tag.isSquare and (PathGeom.isRoughly(p1.z, self.maxZ) or p1.z > self.maxZ) and (PathGeom.isRoughly(p2.z, self.maxZ) or p2.z > self.maxZ):
                     rapid = p1 if flip else p2
                 else:
                     if rapid:
                         commands.append(Path.Command('G0', {'X': rapid.x, 'Y': rapid.y, 'Z': rapid.z}))
                         rapid = None
                     commands.extend(PathGeom.cmdsForEdge(e, False, False, self.segm))
             if rapid:
                 commands.append(Path.Command('G0', {'X': rapid.x, 'Y': rapid.y, 'Z': rapid.z}))
                 rapid = None
             return commands
         except Exception as e:
             PathLog.error("Exception during processing tag @(%.2f, %.2f) (%s) - disabling the tag" % (self.tag.x, self.tag.y, e.args[0]))
             #traceback.print_exc(e)
             self.tag.enabled = False
             commands = []
             for e in self.edges:
                 commands.extend(PathGeom.cmdsForEdge(e))
             failures.append(self)
             return commands
     return []
 def __init__(self, edge, tag, i, segm, maxZ):
     debugEdge(edge, 'MapWireToTag(%.2f, %.2f, %.2f)' % (i.x, i.y, i.z))
     self.tag = tag
     self.segm = segm
     self.maxZ = maxZ
     if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), i):
         tail = edge
         self.commands = []
         debugEdge(tail, '.........=')
     elif PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), i):
         debugEdge(edge, '++++++++ .')
         self.commands = PathGeom.cmdsForEdge(edge, segm=segm)
         tail = None
     else:
         e, tail = PathGeom.splitEdgeAt(edge, i)
         debugEdge(e, '++++++++ .')
         self.commands = PathGeom.cmdsForEdge(e, segm=segm)
         debugEdge(tail, '.........-')
         self.initialEdge = edge
     self.tail = tail
     self.edges = []
     self.entry = i
     if tail:
         PathLog.debug("MapWireToTag(%s - %s)" % (i, tail.valueAt(tail.FirstParameter)))
     else:
         PathLog.debug("MapWireToTag(%s - )" % i)
     self.complete = False
     self.haveProblem = False
    def orderAndFlipEdges(self, inputEdges):
        PathLog.track("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z))
        self.edgesOrder = []
        outputEdges = []
        p0 = self.entry
        lastP = p0
        edges = copy.copy(inputEdges)
        while edges:
            # print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z))
            for e in copy.copy(edges):
                p1 = e.valueAt(e.FirstParameter)
                p2 = e.valueAt(e.LastParameter)
                if PathGeom.pointsCoincide(p1, p0):
                    outputEdges.append((e, False))
                    edges.remove(e)
                    lastP = None
                    p0 = p2
                    debugEdge(e, ">>>>> no flip")
                    break
                elif PathGeom.pointsCoincide(p2, p0):
                    flipped = PathGeom.flipEdge(e)
                    if not flipped is None:
                        outputEdges.append((flipped, True))
                    else:
                        p0 = None
                        cnt = 0
                        for p in reversed(e.discretize(Deflection=0.01)):
                            if not p0 is None:
                                outputEdges.append((Part.Edge(Part.LineSegment(p0, p)), True))
                                cnt = cnt + 1
                            p0 = p
                        PathLog.info("replaced edge with %d straight segments" % cnt)
                    edges.remove(e)
                    lastP = None
                    p0 = p1
                    debugEdge(e, ">>>>> flip")
                    break
                else:
                    debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z))

            if lastP == p0:
                self.edgesOrder.append(outputEdges)
                self.edgesOrder.append(edges)
                print('input edges:')
                for e in inputEdges:
                    debugEdge(e, '  ', False)
                print('ordered edges:')
                for e, flip in outputEdges:
                    debugEdge(e, '  %c ' % ('<' if flip else '>'), False)
                print('remaining edges:')
                for e in edges:
                    debugEdge(e, '    ', False)
                raise ValueError("No connection to %s" % (p0))
            elif lastP:
                PathLog.debug("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z))
            else:
                PathLog.debug("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z))
            lastP = p0
        PathLog.track("-")
        return outputEdges
Beispiel #26
0
 def opSetDefaultValues(self, obj, job):
     PathLog.track(obj.Label, job.Label)
     obj.Width = '1 mm'
     obj.ExtraDepth = '0.1 mm'
     obj.Join = 'Round'
     obj.setExpression('StepDown', '0 mm')
     obj.StepDown = '0 mm'
Beispiel #27
0
 def initOperation(self, obj):
     PathLog.track(obj.Label)
     obj.addProperty('App::PropertyDistance',    'Width',      'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The desired width of the chamfer'))
     obj.addProperty('App::PropertyDistance',    'ExtraDepth', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The additional depth of the tool path'))
     obj.addProperty('App::PropertyEnumeration', 'Join',       'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'How to join chamfer segments'))
     obj.Join = ['Round', 'Miter']
     obj.setEditorMode('Join', 2) # hide for now
 def __init__(self, obj):
     PathLog.track(obj.Base.Name)
     self.obj = obj
     self.wire, rapid = PathGeom.wireForPath(obj.Base.Path)
     self.rapid = _RapidEdges(rapid)
     self.edges = self.wire.Edges
     self.baseWire = self.findBottomWire(self.edges)
Beispiel #29
0
def adjustWirePlacement(obj, shape, wires):
    job = PathUtils.findParentJob(obj)
    if hasattr(shape, 'MapMode') and 'Deactivated' != shape.MapMode:
        if hasattr(shape, 'Support') and 1 == len(shape.Support) and 1 == len(shape.Support[0][1]):
            pmntShape   = shape.Placement
            pmntSupport = shape.Support[0][0].getGlobalPlacement()
            #pmntSupport = shape.Support[0][0].Placement
            pmntBase    = job.Base.Placement
            pmnt = pmntBase.multiply(pmntSupport.inverse().multiply(pmntShape))
            #PathLog.debug("pmnt = %s" % pmnt)
            newWires = []
            for w in wires:
                edges = []
                for e in w.Edges:
                    e = e.copy()
                    e.Placement = FreeCAD.Placement()
                    edges.append(e)
                w = Part.Wire(edges)
                w.Placement = pmnt
                newWires.append(w)
            wires = newWires
        else:
            PathLog.warning(translate("PathEngrave", "Attachment not supported by engraver"))
    else:
        PathLog.debug("MapMode: %s" % (shape.MapMode if hasattr(shape, 'MapMode') else 'None')) 
    return wires
Beispiel #30
0
    def areaOpShapes(self, obj):
        '''areaOpShapes(obj) ... return top face'''
        # Facing is done either against base objects
        if obj.Base:
            PathLog.debug("obj.Base: {}".format(obj.Base))
            faces = []
            for b in obj.Base:
                for sub in b[1]:
                    shape = getattr(b[0].Shape, sub)
                    if isinstance(shape, Part.Face):
                        faces.append(shape)
                    else:
                        PathLog.debug('The base subobject is not a face')
                        return
            planeshape = Part.makeCompound(faces)
            PathLog.debug("Working on a collection of faces {}".format(faces))

        # If no base object, do planing of top surface of entire model
        else:
            planeshape = self.baseobject.Shape
            PathLog.debug("Working on a shape {}".format(self.baseobject.Name))

        # Find the correct shape depending on Boundary shape.
        PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape))
        bb = planeshape.BoundBox
        if obj.BoundaryShape == 'Boundbox':
            bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1))
            env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams)
        elif obj.BoundaryShape == 'Stock':
            stock = PathUtils.findParentJob(obj).Stock.Shape
            env = stock
        else:
            env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams)

        return [(env, False)]
Beispiel #31
0
import PathScripts.PathUtils as PathUtils

# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader

PathGeom = LazyLoader("PathScripts.PathGeom", globals(),
                      "PathScripts.PathGeom")

__title__ = "Path 3D Pocket Operation"
__author__ = "Yorik van Havre <*****@*****.**>"
__url__ = "https://www.freecadweb.org"
__doc__ = "Class and implementation of the 3D Pocket operation."
__created__ = "2014"

if False:
    PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
    PathLog.trackModule(PathLog.thisModule())
else:
    PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())

translate = FreeCAD.Qt.translate


class ObjectPocket(PathPocketBase.ObjectPocket):
    """Proxy object for Pocket operation."""
    def pocketOpFeatures(self, obj):
        return PathOp.FeatureNoFinalDepth

    def initPocketOp(self, obj):
        """initPocketOp(obj) ... setup receiver"""
        if not hasattr(obj, "HandleMultipleFeatures"):
    def execute(self):
        if not self.baseOp or not self.baseOp.isDerivedFrom('Path::Feature') or not self.baseOp.Path:
            return None

        if len(self.baseOp.Path.Commands) == 0:
            PathLog.warning("No Path Commands for %s" % self.baseOp.Label)
            return []

        tc = PathDressup.toolController(self.baseOp)

        self.safeHeight = float(PathUtil.opProperty(self.baseOp, 'SafeHeight'))
        self.clearanceHeight = float(PathUtil.opProperty(self.baseOp, 'ClearanceHeight'))
        self.strG1ZsafeHeight = Path.Command('G1', {'Z': self.safeHeight, 'F': tc.VertFeed.Value})
        self.strG0ZclearanceHeight = Path.Command('G0', {'Z': self.clearanceHeight})

        cmd = self.baseOp.Path.Commands[0]
        pos = cmd.Placement.Base # bogus m/c position to create first edge
        bogusX = True
        bogusY = True
        commands = [cmd]
        lastExit = None
        for cmd in self.baseOp.Path.Commands[1:]:
            if cmd.Name in PathGeom.CmdMoveAll:
                if bogusX == True :
                    bogusX = ( 'X' not in cmd.Parameters  )
                if bogusY :
                    bogusY = ( 'Y' not in cmd.Parameters  )
                edge = PathGeom.edgeForCmd(cmd, pos)
                if edge:
                    inside = edge.common(self.boundary).Edges
                    outside = edge.cut(self.boundary).Edges
                    if not self.inside:  # UI "inside boundary" param
                        tmp = inside
                        inside = outside
                        outside = tmp
                    # it's really a shame that one cannot trust the sequence and/or
                    # orientation of edges
                    if 1 == len(inside) and 0 == len(outside):
                        PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd)
                        # cmd fully included by boundary
                        if lastExit:
                            if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position
                                commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value))
                            lastExit = None
                        commands.append(cmd)
                        pos = PathGeom.commandEndPoint(cmd, pos)
                    elif 0 == len(inside) and 1 == len(outside):
                        PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd)
                        # cmd fully excluded by boundary
                        if not lastExit:
                            lastExit = pos
                        pos = PathGeom.commandEndPoint(cmd, pos)
                    else:
                        PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd)
                        # cmd pierces boundary
                        while inside or outside:
                            ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)]
                            PathLog.track(ie)
                            if ie:
                                e = ie[0]
                                LastPt = e.valueAt(e.LastParameter)
                                flip = PathGeom.pointsCoincide(pos, LastPt)
                                newPos = e.valueAt(e.FirstParameter) if flip else LastPt
                                # inside edges are taken at this point (see swap of inside/outside
                                # above - so we can just connect the dots ...
                                if lastExit:
                                    if not ( bogusX or bogusY ) : commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value))
                                    lastExit = None
                                PathLog.track(e, flip)
                                if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position
                                    commands.extend(PathGeom.cmdsForEdge(e, flip, False, 50, tc.HorizFeed.Value, tc.VertFeed.Value))
                                inside.remove(e)
                                pos = newPos
                                lastExit = newPos
                            else:
                                oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)]
                                PathLog.track(oe)
                                if oe:
                                    e = oe[0]
                                    ptL = e.valueAt(e.LastParameter)
                                    flip = PathGeom.pointsCoincide(pos, ptL)
                                    newPos = e.valueAt(e.FirstParameter) if flip else ptL
                                    # outside edges are never taken at this point (see swap of
                                    # inside/outside above) - so just move along ...
                                    outside.remove(e)
                                    pos = newPos
                                else:
                                    PathLog.error('huh?')
                                    import Part
                                    Part.show(Part.Vertex(pos), 'pos')
                                    for e in inside:
                                        Part.show(e, 'ei')
                                    for e in outside:
                                        Part.show(e, 'eo')
                                    raise Exception('This is not supposed to happen')
                                # Eif
                            # Eif
                        # Ewhile
                    # Eif
                    # pos = PathGeom.commandEndPoint(cmd, pos)
                # Eif
            else:
                PathLog.track('no-move', cmd)
                commands.append(cmd)
        if lastExit:
            commands.extend(self.boundaryCommands(lastExit, None, tc.VertFeed.Value))
            lastExit = None

        PathLog.track(commands)
        return Path.Path(commands)
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************

import FreeCAD
import Path
import PathScripts.PathDressup as PathDressup
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathStock as PathStock
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils

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)


def _vstr(v):
    if v:
        return "(%.2f, %.2f, %.2f)" % (v.x, v.y, v.z)
    return '-'


class DressupPathBoundary(object):
Beispiel #34
0
def CreateFromTemplate(job, template):
    if template.get('version') and 1 == int(template['version']):
        stockType = template.get('create')
        if stockType:
            placement = None
            posX = template.get('posX')
            posY = template.get('posY')
            posZ = template.get('posZ')
            rotX = template.get('rotX')
            rotY = template.get('rotY')
            rotZ = template.get('rotZ')
            rotW = template.get('rotW')
            if posX is not None and posY is not None and posZ is not None and rotX is not None and rotY is not None and rotZ is not None and rotW is not None:
                pos = FreeCAD.Vector(float(posX), float(posY), float(posZ))
                rot = FreeCAD.Rotation(float(rotX), float(rotY), float(rotZ),
                                       float(rotW))
                placement = FreeCAD.Placement(pos, rot)
            elif posX is not None or posY is not None or posZ is not None or rotX is not None or rotY is not None or rotZ is not None or rotW is not None:
                PathLog.warning(
                    translate(
                        'PathStock',
                        'Corrupted or incomplete placement information in template - ignoring'
                    ))

            if stockType == StockType.FromBase:
                xneg = template.get('xneg')
                xpos = template.get('xpos')
                yneg = template.get('yneg')
                ypos = template.get('ypos')
                zneg = template.get('zneg')
                zpos = template.get('zpos')
                neg = None
                pos = None
                if xneg is not None and xpos is not None and yneg is not None and ypos is not None and zneg is not None and zpos is not None:
                    neg = FreeCAD.Vector(
                        FreeCAD.Units.Quantity(xneg).Value,
                        FreeCAD.Units.Quantity(yneg).Value,
                        FreeCAD.Units.Quantity(zneg).Value)
                    pos = FreeCAD.Vector(
                        FreeCAD.Units.Quantity(xpos).Value,
                        FreeCAD.Units.Quantity(ypos).Value,
                        FreeCAD.Units.Quantity(zpos).Value)
                elif xneg is not None or xpos is not None or yneg is not None or ypos is not None or zneg is not None or zpos is not None:
                    PathLog.error(
                        translate(
                            'PathStock',
                            'Corrupted or incomplete specification for creating stock from base - ignoring extent'
                        ))
                return CreateFromBase(job, neg, pos, placement)

            if stockType == StockType.CreateBox:
                PathLog.track(' create box')
                length = template.get('length')
                width = template.get('width')
                height = template.get('height')
                extent = None
                if length is not None and width is not None and height is not None:
                    PathLog.track('  have extent')
                    extent = FreeCAD.Vector(
                        FreeCAD.Units.Quantity(length).Value,
                        FreeCAD.Units.Quantity(width).Value,
                        FreeCAD.Units.Quantity(height).Value)
                elif length is not None or width is not None or height is not None:
                    PathLog.error(
                        translate(
                            'PathStock',
                            'Corrupted or incomplete size for creating a stock box - ignoring size'
                        ))
                else:
                    PathLog.track(
                        "  take placement (%s) and extent (%s) from model" %
                        (placement, extent))
                return CreateBox(job, extent, placement)

            if stockType == StockType.CreateCylinder:
                radius = template.get('radius')
                height = template.get('height')
                if radius is not None and height is not None:
                    pass
                elif radius is not None or height is not None:
                    radius = None
                    height = None
                    PathLog.error(
                        translate(
                            'PathStock',
                            'Corrupted or incomplete size for creating a stock cylinder - ignoring size'
                        ))
                return CreateCylinder(job, radius, height, placement)

            PathLog.error(
                translate('PathStock',
                          'Unsupported stock type named {}').format(stockType))
        else:
            PathLog.error(
                translate('PathStock',
                          'Unsupported PathStock template version {}').format(
                              template.get('version')))
        return None
Beispiel #35
0
    def groupConnectedEdges(self, holding):
        """groupConnectedEdges(self, holding)
        Take edges and determine which are connected.
        Group connected chains/loops into: low and high"""
        holds = []
        grps = []
        searched = []
        stop = False
        attachments = []
        loops = 1

        def updateAttachments(grps):
            atchmnts = []
            lenGrps = len(grps)
            if lenGrps > 0:
                lenG0 = len(grps[0])
                if lenG0 < 2:
                    atchmnts.append((0, 0))
                else:
                    atchmnts.append((0, 0))
                    atchmnts.append((0, lenG0 - 1))
            if lenGrps == 2:
                lenG1 = len(grps[1])
                if lenG1 < 2:
                    atchmnts.append((1, 0))
                else:
                    atchmnts.append((1, 0))
                    atchmnts.append((1, lenG1 - 1))
            return atchmnts

        def isSameVertex(o, t):
            if o.X == t.X:
                if o.Y == t.Y:
                    if o.Z == t.Z:
                        return True
            return False

        for hi in range(0, len(holding)):
            holds.append(hi)

        # Place initial edge in first group and update attachments
        h0 = holds.pop()
        grps.append([h0])
        attachments = updateAttachments(grps)

        while len(holds) > 0:
            if loops > 500:
                PathLog.error("BREAK --- LOOPS LIMIT of 500 ---")
                break
            save = False

            h2 = holds.pop()
            (sub2, face2, ei2) = holding[h2]

            # Cycle through attachments for connection to existing
            for (g, t) in attachments:
                h1 = grps[g][t]
                (sub1, face1, ei1) = holding[h1]

                edg1 = face1.Edges[ei1]
                edg2 = face2.Edges[ei2]

                # CV = self.hasCommonVertex(edg1, edg2, show=False)

                # Check attachment based on attachments order
                if t == 0:
                    # is last vertex of h2 == first vertex of h1
                    e2lv = len(edg2.Vertexes) - 1
                    one = edg2.Vertexes[e2lv]
                    two = edg1.Vertexes[0]
                    if isSameVertex(one, two) is True:
                        # Connected, insert h1 in front of h2
                        grps[g].insert(0, h2)
                        stop = True
                else:
                    # is last vertex of h1 == first vertex of h2
                    e1lv = len(edg1.Vertexes) - 1
                    one = edg1.Vertexes[e1lv]
                    two = edg2.Vertexes[0]
                    if isSameVertex(one, two) is True:
                        # Connected, append h1 after h2
                        grps[g].append(h2)
                        stop = True

                if stop is True:
                    # attachment was found
                    attachments = updateAttachments(grps)
                    holds.extend(searched)
                    stop = False
                    break
                else:
                    # no attachment found
                    save = True
            # Efor
            if save is True:
                searched.append(h2)
                if len(holds) == 0:
                    if len(grps) == 1:
                        h0 = searched.pop(0)
                        grps.append([h0])
                        attachments = updateAttachments(grps)
                        holds.extend(searched)
            # Eif
            loops += 1
        # Ewhile

        low = []
        high = []
        if len(grps) == 1:
            grps.append([])
        grp0 = []
        grp1 = []
        com0 = FreeCAD.Vector(0, 0, 0)
        com1 = FreeCAD.Vector(0, 0, 0)
        if len(grps[0]) > 0:
            for g in grps[0]:
                grp0.append(holding[g])
                (sub, face, ei) = holding[g]
                com0 = com0.add(face.Edges[ei].CenterOfMass)
            com0z = com0.z / len(grps[0])
        if len(grps[1]) > 0:
            for g in grps[1]:
                grp1.append(holding[g])
                (sub, face, ei) = holding[g]
                com1 = com1.add(face.Edges[ei].CenterOfMass)
            com1z = com1.z / len(grps[1])

        if len(grps[1]) > 0:
            if com0z > com1z:
                low = grp1
                high = grp0
            else:
                low = grp0
                high = grp1
        else:
            low = grp0
            high = grp0

        return (low, high)
Beispiel #36
0
    def calculateAdaptivePocket(self, obj, base, subObjTups):
        """calculateAdaptivePocket(obj, base, subObjTups)
        Orient multiple faces around common facial center of mass.
        Identify edges that are connections for adjacent faces.
        Attempt to separate unconnected edges into top and bottom loops of the pocket.
        Trim the top and bottom of the pocket if available and requested.
        return: tuple with pocket shape information"""
        low = []
        high = []
        removeList = []
        Faces = []
        allEdges = []
        makeHighFace = 0
        tryNonPlanar = False
        isHighFacePlanar = True
        isLowFacePlanar = True

        for (sub, face) in subObjTups:
            Faces.append(face)

        # identify max and min face heights for top loop
        (zmin, zmax) = self.getMinMaxOfFaces(Faces)

        # Order faces around common center of mass
        subObjTups = self.orderFacesAroundCenterOfMass(subObjTups)
        # find connected edges and map to edge names of base
        (connectedEdges, touching) = self.findSharedEdges(subObjTups)
        (low, high) = self.identifyUnconnectedEdges(subObjTups, touching)

        if len(high) > 0 and obj.AdaptivePocketStart is True:
            # attempt planar face with top edges of pocket
            allEdges = []
            makeHighFace = 0
            tryNonPlanar = False
            for (sub, face, ei) in high:
                allEdges.append(face.Edges[ei])

            (hzmin, hzmax) = self.getMinMaxOfFaces(allEdges)

            try:
                highFaceShape = Part.Face(
                    Part.Wire(Part.__sortEdges__(allEdges)))
            except Exception as ee:
                PathLog.warning(ee)
                PathLog.error(
                    translate(
                        "Path",
                        "A planar adaptive start is unavailable. The non-planar will be attempted.",
                    ))
                tryNonPlanar = True
            else:
                makeHighFace = 1

            if tryNonPlanar is True:
                try:
                    highFaceShape = Part.makeFilledFace(
                        Part.__sortEdges__(allEdges))  # NON-planar face method
                except Exception as eee:
                    PathLog.warning(eee)
                    PathLog.error(
                        translate(
                            "Path",
                            "The non-planar adaptive start is also unavailable."
                        ) + "(1)")
                    isHighFacePlanar = False
                else:
                    makeHighFace = 2

            if makeHighFace > 0:
                FreeCAD.ActiveDocument.addObject("Part::Feature",
                                                 "topEdgeFace")
                highFace = FreeCAD.ActiveDocument.ActiveObject
                highFace.Shape = highFaceShape
                removeList.append(highFace.Name)

            # verify non-planar face is within high edge loop Z-boundaries
            if makeHighFace == 2:
                mx = hzmax + obj.StepDown.Value
                mn = hzmin - obj.StepDown.Value
                if (highFace.Shape.BoundBox.ZMax > mx
                        or highFace.Shape.BoundBox.ZMin < mn):
                    PathLog.warning("ZMaxDiff: {};  ZMinDiff: {}".format(
                        highFace.Shape.BoundBox.ZMax - mx,
                        highFace.Shape.BoundBox.ZMin - mn,
                    ))
                    PathLog.error(
                        translate(
                            "Path",
                            "The non-planar adaptive start is also unavailable."
                        ) + "(2)")
                    isHighFacePlanar = False
                    makeHighFace = 0
        else:
            isHighFacePlanar = False

        if len(low) > 0 and obj.AdaptivePocketFinish is True:
            # attempt planar face with bottom edges of pocket
            allEdges = []
            for (sub, face, ei) in low:
                allEdges.append(face.Edges[ei])

            # (lzmin, lzmax) = self.getMinMaxOfFaces(allEdges)

            try:
                lowFaceShape = Part.Face(
                    Part.Wire(Part.__sortEdges__(allEdges)))
                # lowFaceShape = Part.makeFilledFace(Part.__sortEdges__(allEdges))  # NON-planar face method
            except Exception as ee:
                PathLog.error(ee)
                PathLog.error("An adaptive finish is unavailable.")
                isLowFacePlanar = False
            else:
                FreeCAD.ActiveDocument.addObject("Part::Feature",
                                                 "bottomEdgeFace")
                lowFace = FreeCAD.ActiveDocument.ActiveObject
                lowFace.Shape = lowFaceShape
                removeList.append(lowFace.Name)
        else:
            isLowFacePlanar = False

        # Start with a regular pocket envelope
        strDep = obj.StartDepth.Value
        finDep = obj.FinalDepth.Value
        cuts = []
        starts = []
        finals = []
        starts.append(obj.StartDepth.Value)
        finals.append(zmin)
        if obj.AdaptivePocketStart is True or len(subObjTups) == 1:
            strDep = zmax + obj.StepDown.Value
            starts.append(zmax + obj.StepDown.Value)

        finish_step = obj.FinishDepth.Value if hasattr(obj,
                                                       "FinishDepth") else 0.0
        depthparams = PathUtils.depth_params(
            clearance_height=obj.ClearanceHeight.Value,
            safe_height=obj.SafeHeight.Value,
            start_depth=strDep,
            step_down=obj.StepDown.Value,
            z_finish_step=finish_step,
            final_depth=finDep,
            user_depths=None,
        )
        shape = Part.makeCompound(Faces)
        env = PathUtils.getEnvelope(base[0].Shape,
                                    subshape=shape,
                                    depthparams=depthparams)
        cuts.append(env.cut(base[0].Shape))

        # Might need to change to .cut(job.Stock.Shape) if pocket has no bottom
        # job = PathUtils.findParentJob(obj)
        # envBody = env.cut(job.Stock.Shape)

        if isHighFacePlanar is True and len(subObjTups) > 1:
            starts.append(hzmax + obj.StepDown.Value)
            # make shape to trim top of reg pocket
            strDep1 = obj.StartDepth.Value + (hzmax - hzmin)
            if makeHighFace == 1:
                # Planar face
                finDep1 = highFace.Shape.BoundBox.ZMin + obj.StepDown.Value
            else:
                # Non-Planar face
                finDep1 = hzmin + obj.StepDown.Value
            depthparams1 = PathUtils.depth_params(
                clearance_height=obj.ClearanceHeight.Value,
                safe_height=obj.SafeHeight.Value,
                start_depth=strDep1,
                step_down=obj.StepDown.Value,
                z_finish_step=finish_step,
                final_depth=finDep1,
                user_depths=None,
            )
            envTop = PathUtils.getEnvelope(base[0].Shape,
                                           subshape=highFace.Shape,
                                           depthparams=depthparams1)
            cbi = len(cuts) - 1
            cuts.append(cuts[cbi].cut(envTop))

        if isLowFacePlanar is True and len(subObjTups) > 1:
            # make shape to trim top of pocket
            if makeHighFace == 1:
                # Planar face
                strDep2 = lowFace.Shape.BoundBox.ZMax
            else:
                # Non-Planar face
                strDep2 = hzmax
            finDep2 = obj.FinalDepth.Value
            depthparams2 = PathUtils.depth_params(
                clearance_height=obj.ClearanceHeight.Value,
                safe_height=obj.SafeHeight.Value,
                start_depth=strDep2,
                step_down=obj.StepDown.Value,
                z_finish_step=finish_step,
                final_depth=finDep2,
                user_depths=None,
            )
            envBottom = PathUtils.getEnvelope(base[0].Shape,
                                              subshape=lowFace.Shape,
                                              depthparams=depthparams2)
            cbi = len(cuts) - 1
            cuts.append(cuts[cbi].cut(envBottom))

        # package pocket details into tuple
        cbi = len(cuts) - 1
        pocket = (cuts[cbi], False, "3DPocket")
        if FreeCAD.GuiUp:
            import FreeCADGui

            for rn in removeList:
                FreeCADGui.ActiveDocument.getObject(rn).Visibility = False

        for rn in removeList:
            FreeCAD.ActiveDocument.getObject(rn).purgeTouched()
            self.tempObjectNames.append(rn)
        return pocket
Beispiel #37
0
    def areaOpShapes(self, obj):
        """areaOpShapes(obj) ... return shapes representing the solids to be removed."""
        PathLog.track()

        subObjTups = []
        removalshapes = []

        if obj.Base:
            PathLog.debug("base items exist.  Processing... ")
            for base in obj.Base:
                PathLog.debug("obj.Base item: {}".format(base))

                # Check if all subs are faces
                allSubsFaceType = True
                Faces = []
                for sub in base[1]:
                    if "Face" in sub:
                        face = getattr(base[0].Shape, sub)
                        Faces.append(face)
                        subObjTups.append((sub, face))
                    else:
                        allSubsFaceType = False
                        break

                if len(Faces) == 0:
                    allSubsFaceType = False

                if (allSubsFaceType is True
                        and obj.HandleMultipleFeatures == "Collectively"):
                    (fzmin, fzmax) = self.getMinMaxOfFaces(Faces)
                    if obj.FinalDepth.Value < fzmin:
                        PathLog.warning(
                            translate(
                                "PathPocket",
                                "Final depth set below ZMin of face(s) selected.",
                            ))

                    if (obj.AdaptivePocketStart is True
                            or obj.AdaptivePocketFinish is True):
                        pocketTup = self.calculateAdaptivePocket(
                            obj, base, subObjTups)
                        if pocketTup is not False:
                            obj.removalshape = pocketTup[0]
                            removalshapes.append(
                                pocketTup)  # (shape, isHole, detail)
                    else:
                        shape = Part.makeCompound(Faces)
                        env = PathUtils.getEnvelope(
                            base[0].Shape,
                            subshape=shape,
                            depthparams=self.depthparams)
                        rawRemovalShape = env.cut(base[0].Shape)
                        faceExtrusions = [
                            f.extrude(FreeCAD.Vector(0.0, 0.0, 1.0))
                            for f in Faces
                        ]
                        obj.removalshape = _identifyRemovalSolids(
                            rawRemovalShape, faceExtrusions)
                        removalshapes.append(
                            (obj.removalshape, False,
                             "3DPocket"))  # (shape, isHole, detail)
                else:
                    for sub in base[1]:
                        if "Face" in sub:
                            shape = Part.makeCompound(
                                [getattr(base[0].Shape, sub)])
                        else:
                            edges = [
                                getattr(base[0].Shape, sub) for sub in base[1]
                            ]
                            shape = Part.makeFace(edges,
                                                  "Part::FaceMakerSimple")

                        env = PathUtils.getEnvelope(
                            base[0].Shape,
                            subshape=shape,
                            depthparams=self.depthparams)
                        rawRemovalShape = env.cut(base[0].Shape)
                        faceExtrusions = [
                            shape.extrude(FreeCAD.Vector(0.0, 0.0, 1.0))
                        ]
                        obj.removalshape = _identifyRemovalSolids(
                            rawRemovalShape, faceExtrusions)
                        removalshapes.append(
                            (obj.removalshape, False, "3DPocket"))

        else:  # process the job base object as a whole
            PathLog.debug("processing the whole job base object")
            for base in self.model:
                if obj.ProcessStockArea is True:
                    job = PathUtils.findParentJob(obj)

                    stockEnvShape = PathUtils.getEnvelope(
                        job.Stock.Shape,
                        subshape=None,
                        depthparams=self.depthparams)

                    rawRemovalShape = stockEnvShape.cut(base.Shape)
                else:
                    env = PathUtils.getEnvelope(base.Shape,
                                                subshape=None,
                                                depthparams=self.depthparams)
                    rawRemovalShape = env.cut(base.Shape)

                # Identify target removal shapes after cutting envelope with base shape
                removalSolids = [
                    s for s in rawRemovalShape.Solids if PathGeom.isRoughly(
                        s.BoundBox.ZMax, rawRemovalShape.BoundBox.ZMax)
                ]

                # Fuse multiple solids
                if len(removalSolids) > 1:
                    seed = removalSolids[0]
                    for tt in removalSolids[1:]:
                        fusion = seed.fuse(tt)
                        seed = fusion
                    removalShape = seed
                else:
                    removalShape = removalSolids[0]

                obj.removalshape = removalShape
                removalshapes.append((obj.removalshape, False, "3DPocket"))

        return removalshapes
def checkWorkingDir():
    # users shouldn't use the example toolbits and libraries.
    # working directory should be writable
    PathLog.track()

    workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary())
    defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath())

    PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir))

    dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK))

    if dirOK():
        return True

    qm = PySide.QtGui.QMessageBox
    ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No)

    if ret == qm.No:
        return False

    msg = translate("Path", "Choose a writable location for your toolbits", None)
    while not dirOK():
        workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg,
                PathPreferences.filePath())

    if workingdir[-8:] == os.path.sep + 'Library':
        workingdir = workingdir[:-8]  # trim off trailing /Library if user chose it

    PathPreferences.setLastPathToolLibrary("{}{}Library".format(workingdir, os.path.sep))
    PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep))
    PathLog.debug('setting workingdir to: {}'.format(workingdir))

    # Copy only files of default Path\Tools folder to working directory (targeting the README.md help file)
    src_toolfiles = os.listdir(defaultdir)
    for file_name in src_toolfiles:
        if file_name in ["README.md"]:
            full_file_name = os.path.join(defaultdir, file_name)
            if os.path.isfile(full_file_name):
                shutil.copy(full_file_name, workingdir)

    # Determine which subdirectories are missing
    subdirlist = ['Bit', 'Library', 'Shape']
    mode = 0o777
    for dir in subdirlist.copy():
        subdir = "{}{}{}".format(workingdir, os.path.sep, dir)
        if os.path.exists(subdir):
            subdirlist.remove(dir)

    # Query user for creation permission of any missing subdirectories
    if len(subdirlist) >= 1:
        needed = ', '.join([str(d) for d in subdirlist])
        qm = PySide.QtGui.QMessageBox
        ret = qm.question(None,'', "Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format(workingdir, needed), qm.Yes | qm.No)

        if ret == qm.No:
            return False
        else:
            # Create missing subdirectories if user agrees to creation
            for dir in subdirlist:
                subdir = "{}{}{}".format(workingdir, os.path.sep, dir)
                os.mkdir(subdir, mode)
                # Query user to copy example files into subdirectories created
                if dir != 'Shape':
                    qm = PySide.QtGui.QMessageBox
                    ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No)
                    if ret == qm.Yes:
                        src="{}{}{}".format(defaultdir, os.path.sep, dir)
                        src_files = os.listdir(src)
                        for file_name in src_files:
                            full_file_name = os.path.join(src, file_name)
                            if os.path.isfile(full_file_name):
                                shutil.copy(full_file_name, subdir)


    # if no library is set, choose the first one in the Library directory
    if PathPreferences.lastFileToolLibrary() is None:
        libFiles = [f for f in glob.glob(PathPreferences.lastPathToolLibrary() + os.path.sep + '*.fctl')]
        PathPreferences.setLastFileToolLibrary(libFiles[0])

    return True
 def toolDelete(self):
     PathLog.track()
     selectedRows = set([index.row() for index in self.toolTableView.selectedIndexes()])
     for row in sorted(list(selectedRows), key=lambda r: -r):
         self.toolModel.removeRows(row, 1)
    def libPaths(self):
        lib = PathPreferences.lastFileToolLibrary()
        loc = PathPreferences.lastPathToolLibrary()

        PathLog.track("lib: {} loc: {}".format(lib, loc))
        return lib, loc
Beispiel #41
0
def offsetWire(wire, base, offset, forward):
    '''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly.
    The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting
    happens in the XY plane.
    '''
    PathLog.track('offsetWire')

    if 1 == len(wire.Edges):
        edge = wire.Edges[0]
        curve = edge.Curve
        if Part.Circle == type(curve) and wire.isClosed():
            # it's a full circle and there are some problems with that, see
            # http://www.freecadweb.org/wiki/Part%20Offset2D
            # it's easy to construct them manually though
            z = -1 if forward else 1
            edge = Part.makeCircle(curve.Radius + offset, curve.Center,
                                   FreeCAD.Vector(0, 0, z))
            if base.isInside(edge.Vertexes[0].Point, offset / 2, True):
                if offset > curve.Radius or PathGeom.isRoughly(
                        offset, curve.Radius):
                    # offsetting a hole by its own radius (or more) makes the hole vanish
                    return None
                edge = Part.makeCircle(curve.Radius - offset, curve.Center,
                                       FreeCAD.Vector(0, 0, -z))
            w = Part.Wire([edge])
            return w
        if Part.Line == type(curve) or Part.LineSegment == type(curve):
            # offsetting a single edge doesn't work because there is an infinite
            # possible planes into which the edge could be offset
            # luckily, the plane here must be the XY-plane ...
            p0 = edge.Vertexes[0].Point
            v0 = edge.Vertexes[1].Point - p0
            n = v0.cross(FreeCAD.Vector(0, 0, 1))
            o = n.normalize() * offset
            edge.translate(o)

            # offset edde the other way if the result is inside
            if base.isInside(
                    edge.valueAt(
                        (edge.FirstParameter + edge.LastParameter) / 2),
                    offset / 2, True):
                edge.translate(-2 * o)

            # flip the edge if it's not on the right side of the original edge
            if forward is not None:
                v1 = edge.Vertexes[1].Point - p0
                left = PathGeom.Side.Left == PathGeom.Side.of(v0, v1)
                if left != forward:
                    edge = PathGeom.flipEdge(edge)
            return Part.Wire([edge])

        # if we get to this point the assumption is that makeOffset2D can deal with the edge
        pass  # pylint: disable=unnecessary-pass

    owire = orientWire(wire.makeOffset2D(offset), True)
    debugWire('makeOffset2D_%d' % len(wire.Edges), owire)

    if wire.isClosed():
        if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset / 2,
                             True):
            PathLog.track('closed - outside')
            return orientWire(owire, forward)
        PathLog.track('closed - inside')
        try:
            owire = wire.makeOffset2D(-offset)
        except Exception:  # pylint: disable=broad-except
            # most likely offsetting didn't work because the wire is a hole
            # and the offset is too big - making the hole vanish
            return None
        # For negative offsets (holes) 'forward' is the other way
        if forward is None:
            return orientWire(owire, None)
        return orientWire(owire, not forward)

    # An edge is considered to be inside of shape if the mid point is inside
    # Of the remaining edges we take the longest wire to be the engraving side
    # Looking for a circle with the start vertex as center marks and end
    #  starting from there follow the edges until a circle with the end vertex as center is found
    #  if the traversed edges include any oof the remainig from above, all those edges are remaining
    #  this is to also include edges which might partially be inside shape
    #  if they need to be discarded, split, that should happen in a post process
    # Depending on the Axis of the circle, and which side remains we know if the wire needs to be flipped

    # first, let's make sure all edges are oriented the proper way
    edges = _orientEdges(wire.Edges)

    # determine the start and end point
    start = edges[0].firstVertex().Point
    end = edges[-1].lastVertex().Point
    debugWire('wire', wire)
    debugWire('wedges', Part.Wire(edges))

    # find edges that are not inside the shape
    common = base.common(owire)
    insideEndpoints = [e.lastVertex().Point for e in common.Edges]
    insideEndpoints.append(common.Edges[0].firstVertex().Point)

    def isInside(edge):
        p0 = edge.firstVertex().Point
        p1 = edge.lastVertex().Point
        for p in insideEndpoints:
            if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide(
                    p, p1, 0.01):
                return True
        return False

    outside = [e for e in owire.Edges if not isInside(e)]
    # discard all edges that are not part of the longest wire
    longestWire = None
    for w in [Part.Wire(el) for el in Part.sortEdges(outside)]:
        if not longestWire or longestWire.Length < w.Length:
            longestWire = w

    debugWire('outside', Part.Wire(outside))
    debugWire('longest', longestWire)

    def isCircleAt(edge, center):
        '''isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.'''
        if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type(
                edge.Curve):
            return PathGeom.pointsCoincide(edge.Curve.Center, center)
        return False

    # split offset wire into edges to the left side and edges to the right side
    collectLeft = False
    collectRight = False
    leftSideEdges = []
    rightSideEdges = []

    # traverse through all edges in order and start collecting them when we encounter
    # an end point (circle centered at one of the end points of the original wire).
    # should we come to an end point and determine that we've already collected the
    # next side, we're done
    for e in (owire.Edges + owire.Edges):
        if isCircleAt(e, start):
            if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)):
                if not collectLeft and leftSideEdges:
                    break
                collectLeft = True
                collectRight = False
            else:
                if not collectRight and rightSideEdges:
                    break
                collectLeft = False
                collectRight = True
        elif isCircleAt(e, end):
            if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)):
                if not collectRight and rightSideEdges:
                    break
                collectLeft = False
                collectRight = True
            else:
                if not collectLeft and leftSideEdges:
                    break
                collectLeft = True
                collectRight = False
        elif collectLeft:
            leftSideEdges.append(e)
        elif collectRight:
            rightSideEdges.append(e)

    debugWire('left', Part.Wire(leftSideEdges))
    debugWire('right', Part.Wire(rightSideEdges))

    # figure out if all the left sided edges or the right sided edges are the ones
    # that are 'outside'. However, we return the full side.
    edges = leftSideEdges
    for e in longestWire.Edges:
        for e0 in rightSideEdges:
            if PathGeom.edgesMatch(e, e0):
                edges = rightSideEdges
                PathLog.debug("#use right side edges")
                if not forward:
                    PathLog.debug("#reverse")
                    edges.reverse()
                return orientWire(Part.Wire(edges), None)

    # at this point we have the correct edges and they are in the order for forward
    # traversal (climb milling). If that's not what we want just reverse the order,
    # orientWire takes care of orienting the edges appropriately.
    PathLog.debug("#use left side edges")
    if not forward:
        PathLog.debug("#reverse")
        edges.reverse()

    return orientWire(Part.Wire(edges), None)
 def open(self):
     PathLog.track()
     return self.form.exec_()
Beispiel #43
0
 def __setstate__(self, state):
     PathLog.track()
     return None
 def __init__(self, path=None):
     PathLog.track()
     self.path = ""
Beispiel #45
0
 def deleteObjectsOnReject(self):
     PathLog.track()
     return hasattr(self, 'deleteOnReject') and self.deleteOnReject
Beispiel #46
0
 def areaOpRetractTool(self, obj):
     PathLog.debug("retracting tool: %d" % (not obj.KeepToolDown))
     return not obj.KeepToolDown
Beispiel #47
0
 def __init__(self, vobj):
     PathLog.track()
     vobj.Proxy = self
Beispiel #48
0
 def __getstate__(self):
     PathLog.track()
     return None
Beispiel #49
0
 def onChanged(self, obj, prop):
     PathLog.track('prop: {}  state: {}'.format(prop, obj.State))
     if prop in ['AreaParams', 'PathParams', 'removalshape']:
         obj.setEditorMode(prop, 2)
Beispiel #50
0
 def attach(self, vobj):
     PathLog.track()
     self.Object = vobj.Object
     return
Beispiel #51
0
 def expression(self):
     """expression() ... returns the expression if one is bound to the property"""
     PathLog.track(self.prop, self.valid)
     if self.valid:
         return self.widget.property("expression")
     return ""
Beispiel #52
0
    def execute(self, obj, getsim=False):
        PathLog.track()
        self.endVector = None

        if not obj.Active:
            path = Path.Path("(inactive operation)")
            obj.Path = path
            obj.ViewObject.Visibility = False
            return

        commandlist = []
        toolLoad = obj.ToolController

        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)

        if toolLoad is None or toolLoad.ToolNumber == 0:

            FreeCAD.Console.PrintError("No Tool Controller is selected. We need a tool to build a Path.")
            return
        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

        commandlist.append(Path.Command("(" + obj.Label + ")"))

        if obj.UseComp:
            commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")"))
        else:
            commandlist.append(Path.Command("(Uncompensated Tool Path)"))

        parentJob = PathUtils.findParentJob(obj)

        if parentJob is None:
            return
        baseobject = parentJob.Base
        if baseobject is None:
            return

        isPanel = False
        if hasattr(baseobject, "Proxy"):
            if isinstance(baseobject.Proxy, ArchPanel.PanelSheet):  # process the sheet
                isPanel = True
                baseobject.Proxy.execute(baseobject)
                shapes = baseobject.Proxy.getOutlines(baseobject, transform=True)
                for shape in shapes:
                    f = Part.makeFace([shape], 'Part::FaceMakerSimple')
                    thickness = baseobject.Group[0].Source.Thickness
                    contourshape = f.extrude(FreeCAD.Vector(0, 0, thickness))
                    try:
                        (pp, sim) = self._buildPathArea(obj, contourshape, start=obj.StartPoint, getsim=getsim)
                        commandlist.extend(pp.Commands)
                    except Exception as e:
                        FreeCAD.Console.PrintError(e)
                        FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")

        if hasattr(baseobject, "Shape") and not isPanel:
            env = PathUtils.getEnvelope(partshape=baseobject.Shape, subshape=None, depthparams=self.depthparams)
            try:
                (pp, sim) = self._buildPathArea(obj, env, start=obj.StartPoint, getsim=getsim)
                commandlist.extend(pp.Commands)
            except Exception as e:
                FreeCAD.Console.PrintError(e)
                FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour 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}))

        PathLog.track()
        path = Path.Path(commandlist)
        obj.Path = path
        return sim
Beispiel #53
0
 def __init__(self, widget, obj, prop, onBeforeChange=None):
     PathLog.track(widget)
     self.widget = widget
     self.onBeforeChange = onBeforeChange
     self.attachTo(obj, prop)
Beispiel #54
0
 def setMinimum(self, quantity):
     """setMinimum(quantity) ... set the minimum"""
     PathLog.track(self.prop, self.valid)
     if self.valid:
         value = quantity.Value if hasattr(quantity, "Value") else quantity
         self.widget.setProperty("setMinimum", value)
Beispiel #55
0
    def areaOpShapes(self, obj):
        '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.'''
        PathLog.track()

        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.Shape.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(
                                translate(
                                    'PathProfileEdges',
                                    'The selected edge(s) are 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
                            (origWire, flatWire) = self._flattenWire(
                                obj, wire, obj.FinalDepth.Value)
                            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(
                                    translate(
                                        'PathProfileEdges',
                                        'The selected edge(s) are inaccessible.'
                                    ))

            # Delete the temporary objects
            if PathLog.getLevel(PathLog.thisModule()) != 4:
                for to in self.tmpGrp.Group:
                    FreeCAD.ActiveDocument.removeObject(to.Name)
                FreeCAD.ActiveDocument.removeObject(tmpGrpNm)
            else:
                if FreeCAD.GuiUp:
                    import FreeCADGui
                    FreeCADGui.ActiveDocument.getObject(
                        tmpGrpNm).Visibility = False

        return shapes
Beispiel #56
0
import PathTurnScripts.PathTurnBaseGui as PathTurnBaseGui
import PathTurnScripts.PathTurnProfile as PathTurnProfile
import PathScripts.PathLog as PathLog
import PathScripts.PathOpGui as PathOpGui

from PySide import QtCore

__title__ = "Path Turn Profile Gui"
__author__ = "dubstar-04 (Daniel Wood)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Gui implementation for turning profiling operations."

LOGLEVEL = False

if LOGLEVEL:
    PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
    PathLog.trackModule(PathLog.thisModule())
else:
    PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule())


class TaskPanelOpPage(PathTurnBaseGui.TaskPanelTurnBase):
    '''Page controller class for Turning operations.'''
    def setOpFields(self, obj):
        '''setFields(obj) ... transfers obj's property values to UI'''
        pass


Command = PathOpGui.SetupOperation(
    'TurnProfile', PathTurnProfile.Create, TaskPanelOpPage, 'Path-TurnProfile',
    QtCore.QT_TRANSLATE_NOOP("PathTurnProfile", "Turn Profile"),
Beispiel #57
0
    def _getCutAreaCrossSection(self, obj, base, origWire, flatWireObj):
        PathLog.debug('_getCutAreaCrossSection()')
        tmpGrp = self.tmpGrp
        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 = flatWireObj.Shape.BoundBox
        wBB = origWire.Shape.BoundBox
        minArea = (self.ofstRadius - tolerance)**2 * math.pi

        useWire = origWire.Shape.Wires[0]
        numOrigEdges = len(useWire.Edges)
        sdv = wBB.ZMax
        fdv = obj.FinalDepth.Value
        extLenFwd = sdv - fdv
        WIRE = flatWireObj.Shape.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)
        self.iTAG = iTAG
        self.eTAG = eTAG

        # Create extended wire boundbox, and extrude
        extBndbox = self._makeExtendedBoundBox(wBB, bbBfr, fdv)
        extBndboxEXT = self._extrudeObject(extBndbox,
                                           extLenFwd)  # (objToExt, extFwdLen)

        # Cut model(selected edges) from extended edges boundbox
        cutArea = extBndboxEXT.Shape.cut(base.Shape)

        # 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.Shape.Wires[0])
            tmpFace = Part.Face(extBndbox.Shape.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

        # Convert compound shapes to FC objects for use in multicommon operation
        TP = FCAD.addObject('Part::Feature', 'tmpTopCompound')
        TP.Shape = topComp
        TP.recompute()
        TP.purgeTouched()
        tmpGrp.addObject(TP)
        BT = FCAD.addObject('Part::Feature', 'tmpBotCompound')
        BT.Shape = botComp
        BT.recompute()
        BT.purgeTouched()
        tmpGrp.addObject(BT)

        # Make common of the two
        comFC = FCAD.addObject('Part::MultiCommon', 'tmpCommonTopBotFaces')
        comFC.Shapes = [TP, BT]
        comFC.recompute()
        TP.purgeTouched()
        BT.purgeTouched()
        comFC.purgeTouched()
        tmpGrp.addObject(comFC)

        # 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.Shape
            tagCOM = begExt.CenterOfMass
        else:
            PathLog.debug('Cutting on Int side.')
            self.cutSide = 'I'
            self.cutSideTags = iTAG.Shape
            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.Shape
        fcShp = workShp
        wire = origWire.Shape  # flatWireObj.Shape
        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.'
                )
                tmpGrp.purgeTouched()
                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)
            # testArea = fcShp
            TA = FreeCAD.ActiveDocument.addObject('Part::Feature',
                                                  'tmpCutFaceTest')
            TA.Shape = testArea
            TA.purgeTouched()
            tmpGrp.addObject(TA)

            isReady = self._checkTagIntersection(iTAG, eTAG, self.cutSide, TA)
            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)

        CF = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCutFace')
        CF.Shape = cutShp
        CF.recompute()
        CF.purgeTouched()
        tmpGrp.addObject(CF)

        tmpGrp.purgeTouched()
        return cutShp  # CF.Shape
Beispiel #58
0
    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])
Beispiel #59
0
def flipWire(wire):
    '''Flip the entire wire and all its edges so it is being processed the other way around.'''
    edges = [flipEdge(e) for e in wire.Edges]
    edges.reverse()
    PathLog.debug(edges)
    return Part.Wire(edges)
Beispiel #60
0
    def _extractPathWire(self, obj, base, fWire, cutShp):
        PathLog.debug('_extractPathWire()')

        subLoops = list()
        rtnWIRES = list()
        osWrIdxs = list()
        subDistFactor = 1.0  # Raise to include sub wires at greater distance from original
        tmpGrp = self.tmpGrp
        fdv = obj.FinalDepth.Value
        wire = fWire.Shape
        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.')
            tmpGrp.purgeTouched()
            return False

        os = FreeCAD.ActiveDocument.addObject('Part::Feature',
                                              'tmpOffsetShape')
        os.Shape = ofstShp
        os.recompute()
        os.purgeTouched()
        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
        near0 = Draft.makeWire([cent0, pnt0],
                               placement=pl,
                               closed=False,
                               face=False,
                               support=None)
        near0.recompute()
        near0.purgeTouched()
        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
        near1 = Draft.makeWire([cent1, pnt1],
                               placement=pl,
                               closed=False,
                               face=False,
                               support=None)
        near1.recompute()
        near1.purgeTouched()
        tmpGrp.addObject(near1)

        if w0 != w1:
            PathLog.debug('w0 is {}.'.format(w0))
            PathLog.debug('w1 is {}.'.format(w1))
            PathLog.warning(
                'Offset wire endpoint indexes are not equal: {}, {}'.format(
                    w0, w1))
        '''
        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, ofstShp.Vertexes[vi0], ofstShp.Vertexes[vi1])
        (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)

        tmpGrp.purgeTouched()
        return rtnWIRES