Make CircularHoleBase use drillableLib

debugs

remove deprecated PathUtils.isDrillable

drillableLib cmake

make PathProfile use new drillableLib
This commit is contained in:
sliptonic
2021-12-05 20:16:23 -06:00
parent 69d2ce2501
commit 18582ff9af
9 changed files with 133 additions and 290 deletions

View File

@@ -23,7 +23,9 @@
import FreeCAD
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
# import PathScripts.PathUtils as PathUtils
import PathScripts.drillableLib as drillableLib
from PySide import QtCore
@@ -46,9 +48,11 @@ def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class ObjectOp(PathOp.ObjectOp):
"""Base class for proxy objects of all operations on circular holes."""
@@ -172,7 +176,6 @@ class ObjectOp(PathOp.ObjectOp):
return False
holes = []
for base, subs in obj.Base:
for sub in subs:
PathLog.debug("processing {} in {}".format(sub, base.Name))
@@ -204,85 +207,19 @@ class ObjectOp(PathOp.ObjectOp):
def findAllHoles(self, obj):
"""findAllHoles(obj) ... find all holes of all base models and assign as features."""
PathLog.track()
if not self.getJob(obj):
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(self.findHoles(obj, base))
features.extend(
drillableLib.getDrillableTargets(
base, ToolDiameter=tooldiameter, vector=matchvector
)
)
obj.Base = features
obj.Disabled = []
def findHoles(self, obj, baseobject):
"""findHoles(obj, baseobject) ... inspect baseobject and identify all features that resemble a straight cricular hole."""
shape = baseobject.Shape
PathLog.track("obj: {} shape: {}".format(obj, shape))
holelist = []
features = []
# tooldiameter = float(obj.ToolController.Proxy.getTool(obj.ToolController).Diameter)
tooldiameter = None
PathLog.debug(
"search for holes larger than tooldiameter: {}: ".format(tooldiameter)
)
if DraftGeomUtils.isPlanar(shape):
PathLog.debug("shape is planar")
for i in range(len(shape.Edges)):
candidateEdgeName = "Edge" + str(i + 1)
e = shape.getElement(candidateEdgeName)
if PathUtils.isDrillable(shape, e, tooldiameter):
PathLog.debug(
"edge candidate: {} (hash {})is drillable ".format(
e, e.hashCode()
)
)
x = e.Curve.Center.x
y = e.Curve.Center.y
diameter = e.BoundBox.XLength
holelist.append(
{
"featureName": candidateEdgeName,
"feature": e,
"x": x,
"y": y,
"d": diameter,
"enabled": True,
}
)
features.append((baseobject, candidateEdgeName))
PathLog.debug(
"Found hole feature %s.%s"
% (baseobject.Label, candidateEdgeName)
)
else:
PathLog.debug("shape is not planar")
for i in range(len(shape.Faces)):
candidateFaceName = "Face" + str(i + 1)
f = shape.getElement(candidateFaceName)
if PathUtils.isDrillable(shape, f, tooldiameter):
PathLog.debug("face candidate: {} is drillable ".format(f))
if hasattr(f.Surface, "Center"):
x = f.Surface.Center.x
y = f.Surface.Center.y
diameter = f.BoundBox.XLength
else:
center = f.Edges[0].Curve.Center
x = center.x
y = center.y
diameter = f.Edges[0].Curve.Radius * 2
holelist.append(
{
"featureName": candidateFaceName,
"feature": f,
"x": x,
"y": y,
"d": diameter,
"enabled": True,
}
)
features.append((baseobject, candidateFaceName))
PathLog.debug(
"Found hole feature %s.%s"
% (baseobject.Label, candidateFaceName)
)
PathLog.debug("holes found: {}".format(holelist))
return features

View File

@@ -43,9 +43,11 @@ __doc__ = "Path Drilling operation."
__contributors__ = "IMBack!"
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):

View File

@@ -187,6 +187,7 @@ class ObjectJob:
"Base",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Type of Job"),
)
obj.setEditorMode("JobType", 2) # Hide
obj.addProperty(
"App::PropertyBool",
@@ -494,6 +495,8 @@ class ObjectJob:
"Base",
QtCore.QT_TRANSLATE_NOOP("PathJob", "Select the Type of Job"),
)
obj.setEditorMode("JobType", 2) # Hide
obj.JobType = ["2D", "2.5D", "Lathe", "Multiaxis"]
def onChanged(self, obj, prop):

View File

@@ -28,6 +28,7 @@ import PathScripts.PathAreaOp as PathAreaOp
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
import PathScripts.drillableLib as drillableLib
import math
import numpy
from PySide.QtCore import QT_TRANSLATE_NOOP
@@ -444,7 +445,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
for baseShape, wire in holes:
cont = False
f = Part.makeFace(wire, "Part::FaceMakerSimple")
drillable = PathUtils.isDrillable(baseShape, wire)
drillable = drillableLib.isDrillable(baseShape, f)
if obj.processCircles:
if drillable:

View File

@@ -21,13 +21,13 @@
# * *
# ***************************************************************************
'''Selection gates and observers to control selectability while building Path operations '''
"""Selection gates and observers to control selectability while building Path operations """
import FreeCAD
import FreeCADGui
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathUtils as PathUtils
import PathScripts.drillableLib as drillableLib
import math
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
@@ -41,12 +41,12 @@ class PathBaseGate(object):
class EGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
return sub and sub[0:4] == 'Edge'
return sub and sub[0:4] == "Edge"
class MESHGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
return obj.TypeId[0:4] == 'Mesh'
return obj.TypeId[0:4] == "Mesh"
class VCARVEGate:
@@ -59,20 +59,20 @@ class VCARVEGate:
if math.fabs(shape.Volume) < 1e-9 and len(shape.Wires) > 0:
return True
if shape.ShapeType == 'Face':
if shape.ShapeType == "Face":
return True
elif shape.ShapeType == 'Solid':
if sub and sub[0:4] == 'Face':
elif shape.ShapeType == "Solid":
if sub and sub[0:4] == "Face":
return True
elif shape.ShapeType == 'Compound':
if sub and sub[0:4] == 'Face':
elif shape.ShapeType == "Compound":
if sub and sub[0:4] == "Face":
return True
if sub:
subShape = shape.getElement(sub)
if subShape.ShapeType == 'Edge':
if subShape.ShapeType == "Edge":
return False
return False
@@ -88,35 +88,35 @@ class ENGRAVEGate(PathBaseGate):
if math.fabs(shape.Volume) < 1e-9 and len(shape.Wires) > 0:
return True
if shape.ShapeType == 'Edge':
if shape.ShapeType == "Edge":
return True
if sub:
subShape = shape.getElement(sub)
if subShape.ShapeType == 'Edge':
if subShape.ShapeType == "Edge":
return True
return False
class CHAMFERGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
try:
shape = obj.Shape
except Exception: # pylint: disable=broad-except
except Exception: # pylint: disable=broad-except
return False
if math.fabs(shape.Volume) < 1e-9 and len(shape.Wires) > 0:
return True
if 'Edge' == shape.ShapeType or 'Face' == shape.ShapeType:
if "Edge" == shape.ShapeType or "Face" == shape.ShapeType:
return True
if sub:
subShape = shape.getElement(sub)
if subShape.ShapeType == 'Edge':
if subShape.ShapeType == "Edge":
return True
elif (subShape.ShapeType == 'Face'):
elif subShape.ShapeType == "Face":
return True
return False
@@ -124,16 +124,19 @@ class CHAMFERGate(PathBaseGate):
class DRILLGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
PathLog.debug('obj: {} sub: {}'.format(obj, sub))
if hasattr(obj, "Shape") and sub:
shape = obj.Shape
subobj = shape.getElement(sub)
return PathUtils.isDrillable(shape, subobj, includePartials=True)
else:
PathLog.debug("obj: {} sub: {}".format(obj, sub))
if not hasattr(obj, "Shape") and sub:
return False
shape = obj.Shape
subobj = shape.getElement(sub)
if subobj.ShapeType not in ["Edge", "Face"]:
return False
return drillableLib.isDrillable(shape, subobj, vector=None)
class FACEGate(PathBaseGate): # formerly PROFILEGate class using allow_ORIG method as allow()
class FACEGate(
PathBaseGate
): # formerly PROFILEGate class using allow_ORIG method as allow()
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
profileable = False
@@ -142,15 +145,15 @@ class FACEGate(PathBaseGate): # formerly PROFILEGate class using allow_ORIG me
except Exception: # pylint: disable=broad-except
return False
if obj.ShapeType == 'Compound':
if sub and sub[0:4] == 'Face':
if obj.ShapeType == "Compound":
if sub and sub[0:4] == "Face":
profileable = True
elif obj.ShapeType == 'Face': # 3D Face, not flat, planar?
profileable = True # Was False
elif obj.ShapeType == "Face": # 3D Face, not flat, planar?
profileable = True # Was False
elif obj.ShapeType == 'Solid':
if sub and sub[0:4] == 'Face':
elif obj.ShapeType == "Solid":
if sub and sub[0:4] == "Face":
profileable = True
return profileable
@@ -163,27 +166,27 @@ class FACEGate(PathBaseGate): # formerly PROFILEGate class using allow_ORIG me
except Exception: # pylint: disable=broad-except
return False
if obj.ShapeType == 'Edge':
if obj.ShapeType == "Edge":
profileable = False
elif obj.ShapeType == 'Compound':
if sub and sub[0:4] == 'Face':
elif obj.ShapeType == "Compound":
if sub and sub[0:4] == "Face":
profileable = True
if sub and sub[0:4] == 'Edge':
if sub and sub[0:4] == "Edge":
profileable = False
elif obj.ShapeType == 'Face':
elif obj.ShapeType == "Face":
profileable = False
elif obj.ShapeType == 'Solid':
if sub and sub[0:4] == 'Face':
elif obj.ShapeType == "Solid":
if sub and sub[0:4] == "Face":
profileable = True
if sub and sub[0:4] == 'Edge':
if sub and sub[0:4] == "Edge":
profileable = False
elif obj.ShapeType == 'Wire':
elif obj.ShapeType == "Wire":
profileable = False
return profileable
@@ -191,7 +194,7 @@ class FACEGate(PathBaseGate): # formerly PROFILEGate class using allow_ORIG me
class PROFILEGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
if sub and sub[0:4] == 'Edge':
if sub and sub[0:4] == "Edge":
return True
try:
@@ -199,18 +202,18 @@ class PROFILEGate(PathBaseGate):
except Exception: # pylint: disable=broad-except
return False
if obj.ShapeType == 'Compound':
if sub and sub[0:4] == 'Face':
if obj.ShapeType == "Compound":
if sub and sub[0:4] == "Face":
return True
elif obj.ShapeType == 'Face':
elif obj.ShapeType == "Face":
return True
elif obj.ShapeType == 'Solid':
if sub and sub[0:4] == 'Face':
elif obj.ShapeType == "Solid":
if sub and sub[0:4] == "Face":
return True
elif obj.ShapeType == 'Wire':
elif obj.ShapeType == "Wire":
return True
return False
@@ -225,18 +228,18 @@ class POCKETGate(PathBaseGate):
except Exception: # pylint: disable=broad-except
return False
if obj.ShapeType == 'Edge':
if obj.ShapeType == "Edge":
pocketable = False
elif obj.ShapeType == 'Face':
elif obj.ShapeType == "Face":
pocketable = True
elif obj.ShapeType == 'Solid':
if sub and sub[0:4] == 'Face':
elif obj.ShapeType == "Solid":
if sub and sub[0:4] == "Face":
pocketable = True
elif obj.ShapeType == 'Compound':
if sub and sub[0:4] == 'Face':
elif obj.ShapeType == "Compound":
if sub and sub[0:4] == "Face":
pocketable = True
return pocketable
@@ -266,22 +269,22 @@ class PROBEGate:
class TURNGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
PathLog.debug('obj: {} sub: {}'.format(obj, sub))
PathLog.debug("obj: {} sub: {}".format(obj, sub))
if hasattr(obj, "Shape") and sub:
shape = obj.Shape
subobj = shape.getElement(sub)
return PathUtils.isDrillable(shape, subobj, includePartials=True)
return drillableLib.isDrillable(shape, subobj, vector=None)
else:
return False
class ALLGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
if sub and sub[0:6] == 'Vertex':
if sub and sub[0:6] == "Vertex":
return True
if sub and sub[0:4] == 'Edge':
if sub and sub[0:4] == "Edge":
return True
if sub and sub[0:4] == 'Face':
if sub and sub[0:4] == "Face":
return True
return False
@@ -348,7 +351,7 @@ def slotselect():
def surfaceselect():
gate = False
if(MESHGate() or FACEGate()):
if MESHGate() or FACEGate():
gate = True
FreeCADGui.Selection.addSelectionGate(gate)
if not PathPreferences.suppressSelectionModeWarning():
@@ -380,30 +383,30 @@ def turnselect():
def select(op):
opsel = {}
opsel['Contour'] = contourselect # (depreciated)
opsel['Deburr'] = chamferselect
opsel['Drilling'] = drillselect
opsel['Engrave'] = engraveselect
opsel['Helix'] = drillselect
opsel['MillFace'] = pocketselect
opsel['Pocket'] = pocketselect
opsel['Pocket 3D'] = pocketselect
opsel['Pocket Shape'] = pocketselect
opsel['Profile Edges'] = eselect # (depreciated)
opsel['Profile Faces'] = fselect # (depreciated)
opsel['Profile'] = profileselect
opsel['Slot'] = slotselect
opsel['Surface'] = surfaceselect
opsel['Waterline'] = surfaceselect
opsel['Adaptive'] = adaptiveselect
opsel['Vcarve'] = vcarveselect
opsel['Probe'] = probeselect
opsel['Custom'] = customselect
opsel['Thread Milling'] = drillselect
opsel['TurnFace'] = turnselect
opsel['TurnProfile'] = turnselect
opsel['TurnPartoff'] = turnselect
opsel['TurnRough'] = turnselect
opsel["Contour"] = contourselect # (depreciated)
opsel["Deburr"] = chamferselect
opsel["Drilling"] = drillselect
opsel["Engrave"] = engraveselect
opsel["Helix"] = drillselect
opsel["MillFace"] = pocketselect
opsel["Pocket"] = pocketselect
opsel["Pocket 3D"] = pocketselect
opsel["Pocket Shape"] = pocketselect
opsel["Profile Edges"] = eselect # (depreciated)
opsel["Profile Faces"] = fselect # (depreciated)
opsel["Profile"] = profileselect
opsel["Slot"] = slotselect
opsel["Surface"] = surfaceselect
opsel["Waterline"] = surfaceselect
opsel["Adaptive"] = adaptiveselect
opsel["Vcarve"] = vcarveselect
opsel["Probe"] = probeselect
opsel["Custom"] = customselect
opsel["Thread Milling"] = drillselect
opsel["TurnFace"] = turnselect
opsel["TurnProfile"] = turnselect
opsel["TurnPartoff"] = turnselect
opsel["TurnRough"] = turnselect
return opsel[op]

View File

@@ -73,119 +73,6 @@ def waiting_effects(function):
return new_function
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)
)
if list == type(obj):
for shape in obj:
if isDrillable(shape, candidate, tooldiameter, includePartials):
return (True, shape)
return (False, None)
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: # pylint: disable=broad-except
PathLog.warning(
translate("Path", "Issue determine drillability: {}").format(ex)
)
return drillable
# set at 4 decimal places for testing
def fmt(val):
return format(val, ".4f")