| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | from PySide.QtCore import QT_TRANSLATE_NOOP |
| | import FreeCAD |
| | import Path |
| | import Path.Op.Base as PathOp |
| | from Path.Base import Drillable |
| | from PathScripts import PathUtils |
| |
|
| | |
| | from lazy_loader.lazy_loader import LazyLoader |
| |
|
| | Draft = LazyLoader("Draft", globals(), "Draft") |
| | Part = LazyLoader("Part", globals(), "Part") |
| | DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") |
| |
|
| |
|
| | __title__ = "CAM Circular Holes Base Operation" |
| | __author__ = "sliptonic (Brad Collette)" |
| | __url__ = "https://www.freecad.org" |
| | __doc__ = "Base class an implementation for operations on circular holes." |
| |
|
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| |
|
| | if False: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| |
|
| | class ObjectOp(PathOp.ObjectOp): |
| | """Base class for proxy objects of all operations on circular holes.""" |
| |
|
| | def opFeatures(self, obj): |
| | """opFeatures(obj) ... calls circularHoleFeatures(obj) and ORs in the standard features required for processing circular holes. |
| | Do not overwrite, implement circularHoleFeatures(obj) instead""" |
| | return ( |
| | PathOp.FeatureTool |
| | | PathOp.FeatureDepths |
| | | PathOp.FeatureHeights |
| | | PathOp.FeatureBaseFaces |
| | | self.circularHoleFeatures(obj) |
| | | PathOp.FeatureCoolant |
| | ) |
| |
|
| | def circularHoleFeatures(self, obj): |
| | """circularHoleFeatures(obj) ... overwrite to add operations specific features. |
| | Can safely be overwritten by subclasses.""" |
| | return 0 |
| |
|
| | def initOperation(self, obj): |
| | """initOperation(obj) ... adds Disabled properties and calls initCircularHoleOperation(obj). |
| | Do not overwrite, implement initCircularHoleOperation(obj) instead.""" |
| | obj.addProperty( |
| | "App::PropertyStringList", |
| | "Disabled", |
| | "Base", |
| | QT_TRANSLATE_NOOP("App::Property", "List of disabled features"), |
| | ) |
| | self.initCircularHoleOperation(obj) |
| |
|
| | def initCircularHoleOperation(self, obj): |
| | """initCircularHoleOperation(obj) ... overwrite if the subclass needs initialisation. |
| | Can safely be overwritten by subclasses.""" |
| | pass |
| |
|
| | def holeDiameter(self, obj, base, sub): |
| | """holeDiameter(obj, base, sub) ... returns the diameter of the specified hole.""" |
| | try: |
| | shape = base.Shape.getElement(sub) |
| | if shape.ShapeType == "Vertex": |
| | return 0 |
| |
|
| | if shape.ShapeType == "Edge" and type(shape.Curve) == Part.Circle: |
| | return shape.Curve.Radius * 2 |
| |
|
| | if shape.ShapeType == "Face": |
| | for i in range(len(shape.Edges)): |
| | if ( |
| | type(shape.Edges[i].Curve) == Part.Circle |
| | and shape.Edges[i].Curve.Radius * 2 < shape.BoundBox.XLength * 1.1 |
| | and shape.Edges[i].Curve.Radius * 2 > shape.BoundBox.XLength * 0.9 |
| | ): |
| | return shape.Edges[i].Curve.Radius * 2 |
| |
|
| | |
| | |
| | Path.Log.warning( |
| | translate( |
| | "CAM", |
| | "Hole diameter may be inaccurate due to tessellation on face. Consider selecting hole edge.", |
| | ) |
| | ) |
| | return shape.BoundBox.XLength |
| | except Part.OCCError as e: |
| | Path.Log.error(e) |
| |
|
| | return 0 |
| |
|
| | def holePosition(self, obj, base, sub): |
| | """holePosition(obj, base, sub) ... returns a Vector for the position defined by the given features. |
| | Note that the value for Z is set to 0.""" |
| |
|
| | try: |
| | shape = base.Shape.getElement(sub) |
| | if shape.ShapeType == "Vertex": |
| | return FreeCAD.Vector(shape.X, shape.Y, 0) |
| |
|
| | if shape.ShapeType == "Edge" and hasattr(shape.Curve, "Center"): |
| | return FreeCAD.Vector(shape.Curve.Center.x, shape.Curve.Center.y, 0) |
| |
|
| | if shape.ShapeType == "Face": |
| | if hasattr(shape.Surface, "Center"): |
| | return FreeCAD.Vector(shape.Surface.Center.x, shape.Surface.Center.y, 0) |
| | if len(shape.Edges) == 1 and type(shape.Edges[0].Curve) == Part.Circle: |
| | return shape.Edges[0].Curve.Center |
| | except Part.OCCError as e: |
| | Path.Log.error(e) |
| |
|
| | Path.Log.error( |
| | translate( |
| | "CAM", |
| | "Feature %s.%s cannot be processed as a circular hole - please remove from Base geometry list.", |
| | ) |
| | % (base.Label, sub) |
| | ) |
| | return None |
| |
|
| | def isHoleEnabled(self, obj, base, sub): |
| | """isHoleEnabled(obj, base, sub) ... return true if hole is enabled.""" |
| | name = "%s.%s" % (base.Name, sub) |
| | return name not in obj.Disabled |
| |
|
| | 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.""" |
| | Path.Log.track() |
| |
|
| | def haveLocations(self, obj): |
| | if PathOp.FeatureLocations & self.opFeatures(obj): |
| | return len(obj.Locations) != 0 |
| | return False |
| |
|
| | holes = [] |
| | for base, subs in obj.Base: |
| | for sub in subs: |
| | Path.Log.debug("processing {} in {}".format(sub, base.Name)) |
| | if self.isHoleEnabled(obj, base, sub): |
| | pos = self.holePosition(obj, base, sub) |
| | if pos: |
| | holes.append( |
| | { |
| | "x": pos.x, |
| | "y": pos.y, |
| | "r": self.holeDiameter(obj, base, sub), |
| | } |
| | ) |
| |
|
| | if haveLocations(self, obj): |
| | for location in obj.Locations: |
| | holes.append({"x": location.x, "y": location.y, "r": 0}) |
| |
|
| | if len(holes) > 0: |
| | holes = PathUtils.sort_locations(holes, ["x", "y"]) |
| | self.circularHoleExecute(obj, holes) |
| |
|
| | def circularHoleExecute(self, obj, holes): |
| | """circularHoleExecute(obj, holes) ... implement processing of holes. |
| | holes is a list of dictionaries with 'x', 'y' and 'r' specified for each hole. |
| | Note that for Vertexes, non-circular Edges and Locations r=0. |
| | Must be overwritten by subclasses.""" |
| | pass |
| |
|
| | def findAllHoles(self, obj): |
| | """findAllHoles(obj) ... find all holes of all base models and assign as features.""" |
| | Path.Log.track() |
| | job = self.getJob(obj) |
| | if not job: |
| | return |
| |
|
| | matchvector = None if job.JobType == "Multiaxis" else FreeCAD.Vector(0, 0, 1) |
| | tooldiameter = obj.ToolController.Tool.Diameter |
| |
|
| | features = [] |
| | for base in self.model: |
| | features.extend( |
| | Drillable.getDrillableTargets(base, ToolDiameter=tooldiameter, vector=matchvector) |
| | ) |
| | obj.Base = features |
| | obj.Disabled = [] |
| |
|