Merge pull request #3663 from Russ4262/Slot_upgrade
[Path] Slot - Add basic support for arc and circular slots
This commit is contained in:
@@ -60,7 +60,7 @@ def translate(context, text, disambig=None):
|
||||
|
||||
|
||||
class ObjectSlot(PathOp.ObjectOp):
|
||||
'''Proxy object for Surfacing operation.'''
|
||||
'''Proxy object for Slot operation.'''
|
||||
|
||||
def opFeatures(self, obj):
|
||||
'''opFeatures(obj) ... return all standard features'''
|
||||
@@ -81,7 +81,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
obj.setEditorMode('ShowTempObjects', 2) # hide
|
||||
|
||||
if not hasattr(obj, 'DoNotSetDefaultValues'):
|
||||
self.setEditorProperties(obj)
|
||||
self.opSetEditorModes(obj)
|
||||
|
||||
def initOpProperties(self, obj, warn=False):
|
||||
'''initOpProperties(obj) ... create operation specific properties'''
|
||||
@@ -133,6 +133,8 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to use on the first selected feature.")),
|
||||
("App::PropertyEnumeration", "Reference2", "Slot",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to use on the second selected feature.")),
|
||||
("App::PropertyDistance", "ExtendRadius", "Slot",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "For arcs/circlular edges, offset the radius for the path.")),
|
||||
("App::PropertyBool", "ReverseDirection", "Slot",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable to reverse the cut direction of the slot path.")),
|
||||
|
||||
@@ -168,6 +170,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
'LayerMode': 'Multi-pass',
|
||||
'CutPattern': 'ZigZag',
|
||||
'PathOrientation': 'Start to End',
|
||||
'ExtendRadius': 0.0,
|
||||
'ReverseDirection': False,
|
||||
|
||||
# For debugging
|
||||
@@ -223,9 +226,10 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
return ENUMS
|
||||
|
||||
def setEditorProperties(self, obj):
|
||||
def opSetEditorModes(self, obj):
|
||||
# Used to hide inputs in properties list
|
||||
A = B = 2
|
||||
C = 0
|
||||
if hasattr(obj, 'Base'):
|
||||
if obj.Base:
|
||||
(base, subsList) = obj.Base[0]
|
||||
@@ -234,16 +238,18 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
A = 0
|
||||
elif subCnt == 2:
|
||||
A = B = 0
|
||||
C = 2
|
||||
|
||||
obj.setEditorMode('Reference1', A)
|
||||
obj.setEditorMode('Reference2', B)
|
||||
obj.setEditorMode('ExtendRadius', C)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if hasattr(self, 'propertiesReady'):
|
||||
if self.propertiesReady:
|
||||
if prop in ['Base']:
|
||||
self.updateEnumerations(obj)
|
||||
self.setEditorProperties(obj)
|
||||
self.opSetEditorModes(obj)
|
||||
|
||||
def opOnDocumentRestored(self, obj):
|
||||
self.propertiesReady = False
|
||||
@@ -266,7 +272,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
if restore:
|
||||
setattr(obj, n, val) # restore the value
|
||||
|
||||
self.setEditorProperties(obj)
|
||||
self.opSetEditorModes(obj)
|
||||
|
||||
def opApplyPropertyDefaults(self, obj, job, propList):
|
||||
# Set standard property defaults
|
||||
@@ -334,7 +340,6 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
'''opExecute(obj) ... process surface operation'''
|
||||
PathLog.track()
|
||||
|
||||
self.cancelOperation = False
|
||||
self.base = None
|
||||
self.shape1 = None
|
||||
self.shape2 = None
|
||||
@@ -345,8 +350,15 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
self.dYdX1 = None
|
||||
self.dYdX2 = None
|
||||
self.bottomEdges = None
|
||||
self.stockZMin = None
|
||||
self.isArc = 0
|
||||
self.arcCenter = None
|
||||
self.arcMidPnt = None
|
||||
self.arcRadius = 0.0
|
||||
self.newRadius = 0.0
|
||||
self.isDebug = False if PathLog.getLevel(PathLog.thisModule()) != 4 else True
|
||||
self.showTempObjects = obj.ShowTempObjects
|
||||
self.showDebugObjects = obj.ShowTempObjects
|
||||
self.stockZMin = self.job.Stock.Shape.BoundBox.ZMin
|
||||
CMDS = list()
|
||||
FCAD = FreeCAD.ActiveDocument
|
||||
|
||||
@@ -357,8 +369,8 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
self.module = __name__[dotIdx:]
|
||||
|
||||
if not self.isDebug:
|
||||
self.showTempObjects = False
|
||||
if self.showTempObjects:
|
||||
self.showDebugObjects = False
|
||||
if self.showDebugObjects:
|
||||
for grpNm in ['tmpDebugGrp', 'tmpDebugGrp001']:
|
||||
if hasattr(FreeCAD.ActiveDocument, grpNm):
|
||||
for go in FreeCAD.ActiveDocument.getObject(grpNm).Group:
|
||||
@@ -369,14 +381,6 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
# self.updateEnumerations(obj)
|
||||
|
||||
# Identify parent Job
|
||||
JOB = PathUtils.findParentJob(obj)
|
||||
self.JOB = JOB
|
||||
if JOB is None:
|
||||
PathLog.error(translate('PathSlot', "No JOB"))
|
||||
return
|
||||
self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin
|
||||
|
||||
# Begin GCode for operation with basic information
|
||||
# ... and move cutter to clearance height and startpoint
|
||||
tool = obj.ToolController.Tool
|
||||
@@ -411,20 +415,15 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
# ###### CLOSING COMMANDS FOR OPERATION ######
|
||||
|
||||
# Hide the temporary objects
|
||||
if self.showTempObjects:
|
||||
if self.showDebugObjects:
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
FreeCADGui.ActiveDocument.getObject(tmpGrpNm).Visibility = False
|
||||
self.tmpGrp.purgeTouched()
|
||||
|
||||
if self.cancelOperation:
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathSlot", "Canceled the Slot operation."))
|
||||
FreeCAD.ActiveDocument.removeObject(obj.Name)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
||||
return True
|
||||
|
||||
# Control method for operation
|
||||
# Control methods for operation
|
||||
def _makeOperation(self, obj):
|
||||
"""This method controls the overall slot creation process."""
|
||||
pnts = False
|
||||
@@ -439,13 +438,6 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
FreeCAD.Console.PrintError(msg + '\n')
|
||||
return False
|
||||
|
||||
# Calculate extensions to slot path
|
||||
begExt, endExt = 0, 0
|
||||
if obj.ExtendPathStart.Value != 0:
|
||||
begExt = obj.ExtendPathStart.Value
|
||||
if obj.ExtendPathEnd.Value != 0:
|
||||
endExt = obj.ExtendPathEnd.Value
|
||||
|
||||
if not obj.Base:
|
||||
# Use custom inputs here
|
||||
p1 = obj.CustomPoint1
|
||||
@@ -486,6 +478,129 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
if not pnts:
|
||||
return False
|
||||
|
||||
if self.isArc:
|
||||
cmds = self._finishArc(obj, pnts, featureCnt)
|
||||
else:
|
||||
cmds = self._finishLine(obj, pnts, featureCnt)
|
||||
|
||||
if cmds:
|
||||
return cmds
|
||||
|
||||
return False
|
||||
|
||||
def _finishArc(self, obj, pnts, featureCnt):
|
||||
"""This method finishes an Arc Slot operation."""
|
||||
PathLog.debug('arc center: {}'.format(self.arcCenter))
|
||||
self._addDebugObject(Part.makeLine(self.arcCenter, self.arcMidPnt), 'CentToMidPnt')
|
||||
|
||||
# PathLog.debug('Pre-offset points are:\np1 = {}\np2 = {}'.format(p1, p2))
|
||||
if obj.ExtendRadius.Value != 0:
|
||||
# verify offset does not force radius < 0
|
||||
newRadius = self.arcRadius + obj.ExtendRadius.Value
|
||||
PathLog.debug('arc radius: {}; offset radius: {}'.format(self.arcRadius, newRadius))
|
||||
if newRadius <= 0:
|
||||
msg = translate('PathSlot',
|
||||
'Current offset value is not possible.')
|
||||
FreeCAD.Console.PrintError(msg + '\n')
|
||||
return False
|
||||
else:
|
||||
(p1, p2) = pnts
|
||||
pnts = self._makeOffsetArc(p1, p2, self.arcCenter, newRadius)
|
||||
self.newRadius = newRadius
|
||||
else:
|
||||
PathLog.debug('arc radius: {}'.format(self.arcRadius))
|
||||
self.newRadius = self.arcRadius
|
||||
|
||||
# Apply path extension for arcs
|
||||
# PathLog.debug('Pre-extension points are:\np1 = {}\np2 = {}'.format(p1, p2))
|
||||
if self.isArc == 1:
|
||||
# Complete circle
|
||||
if (obj.ExtendPathStart.Value != 0 or
|
||||
obj.ExtendPathEnd.Value != 0):
|
||||
msg = translate('PathSlot',
|
||||
'No path extensions available for full circles.')
|
||||
FreeCAD.Console.PrintWarning(msg + '\n')
|
||||
else:
|
||||
# Arc segment
|
||||
# Apply extensions to slot path
|
||||
(p1, p2) = pnts
|
||||
begExt = obj.ExtendPathStart.Value
|
||||
endExt = obj.ExtendPathEnd.Value
|
||||
pnts = self._extendArcSlot(p1, p2, self.arcCenter, begExt, endExt)
|
||||
|
||||
if not pnts:
|
||||
return False
|
||||
|
||||
(p1, p2) = pnts
|
||||
# PathLog.error('Post-offset points are:\np1 = {}\np2 = {}'.format(p1, p2))
|
||||
if self.isDebug:
|
||||
PathLog.debug('Path Points are:\np1 = {}\np2 = {}'.format(p1, p2))
|
||||
if p1.sub(p2).Length != 0:
|
||||
self._addDebugObject(Part.makeLine(p1, p2), 'Path')
|
||||
|
||||
if featureCnt:
|
||||
obj.CustomPoint1 = p1
|
||||
obj.CustomPoint2 = p2
|
||||
|
||||
if self._arcCollisionCheck(obj, p1, p2, self.arcCenter, self.newRadius):
|
||||
msg = obj.Label + ' '
|
||||
msg += translate('PathSlot',
|
||||
'operation collides with model.')
|
||||
FreeCAD.Console.PrintError(msg + '\n')
|
||||
|
||||
# PathLog.warning('Unable to create G-code. _makeArcGCode() is incomplete.')
|
||||
cmds = self._makeArcGCode(obj, p1, p2)
|
||||
return cmds
|
||||
|
||||
def _makeArcGCode(self, obj, p1, p2):
|
||||
"""This method is the last in the overall slot creation process.
|
||||
It accepts the operation object and two end points for the path.
|
||||
It returns the slot gcode for the operation."""
|
||||
CMDS = list()
|
||||
PATHS = [(p1, p2, 'G2'), (p2, p1, 'G3')]
|
||||
|
||||
def arcPass(PNTS, depth):
|
||||
cmds = list()
|
||||
(p1, p2, cmd) = PNTS
|
||||
# cmds.append(Path.Command('N (Tool type: {})'.format(toolType), {}))
|
||||
cmds.append(Path.Command('G0', {'X': p1.x, 'Y': p1.y, 'F': self.horizRapid}))
|
||||
cmds.append(Path.Command('G1', {'Z': depth, 'F': self.vertFeed}))
|
||||
vtc = self.arcCenter.sub(p1) # vector to center
|
||||
cmds.append(
|
||||
Path.Command(cmd,
|
||||
{'X': p2.x, 'Y': p2.y, 'I': vtc.x,
|
||||
'J': vtc.y, 'F': self.horizFeed
|
||||
}))
|
||||
return cmds
|
||||
|
||||
if obj.LayerMode == 'Single-pass':
|
||||
PNTS = PATHS[0]
|
||||
if obj.ReverseDirection:
|
||||
PNTS = PATHS[1]
|
||||
CMDS.extend(arcPass(PNTS, obj.FinalDepth.Value))
|
||||
else:
|
||||
if obj.CutPattern == 'Line':
|
||||
PNTS = PATHS[0]
|
||||
if obj.ReverseDirection:
|
||||
PNTS = PATHS[1]
|
||||
for dep in self.depthParams:
|
||||
CMDS.extend(arcPass(PNTS, dep))
|
||||
CMDS.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
elif obj.CutPattern == 'ZigZag':
|
||||
i = 0
|
||||
for dep in self.depthParams:
|
||||
if i % 2.0 == 0: # even
|
||||
CMDS.extend(arcPass(PATHS[0], dep))
|
||||
else: # odd
|
||||
CMDS.extend(arcPass(PATHS[1], dep))
|
||||
i += 1
|
||||
# Raise to SafeHeight when finished
|
||||
CMDS.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
|
||||
return CMDS
|
||||
|
||||
def _finishLine(self, obj, pnts, featureCnt):
|
||||
"""This method finishes an Line Slot operation."""
|
||||
# Apply perpendicular rotation if requested
|
||||
perpZero = True
|
||||
if obj.PathOrientation == 'Perpendicular':
|
||||
@@ -498,7 +613,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
perpZero = False
|
||||
if perpZero:
|
||||
(p1, p2) = pnts
|
||||
pnts = self._makePerpendicular(p1, p2, 10.0)
|
||||
pnts = self._makePerpendicular(p1, p2, 10.0) # 10.0 offset below
|
||||
else:
|
||||
perpZero = False
|
||||
|
||||
@@ -509,10 +624,13 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
(p1, p2) = pnts
|
||||
|
||||
# Apply extensions to slot path
|
||||
begExt = obj.ExtendPathStart.Value
|
||||
endExt = obj.ExtendPathEnd.Value
|
||||
if perpZero:
|
||||
# Offsets for 10.0 value above in _makePerpendicular()
|
||||
begExt -= 5.0
|
||||
endExt -= 5.0
|
||||
pnts = self._extendSlot(p1, p2, begExt, endExt)
|
||||
pnts = self._extendLineSlot(p1, p2, begExt, endExt)
|
||||
|
||||
if not pnts:
|
||||
return False
|
||||
@@ -520,11 +638,8 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
(p1, p2) = pnts
|
||||
if self.isDebug:
|
||||
PathLog.debug('Path Points are:\np1 = {}\np2 = {}'.format(p1, p2))
|
||||
if p1.sub(p2).Length != 0 and self.showTempObjects:
|
||||
O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_Path')
|
||||
O.Shape = Part.makeLine(p1, p2)
|
||||
O.purgeTouched()
|
||||
self.tmpGrp.addObject(O)
|
||||
if p1.sub(p2).Length != 0:
|
||||
self._addDebugObject(Part.makeLine(p1, p2), 'Path')
|
||||
|
||||
if featureCnt:
|
||||
obj.CustomPoint1 = p1
|
||||
@@ -536,10 +651,10 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
'operation collides with model.')
|
||||
FreeCAD.Console.PrintWarning(msg + '\n')
|
||||
|
||||
cmds = self._makeGCode(obj, p1, p2)
|
||||
cmds = self._makeLineGCode(obj, p1, p2)
|
||||
return cmds
|
||||
|
||||
def _makeGCode(self, obj, p1, p2):
|
||||
def _makeLineGCode(self, obj, p1, p2):
|
||||
"""This method is the last in the overall slot creation process.
|
||||
It accepts the operation object and two end points for the path.
|
||||
It returns the slot gcode for the operation."""
|
||||
@@ -587,7 +702,6 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
if cat1 == 'Face':
|
||||
pnts = False
|
||||
|
||||
norm = shape_1.normalAt(0.0, 0.0)
|
||||
PathLog.debug('{}.normalAt(): {}'.format(sub1, norm))
|
||||
if norm.z == 1 or norm.z == -1:
|
||||
@@ -597,7 +711,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
if faceType:
|
||||
(geo, shp) = faceType
|
||||
if geo == 'Face':
|
||||
pnts = self._processSingleComplexFace(obj, shape_1)
|
||||
pnts = self._processSingleComplexFace(obj, shp)
|
||||
if geo == 'Wire':
|
||||
pnts = self._processSingleVertFace(obj, shp)
|
||||
if geo == 'Edge':
|
||||
@@ -614,11 +728,10 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
elif cat1 == 'Edge':
|
||||
PathLog.debug('Single edge')
|
||||
V1 = shape_1.Vertexes[0]
|
||||
V2 = shape_1.Vertexes[1]
|
||||
p1 = FreeCAD.Vector(V1.X, V1.Y, 0.0)
|
||||
p2 = FreeCAD.Vector(V2.X, V2.Y, 0.0)
|
||||
make = True
|
||||
pnts = self._processSingleEdge(obj, shape_1)
|
||||
if pnts:
|
||||
(p1, p2) = pnts
|
||||
make = True
|
||||
|
||||
elif cat1 == 'Vert':
|
||||
msg = translate('PathSlot',
|
||||
@@ -756,6 +869,112 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
(p1, p2) = self._getCutSidePoints(obj, v0, v1, a1, a2, b1, b2)
|
||||
return (p1, p2)
|
||||
|
||||
def _processSingleEdge(self, obj, edge):
|
||||
"""Determine slot path endpoints from a single horizontally oriented face."""
|
||||
PathLog.debug('_processSingleEdge()')
|
||||
tolrnc = 0.0000001
|
||||
lineTypes = ['Part::GeomLine']
|
||||
curveTypes = ['Part::GeomCircle']
|
||||
|
||||
def oversizedTool(holeDiam):
|
||||
# Test if tool larger than opening
|
||||
if self.tool.Diameter > holeDiam:
|
||||
msg = translate('PathSlot',
|
||||
'Current tool larger than arc diameter.')
|
||||
FreeCAD.Console.PrintError(msg + '\n')
|
||||
return True
|
||||
return False
|
||||
|
||||
def isHorizontal(z1, z2, z3):
|
||||
# Check that all Z values are equal (isRoughly same)
|
||||
if (abs(z1 - z2) > tolrnc or
|
||||
abs(z1 - z3) > tolrnc or
|
||||
abs(z2 - z3) > tolrnc):
|
||||
return False
|
||||
return True
|
||||
|
||||
def circleCentFrom3Points(P1, P2, P3):
|
||||
# Source code for this function copied from:
|
||||
# https://wiki.freecadweb.org/Macro_Draft_Circle_3_Points_3D
|
||||
P1P2 = (P2 - P1).Length
|
||||
P2P3 = (P3 - P2).Length
|
||||
P3P1 = (P1 - P3).Length
|
||||
|
||||
# Circle radius.
|
||||
l = ((P1 - P2).cross(P2 - P3)).Length
|
||||
try:
|
||||
r = P1P2 * P2P3 * P3P1 / 2 / l
|
||||
except:
|
||||
PathLog.error("The three points are aligned.")
|
||||
return False
|
||||
else:
|
||||
# Sphere center.
|
||||
a = P2P3**2 * (P1 - P2).dot(P1 - P3) / 2 / l**2
|
||||
b = P3P1**2 * (P2 - P1).dot(P2 - P3) / 2 / l**2
|
||||
c = P1P2**2 * (P3 - P1).dot(P3 - P2) / 2 / l**2
|
||||
P1.multiply(a)
|
||||
P2.multiply(b)
|
||||
P3.multiply(c)
|
||||
PC = P1 + P2 + P3
|
||||
return PC
|
||||
|
||||
# Process edge based on curve type
|
||||
if edge.Curve.TypeId in lineTypes:
|
||||
V1 = edge.Vertexes[0]
|
||||
V2 = edge.Vertexes[1]
|
||||
p1 = FreeCAD.Vector(V1.X, V1.Y, 0.0)
|
||||
p2 = FreeCAD.Vector(V2.X, V2.Y, 0.0)
|
||||
return (p1, p2)
|
||||
elif edge.Curve.TypeId in curveTypes:
|
||||
if len(edge.Vertexes) == 1:
|
||||
# Circle edge
|
||||
PathLog.debug('Arc with single vertex.')
|
||||
if oversizedTool(edge.BoundBox.XLength):
|
||||
return False
|
||||
|
||||
self.isArc = 1
|
||||
V1 = edge.Vertexes[0]
|
||||
tp1 = edge.valueAt(edge.getParameterByLength(edge.Length * 0.33))
|
||||
tp2 = edge.valueAt(edge.getParameterByLength(edge.Length * 0.66))
|
||||
if not isHorizontal(V1.Z, tp1.z, tp2.z):
|
||||
return False
|
||||
|
||||
cent = edge.BoundBox.Center
|
||||
self.arcCenter = FreeCAD.Vector(cent.x, cent.y, 0.0)
|
||||
midPnt = edge.valueAt(edge.getParameterByLength(edge.Length / 2.0))
|
||||
self.arcMidPnt = FreeCAD.Vector(midPnt.x, midPnt.y, 0.0)
|
||||
self.arcRadius = edge.BoundBox.XLength / 2.0
|
||||
p1 = FreeCAD.Vector(V1.X, V1.Y, 0.0)
|
||||
p2 = FreeCAD.Vector(V1.X, V1.Y, 0.0)
|
||||
else:
|
||||
# Arc edge
|
||||
PathLog.debug('Arc with multiple vertices.')
|
||||
self.isArc = 2
|
||||
V1 = edge.Vertexes[0]
|
||||
V2 = edge.Vertexes[1]
|
||||
midPnt = edge.valueAt(edge.getParameterByLength(edge.Length / 2.0))
|
||||
if not isHorizontal(V1.Z, V2.Z, midPnt.z):
|
||||
return False
|
||||
|
||||
p1 = FreeCAD.Vector(V1.X, V1.Y, 0.0)
|
||||
p2 = FreeCAD.Vector(V2.X, V2.Y, 0.0)
|
||||
# Duplicate points required because
|
||||
# circleCentFrom3Points() alters original arguments
|
||||
pA = FreeCAD.Vector(V1.X, V1.Y, 0.0)
|
||||
pB = FreeCAD.Vector(V2.X, V2.Y, 0.0)
|
||||
pC = FreeCAD.Vector(midPnt.x, midPnt.y, 0.0)
|
||||
cCF3P = circleCentFrom3Points(pA, pB, pC)
|
||||
if not cCF3P:
|
||||
return False
|
||||
self.arcMidPnt = FreeCAD.Vector(midPnt.x, midPnt.y, 0.0)
|
||||
self.arcCenter = cCF3P
|
||||
self.arcRadius = p1.sub(cCF3P).Length
|
||||
|
||||
if oversizedTool(self.arcRadius * 2.0):
|
||||
return False
|
||||
|
||||
return (p1, p2)
|
||||
|
||||
# Methods for processing double geometry
|
||||
def _processDouble(self, obj, shape_1, sub1, shape_2, sub2):
|
||||
PathLog.debug('_processDouble()')
|
||||
@@ -949,7 +1168,77 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
|
||||
return False
|
||||
|
||||
def _extendSlot(self, p1, p2, begExt, endExt):
|
||||
def _extendArcSlot(self, p1, p2, cent, begExt, endExt):
|
||||
cancel = True
|
||||
n1 = p1
|
||||
n2 = p2
|
||||
|
||||
def getArcLine(length, rads):
|
||||
rads = abs(length / self.newRadius)
|
||||
x = self.newRadius * math.cos(rads)
|
||||
y = self.newRadius * math.sin(rads)
|
||||
a = FreeCAD.Vector(self.newRadius, 0.0, 0.0)
|
||||
b = FreeCAD.Vector(x, y, 0.0)
|
||||
c = FreeCAD.Vector(0.0, 0.0, 0.0)
|
||||
return Part.makeLine(a, b)
|
||||
|
||||
if begExt or endExt:
|
||||
cancel = False
|
||||
if cancel:
|
||||
return (p1, p2)
|
||||
|
||||
# Convert extension to radians
|
||||
origin = FreeCAD.Vector(0.0, 0.0, 0.0)
|
||||
if begExt:
|
||||
# Create arc representing extension
|
||||
rads = abs(begExt / self.newRadius)
|
||||
line = getArcLine(begExt, rads)
|
||||
|
||||
rotToRads = self._xyToRadians(p1.sub(self.arcCenter))
|
||||
if begExt < 1:
|
||||
rotToRads -= rads
|
||||
rotToDeg = math.degrees(rotToRads)
|
||||
# PathLog.debug('begExt angles are: {}, {}'.format(rotToRads, rotToDeg))
|
||||
|
||||
line.rotate(origin, FreeCAD.Vector(0, 0, 1), rotToDeg)
|
||||
line.translate(self.arcCenter)
|
||||
self._addDebugObject(line, 'ExtendStart')
|
||||
v1 = line.Vertexes[1]
|
||||
if begExt < 1:
|
||||
v1 = line.Vertexes[0]
|
||||
n1 = FreeCAD.Vector(v1.X, v1.Y, 0.0)
|
||||
|
||||
if endExt:
|
||||
# Create arc representing extension
|
||||
rads = abs(endExt / self.newRadius)
|
||||
line = getArcLine(endExt, rads)
|
||||
|
||||
rotToRads = self._xyToRadians(p2.sub(self.arcCenter)) - rads
|
||||
if endExt < 1:
|
||||
rotToRads += rads
|
||||
rotToDeg = math.degrees(rotToRads)
|
||||
# PathLog.debug('endExt angles are: {}, {}'.format(rotToRads, rotToDeg))
|
||||
|
||||
line.rotate(origin, FreeCAD.Vector(0, 0, 1), rotToDeg)
|
||||
line.translate(self.arcCenter)
|
||||
self._addDebugObject(line, 'ExtendEnd')
|
||||
v1 = line.Vertexes[0]
|
||||
if endExt < 1:
|
||||
v1 = line.Vertexes[1]
|
||||
n2 = FreeCAD.Vector(v1.X, v1.Y, 0.0)
|
||||
|
||||
return (n1, n2)
|
||||
|
||||
def _makeOffsetArc(self, p1, p2, center, newRadius):
|
||||
n1 = p1.sub(center).normalize()
|
||||
n2 = p2.sub(center).normalize()
|
||||
n1.multiply(newRadius)
|
||||
n2.multiply(newRadius)
|
||||
p1 = n1.add(center)
|
||||
p2 = n2.add(center)
|
||||
return (p1, p2)
|
||||
|
||||
def _extendLineSlot(self, p1, p2, begExt, endExt):
|
||||
if begExt:
|
||||
beg = p1.sub(p2)
|
||||
beg.normalize()
|
||||
@@ -1178,10 +1467,10 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
isFace = False
|
||||
csWire = wires[0]
|
||||
if wires[0].isClosed():
|
||||
csWire = Part.Face(wires[0])
|
||||
if csWire.Area > 0:
|
||||
csWire.translate(FreeCAD.Vector(0.0, 0.0, shape.BoundBox.ZMin - csWire.BoundBox.ZMin))
|
||||
return ('Face', csWire)
|
||||
face = Part.Face(wires[0])
|
||||
if face.Area > 0:
|
||||
face.translate(FreeCAD.Vector(0.0, 0.0, shape.BoundBox.ZMin - face.BoundBox.ZMin))
|
||||
return ('Face', face)
|
||||
return ('Wire', wires[0])
|
||||
return False
|
||||
|
||||
@@ -1267,11 +1556,7 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
else:
|
||||
pathTravel = startShp
|
||||
|
||||
if self.showTempObjects:
|
||||
O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_PathTravel')
|
||||
O.Shape = pathTravel
|
||||
O.purgeTouched()
|
||||
self.tmpGrp.addObject(O)
|
||||
self._addDebugObject(pathTravel, 'PathTravel')
|
||||
|
||||
# Check for collision with model
|
||||
try:
|
||||
@@ -1282,6 +1567,124 @@ class ObjectSlot(PathOp.ObjectOp):
|
||||
PathLog.debug('Failed to complete path collision check.')
|
||||
|
||||
return False
|
||||
|
||||
def _arcCollisionCheck(self, obj, p1, p2, arcCenter, arcRadius):
|
||||
"""Make simple circle with diameter of tool, at start and end points.
|
||||
Make arch face between circles. Fuse and extrude it vertically.
|
||||
Check for collision with model."""
|
||||
# Make path travel of tool as 3D solid.
|
||||
rad = self.tool.Diameter / 2.0
|
||||
extFwd = obj.StartDepth.Value - obj.FinalDepth.Value
|
||||
extVect = FreeCAD.Vector(0.0, 0.0, extFwd)
|
||||
|
||||
if self.isArc == 1:
|
||||
# full circular slot
|
||||
# make outer circle
|
||||
oCircle = Part.makeCircle(arcRadius + rad, arcCenter)
|
||||
oWire = Part.Wire(oCircle.Edges[0])
|
||||
outer = Part.Face(oWire)
|
||||
# make inner circle
|
||||
iRadius = arcRadius - rad
|
||||
if iRadius > 0:
|
||||
iCircle = Part.makeCircle(iRadius, arcCenter)
|
||||
iWire = Part.Wire(iCircle.Edges[0])
|
||||
inner = Part.Face(iWire)
|
||||
# Cut outer with inner
|
||||
path = outer.cut(inner)
|
||||
else:
|
||||
path = outer
|
||||
zTrans = obj.FinalDepth.Value - path.BoundBox.ZMin
|
||||
path.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
pathTravel = path.extrude(extVect)
|
||||
else:
|
||||
# arc slot
|
||||
# Make first cylinder
|
||||
ce1 = Part.Wire(Part.makeCircle(rad, p1).Edges)
|
||||
C1 = Part.Face(ce1)
|
||||
zTrans = obj.FinalDepth.Value - C1.BoundBox.ZMin
|
||||
C1.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
startShp = C1.extrude(extVect)
|
||||
# self._addDebugObject(startShp, 'StartCyl')
|
||||
|
||||
# Make second cylinder
|
||||
ce2 = Part.Wire(Part.makeCircle(rad, p2).Edges)
|
||||
C2 = Part.Face(ce2)
|
||||
zTrans = obj.FinalDepth.Value - C2.BoundBox.ZMin
|
||||
C2.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
endShp = C2.extrude(extVect)
|
||||
# self._addDebugObject(endShp, 'EndCyl')
|
||||
|
||||
# Make wire with inside and outside arcs, and lines on ends.
|
||||
# Convert wire to face, then extrude
|
||||
import draftgeoutils.arcs as Arcs
|
||||
# Arc 1 - inside
|
||||
# verify offset does not force radius < 0
|
||||
newRadius = arcRadius - rad
|
||||
# PathLog.debug('arcRadius, newRadius: {}, {}'.format(arcRadius, newRadius))
|
||||
if newRadius <= 0:
|
||||
msg = translate('PathSlot',
|
||||
'Current offset value is not possible.')
|
||||
FreeCAD.Console.PrintError(msg + '\n')
|
||||
return False
|
||||
else:
|
||||
(pA, pB) = self._makeOffsetArc(p1, p2, arcCenter, newRadius)
|
||||
arc_inside = Arcs.arcFrom2Pts(pA, pB, arcCenter)
|
||||
|
||||
# Arc 2 - outside
|
||||
# verify offset does not force radius < 0
|
||||
newRadius = arcRadius + rad
|
||||
# PathLog.debug('arcRadius, newRadius: {}, {}'.format(arcRadius, newRadius))
|
||||
if newRadius <= 0:
|
||||
msg = translate('PathSlot',
|
||||
'Current offset value is not possible.')
|
||||
FreeCAD.Console.PrintError(msg + '\n')
|
||||
return False
|
||||
else:
|
||||
(pC, pD) = self._makeOffsetArc(p1, p2, arcCenter, newRadius)
|
||||
arc_outside = Arcs.arcFrom2Pts(pC, pD, arcCenter)
|
||||
|
||||
# Make end lines to connect arcs
|
||||
vA = arc_inside.Vertexes[0]
|
||||
vB = arc_inside.Vertexes[1]
|
||||
vC = arc_outside.Vertexes[1]
|
||||
vD = arc_outside.Vertexes[0]
|
||||
pa = FreeCAD.Vector(vA.X, vA.Y, 0.0)
|
||||
pb = FreeCAD.Vector(vB.X, vB.Y, 0.0)
|
||||
pc = FreeCAD.Vector(vC.X, vC.Y, 0.0)
|
||||
pd = FreeCAD.Vector(vD.X, vD.Y, 0.0)
|
||||
|
||||
# Make closed arch face and extrude
|
||||
e1 = Part.makeLine(pb, pc)
|
||||
e2 = Part.makeLine(pd, pa)
|
||||
edges = Part.__sortEdges__([arc_inside, e1, arc_outside, e2])
|
||||
rectFace = Part.Face(Part.Wire(edges))
|
||||
zTrans = obj.FinalDepth.Value - rectFace.BoundBox.ZMin
|
||||
rectFace.translate(FreeCAD.Vector(0.0, 0.0, zTrans))
|
||||
boxShp = rectFace.extrude(extVect)
|
||||
# self._addDebugObject(boxShp, 'ArcBox')
|
||||
|
||||
# Fuse two cylinders and box together
|
||||
part1 = startShp.fuse(boxShp)
|
||||
pathTravel = part1.fuse(endShp)
|
||||
|
||||
self._addDebugObject(pathTravel, 'PathTravel')
|
||||
|
||||
# Check for collision with model
|
||||
try:
|
||||
cmn = self.base.Shape.common(pathTravel)
|
||||
if cmn.Volume > 0.000001:
|
||||
return True
|
||||
except Exception:
|
||||
PathLog.debug('Failed to complete path collision check.')
|
||||
|
||||
return False
|
||||
|
||||
def _addDebugObject(self, objShape, objName):
|
||||
if self.showDebugObjects:
|
||||
do = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_' + objName)
|
||||
do.Shape = objShape
|
||||
do.purgeTouched()
|
||||
self.tempGroup.addObject(do)
|
||||
# Eclass
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user