Esempio n. 1
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
Esempio n. 2
0
 def areaOpShapeForDepths(self, obj):
     '''areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used.
     The default implementation returns the job's Base.Shape'''
     job = PathUtils.findParentJob(obj)
     if job and job.Base:
         PathLog.debug("job=%s base=%s shape=%s" % (job, job.Base, job.Base.Shape))
         return job.Base.Shape
     if job:
         PathLog.warning(translate("PathAreaOp", "job %s has no Base.") % job.Label)
     else:
         PathLog.warning(translate("PathAreaOp", "no job for op %s found.") % obj.Label)
     return None
Esempio n. 3
0
 def __init__(self, widget, obj, prop, onBeforeChange=None):
     self.obj = obj
     self.widget = widget
     self.prop = prop
     self.onBeforeChange = onBeforeChange
     attr = getProperty(self.obj, self.prop)
     if attr is not None:
         if hasattr(attr, 'Value'):
             widget.setProperty('unit', attr.getUserPreferred()[2])
         widget.setProperty('binding', "%s.%s" % (obj.Name, prop))
         self.valid = True
     else:
         PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label))
         self.valid = False
Esempio n. 4
0
def _getProperty(obj, prop):
    o = obj
    attr = obj
    for name in prop.split('.'):
        o = attr
        if not hasattr(o, name):
            break
        attr = getattr(o, name)

    if o == attr:
        PathLog.warning(translate('PathGui', "%s has no property %s (%s))") % (obj.Label, prop, name))
        return (None, None, None)

    #PathLog.debug("found property %s of %s (%s: %s)" % (prop, obj.Label, name, attr))
    return(o, attr, name)
Esempio n. 5
0
def flipEdge(edge):
    '''flipEdge(edge)
    Flips given edge around so the new Vertexes[0] was the old Vertexes[-1] and vice versa, without changing the shape.
    Currently only lines, line segments, circles and arcs are supported.'''

    if Part.Line == type(edge.Curve) and not edge.Vertexes:
        return Part.Edge(Part.Line(edge.valueAt(edge.LastParameter), edge.valueAt(edge.FirstParameter)))
    elif Part.Line == type(edge.Curve) or Part.LineSegment == type(edge.Curve):
        return Part.Edge(Part.LineSegment(edge.Vertexes[-1].Point, edge.Vertexes[0].Point))
    elif Part.Circle == type(edge.Curve):
        # Create an inverted circle
        circle = Part.Circle(edge.Curve.Center, -edge.Curve.Axis, edge.Curve.Radius)
        # Rotate the circle appropriately so it starts at edge.valueAt(edge.LastParameter)
        circle.rotate(FreeCAD.Placement(circle.Center, circle.Axis, 180 - math.degrees(edge.LastParameter + edge.Curve.AngleXU)))
        # Now the edge always starts at 0 and LastParameter is the value range
        arc = Part.Edge(circle, 0, edge.LastParameter - edge.FirstParameter)
        return arc
    elif Part.BSplineCurve == type(edge.Curve):
        spline = edge.Curve

        mults = spline.getMultiplicities()
        weights = spline.getWeights()
        knots = spline.getKnots()
        poles = spline.getPoles()
        perio = spline.isPeriodic()
        ratio = spline.isRational()
        degree = spline.Degree

        ma = max(knots)
        mi = min(knots)
        knots = [ma+mi-k for k in knots]

        mults.reverse()
        weights.reverse()
        poles.reverse()
        knots.reverse()

        flipped = Part.BSplineCurve()
        flipped.buildFromPolesMultsKnots(poles, mults , knots, perio, degree, weights, ratio)

        return Part.Edge(flipped)

    global OddsAndEnds
    OddsAndEnds.append(edge)
    PathLog.warning(translate('PathGeom', "%s not support for flipping") % type(edge.Curve))
Esempio n. 6
0
    def opExecute(self, obj):
        '''opExecute(obj) ... processes all Base features and Locations and collects
        them in a list of positions and radii which is then passed to circularHoleExecute(obj, holes).
        If no Base geometries and no Locations are present, the job's Base is inspected and all
        drillable features are added to Base. In this case appropriate values for depths are also
        calculated and assigned.
        Do not overwrite, implement circularHoleExecute(obj, holes) instead.'''
        PathLog.track()

        holes = []
        baseSubsTuples = []
        subCount = 0
        allTuples = []
        self.cloneNames = []  # pylint: disable=attribute-defined-outside-init
        self.guiMsgs = []  # pylint: disable=attribute-defined-outside-init
        self.rotateFlag = False  # pylint: disable=attribute-defined-outside-init
        self.useTempJobClones('Delete')  # pylint: disable=attribute-defined-outside-init
        self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox  # pylint: disable=attribute-defined-outside-init
        self.clearHeight = obj.ClearanceHeight.Value  # pylint: disable=attribute-defined-outside-init
        self.safeHeight = obj.SafeHeight.Value  # pylint: disable=attribute-defined-outside-init
        self.axialFeed = 0.0  # pylint: disable=attribute-defined-outside-init
        self.axialRapid = 0.0  # pylint: disable=attribute-defined-outside-init
        trgtDep = None

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

        if obj.EnableRotation == 'Off':
            strDep = obj.StartDepth.Value
            finDep = obj.FinalDepth.Value
        else:
            # Calculate operation heights based upon rotation radii
            opHeights = self.opDetermineRotationRadii(obj)
            (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0]  # pylint: disable=attribute-defined-outside-init
            (clrOfset, safOfst) = opHeights[1]
            PathLog.debug("Exec. opHeights[0]: " + str(opHeights[0]))
            PathLog.debug("Exec. opHeights[1]: " + str(opHeights[1]))

            # Set clearance and safe heights based upon rotation radii
            if obj.EnableRotation == 'A(x)':
                strDep = self.xRotRad
            elif obj.EnableRotation == 'B(y)':
                strDep = self.yRotRad
            else:
                strDep = max(self.xRotRad, self.yRotRad)
            finDep = -1 * strDep

            obj.ClearanceHeight.Value = strDep + clrOfset
            obj.SafeHeight.Value = strDep + safOfst

            # Create visual axes when debugging.
            if PathLog.getLevel(PathLog.thisModule()) == 4:
                self.visualAxis()

            # Set axial feed rates based upon horizontal feed rates
            safeCircum = 2 * math.pi * obj.SafeHeight.Value
            self.axialFeed = 360 / safeCircum * self.horizFeed  # pylint: disable=attribute-defined-outside-init
            self.axialRapid = 360 / safeCircum * self.horizRapid  # pylint: disable=attribute-defined-outside-init

        # Complete rotational analysis and temp clone creation as needed
        if obj.EnableRotation == 'Off':
            PathLog.debug("Enable Rotation setting is 'Off' for {}.".format(
                obj.Name))
            stock = PathUtils.findParentJob(obj).Stock
            for (base, subList) in obj.Base:
                baseSubsTuples.append((base, subList, 0.0, 'A', stock))
        else:
            for p in range(0, len(obj.Base)):
                (base, subsList) = obj.Base[p]
                for sub in subsList:
                    if self.isHoleEnabled(obj, base, sub):
                        shape = getattr(base.Shape, sub)
                        rtn = False
                        (norm, surf) = self.getFaceNormAndSurf(shape)
                        (rtn, angle, axis,
                         praInfo) = self.faceRotationAnalysis(obj, norm, surf)  # pylint: disable=unused-variable
                        if rtn is True:
                            (clnBase, angle, clnStock,
                             tag) = self.applyRotationalAnalysis(
                                 obj, base, angle, axis, subCount)
                            # Verify faces are correctly oriented - InverseAngle might be necessary
                            PathLog.debug(
                                "Verifying {} orientation: running faceRotationAnalysis() again."
                                .format(sub))
                            faceIA = getattr(clnBase.Shape, sub)
                            (norm, surf) = self.getFaceNormAndSurf(faceIA)
                            (rtn, praAngle, praAxis,
                             praInfo) = self.faceRotationAnalysis(
                                 obj, norm, surf)  # pylint: disable=unused-variable
                            if rtn is True:
                                msg = obj.Name + ":: "
                                msg += translate(
                                    "Path",
                                    "{} might be misaligned after initial rotation."
                                    .format(sub)) + "  "
                                if obj.AttemptInverseAngle is True and obj.InverseAngle is False:
                                    (clnBase, clnStock,
                                     angle) = self.applyInverseAngle(
                                         obj, clnBase, clnStock, axis, angle)
                                    msg += translate(
                                        "Path",
                                        "Rotated to 'InverseAngle' to attempt access."
                                    )
                                else:
                                    if len(subsList) == 1:
                                        msg += translate(
                                            "Path",
                                            "Consider toggling the 'InverseAngle' property and recomputing."
                                        )
                                    else:
                                        msg += translate(
                                            "Path",
                                            "Consider transferring '{}' to independent operation."
                                            .format(sub))
                                PathLog.warning(msg)
                                # title = translate("Path", 'Rotation Warning')
                                # self.guiMessage(title, msg, False)
                            else:
                                PathLog.debug(
                                    "Face appears to be oriented correctly.")

                            cmnt = "{}: {} @ {};  ".format(
                                sub, axis, str(round(angle, 5)))
                            if cmnt not in obj.Comment:
                                obj.Comment += cmnt

                            tup = clnBase, sub, tag, angle, axis, clnStock
                            allTuples.append(tup)
                        else:
                            if self.warnDisabledAxis(obj, axis, sub) is True:
                                pass  # Skip drill feature due to access issue
                            else:
                                PathLog.debug(str(sub) + ": No rotation used")
                                axis = 'X'
                                angle = 0.0
                                tag = base.Name + '_' + axis + str(
                                    angle).replace('.', '_')
                                stock = PathUtils.findParentJob(obj).Stock
                                tup = base, sub, tag, angle, axis, stock
                                allTuples.append(tup)
                        # Eif
                    # Eif
                    subCount += 1
                # Efor
            # Efor
            (Tags,
             Grps) = self.sortTuplesByIndex(allTuples,
                                            2)  # return (TagList, GroupList)
            subList = []
            for o in range(0, len(Tags)):
                PathLog.debug('hTag: {}'.format(Tags[o]))
                subList = []
                for (base, sub, tag, angle, axis, stock) in Grps[o]:
                    subList.append(sub)
                pair = base, subList, angle, axis, stock
                baseSubsTuples.append(pair)
            # Efor

        for base, subs, angle, axis, stock in baseSubsTuples:
            for sub in subs:
                if self.isHoleEnabled(obj, base, sub):
                    pos = self.holePosition(obj, base, sub)
                    if pos:
                        # Default is treat selection as 'Face' shape
                        finDep = base.Shape.getElement(sub).BoundBox.ZMin
                        if base.Shape.getElement(sub).ShapeType == 'Edge':
                            msg = translate(
                                "Path",
                                "Verify Final Depth of holes based on edges. {} depth is: {} mm"
                                .format(sub, round(finDep, 4))) + "  "
                            msg += translate(
                                "Path",
                                "Always select the bottom edge of the hole when using an edge."
                            )
                            PathLog.warning(msg)

                        # If user has not adjusted Final Depth value, attempt to determine from sub
                        trgtDep = obj.FinalDepth.Value
                        if obj.OpFinalDepth.Value == obj.FinalDepth.Value:
                            trgtDep = finDep
                        if obj.FinalDepth.Value < finDep:
                            msg = translate(
                                "Path",
                                "Final Depth setting is below the hole bottom for {}."
                                .format(sub))
                            PathLog.warning(msg)

                        holes.append({
                            'x': pos.x,
                            'y': pos.y,
                            'r': self.holeDiameter(obj, base, sub),
                            'angle': angle,
                            'axis': axis,
                            'trgtDep': trgtDep,
                            'stkTop': stock.Shape.BoundBox.ZMax
                        })

        if haveLocations(self, obj):
            for location in obj.Locations:
                # holes.append({'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'finDep': obj.FinalDepth.Value})
                trgtDep = obj.FinalDepth.Value
                holes.append({
                    'x':
                    location.x,
                    'y':
                    location.y,
                    'r':
                    0,
                    'angle':
                    0.0,
                    'axis':
                    'X',
                    'trgtDep':
                    trgtDep,
                    'stkTop':
                    PathUtils.findParentJob(obj).stock.Shape.BoundBox.ZMax
                })

        # If all holes based upon edges, set post-operation Final Depth to highest edge height
        if obj.OpFinalDepth.Value == obj.FinalDepth.Value:
            if len(holes) == 1:
                PathLog.info(
                    translate(
                        'Path',
                        "Single-hole operation. Saving Final Depth determined from hole base."
                    ))
                obj.FinalDepth.Value = trgtDep

        if len(holes) > 0:
            self.circularHoleExecute(
                obj, holes)  # circularHoleExecute() located in PathDrilling.py

        self.useTempJobClones(
            'Delete')  # Delete temp job clone group and contents
        self.guiMessage('title', None,
                        show=True)  # Process GUI messages to user
        PathLog.debug("obj.Name: " + str(obj.Name))
    def generateRamps(self, allowBounce=True):
        edges = self.wire.Edges
        outedges = []
        for edge in edges:
            israpid = False
            for redge in self.rapids:
                if PathGeom.edgesMatch(edge, redge):
                    israpid = True
            if not israpid:
                bb = edge.BoundBox
                p0 = edge.Vertexes[0].Point
                p1 = edge.Vertexes[1].Point
                rampangle = self.angle
                if bb.XLength < 1e-6 and bb.YLength < 1e-6 and bb.ZLength > 0 and p0.z > p1.z:

                    # check if above ignoreAbove parameter - do not generate ramp if it is
                    newEdge, cont = self.checkIgnoreAbove(edge)
                    if newEdge is not None:
                        outedges.append(newEdge)
                        p0.z = self.ignoreAbove
                    if cont:
                        continue

                    plungelen = abs(p0.z - p1.z)
                    projectionlen = plungelen * math.tan(
                        math.radians(rampangle)
                    )  # length of the forthcoming ramp projected to XY plane
                    PathLog.debug(
                        "Found plunge move at X:{} Y:{} From Z:{} to Z{}, length of ramp: {}"
                        .format(p0.x, p0.y, p0.z, p1.z, projectionlen))
                    if self.method == 'RampMethod3':
                        projectionlen = projectionlen / 2

                    # next need to determine how many edges in the path after
                    # plunge are needed to cover the length:
                    covered = False
                    coveredlen = 0
                    rampedges = []
                    i = edges.index(edge) + 1
                    while not covered:
                        candidate = edges[i]
                        cp0 = candidate.Vertexes[0].Point
                        cp1 = candidate.Vertexes[1].Point
                        if abs(cp0.z - cp1.z) > 1e-6:
                            # this edge is not parallel to XY plane, not qualified for ramping.
                            break
                        # PathLog.debug("Next edge length {}".format(candidate.Length))
                        rampedges.append(candidate)
                        coveredlen = coveredlen + candidate.Length

                        if coveredlen > projectionlen:
                            covered = True
                        i = i + 1
                        if i >= len(edges):
                            break
                    if len(rampedges) == 0:
                        PathLog.debug(
                            "No suitable edges for ramping, plunge will remain as such"
                        )
                        outedges.append(edge)
                    else:
                        if not covered:
                            if (not allowBounce
                                ) or self.method == 'RampMethod2':
                                l = 0
                                for redge in rampedges:
                                    l = l + redge.Length
                                if self.method == 'RampMethod3':
                                    rampangle = math.degrees(
                                        math.atan(l / (plungelen / 2)))
                                else:
                                    rampangle = math.degrees(
                                        math.atan(l / plungelen))
                                PathLog.warning(
                                    "Cannot cover with desired angle, tightening angle to: {}"
                                    .format(rampangle))

                        # PathLog.debug("Doing ramp to edges: {}".format(rampedges))
                        if self.method == 'RampMethod1':
                            outedges.extend(
                                self.createRampMethod1(rampedges, p0,
                                                       projectionlen,
                                                       rampangle))
                        elif self.method == 'RampMethod2':
                            outedges.extend(
                                self.createRampMethod2(rampedges, p0,
                                                       projectionlen,
                                                       rampangle))
                        else:
                            # if the ramp cannot be covered with Method3, revert to Method1
                            # because Method1 support going back-and-forth and thus results in same path as Method3 when
                            # length of the ramp is smaller than needed for single ramp.
                            if (not covered) and allowBounce:
                                projectionlen = projectionlen * 2
                                outedges.extend(
                                    self.createRampMethod1(
                                        rampedges, p0, projectionlen,
                                        rampangle))
                            else:
                                outedges.extend(
                                    self.createRampMethod3(
                                        rampedges, p0, projectionlen,
                                        rampangle))
                else:
                    outedges.append(edge)
            else:
                outedges.append(edge)
        return outedges
Esempio n. 8
0
    def areaOpShapes(self, obj):
        '''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.'''
        PathLog.track()
        PathLog.debug("----- areaOpShapes() in PathProfileFaces.py")

        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 = []
        self.profileshape = []
        finalDepths = []
        startDepths = []
        faceDepths = []

        baseSubsTuples = []
        subCount = 0
        allTuples = []

        if obj.Base:  # The user has selected subobjects from the base.  Process each.
            if obj.EnableRotation != 'Off':
                for p in range(0, len(obj.Base)):
                    (base, subsList) = obj.Base[p]
                    for sub in subsList:
                        subCount += 1
                        shape = getattr(base.Shape, sub)
                        if isinstance(shape, Part.Face):
                            rtn = False
                            (norm, surf) = self.getFaceNormAndSurf(shape)
                            (rtn, angle, axis,
                             praInfo) = self.faceRotationAnalysis(
                                 obj, norm, surf)
                            if rtn is True:
                                (clnBase, angle, clnStock,
                                 tag) = self.applyRotationalAnalysis(
                                     obj, base, angle, axis, subCount)
                                # Verify faces are correctly oriented - InverseAngle might be necessary
                                faceIA = getattr(clnBase.Shape, sub)
                                (norm, surf) = self.getFaceNormAndSurf(faceIA)
                                (rtn, praAngle, praAxis,
                                 praInfo) = self.faceRotationAnalysis(
                                     obj, norm, surf)
                                if rtn is True:
                                    PathLog.error(
                                        translate(
                                            "Path",
                                            "Face appears misaligned after initial rotation."
                                        ))
                                    if obj.AttemptInverseAngle is True and obj.InverseAngle is False:
                                        (clnBase, clnStock,
                                         angle) = self.applyInverseAngle(
                                             obj, clnBase, clnStock, axis,
                                             angle)
                                    else:
                                        msg = translate(
                                            "Path",
                                            "Consider toggling the 'InverseAngle' property and recomputing."
                                        )
                                        PathLog.error(msg)
                                        # title = translate("Path", 'Rotation Warning')
                                        # self.guiMessage(title, msg, False)
                                else:
                                    PathLog.debug(
                                        "Face appears to be oriented correctly."
                                    )

                                tup = clnBase, sub, tag, angle, axis, clnStock
                            else:
                                if self.warnDisabledAxis(obj, axis) is False:
                                    PathLog.debug(
                                        str(sub) + ": No rotation used")
                                axis = 'X'
                                angle = 0.0
                                tag = base.Name + '_' + axis + str(
                                    angle).replace('.', '_')
                                stock = PathUtils.findParentJob(obj).Stock
                                tup = base, sub, tag, angle, axis, stock
                            # Eif
                            allTuples.append(tup)
                        # Eif
                    # Efor
                # Efor
                if subCount > 1:
                    msg = translate('Path',
                                    "Multiple faces in Base Geometry.") + "  "
                    msg += translate(
                        'Path', "Depth settings will be applied to all faces.")
                    PathLog.warning(msg)
                    # title = translate("Path", "Depth Warning")
                    # self.guiMessage(title, msg)
                (Tags, Grps) = self.sortTuplesByIndex(
                    allTuples, 2)  # return (TagList, GroupList)
                subList = []
                for o in range(0, len(Tags)):
                    subList = []
                    for (base, sub, tag, angle, axis, stock) in Grps[o]:
                        subList.append(sub)
                    pair = base, subList, angle, axis, stock
                    baseSubsTuples.append(pair)
                # Efor
            else:
                PathLog.info(
                    translate("Path", "EnableRotation property is 'Off'."))
                stock = PathUtils.findParentJob(obj).Stock
                for (base, subList) in obj.Base:
                    baseSubsTuples.append((base, subList, 0.0, 'X', stock))

            # for base in obj.Base:
            finish_step = obj.FinishDepth.Value if hasattr(
                obj, "FinishDepth") else 0.0
            for (base, subsList, angle, axis, stock) in baseSubsTuples:
                holes = []
                faces = []
                faceDepths = []
                startDepths = []

                for sub in subsList:
                    shape = getattr(base.Shape, sub)
                    if isinstance(shape, Part.Face):
                        faces.append(shape)
                        if numpy.isclose(abs(shape.normalAt(0, 0).z),
                                         1):  # horizontal face
                            for wire in shape.Wires[1:]:
                                holes.append((base.Shape, wire))
                        # Add face depth to list
                        faceDepths.append(shape.BoundBox.ZMin)
                    else:
                        ignoreSub = base.Name + '.' + sub
                        msg = translate(
                            'Path',
                            "Found a selected object which is not a face. Ignoring: {}"
                            .format(ignoreSub))
                        PathLog.error(msg)
                        FreeCAD.Console.PrintWarning(msg)

                # Raise FinalDepth to lowest face in list on Inside profile ops
                finDep = obj.FinalDepth.Value
                if obj.Side == 'Inside':
                    finDep = min(faceDepths)
                finalDepths.append(finDep)

                strDep = obj.StartDepth.Value
                if strDep > stock.Shape.BoundBox.ZMax:
                    strDep = stock.Shape.BoundBox.ZMax
                startDepths.append(strDep)

                # Recalculate depthparams
                self.depthparams = PathUtils.depth_params(
                    clearance_height=obj.ClearanceHeight.Value,
                    safe_height=obj.SafeHeight.Value,
                    start_depth=strDep,  # obj.StartDepth.Value,
                    step_down=obj.StepDown.Value,
                    z_finish_step=finish_step,
                    final_depth=finDep,  # obj.FinalDepth.Value,
                    user_depths=None)

                for shape, wire in holes:
                    f = Part.makeFace(wire, 'Part::FaceMakerSimple')
                    drillable = PathUtils.isDrillable(shape, wire)
                    if (drillable
                            and obj.processCircles) or (not drillable
                                                        and obj.processHoles):
                        PathLog.track()
                        env = PathUtils.getEnvelope(
                            shape, subshape=f, depthparams=self.depthparams)
                        # shapes.append((env, True))
                        tup = env, True, 'pathProfileFaces', angle, axis, strDep, finDep
                        shapes.append(tup)

                if len(faces) > 0:
                    profileshape = Part.makeCompound(faces)
                    self.profileshape.append(profileshape)

                if obj.processPerimeter:
                    PathLog.track()
                    try:
                        env = PathUtils.getEnvelope(
                            base.Shape,
                            subshape=profileshape,
                            depthparams=self.depthparams)
                    except Exception:
                        # PathUtils.getEnvelope() failed to return an object.
                        PathLog.error(
                            translate('Path',
                                      'Unable to create path for face(s).'))
                    else:
                        # shapes.append((env, False))
                        tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep
                        shapes.append(tup)
                else:
                    for shape in faces:
                        finalDep = finDep
                        # Recalculate depthparams
                        if obj.Side == 'Inside':
                            if finalDep < shape.BoundBox.ZMin:
                                custDepthparams = PathUtils.depth_params(
                                    clearance_height=obj.ClearanceHeight.Value,
                                    safe_height=obj.SafeHeight.Value,
                                    start_depth=strDep,  # obj.StartDepth.Value,
                                    step_down=obj.StepDown.Value,
                                    z_finish_step=finish_step,
                                    final_depth=shape.BoundBox.
                                    ZMin,  # obj.FinalDepth.Value,
                                    user_depths=None)
                                env = PathUtils.getEnvelope(
                                    base.Shape,
                                    subshape=shape,
                                    depthparams=custDepthparams)
                                finalDep = shape.BoundBox.ZMin
                            else:
                                env = PathUtils.getEnvelope(
                                    base.Shape,
                                    subshape=shape,
                                    depthparams=self.depthparams)
                        else:
                            env = PathUtils.getEnvelope(
                                base.Shape,
                                subshape=shape,
                                depthparams=self.depthparams)
                        tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep
                        shapes.append(tup)
                # Eif

            # adjust Start/Final Depths as needed
            # Raise existing Final Depth to level of lowest profile face
            if obj.Side == 'Inside':
                finalDepth = min(finalDepths)
                if obj.FinalDepth.Value < finalDepth:
                    obj.FinalDepth.Value = finalDepth
            # Lower high Start Depth to top of Stock
            startDepth = max(startDepths)
            if obj.StartDepth.Value > startDepth:
                obj.StartDepth.Value = startDepth
        else:  # Try to build targets from the job base
            if 1 == len(self.model):
                if hasattr(self.model[0], "Proxy"):
                    PathLog.info("hasattr() Proxy")
                    if isinstance(self.model[0].Proxy,
                                  ArchPanel.PanelSheet):  # process the sheet
                        if obj.processCircles or obj.processHoles:
                            for shape in self.model[0].Proxy.getHoles(
                                    self.model[0], transform=True):
                                for wire in shape.Wires:
                                    drillable = PathUtils.isDrillable(
                                        self.model[0].Proxy, wire)
                                    if (drillable and obj.processCircles) or (
                                            not drillable
                                            and obj.processHoles):
                                        f = Part.makeFace(
                                            wire, 'Part::FaceMakerSimple')
                                        env = PathUtils.getEnvelope(
                                            self.model[0].Shape,
                                            subshape=f,
                                            depthparams=self.depthparams)
                                        # shapes.append((env, True))
                                        tup = env, True, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
                                        shapes.append(tup)

                        if obj.processPerimeter:
                            for shape in self.model[0].Proxy.getOutlines(
                                    self.model[0], transform=True):
                                for wire in shape.Wires:
                                    f = Part.makeFace(wire,
                                                      'Part::FaceMakerSimple')
                                    env = PathUtils.getEnvelope(
                                        self.model[0].Shape,
                                        subshape=f,
                                        depthparams=self.depthparams)
                                    # shapes.append((env, False))
                                    tup = env, False, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
                                    shapes.append(tup)

        self.removalshapes = shapes
        PathLog.debug("%d shapes" % len(shapes))

        return shapes
Esempio n. 9
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
Esempio n. 10
0
def isDrillable(obj, candidate, tooldiameter=None, includePartials=False):
    """
    Checks candidates to see if they can be drilled.
    Candidates can be either faces - circular or cylindrical or circular edges.
    The tooldiameter can be optionally passed.  if passed, the check will return
    False for any holes smaller than the tooldiameter.
    obj=Shape
    candidate = Face or Edge
    tooldiameter=float
    """
    PathLog.track('obj: {} candidate: {} tooldiameter {}'.format(obj, candidate, tooldiameter))
    drillable = False
    try:
        if candidate.ShapeType == 'Face':
            face = candidate
            # eliminate flat faces
            if (round(face.ParameterRange[0], 8) == 0.0) and (round(face.ParameterRange[1], 8) == round(math.pi * 2, 8)):
                for edge in face.Edges:  # Find seam edge and check if aligned to Z axis.
                    if (isinstance(edge.Curve, Part.Line)):
                        PathLog.debug("candidate is a circle")
                        v0 = edge.Vertexes[0].Point
                        v1 = edge.Vertexes[1].Point
                        #check if the cylinder seam is vertically aligned.  Eliminate tilted holes
                        if (numpy.isclose(v1.sub(v0).x, 0, rtol=1e-05, atol=1e-06)) and \
                                (numpy.isclose(v1.sub(v0).y, 0, rtol=1e-05, atol=1e-06)):
                            drillable = True
                            # vector of top center
                            lsp = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMax)
                            # vector of bottom center
                            lep = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMin)
                            # check if the cylindrical 'lids' are inside the base
                            # object.  This eliminates extruded circles but allows
                            # actual holes.
                            if obj.isInside(lsp, 1e-6, False) or obj.isInside(lep, 1e-6, False):
                                PathLog.track("inside check failed. lsp: {}  lep: {}".format(lsp,lep))
                                drillable = False
                            # eliminate elliptical holes
                            elif not hasattr(face.Surface, "Radius"):
                                PathLog.debug("candidate face has no radius attribute")
                                drillable = False
                            else:
                                if tooldiameter is not None:
                                    drillable = face.Surface.Radius >= tooldiameter/2
                                else:
                                    drillable = True
            elif type(face.Surface) == Part.Plane and PathGeom.pointsCoincide(face.Surface.Axis, FreeCAD.Vector(0,0,1)):
                if len(face.Edges) == 1 and type(face.Edges[0].Curve) == Part.Circle:
                    center = face.Edges[0].Curve.Center
                    if obj.isInside(center, 1e-6, False):
                        if tooldiameter is not None:
                            drillable = face.Edges[0].Curve.Radius >= tooldiameter/2
                        else:
                            drillable = True
        else:
            for edge in candidate.Edges:
                if isinstance(edge.Curve, Part.Circle) and (includePartials or edge.isClosed()):
                    PathLog.debug("candidate is a circle or ellipse")
                    if not hasattr(edge.Curve, "Radius"):
                        PathLog.debug("No radius.  Ellipse.")
                        drillable = False
                    else:
                        PathLog.debug("Has Radius, Circle")
                        if tooldiameter is not None:
                            drillable = edge.Curve.Radius >= tooldiameter/2
                            if not drillable:
                                FreeCAD.Console.PrintMessage(
                                        "Found a drillable hole with diameter: {}: "
                                        "too small for the current tool with "
                                        "diameter: {}".format(edge.Curve.Radius*2, tooldiameter))
                        else:
                            drillable = True
        PathLog.debug("candidate is drillable: {}".format(drillable))
    except Exception as ex:
        PathLog.warning(translate("PathUtils", "Issue determine drillability: {}").format(ex))
    return drillable
Esempio n. 11
0
    def process_nonloop_sublist(self, obj, base, sub):
        '''process_nonloop_sublist(obj, sub)...
        Process sublist with non-looped set of features when rotation is enabled.
        '''

        rtn = False
        face = base.Shape.getElement(sub)

        if sub[:4] != 'Face':
            if face.ShapeType == 'Edge':
                edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([face])))
                face = edgToFace
            else:
                ignoreSub = base.Name + '.' + sub
                PathLog.error(
                    translate(
                        'Path',
                        "Selected feature is not a Face. Ignoring: {}".format(
                            ignoreSub)))
                return False

        (norm, surf) = self.getFaceNormAndSurf(face)
        (rtn, angle, axis,
         praInfo) = self.faceRotationAnalysis(obj, norm, surf)  # pylint: disable=unused-variable
        PathLog.debug("initial rotational analysis: {}".format(praInfo))

        clnBase = base
        faceIA = clnBase.Shape.getElement(sub)
        if faceIA.ShapeType == 'Edge':
            edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([faceIA])))
            faceIA = edgToFace

        if rtn is True:
            faceNum = sub.replace('Face', '')
            PathLog.debug("initial applyRotationalAnalysis")
            (clnBase, angle, clnStock,
             tag) = self.applyRotationalAnalysis(obj, base, angle, axis,
                                                 faceNum)
            # Verify faces are correctly oriented - InverseAngle might be necessary
            faceIA = clnBase.Shape.getElement(sub)
            if faceIA.ShapeType == 'Edge':
                edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([faceIA])))
                faceIA = edgToFace

            (norm, surf) = self.getFaceNormAndSurf(faceIA)
            (rtn, praAngle, praAxis,
             praInfo2) = self.faceRotationAnalysis(obj, norm, surf)  # pylint: disable=unused-variable
            PathLog.debug("follow-up rotational analysis: {}".format(praInfo2))

            isFaceUp = self.isFaceUp(clnBase, faceIA)
            PathLog.debug('... initial isFaceUp: {}'.format(isFaceUp))

            if isFaceUp:
                rtn = False
                PathLog.debug('returning analysis: {}, {}'.format(
                    praAngle, praAxis))
                return (clnBase, [sub], angle, axis, clnStock)

            if round(abs(praAngle), 8) == 180.0:
                rtn = False
                if not isFaceUp:
                    PathLog.debug('initial isFaceUp is False')
                    angle = 0.0
        # Eif

        if rtn:
            # initial rotation failed, attempt inverse rotation if user requests it
            PathLog.debug(
                translate("Path",
                          "Face appears misaligned after initial rotation.") +
                ' 2')
            if obj.AttemptInverseAngle:
                PathLog.debug(
                    translate("Path", "Applying inverse angle automatically."))
                (clnBase, clnStock,
                 angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis,
                                                 angle)
            else:
                if obj.InverseAngle:
                    PathLog.debug(
                        translate("Path", "Applying inverse angle manually."))
                    (clnBase, clnStock,
                     angle) = self.applyInverseAngle(obj, clnBase, clnStock,
                                                     axis, angle)
                else:
                    msg = translate(
                        "Path",
                        "Consider toggling the 'InverseAngle' property and recomputing."
                    )
                    PathLog.warning(msg)

            faceIA = clnBase.Shape.getElement(sub)
            if faceIA.ShapeType == 'Edge':
                edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([faceIA])))
                faceIA = edgToFace
            if not self.isFaceUp(clnBase, faceIA):
                angle += 180.0

            # Normalize rotation angle
            if angle < 0.0:
                angle += 360.0
            elif angle > 360.0:
                angle -= 360.0

            return (clnBase, [sub], angle, axis, clnStock)

        if not self.warnDisabledAxis(obj, axis):
            PathLog.debug(str(sub) + ": No rotation used")
        axis = 'X'
        angle = 0.0
        stock = PathUtils.findParentJob(obj).Stock
        return (base, [sub], angle, axis, stock)
def warn(msg):
    PathLog.warning(msg)
Esempio n. 13
0
    def _extractPathWire(self, obj, base, flatWire, cutShp):
        PathLog.debug('_extractPathWire()')

        subLoops = list()
        rtnWIRES = list()
        osWrIdxs = list()
        subDistFactor = 1.0  # Raise to include sub wires at greater distance from original
        fdv = obj.FinalDepth.Value
        wire = flatWire
        lstVrtIdx = len(wire.Vertexes) - 1
        lstVrt = wire.Vertexes[lstVrtIdx]
        frstVrt = wire.Vertexes[0]
        cent0 = FreeCAD.Vector(frstVrt.X, frstVrt.Y, fdv)
        cent1 = FreeCAD.Vector(lstVrt.X, lstVrt.Y, fdv)

        pl = FreeCAD.Placement()
        pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0)
        pl.Base = FreeCAD.Vector(0, 0, 0)

        # Calculate offset shape, containing cut region
        ofstShp = self._extractFaceOffset(obj, cutShp, False)

        # CHECK for ZERO area of offset shape
        try:
            osArea = ofstShp.Area
        except Exception as ee:
            PathLog.error('No area to offset shape returned.')
            return False

        if PathLog.getLevel(PathLog.thisModule()) == 4:
            os = FreeCAD.ActiveDocument.addObject('Part::Feature',
                                                  'tmpOffsetShape')
            os.Shape = ofstShp
            os.recompute()
            os.purgeTouched()
            self.tmpGrp.addObject(os)

        numOSWires = len(ofstShp.Wires)
        for w in range(0, numOSWires):
            osWrIdxs.append(w)

        # Identify two vertexes for dividing offset loop
        NEAR0 = self._findNearestVertex(ofstShp, cent0)
        min0i = 0
        min0 = NEAR0[0][4]
        for n in range(0, len(NEAR0)):
            N = NEAR0[n]
            if N[4] < min0:
                min0 = N[4]
                min0i = n
        (w0, vi0, pnt0, vrt0, d0) = NEAR0[0]  # min0i
        if PathLog.getLevel(PathLog.thisModule()) == 4:
            near0 = FreeCAD.ActiveDocument.addObject('Part::Feature',
                                                     'tmpNear0')
            near0.Shape = Part.makeLine(cent0, pnt0)
            near0.recompute()
            near0.purgeTouched()
            self.tmpGrp.addObject(near0)

        NEAR1 = self._findNearestVertex(ofstShp, cent1)
        min1i = 0
        min1 = NEAR1[0][4]
        for n in range(0, len(NEAR1)):
            N = NEAR1[n]
            if N[4] < min1:
                min1 = N[4]
                min1i = n
        (w1, vi1, pnt1, vrt1, d1) = NEAR1[0]  # min1i
        if PathLog.getLevel(PathLog.thisModule()) == 4:
            near1 = FreeCAD.ActiveDocument.addObject('Part::Feature',
                                                     'tmpNear1')
            near1.Shape = Part.makeLine(cent1, pnt1)
            near1.recompute()
            near1.purgeTouched()
            self.tmpGrp.addObject(near1)

        if w0 != w1:
            PathLog.warning(
                'Offset wire endpoint indexes are not equal - w0, w1: {}, {}'.
                format(w0, w1))

        if PathLog.getLevel(PathLog.thisModule()) == 4:
            PathLog.debug('min0i is {}.'.format(min0i))
            PathLog.debug('min1i is {}.'.format(min1i))
            PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0]))
            PathLog.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1]))
            PathLog.debug('NEAR0 is {}.'.format(NEAR0))
            PathLog.debug('NEAR1 is {}.'.format(NEAR1))

        mainWire = ofstShp.Wires[w0]

        # Check for additional closed loops in offset wire by checking distance to iTAG or eTAG elements
        if numOSWires > 1:
            # check all wires for proximity(children) to intersection tags
            tagsComList = list()
            for T in self.cutSideTags.Faces:
                tcom = T.CenterOfMass
                tv = FreeCAD.Vector(tcom.x, tcom.y, 0.0)
                tagsComList.append(tv)
            subDist = self.ofstRadius * subDistFactor
            for w in osWrIdxs:
                if w != w0:
                    cutSub = False
                    VTXS = ofstShp.Wires[w].Vertexes
                    for V in VTXS:
                        v = FreeCAD.Vector(V.X, V.Y, 0.0)
                        for t in tagsComList:
                            if t.sub(v).Length < subDist:
                                cutSub = True
                                break
                        if cutSub is True:
                            break
                    if cutSub is True:
                        sub = Part.Wire(
                            Part.__sortEdges__(ofstShp.Wires[w].Edges))
                        subLoops.append(sub)
                # Eif

        # Break offset loop into two wires - one of which is the desired profile path wire.
        (edgeIdxs0,
         edgeIdxs1) = self._separateWireAtVertexes(mainWire,
                                                   mainWire.Vertexes[vi0],
                                                   mainWire.Vertexes[vi1])
        edgs0 = list()
        edgs1 = list()
        for e in edgeIdxs0:
            edgs0.append(mainWire.Edges[e])
        for e in edgeIdxs1:
            edgs1.append(mainWire.Edges[e])
        part0 = Part.Wire(Part.__sortEdges__(edgs0))
        part1 = Part.Wire(Part.__sortEdges__(edgs1))

        # Determine which part is nearest original edge(s)
        distToPart0 = self._distMidToMid(wire.Wires[0], part0.Wires[0])
        distToPart1 = self._distMidToMid(wire.Wires[0], part1.Wires[0])
        if distToPart0 < distToPart1:
            rtnWIRES.append(part0)
        else:
            rtnWIRES.append(part1)
        rtnWIRES.extend(subLoops)

        return rtnWIRES
        def clasifySub(self, bs, sub):
            face = bs.Shape.getElement(sub)

            if type(face.Surface) == Part.Plane:
                PathLog.debug('type() == Part.Plane')
                if PathGeom.isVertical(face.Surface.Axis):
                    PathLog.debug('  -isVertical()')
                    # it's a flat horizontal face
                    self.horiz.append(face)
                    return True
                elif PathGeom.isHorizontal(face.Surface.Axis):
                    PathLog.debug('  -isHorizontal()')
                    self.vert.append(face)
                    return True
                else:
                    return False
            elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(
                    face.Surface.Axis):
                PathLog.debug('type() == Part.Cylinder')
                # vertical cylinder wall
                if any(e.isClosed() for e in face.Edges):
                    PathLog.debug('  -e.isClosed()')
                    # complete cylinder
                    circle = Part.makeCircle(face.Surface.Radius,
                                             face.Surface.Center)
                    disk = Part.Face(Part.Wire(circle))
                    disk.translate(
                        FreeCAD.Vector(0, 0, face.BoundBox.ZMin -
                                       disk.BoundBox.ZMin))
                    self.horiz.append(disk)
                    return True
                else:
                    PathLog.debug('  -none isClosed()')
                    # partial cylinder wall
                    self.vert.append(face)
                    return True
            elif type(face.Surface) == Part.SurfaceOfExtrusion:
                # extrusion wall
                PathLog.debug('type() == Part.SurfaceOfExtrusion')
                # Attempt to extract planar face from surface of extrusion
                (planar, useFace) = planarFaceFromExtrusionEdges(face,
                                                                 trans=True)
                # Save face object to self.horiz for processing or display error
                if planar is True:
                    uFace = FreeCAD.ActiveDocument.getObject(useFace)
                    self.horiz.append(uFace.Shape.Faces[0])
                    msg = translate(
                        'Path',
                        "<b>Verify depth of pocket for '{}'.</b>".format(sub))
                    msg += translate(
                        'Path', "\n<br>Pocket is based on extruded surface.")
                    msg += translate(
                        'Path',
                        "\n<br>Bottom of pocket might be non-planar and/or not normal to spindle axis."
                    )
                    msg += translate(
                        'Path',
                        "\n<br>\n<br><i>3D pocket bottom is NOT available in this operation</i>."
                    )
                    PathLog.warning(msg)
                    # title = translate('Path', 'Depth Warning')
                    # self.guiMessage(title, msg, False)
                else:
                    PathLog.error(
                        translate(
                            "Path",
                            "Failed to create a planar face from edges in {}.".
                            format(sub)))
            else:
                PathLog.debug('  -type(face.Surface): {}'.format(
                    type(face.Surface)))
                return False
Esempio n. 15
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
        faceType = 0

        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
        sdi = len(starts) - 1
        fdi = len(finals) - 1
        cbi = len(cuts) - 1
        pocket = (cuts[cbi], False, '3DPocket', 0.0, 'X', starts[sdi],
                  finals[fdi])
        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
Esempio n. 16
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)
                        obj.removalshape = env.cut(base[0].Shape)
                        # obj.removalshape.tessellate(0.1)
                        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)
                        obj.removalshape = env.cut(base[0].Shape)
                        # obj.removalshape.tessellate(0.1)
                        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)

                    obj.removalshape = stockEnvShape.cut(base.Shape)
                    # obj.removalshape.tessellate(0.1)
                else:
                    env = PathUtils.getEnvelope(base.Shape,
                                                subshape=None,
                                                depthparams=self.depthparams)
                    obj.removalshape = env.cut(base.Shape)
                    # obj.removalshape.tessellate(0.1)

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

        return removalshapes
Esempio n. 17
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.OpFinalDepth == obj.FinalDepth:
                        obj.FinalDepth.Value = fzmin
                        finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
                        self.depthparams = PathUtils.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=finish_step,
                            final_depth=fzmin,
                            user_depths=None)
                        PathLog.info("Updated obj.FinalDepth.Value and self.depthparams to zmin: {}".format(fzmin))
                    '''

                    if obj.AdaptivePocketStart is True or obj.AdaptivePocketFinish is True:
                        pocketTup = self.calculateAdaptivePocket(
                            obj, base, subObjTups)
                        if pocketTup is not False:
                            removalshapes.append(
                                pocketTup
                            )  # (shape, isHole, sub, angle, axis, strDep, finDep)
                    else:
                        strDep = obj.StartDepth.Value
                        finDep = obj.FinalDepth.Value

                        shape = Part.makeCompound(Faces)
                        env = PathUtils.getEnvelope(
                            base[0].Shape,
                            subshape=shape,
                            depthparams=self.depthparams)
                        obj.removalshape = env.cut(base[0].Shape)
                        obj.removalshape.tessellate(0.1)
                        # (shape, isHole, sub, angle, axis, strDep, finDep)
                        removalshapes.append(
                            (obj.removalshape, False, '3DPocket', 0.0, 'X',
                             strDep, finDep))
                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)
                        obj.removalshape = env.cut(base[0].Shape)
                        obj.removalshape.tessellate(0.1)

                        removalshapes.append((obj.removalshape, False))

        else:  # process the job base object as a whole
            PathLog.debug("processing the whole job base object")
            strDep = obj.StartDepth.Value
            finDep = obj.FinalDepth.Value
            # recomputeDepthparams = False
            for base in self.model:
                '''
                if obj.OpFinalDepth == obj.FinalDepth:
                    if base.Shape.BoundBox.ZMin < obj.FinalDepth.Value:
                        obj.FinalDepth.Value = base.Shape.BoundBox.ZMin
                        finDep = base.Shape.BoundBox.ZMin
                        recomputeDepthparams = True
                        PathLog.info("Updated obj.FinalDepth.Value to {}".format(finDep))
                if obj.OpStartDepth == obj.StartDepth:
                    if base.Shape.BoundBox.ZMax > obj.StartDepth.Value:
                        obj.StartDepth.Value = base.Shape.BoundBox.ZMax
                        finDep = base.Shape.BoundBox.ZMax
                        recomputeDepthparams = True
                        PathLog.info("Updated obj.StartDepth.Value to {}".format(strDep))
                if recomputeDepthparams is True:
                    finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
                    self.depthparams = PathUtils.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=finish_step,
                        final_depth=obj.FinalDepth.Value,
                        user_depths=None)
                    recomputeDepthparams = False
                '''

                if obj.ProcessStockArea is True:
                    job = PathUtils.findParentJob(obj)
                    '''
                    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=obj.StartDepth.Value,
                        step_down=obj.StepDown.Value,
                        z_finish_step=finish_step,
                        final_depth=base.Shape.BoundBox.ZMin,
                        user_depths=None)
                    stockEnvShape = PathUtils.getEnvelope(job.Stock.Shape, subshape=None, depthparams=depthparams)
                    '''
                    stockEnvShape = PathUtils.getEnvelope(
                        job.Stock.Shape,
                        subshape=None,
                        depthparams=self.depthparams)

                    obj.removalshape = stockEnvShape.cut(base.Shape)
                    obj.removalshape.tessellate(0.1)
                else:
                    env = PathUtils.getEnvelope(base.Shape,
                                                subshape=None,
                                                depthparams=self.depthparams)
                    obj.removalshape = env.cut(base.Shape)
                    obj.removalshape.tessellate(0.1)

                removalshapes.append((obj.removalshape, False, '3DPocket', 0.0,
                                      'X', strDep, finDep))

        return removalshapes
    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)
Esempio n. 19
0
    def execute(self, obj):
        if not obj.Base or not obj.Base.isDerivedFrom(
                'Path::Feature') or not obj.Base.Path:
            return

        tc = PathDressup.toolController(obj.Base)

        if len(obj.Base.Path.Commands) > 0:
            self.safeHeight = float(PathUtil.opProperty(
                obj.Base, 'SafeHeight'))
            self.clearanceHeight = float(
                PathUtil.opProperty(obj.Base, 'ClearanceHeight'))

            boundary = obj.Stock.Shape
            cmd = obj.Base.Path.Commands[0]
            pos = cmd.Placement.Base
            commands = [cmd]
            lastExit = None
            for cmd in obj.Base.Path.Commands[1:]:
                if cmd.Name in PathGeom.CmdMoveAll:
                    edge = PathGeom.edgeForCmd(cmd, pos)
                    if edge:
                        inside = edge.common(boundary).Edges
                        outside = edge.cut(boundary).Edges
                        if not obj.Inside:
                            t = inside
                            inside = outside
                            outside = t
                        # 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:
                                commands.extend(
                                    self.boundaryCommands(
                                        obj, 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]
                                    ptL = e.valueAt(e.LastParameter)
                                    flip = PathGeom.pointsCoincide(pos, ptL)
                                    newPos = e.valueAt(
                                        e.FirstParameter) if flip else ptL
                                    # inside edges are taken at this point (see swap of inside/outside
                                    # above - so we can just connect the dots ...
                                    if lastExit:
                                        commands.extend(
                                            self.boundaryCommands(
                                                obj, lastExit, pos,
                                                tc.VertFeed.Value))
                                        lastExit = None
                                    PathLog.track(e, flip)
                                    commands.extend(
                                        PathGeom.cmdsForEdge(
                                            e, flip, False, 50,
                                            tc.HorizFeed.Value,
                                            tc.VertFeed.Value)
                                    )  # add missing HorizFeed to G2 paths
                                    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(obj, lastExit, None,
                                          tc.VertFeed.Value))
                lastExit = None
        else:
            PathLog.warning("No Path Commands for %s" % obj.Base.Label)
            commands = []
        PathLog.track(commands)
        obj.Path = Path.Path(commands)
Esempio n. 20
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
Esempio n. 21
0
    def areaOpShapes(self, obj):
        '''areaOpShapes(obj) ... return shapes representing the solids to be removed.'''
        PathLog.track()
        PathLog.debug("----- areaOpShapes() in PathPocketShape.py")

        baseSubsTuples = []
        subCount = 0
        allTuples = []

        def planarFaceFromExtrusionEdges(face, trans):
            useFace = 'useFaceName'
            minArea = 0.0
            fCnt = 0
            clsd = []
            planar = False
            # Identify closed edges
            for edg in face.Edges:
                if edg.isClosed():
                    PathLog.debug('  -e.isClosed()')
                    clsd.append(edg)
                    planar = True

            # Attempt to create planar faces and select that with smallest area for use as pocket base
            if planar is True:
                planar = False
                for edg in clsd:
                    fCnt += 1
                    fName = sub + '_face_' + str(fCnt)
                    # Create planar face from edge
                    mFF = Part.Face(Part.Wire(Part.__sortEdges__([edg])))
                    if mFF.isNull():
                        PathLog.debug('Face(Part.Wire()) failed')
                    else:
                        if trans is True:
                            mFF.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - mFF.BoundBox.ZMin))

                        if FreeCAD.ActiveDocument.getObject(fName):
                            FreeCAD.ActiveDocument.removeObject(fName)

                        tmpFace = FreeCAD.ActiveDocument.addObject('Part::Feature', fName).Shape = mFF
                        tmpFace = FreeCAD.ActiveDocument.getObject(fName)
                        tmpFace.purgeTouched()

                        if minArea == 0.0:
                            minArea = tmpFace.Shape.Face1.Area
                            useFace = fName
                            planar = True
                        elif tmpFace.Shape.Face1.Area < minArea:
                            minArea = tmpFace.Shape.Face1.Area
                            FreeCAD.ActiveDocument.removeObject(useFace)
                            useFace = fName
                        else:
                            FreeCAD.ActiveDocument.removeObject(fName)

            if useFace != 'useFaceName':
                self.useTempJobClones(useFace)

            return (planar, useFace)

        def clasifySub(self, bs, sub):
            face = bs.Shape.getElement(sub)

            if type(face.Surface) == Part.Plane:
                PathLog.debug('type() == Part.Plane')
                if PathGeom.isVertical(face.Surface.Axis):
                    PathLog.debug('  -isVertical()')
                    # it's a flat horizontal face
                    self.horiz.append(face)
                    return True

                elif PathGeom.isHorizontal(face.Surface.Axis):
                    PathLog.debug('  -isHorizontal()')
                    self.vert.append(face)
                    return True

                else:
                    return False

            elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(face.Surface.Axis):
                PathLog.debug('type() == Part.Cylinder')
                # vertical cylinder wall
                if any(e.isClosed() for e in face.Edges):
                    PathLog.debug('  -e.isClosed()')
                    # complete cylinder
                    circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center)
                    disk = Part.Face(Part.Wire(circle))
                    disk.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - disk.BoundBox.ZMin))
                    self.horiz.append(disk)
                    return True

                else:
                    PathLog.debug('  -none isClosed()')
                    # partial cylinder wall
                    self.vert.append(face)
                    return True

            elif type(face.Surface) == Part.SurfaceOfExtrusion:
                # extrusion wall
                PathLog.debug('type() == Part.SurfaceOfExtrusion')
                # Attempt to extract planar face from surface of extrusion
                (planar, useFace) = planarFaceFromExtrusionEdges(face, trans=True)
                # Save face object to self.horiz for processing or display error
                if planar is True:
                    uFace = FreeCAD.ActiveDocument.getObject(useFace)
                    self.horiz.append(uFace.Shape.Faces[0])
                    msg = translate('Path', "<b>Verify depth of pocket for '{}'.</b>".format(sub))
                    msg += translate('Path', "\n<br>Pocket is based on extruded surface.")
                    msg += translate('Path', "\n<br>Bottom of pocket might be non-planar and/or not normal to spindle axis.")
                    msg += translate('Path', "\n<br>\n<br><i>3D pocket bottom is NOT available in this operation</i>.")
                    PathLog.warning(msg)
                    # title = translate('Path', 'Depth Warning')
                    # self.guiMessage(title, msg, False)
                else:
                    PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub)))

            else:
                PathLog.debug('  -type(face.Surface): {}'.format(type(face.Surface)))
                return False

        if obj.Base:
            PathLog.debug('Processing... obj.Base')
            self.removalshapes = []  # pylint: disable=attribute-defined-outside-init

            if obj.EnableRotation == 'Off':
                stock = PathUtils.findParentJob(obj).Stock
                for (base, subList) in obj.Base:
                    baseSubsTuples.append((base, subList, 0.0, 'X', stock))
            else:
                for p in range(0, len(obj.Base)):
                    (base, subsList) = obj.Base[p]
                    isLoop = False

                    # First, check all subs collectively for loop of faces
                    if len(subsList) > 2:
                        (isLoop, norm, surf) = self.checkForFacesLoop(base, subsList)

                    if isLoop is True:
                        PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.")
                        rtn = False
                        subCount += 1
                        (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf)  # pylint: disable=unused-variable
                        PathLog.debug("angle: {};  axis: {}".format(angle, axis))

                        if rtn is True:
                            faceNums = ""
                            for f in subsList:
                                faceNums += '_' + f.replace('Face', '')
                            (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNums)  # pylint: disable=unused-variable

                            # Verify faces are correctly oriented - InverseAngle might be necessary
                            PathLog.debug("Checking if faces are oriented correctly after rotation...")
                            for sub in subsList:
                                face = clnBase.Shape.getElement(sub)
                                if type(face.Surface) == Part.Plane:
                                    if not PathGeom.isHorizontal(face.Surface.Axis):
                                        rtn = False
                                        PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied."))
                                        break

                            if rtn is False:
                                PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
                                if obj.InverseAngle is False:
                                    if obj.AttemptInverseAngle is True:
                                        (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
                                    else:
                                        msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
                                        PathLog.warning(msg)

                            if angle < 0.0:
                                angle += 360.0

                            tup = clnBase, subsList, angle, axis, clnStock
                        else:
                            if self.warnDisabledAxis(obj, axis) is False:
                                PathLog.debug("No rotation used")
                            axis = 'X'
                            angle = 0.0
                            stock = PathUtils.findParentJob(obj).Stock
                            tup = base, subsList, angle, axis, stock
                        # Eif

                        allTuples.append(tup)
                        baseSubsTuples.append(tup)
                    # Eif

                    if isLoop is False:
                        PathLog.debug(translate('Path', "Processing subs individually ..."))
                        for sub in subsList:
                            subCount += 1
                            if 'Face' in sub:
                                rtn = False
                                face = base.Shape.getElement(sub)
                                if type(face.Surface) == Part.SurfaceOfExtrusion:
                                    # extrusion wall
                                    PathLog.debug('analyzing type() == Part.SurfaceOfExtrusion')
                                    # Attempt to extract planar face from surface of extrusion
                                    (planar, useFace) = planarFaceFromExtrusionEdges(face, trans=False)
                                    # Save face object to self.horiz for processing or display error
                                    if planar is True:
                                        base = FreeCAD.ActiveDocument.getObject(useFace)
                                        sub = 'Face1'
                                        PathLog.debug('  -successful face created: {}'.format(useFace))
                                    else:
                                        PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub)))

                                (norm, surf) = self.getFaceNormAndSurf(face)
                                (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf)  # pylint: disable=unused-variable
                                PathLog.debug("initial {}".format(praInfo))

                                if rtn is True:
                                    faceNum = sub.replace('Face', '')
                                    (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNum)
                                    # Verify faces are correctly oriented - InverseAngle might be necessary
                                    faceIA = clnBase.Shape.getElement(sub)
                                    (norm, surf) = self.getFaceNormAndSurf(faceIA)
                                    (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf)  # pylint: disable=unused-variable
                                    PathLog.debug("follow-up {}".format(praInfo2))

                                    if abs(praAngle) == 180.0:
                                        rtn = False
                                        if self.isFaceUp(clnBase, faceIA) is False:
                                            PathLog.debug('isFaceUp is False')
                                            angle -= 180.0

                                    if rtn is True:
                                        PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
                                        if obj.InverseAngle is False:
                                            if obj.AttemptInverseAngle is True:
                                                (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
                                            else:
                                                msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
                                                PathLog.warning(msg)

                                            if self.isFaceUp(clnBase, faceIA) is False:
                                                PathLog.debug('isFaceUp is False')
                                                angle += 180.0
                                    else:
                                        PathLog.debug("Face appears to be oriented correctly.")

                                    if angle < 0.0:
                                        angle += 360.0

                                    tup = clnBase, [sub], angle, axis, clnStock
                                else:
                                    if self.warnDisabledAxis(obj, axis) is False:
                                        PathLog.debug(str(sub) + ": No rotation used")
                                    axis = 'X'
                                    angle = 0.0
                                    stock = PathUtils.findParentJob(obj).Stock
                                    tup = base, [sub], angle, axis, stock
                                # Eif
                                allTuples.append(tup)
                                baseSubsTuples.append(tup)
                            else:
                                ignoreSub = base.Name + '.' + sub
                                PathLog.error(translate('Path', "Selected feature is not a Face. Ignoring: {}".format(ignoreSub)))

            for o in baseSubsTuples:
                self.horiz = []  # pylint: disable=attribute-defined-outside-init
                self.vert = []  # pylint: disable=attribute-defined-outside-init
                subBase = o[0]
                subsList = o[1]
                angle = o[2]
                axis = o[3]
                stock = o[4]

                for sub in subsList:
                    if 'Face' in sub:
                        if clasifySub(self, subBase, sub) is False:
                            PathLog.error(translate('PathPocket', 'Pocket does not support shape %s.%s') % (subBase.Label, sub))
                            if obj.EnableRotation != 'Off':
                                PathLog.warning(translate('PathPocket', 'Face might not be within rotation accessibility limits.'))

                # Determine final depth as highest value of bottom boundbox of vertical face,
                #   in case of uneven faces on bottom
                if len(self.vert) > 0:
                    vFinDep = self.vert[0].BoundBox.ZMin
                    for vFace in self.vert:
                        if vFace.BoundBox.ZMin > vFinDep:
                            vFinDep = vFace.BoundBox.ZMin
                    # Determine if vertical faces for a loop: Extract planar loop wire as new horizontal face.
                    self.vertical = PathGeom.combineConnectedShapes(self.vert) # pylint: disable=attribute-defined-outside-init
                    self.vWires = [TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical] # pylint: disable=attribute-defined-outside-init
                    for wire in self.vWires:
                        w = PathGeom.removeDuplicateEdges(wire)
                        face = Part.Face(w)
                        # face.tessellate(0.1)
                        if PathGeom.isRoughly(face.Area, 0):
                            msg = translate('PathPocket', 'Vertical faces do not form a loop - ignoring')
                            PathLog.error(msg)
                            # title = translate("Path", "Face Selection Warning")
                            # self.guiMessage(title, msg, True)
                        else:
                            face.translate(FreeCAD.Vector(0, 0, vFinDep - face.BoundBox.ZMin))
                            self.horiz.append(face)
                            msg = translate('Path', 'Verify final depth of pocket shaped by vertical faces.')
                            PathLog.warning(msg)
                            # title = translate('Path', 'Depth Warning')
                            # self.guiMessage(title, msg, False)

                # add faces for extensions
                self.exts = [] # pylint: disable=attribute-defined-outside-init
                for ext in self.getExtensions(obj):
                    wire = ext.getWire()
                    if wire:
                        face = Part.Face(wire)
                        self.horiz.append(face)
                        self.exts.append(face)

                # move all horizontal faces to FinalDepth
                # for f in self.horiz:
                #     f.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - f.BoundBox.ZMin))

                # check all faces and see if they are touching/overlapping and combine those into a compound
                self.horizontal = [] # pylint: disable=attribute-defined-outside-init
                for shape in PathGeom.combineConnectedShapes(self.horiz):
                    shape.sewShape()
                    # shape.tessellate(0.1)
                    shpZMin = shape.BoundBox.ZMin
                    PathLog.debug('PathGeom.combineConnectedShapes shape.BoundBox.ZMin: {}'.format(shape.BoundBox.ZMin))
                    if obj.UseOutline:
                        wire = TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1))
                        wFace = Part.Face(wire)
                        if wFace.BoundBox.ZMin != shpZMin:
                            wFace.translate(FreeCAD.Vector(0, 0, shpZMin - wFace.BoundBox.ZMin))
                        self.horizontal.append(wFace)
                        PathLog.debug('PathGeom.combineConnectedShapes shape.BoundBox.ZMin: {}'.format(wFace.BoundBox.ZMin))
                    else:
                        self.horizontal.append(shape)

                # extrude all faces up to StartDepth and those are the removal shapes
                sD = obj.StartDepth.Value
                fD = obj.FinalDepth.Value
                clrnc = 0.5
                for face in self.horizontal:
                    afD = fD
                    useAngle = angle
                    shpZMin = face.BoundBox.ZMin
                    PathLog.debug('self.horizontal shpZMin: {}'.format(shpZMin))
                    if self.isFaceUp(subBase, face) is False:
                        useAngle += 180.0
                        invZ = (-2 * shpZMin) - clrnc
                        face.translate(FreeCAD.Vector(0.0, 0.0, invZ))
                        shpZMin = -1 * shpZMin
                    else:
                        face.translate(FreeCAD.Vector(0.0, 0.0, -1 * clrnc))
                    
                    if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off':
                        if shpZMin > obj.FinalDepth.Value:
                            afD = shpZMin
                            if sD <= afD:
                                sD = afD + 1.0
                                msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ')
                                PathLog.warning(msg + ' {} mm.'.format(sD))
                    else:
                        face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - shpZMin))
                    
                    extent = FreeCAD.Vector(0, 0, sD - afD + clrnc)
                    extShp = face.removeSplitter().extrude(extent)
                    self.removalshapes.append((extShp, False, 'pathPocketShape', useAngle, axis, sD, afD))
                    PathLog.debug("Extent values are strDep: {}, finDep: {},  extrd: {}".format(sD, afD, extent))
                # Efor face
            # Efor

        else:
            # process the job base object as a whole
            PathLog.debug(translate("Path", 'Processing model as a whole ...'))
            finDep = obj.FinalDepth.Value
            strDep = obj.StartDepth.Value
            self.outlines = [Part.Face(TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model] # pylint: disable=attribute-defined-outside-init
            stockBB = self.stock.Shape.BoundBox

            self.removalshapes = [] # pylint: disable=attribute-defined-outside-init
            self.bodies = [] # pylint: disable=attribute-defined-outside-init
            for outline in self.outlines:
                outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1))
                body = outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2))
                self.bodies.append(body)
                self.removalshapes.append((self.stock.Shape.cut(body), False, 'pathPocketShape', 0.0, 'X', strDep, finDep))

        for (shape, hole, sub, angle, axis, strDep, finDep) in self.removalshapes: # pylint: disable=unused-variable
            shape.tessellate(0.05)  # originally 0.1

        if self.removalshapes:
            obj.removalshape = self.removalshapes[0][0]

        return self.removalshapes
Esempio n. 22
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:
                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:
                    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'))
                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
Esempio n. 23
0
    def opExecute(self, obj):
        '''opExecute(obj) ... processes all Base features and Locations and collects
        them in a list of positions and radii which is then passed to circularHoleExecute(obj, holes).
        If no Base geometries and no Locations are present, the job's Base is inspected and all
        drillable features are added to Base. In this case appropriate values for depths are also
        calculated and assigned.
        Do not overwrite, implement circularHoleExecute(obj, holes) instead.'''
        PathLog.track()

        holes = []
        baseSubsTuples = []
        subCount = 0
        allTuples = []
        self.cloneNames = []  # pylint: disable=attribute-defined-outside-init
        self.guiMsgs = []  # pylint: disable=attribute-defined-outside-init
        self.rotateFlag = False  # pylint: disable=attribute-defined-outside-init
        self.useTempJobClones('Delete')  # pylint: disable=attribute-defined-outside-init
        self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox  # pylint: disable=attribute-defined-outside-init
        self.clearHeight = obj.ClearanceHeight.Value  # pylint: disable=attribute-defined-outside-init
        self.safeHeight = obj.SafeHeight.Value  # pylint: disable=attribute-defined-outside-init
        self.axialFeed = 0.0  # pylint: disable=attribute-defined-outside-init
        self.axialRapid = 0.0  # pylint: disable=attribute-defined-outside-init

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

        if obj.EnableRotation == 'Off':
            strDep = obj.StartDepth.Value
            finDep = obj.FinalDepth.Value
        else:
            # Calculate operation heights based upon rotation radii
            opHeights = self.opDetermineRotationRadii(obj)
            (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0]  # pylint: disable=attribute-defined-outside-init
            (clrOfset, safOfst) = opHeights[1]
            PathLog.debug("Exec. opHeights[0]: " + str(opHeights[0]))
            PathLog.debug("Exec. opHeights[1]: " + str(opHeights[1]))

            # Set clearance and safe heights based upon rotation radii
            if obj.EnableRotation == 'A(x)':
                strDep = self.xRotRad
            elif obj.EnableRotation == 'B(y)':
                strDep = self.yRotRad
            else:
                strDep = max(self.xRotRad, self.yRotRad)
            finDep = -1 * strDep

            obj.ClearanceHeight.Value = strDep + clrOfset
            obj.SafeHeight.Value = strDep + safOfst

            # Create visual axes when debugging.
            if PathLog.getLevel(PathLog.thisModule()) == 4:
                self.visualAxis()

            # Set axial feed rates based upon horizontal feed rates
            safeCircum = 2 * math.pi * obj.SafeHeight.Value
            self.axialFeed = 360 / safeCircum * self.horizFeed  # pylint: disable=attribute-defined-outside-init
            self.axialRapid = 360 / safeCircum * self.horizRapid  # pylint: disable=attribute-defined-outside-init

        # Complete rotational analysis and temp clone creation as needed
        if obj.EnableRotation == 'Off':
            PathLog.debug("Enable Rotation setting is 'Off' for {}.".format(
                obj.Name))
            stock = PathUtils.findParentJob(obj).Stock
            for (base, subList) in obj.Base:
                baseSubsTuples.append((base, subList, 0.0, 'A', stock))
        else:
            for p in range(0, len(obj.Base)):
                (bst, at) = self.process_base_geometry_with_rotation(
                    obj, p, subCount)
                allTuples.extend(at)
                baseSubsTuples.extend(bst)

        for base, subs, angle, axis, stock in baseSubsTuples:
            # rotate shorter angle in opposite direction
            if angle > 180:
                angle -= 360
            elif angle < -180:
                angle += 360

            # Re-analyze rotated model for drillable holes
            if obj.EnableRotation != 'Off':
                rotated_features = self.findHoles(obj, base)

            for sub in subs:
                PathLog.debug('sub, angle, axis: {}, {}, {}'.format(
                    sub, angle, axis))
                if self.isHoleEnabled(obj, base, sub):
                    pos = self.holePosition(obj, base, sub)
                    if pos:
                        # Identify face to which edge belongs
                        sub_shape = base.Shape.getElement(sub)

                        # Default is to treat selection as 'Face' shape
                        holeBtm = sub_shape.BoundBox.ZMin

                        if obj.EnableRotation != 'Off':
                            # Update Start and Final depths due to rotation, if auto defaults are active
                            parent_face = self._find_parent_face_of_edge(
                                rotated_features, sub_shape)
                            if parent_face:
                                PathLog.debug('parent_face found')
                                holeBtm = parent_face.BoundBox.ZMin
                                if obj.OpStartDepth == obj.StartDepth:
                                    obj.StartDepth.Value = parent_face.BoundBox.ZMax
                                    PathLog.debug('new StartDepth: {}'.format(
                                        obj.StartDepth.Value))
                                if obj.OpFinalDepth == obj.FinalDepth:
                                    obj.FinalDepth.Value = holeBtm
                                    PathLog.debug(
                                        'new FinalDepth: {}'.format(holeBtm))
                            else:
                                PathLog.debug('NO parent_face identified')

                        if base.Shape.getElement(sub).ShapeType == 'Edge':
                            msg = translate(
                                "Path",
                                "Verify Final Depth of holes based on edges. {} depth is: {} mm"
                                .format(sub, round(holeBtm, 4))) + "  "
                            msg += translate(
                                "Path",
                                "Always select the bottom edge of the hole when using an edge."
                            )
                            PathLog.warning(msg)

                        # Warn user if Final Depth set lower than bottom of hole
                        if obj.FinalDepth.Value < holeBtm:
                            msg = translate(
                                "Path",
                                "Final Depth setting is below the hole bottom for {}."
                                .format(sub)) + '  '
                            msg += translate(
                                "Path",
                                "{} depth is calculated at {} mm".format(
                                    sub, round(holeBtm, 4)))
                            PathLog.warning(msg)

                        holes.append({
                            'x': pos.x,
                            'y': pos.y,
                            'r': self.holeDiameter(obj, base, sub),
                            'angle': angle,
                            'axis': axis,
                            'trgtDep': obj.FinalDepth.Value,
                            'stkTop': stock.Shape.BoundBox.ZMax
                        })

        # haveLocations are populated from user-provided (x, y) coordinates
        # provided by the user in the Base Locations tab of the Task Editor window
        if haveLocations(self, obj):
            for location in obj.Locations:
                # holes.append({'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'holeBtm': obj.FinalDepth.Value})
                holes.append({
                    'x':
                    location.x,
                    'y':
                    location.y,
                    'r':
                    0,
                    'angle':
                    0.0,
                    'axis':
                    'X',
                    'trgtDep':
                    obj.FinalDepth.Value,
                    'stkTop':
                    PathUtils.findParentJob(obj).Stock.Shape.BoundBox.ZMax
                })

        if len(holes) > 0:
            self.circularHoleExecute(
                obj, holes)  # circularHoleExecute() located in PathDrilling.py

        self.useTempJobClones(
            'Delete')  # Delete temp job clone group and contents
        self.guiMessage('title', None,
                        show=True)  # Process GUI messages to user
        PathLog.debug("obj.Name: " + str(obj.Name))
    def generateRamps(self, allowBounce=True):
        edges = self.wire.Edges
        outedges = []
        for edge in edges:
            israpid = False
            for redge in self.rapids:
                if PathGeom.edgesMatch(edge, redge):
                    israpid = True
            if not israpid:
                bb = edge.BoundBox
                p0 = edge.Vertexes[0].Point
                p1 = edge.Vertexes[1].Point
                rampangle = self.angle
                if bb.XLength < 1e-6 and bb.YLength < 1e-6 and bb.ZLength > 0 and p0.z > p1.z:

                    # check if above ignoreAbove parameter - do not generate ramp if it is
                    newEdge, cont = self.checkIgnoreAbove(edge)
                    if newEdge is not None:
                        outedges.append(newEdge)
                        p0.z = self.ignoreAbove
                    if cont:
                        continue

                    plungelen = abs(p0.z - p1.z)
                    projectionlen = plungelen * math.tan(math.radians(rampangle))  # length of the forthcoming ramp projected to XY plane
                    PathLog.debug("Found plunge move at X:{} Y:{} From Z:{} to Z{}, length of ramp: {}".format(p0.x, p0.y, p0.z, p1.z, projectionlen))
                    if self.method == 'RampMethod3':
                        projectionlen = projectionlen / 2

                    # next need to determine how many edges in the path after
                    # plunge are needed to cover the length:
                    covered = False
                    coveredlen = 0
                    rampedges = []
                    i = edges.index(edge) + 1
                    while not covered:
                        candidate = edges[i]
                        cp0 = candidate.Vertexes[0].Point
                        cp1 = candidate.Vertexes[1].Point
                        if abs(cp0.z - cp1.z) > 1e-6:
                            # this edge is not parallel to XY plane, not qualified for ramping.
                            break
                        # PathLog.debug("Next edge length {}".format(candidate.Length))
                        rampedges.append(candidate)
                        coveredlen = coveredlen + candidate.Length

                        if coveredlen > projectionlen:
                            covered = True
                        i = i + 1
                        if i >= len(edges):
                            break
                    if len(rampedges) == 0:
                        PathLog.debug("No suitable edges for ramping, plunge will remain as such")
                        outedges.append(edge)
                    else:
                        if not covered:
                            if (not allowBounce) or self.method == 'RampMethod2':
                                l = 0
                                for redge in rampedges:
                                    l = l + redge.Length
                                if self.method == 'RampMethod3':
                                    rampangle = math.degrees(math.atan(l / (plungelen / 2)))
                                else:
                                    rampangle = math.degrees(math.atan(l / plungelen))
                                PathLog.warning("Cannot cover with desired angle, tightening angle to: {}".format(rampangle))

                        # PathLog.debug("Doing ramp to edges: {}".format(rampedges))
                        if self.method == 'RampMethod1':
                            outedges.extend(self.createRampMethod1(rampedges, p0, projectionlen, rampangle))
                        elif self.method == 'RampMethod2':
                            outedges.extend(self.createRampMethod2(rampedges, p0, projectionlen, rampangle))
                        else:
                            # if the ramp cannot be covered with Method3, revert to Method1
                            # because Method1 support going back-and-forth and thus results in same path as Method3 when
                            # length of the ramp is smaller than needed for single ramp.
                            if (not covered) and allowBounce:
                                projectionlen = projectionlen * 2
                                outedges.extend(self.createRampMethod1(rampedges, p0, projectionlen, rampangle))
                            else:
                                outedges.extend(self.createRampMethod3(rampedges, p0, projectionlen, rampangle))
                else:
                    outedges.append(edge)
            else:
                outedges.append(edge)
        return outedges
Esempio n. 25
0
    def getPath(self):
        """getPath() ... Call this method on an instance of the class to generate and return
        path data for the requested path array."""

        if len(self.baseList) == 0:
            PathLog.error(
                translate("PathArray", "No base objects for PathArray."))
            return None

        base = self.baseList
        for b in base:
            if not b.isDerivedFrom("Path::Feature"):
                return
            if not b.Path:
                return

            b_tool_controller = toolController(b)
            if not b_tool_controller:
                return

            if b_tool_controller != toolController(base[0]):
                # this may be important if Job output is split by tool controller
                PathLog.warning(
                    translate(
                        "PathArray",
                        "Arrays of paths having different tool controllers are handled according to the tool controller of the first path.",
                    ))

        # build copies
        output = ""
        random.seed(self.seed)

        if self.arrayType == "Linear1D":
            for i in range(self.copies):
                pos = FreeCAD.Vector(
                    self.offsetVector.x * (i + 1),
                    self.offsetVector.y * (i + 1),
                    self.offsetVector.z * (i + 1),
                )
                pos = self._calculateJitter(pos)

                for b in base:
                    pl = FreeCAD.Placement()
                    pl.move(pos)
                    np = Path.Path(
                        [cm.transform(pl) for cm in b.Path.Commands])
                    output += np.toGCode()

        elif self.arrayType == "Linear2D":
            if self.swapDirection:
                for i in range(self.copiesY + 1):
                    for j in range(self.copiesX + 1):
                        if (i % 2) == 0:
                            pos = FreeCAD.Vector(
                                self.offsetVector.x * j,
                                self.offsetVector.y * i,
                                self.offsetVector.z * i,
                            )
                        else:
                            pos = FreeCAD.Vector(
                                self.offsetVector.x * (self.copiesX - j),
                                self.offsetVector.y * i,
                                self.offsetVector.z * i,
                            )
                        pos = self._calculateJitter(pos)

                        for b in base:
                            pl = FreeCAD.Placement()
                            # do not process the index 0,0. It will be processed by the base Paths themselves
                            if not (i == 0 and j == 0):
                                pl.move(pos)
                                np = Path.Path([
                                    cm.transform(pl) for cm in b.Path.Commands
                                ])
                                output += np.toGCode()
            else:
                for i in range(self.copiesX + 1):
                    for j in range(self.copiesY + 1):
                        if (i % 2) == 0:
                            pos = FreeCAD.Vector(
                                self.offsetVector.x * i,
                                self.offsetVector.y * j,
                                self.offsetVector.z * i,
                            )
                        else:
                            pos = FreeCAD.Vector(
                                self.offsetVector.x * i,
                                self.offsetVector.y * (self.copiesY - j),
                                self.offsetVector.z * i,
                            )
                        pos = self._calculateJitter(pos)

                        for b in base:
                            pl = FreeCAD.Placement()
                            # do not process the index 0,0. It will be processed by the base Paths themselves
                            if not (i == 0 and j == 0):
                                pl.move(pos)
                                np = Path.Path([
                                    cm.transform(pl) for cm in b.Path.Commands
                                ])
                                output += np.toGCode()
            # Eif
        else:
            for i in range(self.copies):
                for b in base:
                    ang = 360
                    if self.copies > 0:
                        ang = self.angle / self.copies * (1 + i)
                    np = self.rotatePath(b.Path, ang, self.centre)
                    output += np.toGCode()

        # return output
        return Path.Path(output)
Esempio n. 26
0
    def areaOpShapes(self, obj):
        '''areaOpShapes(obj) ... return top face'''
        # Facing is done either against base objects
        holeShape = None

        PathLog.debug('depthparams: {}'.format([i for i in self.depthparams]))

        if obj.Base:
            PathLog.debug("obj.Base: {}".format(obj.Base))
            faces = []
            holes = []
            holeEnvs = []
            oneBase = [obj.Base[0][0], True]
            sub0 = getattr(obj.Base[0][0].Shape, obj.Base[0][1][0])
            minHeight = sub0.BoundBox.ZMax

            for b in obj.Base:
                for sub in b[1]:
                    shape = getattr(b[0].Shape, sub)
                    if isinstance(shape, Part.Face):
                        faces.append(shape)
                        if shape.BoundBox.ZMin < minHeight:
                            minHeight = shape.BoundBox.ZMin
                        # Limit to one model base per operation
                        if oneBase[0] is not b[0]:
                            oneBase[1] = False
                        if numpy.isclose(abs(shape.normalAt(0, 0).z), 1):  # horizontal face
                            # Analyze internal closed wires to determine if raised or a recess
                            for wire in shape.Wires[1:]:
                                if obj.ExcludeRaisedAreas:
                                    ip = self.isPocket(b[0], shape, wire)
                                    if ip is False:
                                        holes.append((b[0].Shape, wire))
                                else:
                                    holes.append((b[0].Shape, wire))
                    else:
                        PathLog.warning('The base subobject, "{0}," is not a face. Ignoring "{0}."'.format(sub))

            if obj.ExcludeRaisedAreas and len(holes) > 0:
                for shape, wire in holes:
                    f = Part.makeFace(wire, 'Part::FaceMakerSimple')
                    env = PathUtils.getEnvelope(shape, subshape=f, depthparams=self.depthparams)
                    holeEnvs.append(env)
                    holeShape = Part.makeCompound(holeEnvs)

            PathLog.debug("Working on a collection of faces {}".format(faces))
            planeshape = Part.makeCompound(faces)

        # If no base object, do planing of top surface of entire model
        else:
            planeshape = Part.makeCompound([base.Shape for base in self.model])
            PathLog.debug("Working on a shape {}".format(obj.Label))

        # Find the correct shape depending on Boundary shape.
        PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape))
        bb = planeshape.BoundBox

        # Apply offset for clearing edges
        offset = 0
        if obj.ClearEdges:
            offset = self.radius + 0.1

        bb.XMin = bb.XMin - offset
        bb.YMin = bb.YMin - offset
        bb.XMax = bb.XMax + offset
        bb.YMax = bb.YMax + offset

        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)
            if obj.ExcludeRaisedAreas and oneBase[1]:
                includedFaces = self.getAllIncludedFaces(oneBase[0], env, faceZ=minHeight)
                if len(includedFaces) > 0:
                    includedShape = Part.makeCompound(includedFaces)
                    includedEnv = PathUtils.getEnvelope(oneBase[0].Shape, subshape=includedShape, depthparams=self.depthparams)
                    env = env.cut(includedEnv)
        elif obj.BoundaryShape == 'Stock':
            stock = PathUtils.findParentJob(obj).Stock.Shape
            env = stock

            if obj.ExcludeRaisedAreas and oneBase[1]:
                includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight)
                if len(includedFaces) > 0:
                    stockEnv = PathUtils.getEnvelope(partshape=stock, depthparams=self.depthparams)
                    includedShape = Part.makeCompound(includedFaces)
                    includedEnv = PathUtils.getEnvelope(oneBase[0].Shape, subshape=includedShape, depthparams=self.depthparams)
                    env = stockEnv.cut(includedEnv)
        elif obj.BoundaryShape == 'Perimeter':
            if obj.ClearEdges:
                psZMin = planeshape.BoundBox.ZMin
                ofstShape = PathUtils.getOffsetArea(planeshape,
                                                    self.radius * 1.25,
                                                    plane=planeshape)
                ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin))
                env = PathUtils.getEnvelope(partshape=ofstShape, depthparams=self.depthparams)
            else:
                env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams)
        elif obj.BoundaryShape == 'Face Region':
            import PathScripts.PathSurfaceSupport as PathSurfaceSupport
            baseShape = oneBase[0].Shape
            psZMin = planeshape.BoundBox.ZMin
            ofstShape = PathUtils.getOffsetArea(planeshape,
                                                self.tool.Diameter * 1.1,
                                                plane=planeshape)
            ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin))

            custDepthparams = self._customDepthParams(obj, obj.StartDepth.Value + 0.1, obj.FinalDepth.Value - 0.1)  # only an envelope
            ofstShapeEnv = PathUtils.getEnvelope(partshape=ofstShape, depthparams=custDepthparams)
            env = ofstShapeEnv.cut(baseShape)

        if holeShape:
            PathLog.debug("Processing holes and face ...")
            holeEnv = PathUtils.getEnvelope(partshape=holeShape, depthparams=self.depthparams)
            newEnv = env.cut(holeEnv)
            tup = newEnv, False, 'pathMillFace', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
        else:
            PathLog.debug("Processing solid face ...")
            tup = env, False, 'pathMillFace', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
        return [tup]
Esempio n. 27
0
    def areaOpShapes(self, obj):
        '''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.'''
        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 = []
        self.profileshape = [] # pylint: disable=attribute-defined-outside-init

        baseSubsTuples = []
        subCount = 0
        allTuples = []

        if obj.Base:  # The user has selected subobjects from the base.  Process each.
            if obj.EnableRotation != 'Off':
                for p in range(0, len(obj.Base)):
                    (base, subsList) = obj.Base[p]
                    for sub in subsList:
                        subCount += 1
                        shape = getattr(base.Shape, sub)
                        if isinstance(shape, Part.Face):
                            rtn = False
                            (norm, surf) = self.getFaceNormAndSurf(shape)
                            (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
                            PathLog.debug("initial faceRotationAnalysis: {}".format(praInfo))
                            if rtn is True:
                                (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount)
                                # Verify faces are correctly oriented - InverseAngle might be necessary
                                faceIA = getattr(clnBase.Shape, sub)
                                (norm, surf) = self.getFaceNormAndSurf(faceIA)
                                (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
                                PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2))

                                if abs(praAngle) == 180.0:
                                    rtn = False
                                    if self.isFaceUp(clnBase, faceIA) is False:
                                        PathLog.debug('isFaceUp 1 is False')
                                        angle -= 180.0

                                if rtn is True:
                                    PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
                                    if obj.InverseAngle is False:
                                        if obj.AttemptInverseAngle is True:
                                            (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
                                        else:
                                            msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
                                            PathLog.warning(msg)

                                    if self.isFaceUp(clnBase, faceIA) is False:
                                        PathLog.debug('isFaceUp 2 is False')
                                        angle += 180.0
                                    else:
                                        PathLog.debug('  isFaceUp')

                                else:
                                    PathLog.debug("Face appears to be oriented correctly.")

                                if angle < 0.0:
                                    angle += 360.0

                                tup = clnBase, sub, tag, angle, axis, clnStock
                            else:
                                if self.warnDisabledAxis(obj, axis) is False:
                                    PathLog.debug(str(sub) + ": No rotation used")
                                axis = 'X'
                                angle = 0.0
                                tag = base.Name + '_' + axis + str(angle).replace('.', '_')
                                stock = PathUtils.findParentJob(obj).Stock
                                tup = base, sub, tag, angle, axis, stock

                            allTuples.append(tup)

                if subCount > 1:
                    msg = translate('Path', "Multiple faces in Base Geometry.") + "  "
                    msg += translate('Path', "Depth settings will be applied to all faces.")
                    PathLog.warning(msg)

                (Tags, Grps) = self.sortTuplesByIndex(allTuples, 2)  # return (TagList, GroupList)
                subList = []
                for o in range(0, len(Tags)):
                    subList = []
                    for (base, sub, tag, angle, axis, stock) in Grps[o]:
                        subList.append(sub)

                    pair = base, subList, angle, axis, stock
                    baseSubsTuples.append(pair)
                # Efor
            else:
                PathLog.debug(translate("Path", "EnableRotation property is 'Off'."))
                stock = PathUtils.findParentJob(obj).Stock
                for (base, subList) in obj.Base:
                    baseSubsTuples.append((base, subList, 0.0, 'X', stock))

            # for base in obj.Base:
            finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
            for (base, subsList, angle, axis, stock) in baseSubsTuples:
                holes = []
                faces = []
                faceDepths = []
                startDepths = []

                for sub in subsList:
                    shape = getattr(base.Shape, sub)
                    if isinstance(shape, Part.Face):
                        faces.append(shape)
                        if numpy.isclose(abs(shape.normalAt(0, 0).z), 1):  # horizontal face
                            for wire in shape.Wires[1:]:
                                holes.append((base.Shape, wire))

                        # Add face depth to list
                        faceDepths.append(shape.BoundBox.ZMin)
                    else:
                        ignoreSub = base.Name + '.' + sub
                        msg = translate('Path', "Found a selected object which is not a face. Ignoring: {}".format(ignoreSub))
                        PathLog.error(msg)
                        FreeCAD.Console.PrintWarning(msg)

                # Set initial Start and Final Depths and recalculate depthparams
                finDep = obj.FinalDepth.Value
                strDep = obj.StartDepth.Value
                if strDep > stock.Shape.BoundBox.ZMax:
                    strDep = stock.Shape.BoundBox.ZMax

                startDepths.append(strDep)
                self.depthparams = self._customDepthParams(obj, strDep, finDep)

                for shape, wire in holes:
                    f = Part.makeFace(wire, 'Part::FaceMakerSimple')
                    drillable = PathUtils.isDrillable(shape, wire)
                    if (drillable and obj.processCircles) or (not drillable and obj.processHoles):
                        env = PathUtils.getEnvelope(shape, subshape=f, depthparams=self.depthparams)
                        tup = env, True, 'pathProfileFaces', angle, axis, strDep, finDep
                        shapes.append(tup)

                if len(faces) > 0:
                    profileshape = Part.makeCompound(faces)
                    self.profileshape.append(profileshape)

                if obj.processPerimeter:
                    if obj.HandleMultipleFeatures == 'Collectively':
                        custDepthparams = self.depthparams

                        if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off':
                            if profileshape.BoundBox.ZMin > obj.FinalDepth.Value:
                                finDep = profileshape.BoundBox.ZMin
                                envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep)  # only an envelope
                        try:
                            # env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=envDepthparams)
                            env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams)
                        except Exception: # pylint: disable=broad-except
                            # PathUtils.getEnvelope() failed to return an object.
                            PathLog.error(translate('Path', 'Unable to create path for face(s).'))
                        else:
                            tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep
                            shapes.append(tup)

                    elif obj.HandleMultipleFeatures == 'Individually':
                        for shape in faces:
                            # profShape = Part.makeCompound([shape])
                            finalDep = obj.FinalDepth.Value
                            custDepthparams = self.depthparams
                            if obj.Side == 'Inside':
                                if finalDep < shape.BoundBox.ZMin:
                                    # Recalculate depthparams
                                    finalDep = shape.BoundBox.ZMin
                                    custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep)

                            # env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams)
                            env = PathUtils.getEnvelope(shape, depthparams=custDepthparams)
                            tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep
                            shapes.append(tup)

            # Lower high Start Depth to top of Stock
            startDepth = max(startDepths)
            if obj.StartDepth.Value > startDepth:
                obj.StartDepth.Value = startDepth

        else:  # Try to build targets from the job base
            if 1 == len(self.model):
                if hasattr(self.model[0], "Proxy"):
                    PathLog.debug("hasattr() Proxy")
                    if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet):  # process the sheet
                        if obj.processCircles or obj.processHoles:
                            for shape in self.model[0].Proxy.getHoles(self.model[0], transform=True):
                                for wire in shape.Wires:
                                    drillable = PathUtils.isDrillable(self.model[0].Proxy, wire)
                                    if (drillable and obj.processCircles) or (not drillable and obj.processHoles):
                                        f = Part.makeFace(wire, 'Part::FaceMakerSimple')
                                        env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams)
                                        tup = env, True, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
                                        shapes.append(tup)

                        if obj.processPerimeter:
                            for shape in self.model[0].Proxy.getOutlines(self.model[0], transform=True):
                                for wire in shape.Wires:
                                    f = Part.makeFace(wire, 'Part::FaceMakerSimple')
                                    env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams)
                                    tup = env, False, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
                                    shapes.append(tup)

        self.removalshapes = shapes # pylint: disable=attribute-defined-outside-init
        PathLog.debug("%d shapes" % len(shapes))

        return shapes
Esempio n. 28
0
def isDrillable(obj, candidate, tooldiameter=None, includePartials=False):
    """
    Checks candidates to see if they can be drilled.
    Candidates can be either faces - circular or cylindrical or circular edges.
    The tooldiameter can be optionally passed.  if passed, the check will return
    False for any holes smaller than the tooldiameter.
    obj=Shape
    candidate = Face or Edge
    tooldiameter=float
    """
    PathLog.track('obj: {} candidate: {} tooldiameter {}'.format(
        obj, candidate, tooldiameter))
    drillable = False
    try:
        if candidate.ShapeType == 'Face':
            face = candidate
            # eliminate flat faces
            if (round(face.ParameterRange[0], 8) == 0.0) and (round(
                    face.ParameterRange[1], 8) == round(math.pi * 2, 8)):
                for edge in face.Edges:  # Find seam edge and check if aligned to Z axis.
                    if (isinstance(edge.Curve, Part.Line)):
                        PathLog.debug("candidate is a circle")
                        v0 = edge.Vertexes[0].Point
                        v1 = edge.Vertexes[1].Point
                        #check if the cylinder seam is vertically aligned.  Eliminate tilted holes
                        if (numpy.isclose(v1.sub(v0).x, 0, rtol=1e-05, atol=1e-06)) and \
                                (numpy.isclose(v1.sub(v0).y, 0, rtol=1e-05, atol=1e-06)):
                            drillable = True
                            # vector of top center
                            lsp = Vector(face.BoundBox.Center.x,
                                         face.BoundBox.Center.y,
                                         face.BoundBox.ZMax)
                            # vector of bottom center
                            lep = Vector(face.BoundBox.Center.x,
                                         face.BoundBox.Center.y,
                                         face.BoundBox.ZMin)
                            # check if the cylindrical 'lids' are inside the base
                            # object.  This eliminates extruded circles but allows
                            # actual holes.
                            if obj.isInside(lsp, 1e-6, False) or obj.isInside(
                                    lep, 1e-6, False):
                                PathLog.track(
                                    "inside check failed. lsp: {}  lep: {}".
                                    format(lsp, lep))
                                drillable = False
                            # eliminate elliptical holes
                            elif not hasattr(face.Surface, "Radius"):
                                PathLog.debug(
                                    "candidate face has no radius attribute")
                                drillable = False
                            else:
                                if tooldiameter is not None:
                                    drillable = face.Surface.Radius >= tooldiameter / 2
                                else:
                                    drillable = True
            elif type(face.Surface) == Part.Plane and PathGeom.pointsCoincide(
                    face.Surface.Axis, FreeCAD.Vector(0, 0, 1)):
                if len(face.Edges) == 1 and type(
                        face.Edges[0].Curve) == Part.Circle:
                    center = face.Edges[0].Curve.Center
                    if obj.isInside(center, 1e-6, False):
                        if tooldiameter is not None:
                            drillable = face.Edges[
                                0].Curve.Radius >= tooldiameter / 2
                        else:
                            drillable = True
        else:
            for edge in candidate.Edges:
                if isinstance(edge.Curve, Part.Circle) and (includePartials or
                                                            edge.isClosed()):
                    PathLog.debug("candidate is a circle or ellipse")
                    if not hasattr(edge.Curve, "Radius"):
                        PathLog.debug("No radius.  Ellipse.")
                        drillable = False
                    else:
                        PathLog.debug("Has Radius, Circle")
                        if tooldiameter is not None:
                            drillable = edge.Curve.Radius >= tooldiameter / 2
                            if not drillable:
                                FreeCAD.Console.PrintMessage(
                                    "Found a drillable hole with diameter: {}: "
                                    "too small for the current tool with "
                                    "diameter: {}".format(
                                        edge.Curve.Radius * 2, tooldiameter))
                        else:
                            drillable = True
        PathLog.debug("candidate is drillable: {}".format(drillable))
    except Exception as ex:
        PathLog.warning(
            translate("PathUtils",
                      "Issue determine drillability: {}").format(ex))
    return drillable
Esempio n. 29
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(
                    "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(
                        "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(
                        "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(
                        "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