Merge pull request #5470 from sliptonic/bug/bottomfacedrilling
[Path] Bug/bottomfacedrilling
This commit is contained in:
@@ -28,6 +28,8 @@ import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
import PathScripts.PathToolBit as PathToolBit
|
||||
from Generators import toolchange_generator as toolchange_generator
|
||||
from Generators.toolchange_generator import SpindleDirection
|
||||
|
||||
|
||||
if False:
|
||||
@@ -129,6 +131,7 @@ class ToolController:
|
||||
# Enumeration lists for App::PropertyEnumeration properties
|
||||
enums = {
|
||||
"SpindleDir": [
|
||||
(translate("Path_ToolController", "None"), "None"),
|
||||
(translate("Path_ToolController", "Forward"), "Forward"),
|
||||
(translate("Path_ToolController", "Reverse"), "Reverse"),
|
||||
], # this is the direction that the profile runs
|
||||
@@ -259,27 +262,30 @@ class ToolController:
|
||||
def execute(self, obj):
|
||||
PathLog.track()
|
||||
|
||||
commands = ""
|
||||
commands += "(" + obj.Label + ")" + "\n"
|
||||
commands += "M6 T" + str(obj.ToolNumber) + "\n"
|
||||
args = {
|
||||
"toolnumber": obj.ToolNumber,
|
||||
"toollabel": obj.Label,
|
||||
"spindlespeed": obj.SpindleSpeed,
|
||||
"spindledirection": SpindleDirection.OFF,
|
||||
}
|
||||
|
||||
# If a toolbit is used, check to see if spindlepower is allowed.
|
||||
# This is to prevent accidentally spinning the spindle with an
|
||||
# unpowered tool like probe or dragknife
|
||||
|
||||
allowSpindlePower = True
|
||||
if not isinstance(obj.Tool, Path.Tool) and hasattr(obj.Tool, "SpindlePower"):
|
||||
allowSpindlePower = obj.Tool.SpindlePower
|
||||
|
||||
if allowSpindlePower:
|
||||
PathLog.debug("selected tool preventing spindle power")
|
||||
if obj.SpindleDir == "Forward":
|
||||
commands += "M3 S" + str(obj.SpindleSpeed) + "\n"
|
||||
if hasattr(obj.Tool, "SpindlePower"):
|
||||
if not obj.Tool.SpindlePower:
|
||||
args["spindledirection"] = SpindleDirection.OFF
|
||||
else:
|
||||
commands += "M4 S" + str(obj.SpindleSpeed) + "\n"
|
||||
if obj.SpindleDir == "Forward":
|
||||
args["spindledirection"] = SpindleDirection.CW
|
||||
else:
|
||||
args["spindledirection"] = SpindleDirection.CCW
|
||||
elif obj.SpindleDir == "None":
|
||||
args["spindledirection"] = SpindleDirection.OFF
|
||||
else:
|
||||
if obj.SpindleDir == "Forward":
|
||||
args["spindledirection"] = SpindleDirection.CW
|
||||
else:
|
||||
args["spindledirection"] = SpindleDirection.CCW
|
||||
|
||||
if commands == "":
|
||||
commands += "(No commands processed)"
|
||||
commands = toolchange_generator.generate(**args)
|
||||
|
||||
path = Path.Path(commands)
|
||||
obj.Path = path
|
||||
|
||||
@@ -11,6 +11,34 @@ else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
def checkForBlindHole(baseshape, selectedFace):
|
||||
"""
|
||||
check for blind holes, returns the bottom face if found, none
|
||||
if the hole is a thru-hole
|
||||
"""
|
||||
circularFaces = [
|
||||
f
|
||||
for f in baseshape.Faces
|
||||
if len(f.OuterWire.Edges) == 1
|
||||
and type(f.OuterWire.Edges[0].Curve) == Part.Circle
|
||||
]
|
||||
|
||||
circularFaceEdges = [f.OuterWire.Edges[0] for f in circularFaces]
|
||||
commonedges = [
|
||||
i for i in selectedFace.Edges for x in circularFaceEdges if i.isSame(x)
|
||||
]
|
||||
|
||||
bottomface = None
|
||||
for f in circularFaces:
|
||||
for e in f.Edges:
|
||||
for i in commonedges:
|
||||
if e.isSame(i):
|
||||
bottomface = f
|
||||
break
|
||||
|
||||
return bottomface
|
||||
|
||||
|
||||
def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
"""
|
||||
checks if a candidate cylindrical face is drillable
|
||||
@@ -69,10 +97,20 @@ def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0,
|
||||
if not matchToolDiameter and not matchVector:
|
||||
return True
|
||||
|
||||
elif matchToolDiameter and tooldiameter / 2 > candidate.Surface.Radius:
|
||||
if matchToolDiameter and tooldiameter / 2 > candidate.Surface.Radius:
|
||||
PathLog.debug("The tool is larger than the target")
|
||||
return False
|
||||
|
||||
bottomface = checkForBlindHole(obj, candidate)
|
||||
PathLog.track("candidate is a blind hole")
|
||||
|
||||
if (
|
||||
bottomface is not None and matchVector
|
||||
): # blind holes only drillable at exact vector
|
||||
result = compareVecs(bottomface.normalAt(0, 0), vector, exact=True)
|
||||
PathLog.track(result)
|
||||
return result
|
||||
|
||||
elif matchVector and not (compareVecs(getSeam(candidate).Curve.Direction, vector)):
|
||||
PathLog.debug("The feature is not aligned with the given vector")
|
||||
return False
|
||||
@@ -80,7 +118,7 @@ def isDrillableCylinder(obj, candidate, tooldiameter=None, vector=App.Vector(0,
|
||||
return True
|
||||
|
||||
|
||||
def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
def isDrillableFace(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
"""
|
||||
checks if a flat face or edge is drillable
|
||||
"""
|
||||
@@ -93,36 +131,57 @@ def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0,
|
||||
)
|
||||
)
|
||||
|
||||
if candidate.ShapeType == "Face":
|
||||
if not type(candidate.Surface) == Part.Plane:
|
||||
PathLog.debug("Drilling on non-planar faces not supported")
|
||||
return False
|
||||
PathLog.track()
|
||||
if not type(candidate.Surface) == Part.Plane:
|
||||
PathLog.debug("Drilling on non-planar faces not supported")
|
||||
return False
|
||||
|
||||
if (
|
||||
len(candidate.Edges) == 1 and type(candidate.Edges[0].Curve) == Part.Circle
|
||||
): # Regular circular face
|
||||
edge = candidate.Edges[0]
|
||||
elif (
|
||||
len(candidate.Edges) == 2
|
||||
and type(candidate.Edges[0].Curve) == Part.Circle
|
||||
and type(candidate.Edges[1].Curve) == Part.Circle
|
||||
): # process a donut
|
||||
e1 = candidate.Edges[0]
|
||||
e2 = candidate.Edges[1]
|
||||
edge = e1 if e1.Curve.Radius < e2.Curve.Radius else e2
|
||||
else:
|
||||
PathLog.debug(
|
||||
"expected a Face with one or two circular edges got a face with {} edges".format(
|
||||
len(candidate.Edges)
|
||||
)
|
||||
if (
|
||||
len(candidate.Edges) == 1 and type(candidate.Edges[0].Curve) == Part.Circle
|
||||
): # Regular circular face
|
||||
edge = candidate.Edges[0]
|
||||
elif (
|
||||
len(candidate.Edges) == 2
|
||||
and type(candidate.Edges[0].Curve) == Part.Circle
|
||||
and type(candidate.Edges[1].Curve) == Part.Circle
|
||||
): # process a donut
|
||||
e1 = candidate.Edges[0]
|
||||
e2 = candidate.Edges[1]
|
||||
edge = e1 if e1.Curve.Radius < e2.Curve.Radius else e2
|
||||
else:
|
||||
PathLog.debug(
|
||||
"expected a Face with one or two circular edges got a face with {} edges".format(
|
||||
len(candidate.Edges)
|
||||
)
|
||||
)
|
||||
return False
|
||||
if vector is not None: # Check for blind hole alignment
|
||||
if not compareVecs(candidate.normalAt(0, 0), vector, exact=True):
|
||||
return False
|
||||
if matchToolDiameter and edge.Curve.Radius < tooldiameter / 2:
|
||||
PathLog.track()
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
else: # edge
|
||||
edge = candidate
|
||||
if not (isinstance(edge.Curve, Part.Circle) and edge.isClosed()):
|
||||
PathLog.debug("expected a closed circular edge")
|
||||
return False
|
||||
|
||||
def isDrillableEdge(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
"""
|
||||
checks if an edge is drillable
|
||||
"""
|
||||
|
||||
matchToolDiameter = tooldiameter is not None
|
||||
matchVector = vector is not None
|
||||
PathLog.debug(
|
||||
"\n match tool diameter {} \n match vector {}".format(
|
||||
matchToolDiameter, matchVector
|
||||
)
|
||||
)
|
||||
|
||||
edge = candidate
|
||||
if not (isinstance(edge.Curve, Part.Circle) and edge.isClosed()):
|
||||
PathLog.debug("expected a closed circular edge")
|
||||
return False
|
||||
|
||||
if not hasattr(edge.Curve, "Radius"):
|
||||
PathLog.debug("The Feature edge has no radius - Ellipse.")
|
||||
@@ -131,11 +190,11 @@ def isDrillableCircle(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0,
|
||||
if not matchToolDiameter and not matchVector:
|
||||
return True
|
||||
|
||||
elif matchToolDiameter and tooldiameter / 2 > edge.Curve.Radius:
|
||||
if matchToolDiameter and tooldiameter / 2 > edge.Curve.Radius:
|
||||
PathLog.debug("The tool is larger than the target")
|
||||
return False
|
||||
|
||||
elif matchVector and not (compareVecs(edge.Curve.Axis, vector)):
|
||||
if matchVector and not (compareVecs(edge.Curve.Axis, vector)):
|
||||
PathLog.debug("The feature is not aligned with the given vector")
|
||||
return False
|
||||
else:
|
||||
@@ -175,30 +234,38 @@ def isDrillable(obj, candidate, tooldiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
raise TypeError("expected a Face or Edge. Got a {}".format(candidate.ShapeType))
|
||||
|
||||
try:
|
||||
if candidate.ShapeType == "Face" and isinstance(
|
||||
candidate.Surface, Part.Cylinder
|
||||
):
|
||||
return isDrillableCylinder(obj, candidate, tooldiameter, vector)
|
||||
if candidate.ShapeType == "Face":
|
||||
if isinstance(candidate.Surface, Part.Cylinder):
|
||||
return isDrillableCylinder(obj, candidate, tooldiameter, vector)
|
||||
else:
|
||||
return isDrillableFace(obj, candidate, tooldiameter, vector)
|
||||
if candidate.ShapeType == "Edge":
|
||||
return isDrillableEdge(obj, candidate, tooldiameter, vector)
|
||||
else:
|
||||
return isDrillableCircle(obj, candidate, tooldiameter, vector)
|
||||
return False
|
||||
|
||||
except TypeError as e:
|
||||
PathLog.debug(e)
|
||||
return False
|
||||
# raise TypeError("{}".format(e))
|
||||
|
||||
|
||||
def compareVecs(vec1, vec2):
|
||||
def compareVecs(vec1, vec2, exact=False):
|
||||
"""
|
||||
compare the two vectors to see if they are aligned for drilling
|
||||
compare the two vectors to see if they are aligned for drilling.
|
||||
if exact is True, vectors must match direction. Otherwise,
|
||||
alignment can indicate the vectors are the same or exactly opposite
|
||||
"""
|
||||
|
||||
angle = vec1.getAngle(vec2)
|
||||
angle = 0 if math.isnan(angle) else math.degrees(angle)
|
||||
PathLog.debug("vector angle: {}".format(angle))
|
||||
return numpy.isclose(angle, 0, rtol=1e-05, atol=1e-06) or numpy.isclose(
|
||||
angle, 180, rtol=1e-05, atol=1e-06
|
||||
)
|
||||
if exact:
|
||||
return numpy.isclose(angle, 0, rtol=1e-05, atol=1e-06)
|
||||
else:
|
||||
return numpy.isclose(angle, 0, rtol=1e-05, atol=1e-06) or numpy.isclose(
|
||||
angle, 180, rtol=1e-05, atol=1e-06
|
||||
)
|
||||
|
||||
|
||||
def getDrillableTargets(obj, ToolDiameter=None, vector=App.Vector(0, 0, 1)):
|
||||
|
||||
Reference in New Issue
Block a user