Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
@@ -49,6 +48,7 @@ PathLog.setLevel(LOGLEVEL, PathLog.thisModule())
|
||||
if LOGLEVEL is PathLog.Level.DEBUG:
|
||||
PathLog.trackModule()
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
@@ -66,7 +66,6 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
'''opFeatures(obj) ... returns the base features supported by all Path.Area based operations.
|
||||
The standard feature list is OR'ed with the return value of areaOpFeatures().
|
||||
Do not overwrite, implement areaOpFeatures(obj) instead.'''
|
||||
# return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureRotation
|
||||
return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureCoolant
|
||||
|
||||
def areaOpFeatures(self, obj):
|
||||
@@ -304,8 +303,6 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
pathParams['return_end'] = True
|
||||
# Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers
|
||||
pathParams['preamble'] = False
|
||||
#if not self.areaOpRetractTool(obj):
|
||||
# pathParams['threshold'] = 2.001 * self.radius
|
||||
|
||||
if self.endVector is None:
|
||||
V = hWire.Wires[0].Vertexes
|
||||
@@ -374,12 +371,6 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
obj.ClearanceHeight.Value = strDep + self.clrOfset
|
||||
obj.SafeHeight.Value = strDep + self.safOfst
|
||||
|
||||
#if self.initWithRotation is False:
|
||||
# if obj.FinalDepth.Value == obj.OpFinalDepth.Value:
|
||||
# obj.FinalDepth.Value = finDep
|
||||
# if obj.StartDepth.Value == obj.OpStartDepth.Value:
|
||||
# obj.StartDepth.Value = strDep
|
||||
|
||||
# Create visual axes when debugging.
|
||||
if PathLog.getLevel(PathLog.thisModule()) == 4:
|
||||
self.visualAxis()
|
||||
@@ -467,10 +458,14 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
# Rotate Model to correct angle
|
||||
ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid}))
|
||||
|
||||
# Raise cutter to safe depth and return index to starting position
|
||||
ppCmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
if axis != nextAxis:
|
||||
ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid}))
|
||||
# Raise cutter to safe height
|
||||
ppCmds.insert(0, Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
|
||||
# Return index to starting position if axis of rotation changes.
|
||||
if numShapes > 1:
|
||||
if ns != numShapes - 1:
|
||||
if axis != nextAxis:
|
||||
ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid}))
|
||||
# Eif
|
||||
|
||||
# Save gcode commands to object command list
|
||||
@@ -483,9 +478,43 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
|
||||
# Raise cutter to safe height and rotate back to original orientation
|
||||
if self.rotateFlag is True:
|
||||
resetAxis = False
|
||||
lastJobOp = None
|
||||
nextJobOp = None
|
||||
opIdx = 0
|
||||
JOB = PathUtils.findParentJob(obj)
|
||||
jobOps = JOB.Operations.Group
|
||||
numJobOps = len(jobOps)
|
||||
|
||||
for joi in range(0, numJobOps):
|
||||
jo = jobOps[joi]
|
||||
if jo.Name == obj.Name:
|
||||
opIdx = joi
|
||||
lastOpIdx = opIdx - 1
|
||||
nextOpIdx = opIdx + 1
|
||||
if lastOpIdx > -1:
|
||||
lastJobOp = jobOps[lastOpIdx]
|
||||
if nextOpIdx < numJobOps:
|
||||
nextJobOp = jobOps[nextOpIdx]
|
||||
|
||||
if lastJobOp is not None:
|
||||
if hasattr(lastJobOp, 'EnableRotation'):
|
||||
PathLog.debug('Last Op, {}, has `EnableRotation` set to {}'.format(lastJobOp.Label, lastJobOp.EnableRotation))
|
||||
if lastJobOp.EnableRotation != obj.EnableRotation:
|
||||
resetAxis = True
|
||||
if ns == numShapes - 1: # If last shape, check next op EnableRotation setting
|
||||
if nextJobOp is not None:
|
||||
if hasattr(nextJobOp, 'EnableRotation'):
|
||||
PathLog.debug('Next Op, {}, has `EnableRotation` set to {}'.format(nextJobOp.Label, nextJobOp.EnableRotation))
|
||||
if nextJobOp.EnableRotation != obj.EnableRotation:
|
||||
resetAxis = True
|
||||
|
||||
# Raise to safe height if rotation activated
|
||||
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid}))
|
||||
self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid}))
|
||||
# reset rotational axes if necessary
|
||||
if resetAxis is True:
|
||||
self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid}))
|
||||
self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid}))
|
||||
|
||||
self.useTempJobClones('Delete') # Delete temp job clone group and contents
|
||||
self.guiMessage('title', None, show=True) # Process GUI messages to user
|
||||
@@ -531,10 +560,8 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
Determine rotational radii for 4th-axis rotations, for clearance/safe heights '''
|
||||
|
||||
parentJob = PathUtils.findParentJob(obj)
|
||||
# bb = parentJob.Stock.Shape.BoundBox
|
||||
xlim = 0.0
|
||||
ylim = 0.0
|
||||
# zlim = 0.0
|
||||
|
||||
# Determine boundbox radius based upon xzy limits data
|
||||
if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax):
|
||||
|
||||
@@ -333,7 +333,6 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
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)
|
||||
finDep = holeBtm
|
||||
|
||||
holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub),
|
||||
'angle': angle, 'axis': axis, 'trgtDep': finDep,
|
||||
@@ -441,7 +440,7 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
zlim = 0.0
|
||||
xRotRad = 0.01
|
||||
yRotRad = 0.01
|
||||
#xRotRad = 0.01
|
||||
zRotRad = 0.01
|
||||
|
||||
# Determine boundbox radius based upon xzy limits data
|
||||
if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax):
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2020 Schildkroet *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
@@ -58,6 +59,7 @@ def toolDepthAndOffset(width, extraDepth, tool):
|
||||
toolOffset = float(tool.FlatRadius)
|
||||
extraOffset = float(tool.Diameter) / 2 - width if 180 == angle else extraDepth / tan
|
||||
offset = toolOffset + extraOffset
|
||||
|
||||
return (depth, offset)
|
||||
|
||||
|
||||
@@ -76,6 +78,10 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
|
||||
obj.setEditorMode('Join', 2) # hide for now
|
||||
obj.addProperty('App::PropertyEnumeration', 'Direction', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Direction of Operation'))
|
||||
obj.Direction = ['CW', 'CCW']
|
||||
obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation'))
|
||||
obj.Side = ['Outside', 'Inside']
|
||||
obj.setEditorMode('Side', 2) # Hide property, it's calculated by op
|
||||
obj.addProperty('App::PropertyInteger', 'EntryPoint', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Select the segment, there the operations starts'))
|
||||
|
||||
def opOnDocumentRestored(self, obj):
|
||||
obj.setEditorMode('Join', 2) # hide for now
|
||||
@@ -104,13 +110,20 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
|
||||
basewires.append(Part.Wire(edgelist))
|
||||
|
||||
self.basewires.extend(basewires)
|
||||
|
||||
|
||||
# Set default value
|
||||
side = ["Outside"]
|
||||
|
||||
for w in basewires:
|
||||
self.adjusted_basewires.append(w)
|
||||
wire = PathOpTools.offsetWire(w, base.Shape, offset, True)
|
||||
wire = PathOpTools.offsetWire(w, base.Shape, offset, True, side)
|
||||
if wire:
|
||||
wires.append(wire)
|
||||
|
||||
|
||||
# Save Outside or Inside
|
||||
obj.Side = side[0]
|
||||
|
||||
# Set direction of op
|
||||
forward = True
|
||||
if obj.Direction == 'CCW':
|
||||
forward = False
|
||||
@@ -123,9 +136,12 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
|
||||
zValues.append(z)
|
||||
zValues.append(depth)
|
||||
PathLog.track(obj.Label, depth, zValues)
|
||||
|
||||
|
||||
if obj.EntryPoint < 0:
|
||||
obj.EntryPoint = 0;
|
||||
|
||||
self.wires = wires # pylint: disable=attribute-defined-outside-init
|
||||
self.buildpathocc(obj, wires, zValues, True, forward)
|
||||
self.buildpathocc(obj, wires, zValues, True, forward, obj.EntryPoint)
|
||||
|
||||
# the last command is a move to clearance, which is automatically added by PathOp
|
||||
if self.commandlist:
|
||||
@@ -138,11 +154,13 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
|
||||
def opSetDefaultValues(self, obj, job):
|
||||
PathLog.track(obj.Label, job.Label)
|
||||
obj.Width = '1 mm'
|
||||
obj.ExtraDepth = '0.1 mm'
|
||||
obj.ExtraDepth = '0.5 mm'
|
||||
obj.Join = 'Round'
|
||||
obj.setExpression('StepDown', '0 mm')
|
||||
obj.StepDown = '0 mm'
|
||||
obj.Direction = 'CW'
|
||||
obj.Side = "Outside"
|
||||
obj.EntryPoint = 0;
|
||||
|
||||
|
||||
def SetupProperties():
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 LTS <SammelLothar@gmx.de> under LGPL *
|
||||
# * Copyright (c) 2020 Schildkroet *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
@@ -31,6 +32,7 @@ import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import math
|
||||
import copy
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
@@ -68,7 +70,11 @@ class ObjectDressup:
|
||||
obj.addProperty("App::PropertyEnumeration", "RadiusCenter", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupLeadInOut", "The Mode of Point Radiusoffset or Center"))
|
||||
obj.RadiusCenter = ["Radius", "Center"]
|
||||
obj.Proxy = self
|
||||
|
||||
obj.addProperty("App::PropertyDistance", "ExtendLeadIn", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadIn distance"))
|
||||
obj.addProperty("App::PropertyDistance", "ExtendLeadOut", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadOut distance"))
|
||||
obj.addProperty("App::PropertyBool", "RapidPlunge", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Perform plunges with G0"))
|
||||
obj.addProperty("App::PropertyBool", "IncludeLayers", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Apply LeadInOut to layers within an operation"))
|
||||
|
||||
self.wire = None
|
||||
self.rapids = None
|
||||
|
||||
@@ -80,7 +86,7 @@ class ObjectDressup:
|
||||
return None
|
||||
|
||||
def setup(self, obj):
|
||||
obj.Length = 5.0
|
||||
obj.Length = obj.Base.ToolController.Tool.Diameter * 0.75
|
||||
obj.LeadIn = True
|
||||
obj.LeadOut = True
|
||||
obj.KeepToolDown = False
|
||||
@@ -88,6 +94,10 @@ class ObjectDressup:
|
||||
obj.StyleOn = 'Arc'
|
||||
obj.StyleOff = 'Arc'
|
||||
obj.RadiusCenter = 'Radius'
|
||||
obj.ExtendLeadIn = 0
|
||||
obj.ExtendLeadOut = 0
|
||||
obj.RapidPlunge = False
|
||||
obj.IncludeLayers = True
|
||||
|
||||
def execute(self, obj):
|
||||
if not obj.Base:
|
||||
@@ -113,78 +123,173 @@ class ObjectDressup:
|
||||
if hasattr(op, 'Direction') and op.Direction == 'CW':
|
||||
return 'right'
|
||||
return 'left'
|
||||
|
||||
def getSideOfPath(self, obj):
|
||||
op = PathDressup.baseOp(obj.Base)
|
||||
if hasattr(op, 'Side'):
|
||||
return op.Side
|
||||
|
||||
return ''
|
||||
|
||||
def normalize(self, Vector):
|
||||
x = Vector.x
|
||||
y = Vector.y
|
||||
length = math.sqrt(x*x + y*y)
|
||||
if((math.fabs(length)) > 0.0000000000001):
|
||||
vx = round(x / length, 0)
|
||||
vy = round(y / length, 0)
|
||||
vx = round(x / length, 3)
|
||||
vy = round(y / length, 3)
|
||||
return FreeCAD.Vector(vx, vy, 0)
|
||||
|
||||
def invert(self, Vector):
|
||||
x = Vector.x * -1
|
||||
y = Vector.y * -1
|
||||
z = Vector.z * -1
|
||||
return FreeCAD.Vector(x, y, z)
|
||||
|
||||
def multiply(self, Vector, len):
|
||||
x = Vector.x * len
|
||||
y = Vector.y * len
|
||||
z = Vector.z * len
|
||||
return FreeCAD.Vector(x, y, z)
|
||||
|
||||
def rotate(self, Vector, angle):
|
||||
s = math.sin(math.radians(angle))
|
||||
c = math.cos(math.radians(angle))
|
||||
xnew = Vector.x * c - Vector.y * s;
|
||||
ynew = Vector.x * s + Vector.y * c;
|
||||
return FreeCAD.Vector(xnew, ynew, Vector.z)
|
||||
|
||||
def getLeadStart(self, obj, queue, action):
|
||||
'''returns Lead In G-code.'''
|
||||
results = []
|
||||
# zdepth = currLocation["Z"]
|
||||
op = PathDressup.baseOp(obj.Base)
|
||||
tc = PathDressup.toolController(obj.Base)
|
||||
horizFeed = tc.HorizFeed.Value
|
||||
vertFeed = tc.VertFeed.Value
|
||||
toolnummer = tc.ToolNumber
|
||||
# set the correct twist command
|
||||
arcs_identical = False
|
||||
|
||||
# Set the correct twist command
|
||||
if self.getDirectionOfPath(obj) == 'left':
|
||||
arcdir = "G3"
|
||||
else:
|
||||
arcdir = "G2"
|
||||
|
||||
R = obj.Length.Value # Radius of roll or length
|
||||
if queue[1].Name == "G1": # line
|
||||
p0 = queue[0].Placement.Base
|
||||
p1 = queue[1].Placement.Base
|
||||
v = self.normalize(p1.sub(p0))
|
||||
# PathLog.notice(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z))
|
||||
# PathLog.debug(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z))
|
||||
else:
|
||||
p0 = queue[0].Placement.Base
|
||||
p1 = queue[1].Placement.Base
|
||||
# PathLog.notice(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y))
|
||||
v = self.normalize(p1.sub(p0))
|
||||
# PathLog.debug(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y))
|
||||
|
||||
# Calculate offset vector (will be overwritten for arcs)
|
||||
if self.getDirectionOfPath(obj) == 'right':
|
||||
off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0)
|
||||
else:
|
||||
off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0)
|
||||
offsetvector = FreeCAD.Vector(v.x*R, v.y*R, 0) # IJ
|
||||
|
||||
# Check if we enter at line or arc command
|
||||
if queue[1].Name in movecommands and queue[1].Name not in arccommands:
|
||||
# We have a line move
|
||||
vec = p1.sub(p0)
|
||||
vec_n = self.normalize(vec)
|
||||
vec_inv = self.invert(vec_n)
|
||||
vec_off = self.multiply(vec_inv, obj.ExtendLeadIn)
|
||||
#PathLog.debug("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y))
|
||||
else:
|
||||
# We have an arc move
|
||||
# Calculate coordinates for middle of circle
|
||||
pij = copy.deepcopy(p0)
|
||||
pij.x += queue[1].Parameters['I']
|
||||
pij.y += queue[1].Parameters['J']
|
||||
|
||||
# Check if lead in and operation go in same direction (usually for inner circles)
|
||||
if arcdir == queue[1].Name:
|
||||
arcs_identical = True
|
||||
|
||||
# Calculate vector circle start -> circle middle
|
||||
vec_circ = pij.sub(p0)
|
||||
|
||||
# Rotate vector to get direction for lead in
|
||||
if arcdir == "G2":
|
||||
vec_rot = self.rotate(vec_circ, 90)
|
||||
else:
|
||||
vec_rot = self.rotate(vec_circ, -90)
|
||||
|
||||
# Normalize and invert vector
|
||||
vec_n = self.normalize(vec_rot)
|
||||
|
||||
v = self.invert(vec_n)
|
||||
|
||||
# Calculate offset of lead in
|
||||
if arcdir == "G3":
|
||||
off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0)
|
||||
else:
|
||||
off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0)
|
||||
|
||||
# Multiply offset by LeadIn length
|
||||
vec_off = self.multiply(vec_n, obj.ExtendLeadIn)
|
||||
|
||||
offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0) # IJ
|
||||
|
||||
if obj.RadiusCenter == 'Radius':
|
||||
leadstart = (p0.add(off_v)).sub(offsetvector) # Rmode
|
||||
if arcs_identical:
|
||||
t = p0.sub(leadstart)
|
||||
t = p0.add(t)
|
||||
leadstart = t
|
||||
offsetvector = self.multiply(offsetvector, -1)
|
||||
else:
|
||||
leadstart = p0.add(off_v) # Dmode
|
||||
|
||||
if action == 'start':
|
||||
extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": op.ClearanceHeight.Value})
|
||||
results.append(extendcommand)
|
||||
#extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": op.ClearanceHeight.Value})
|
||||
#results.append(extendcommand)
|
||||
extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.ClearanceHeight.Value})
|
||||
results.append(extendcommand)
|
||||
extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.SafeHeight.Value})
|
||||
extendcommand = Path.Command('G0', {"Z": op.SafeHeight.Value})
|
||||
results.append(extendcommand)
|
||||
|
||||
if action == 'layer':
|
||||
if not obj.KeepToolDown:
|
||||
extendcommand = Path.Command('G0', {"Z": op.SafeHeight.Value})
|
||||
results.append(extendcommand)
|
||||
|
||||
extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y})
|
||||
results.append(extendcommand)
|
||||
extendcommand = Path.Command('G1', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z, "F": vertFeed})
|
||||
|
||||
if not obj.RapidPlunge:
|
||||
extendcommand = Path.Command('G1', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z, "F": vertFeed})
|
||||
else:
|
||||
extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z,})
|
||||
results.append(extendcommand)
|
||||
|
||||
if obj.UseMachineCRC:
|
||||
if self.getDirectionOfPath(obj) == 'right':
|
||||
results.append(Path.Command('G42', {'D': toolnummer}))
|
||||
else:
|
||||
results.append(Path.Command('G41', {'D': toolnummer}))
|
||||
|
||||
if obj.StyleOn == 'Arc':
|
||||
arcmove = Path.Command(arcdir, {"X": p0.x, "Y": p0.y, "I": offsetvector.x, "J": offsetvector.y, "F": horizFeed}) # add G2/G3 move
|
||||
arcmove = Path.Command(arcdir, {"X": p0.x+vec_off.x, "Y": p0.y+vec_off.y, "I": offsetvector.x+vec_off.x, "J": offsetvector.y+vec_off.y, "F": horizFeed}) # add G2/G3 move
|
||||
results.append(arcmove)
|
||||
if obj.ExtendLeadIn != 0:
|
||||
extendcommand = Path.Command('G1', {"X": p0.x, "Y": p0.y, "F": horizFeed})
|
||||
results.append(extendcommand)
|
||||
elif obj.StyleOn == 'Tangent':
|
||||
extendcommand = Path.Command('G1', {"X": p0.x, "Y": p0.y, "F": horizFeed})
|
||||
results.append(extendcommand)
|
||||
else:
|
||||
PathLog.notice(" CURRENT_IN Perp")
|
||||
PathLog.debug(" CURRENT_IN Perp")
|
||||
|
||||
currLocation.update(results[-1].Parameters)
|
||||
currLocation['Z'] = p1.z
|
||||
|
||||
return results
|
||||
|
||||
def getLeadEnd(self, obj, queue, action):
|
||||
@@ -193,11 +298,14 @@ class ObjectDressup:
|
||||
results = []
|
||||
horizFeed = PathDressup.toolController(obj.Base).HorizFeed.Value
|
||||
R = obj.Length.Value # Radius of roll or length
|
||||
# set the correct twist command
|
||||
arcs_identical = False
|
||||
|
||||
# Set the correct twist command
|
||||
if self.getDirectionOfPath(obj) == 'right':
|
||||
arcdir = "G2"
|
||||
else:
|
||||
arcdir = "G3"
|
||||
|
||||
if queue[1].Name == "G1": # line
|
||||
p0 = queue[0].Placement.Base
|
||||
p1 = queue[1].Placement.Base
|
||||
@@ -206,97 +314,157 @@ class ObjectDressup:
|
||||
p0 = queue[0].Placement.Base
|
||||
p1 = queue[1].Placement.Base
|
||||
v = self.normalize(p1.sub(p0))
|
||||
|
||||
if self.getDirectionOfPath(obj) == 'right':
|
||||
off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0)
|
||||
else:
|
||||
off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0)
|
||||
offsetvector = FreeCAD.Vector(v.x*R, v.y*R, 0.0)
|
||||
|
||||
# Check if we leave at line or arc command
|
||||
if queue[1].Name in movecommands and queue[1].Name not in arccommands:
|
||||
# We have a line move
|
||||
vec = p1.sub(p0)
|
||||
vec_n = self.normalize(vec)
|
||||
vec_inv = self.invert(vec_n)
|
||||
vec_off = self.multiply(vec_inv, obj.ExtendLeadOut)
|
||||
#PathLog.debug("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y))
|
||||
else:
|
||||
# We have an arc move
|
||||
pij = copy.deepcopy(p0)
|
||||
pij.x += queue[1].Parameters['I']
|
||||
pij.y += queue[1].Parameters['J']
|
||||
ve = pij.sub(p1)
|
||||
|
||||
if arcdir == queue[1].Name:
|
||||
arcs_identical = True
|
||||
|
||||
if arcdir == "G2":
|
||||
vec_rot = self.rotate(ve, -90)
|
||||
else:
|
||||
vec_rot = self.rotate(ve, 90)
|
||||
|
||||
vec_n = self.normalize(vec_rot)
|
||||
v = vec_n
|
||||
|
||||
if arcdir == "G3":
|
||||
off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0)
|
||||
else:
|
||||
off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0)
|
||||
|
||||
vec_inv = self.invert(vec_rot)
|
||||
|
||||
vec_off = self.multiply(vec_inv, obj.ExtendLeadOut)
|
||||
|
||||
offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0.0)
|
||||
if obj.RadiusCenter == 'Radius':
|
||||
leadend = (p1.add(off_v)).add(offsetvector) # Rmode
|
||||
if arcs_identical:
|
||||
t = p1.sub(leadend)
|
||||
t = p1.add(t)
|
||||
leadend = t
|
||||
off_v = self.multiply(off_v, -1)
|
||||
else:
|
||||
leadend = p1.add(off_v) # Dmode
|
||||
|
||||
IJ = off_v # .negative()
|
||||
#results.append(queue[1])
|
||||
if obj.StyleOff == 'Arc':
|
||||
if obj.ExtendLeadOut != 0:
|
||||
extendcommand = Path.Command('G1', {"X": p1.x-vec_off.x, "Y": p1.y-vec_off.y, "F": horizFeed})
|
||||
results.append(extendcommand)
|
||||
arcmove = Path.Command(arcdir, {"X": leadend.x, "Y": leadend.y, "I": IJ.x, "J": IJ.y, "F": horizFeed}) # add G2/G3 move
|
||||
results.append(arcmove)
|
||||
elif obj.StyleOff == 'Tangent':
|
||||
extendcommand = Path.Command('G1', {"X": leadend.x, "Y": leadend.y, "F": horizFeed})
|
||||
results.append(extendcommand)
|
||||
else:
|
||||
PathLog.notice(" CURRENT_IN Perp")
|
||||
PathLog.debug(" CURRENT_IN Perp")
|
||||
|
||||
if obj.UseMachineCRC: # crc off
|
||||
results.append(Path.Command('G40', {}))
|
||||
|
||||
return results
|
||||
|
||||
def generateLeadInOutCurve(self, obj):
|
||||
global currLocation # pylint: disable=global-statement
|
||||
firstmove = Path.Command("G0", {"X": 0, "Y": 0, "Z": 0})
|
||||
op = PathDressup.baseOp(obj.Base)
|
||||
currLocation.update(firstmove.Parameters)
|
||||
newpath = []
|
||||
queue = []
|
||||
action = 'start'
|
||||
prevCmd = ''
|
||||
layers = []
|
||||
|
||||
# Read in all commands
|
||||
for curCommand in obj.Base.Path.Commands:
|
||||
# replace = None
|
||||
# don't worry about non-move commands, just add to output
|
||||
#PathLog.debug("CurCMD: {}".format(curCommand))
|
||||
if curCommand.Name not in movecommands + rapidcommands:
|
||||
# Don't worry about non-move commands, just add to output
|
||||
newpath.append(curCommand)
|
||||
continue
|
||||
|
||||
# rapid retract triggers exit move, else just add to output
|
||||
|
||||
if curCommand.Name in rapidcommands:
|
||||
# detect start position
|
||||
if (curCommand.x is not None) or (curCommand.y is not None):
|
||||
firstmove = curCommand
|
||||
# We don't care about rapid moves
|
||||
prevCmd = curCommand
|
||||
currLocation.update(curCommand.Parameters)
|
||||
if action != 'start': # done move out
|
||||
if obj.LeadOut:
|
||||
temp = self.getLeadEnd(obj, queue, 'end')
|
||||
newpath.extend(temp)
|
||||
newpath.append(curCommand) # Z clear DONE
|
||||
|
||||
continue
|
||||
|
||||
if curCommand.Name in movecommands:
|
||||
if prevCmd.Name in rapidcommands and curCommand.Name in movecommands and len(queue) > 0:
|
||||
# Layer changed: Save current layer cmds and prepare next layer
|
||||
layers.append(queue)
|
||||
queue = []
|
||||
if obj.IncludeLayers and curCommand.z < currLocation['Z'] and prevCmd.Name in movecommands:
|
||||
# Layer change within move cmds
|
||||
#PathLog.debug("Layer change in move: {}->{}".format(currLocation['Z'], curCommand.z))
|
||||
layers.append(queue)
|
||||
queue = []
|
||||
|
||||
# Save all move commands
|
||||
queue.append(curCommand)
|
||||
if action == 'start' and len(queue) < 2:
|
||||
continue
|
||||
if action == 'layer':
|
||||
if len(queue) > 2:
|
||||
queue.pop(0)
|
||||
if obj.LeadIn:
|
||||
temp = self.getLeadStart(obj, queue, action)
|
||||
newpath.extend(temp)
|
||||
#newpath.append(curCommand)
|
||||
action = 'none'
|
||||
currLocation.update(curCommand.Parameters)
|
||||
else:
|
||||
newpath.append(curCommand)
|
||||
if curCommand.z != currLocation["Z"] and action != 'start': # vertical feeding to depth
|
||||
if obj.LeadOut: # fish cycle
|
||||
if len(queue) > 2:
|
||||
queue.pop(len(queue)-1)
|
||||
temp = self.getLeadEnd(obj, queue, action)
|
||||
newpath.extend(temp)
|
||||
action = 'layer'
|
||||
if len(queue) > 2:
|
||||
queue.pop(0)
|
||||
continue
|
||||
else:
|
||||
newpath.append(curCommand)
|
||||
if len(queue) > 2:
|
||||
queue.pop(0)
|
||||
if obj.LeadIn and len(queue) >= 2 and action == 'start':
|
||||
temp = self.getLeadStart(obj, queue, action)
|
||||
newpath.extend(temp)
|
||||
newpath.append(curCommand)
|
||||
action = 'none'
|
||||
currLocation.update(curCommand.Parameters)
|
||||
else:
|
||||
newpath.append(curCommand)
|
||||
currLocation.update(curCommand.Parameters)
|
||||
|
||||
currLocation.update(curCommand.Parameters)
|
||||
prevCmd = curCommand
|
||||
|
||||
# Add last layer
|
||||
if len(queue) > 0:
|
||||
layers.append(queue)
|
||||
queue = []
|
||||
|
||||
# Go through each layer and add leadIn/Out
|
||||
idx = 0
|
||||
for layer in layers:
|
||||
#PathLog.debug("Layer {}".format(idx))
|
||||
|
||||
if obj.LeadIn:
|
||||
temp = self.getLeadStart(obj, layer, action)
|
||||
newpath.extend(temp)
|
||||
|
||||
for cmd in layer:
|
||||
#PathLog.debug("CurLoc: {}, NewCmd: {}".format(currLocation, cmd))
|
||||
#if currLocation['X'] == cmd.x and currLocation['Y'] == cmd.y and currLocation['Z'] == cmd.z and cmd.Name in ['G1', 'G01']:
|
||||
#continue
|
||||
newpath.append(cmd)
|
||||
|
||||
if obj.LeadOut:
|
||||
tmp = []
|
||||
tmp.append(layer[-2])
|
||||
tmp.append(layer[-1])
|
||||
temp = self.getLeadEnd(obj, tmp, action)
|
||||
newpath.extend(temp)
|
||||
|
||||
if not obj.KeepToolDown or idx == len(layers)-1:
|
||||
extendcommand = Path.Command('G0', {"Z": op.ClearanceHeight.Value})
|
||||
newpath.append(extendcommand)
|
||||
else:
|
||||
action = 'layer'
|
||||
|
||||
idx += 1
|
||||
|
||||
commands = newpath
|
||||
return Path.Path(commands)
|
||||
|
||||
|
||||
class ViewProviderDressup:
|
||||
|
||||
def __init__(self, vobj):
|
||||
|
||||
@@ -47,7 +47,7 @@ else:
|
||||
PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE)
|
||||
|
||||
|
||||
# Qt tanslation handling
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
zValues.append(obj.FinalDepth.Value)
|
||||
return zValues
|
||||
|
||||
def buildpathocc(self, obj, wires, zValues, relZ=False, forward=True):
|
||||
def buildpathocc(self, obj, wires, zValues, relZ=False, forward=True, start_idx=0):
|
||||
'''buildpathocc(obj, wires, zValues, relZ=False) ... internal helper function to generate engraving commands.'''
|
||||
PathLog.track(obj.Label, len(wires), zValues)
|
||||
|
||||
@@ -78,6 +78,10 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
self.appendCommand(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z}), z, relZ, self.vertFeed)
|
||||
|
||||
first = True
|
||||
if start_idx > len(edges)-1:
|
||||
start_idx = len(edges)-1
|
||||
|
||||
edges = edges[start_idx:] + edges[:start_idx]
|
||||
for edge in edges:
|
||||
if first and (not last or not wire.isClosed()):
|
||||
# we set the first move to our first point
|
||||
@@ -86,7 +90,7 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
|
||||
self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'F': self.horizRapid}))
|
||||
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
self.appendCommand(Path.Command('G1', {'Z': last.z}), z, relZ, self.vertFeed)
|
||||
self.appendCommand(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z}), z, relZ, self.vertFeed)
|
||||
first = False
|
||||
|
||||
if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point):
|
||||
|
||||
@@ -45,41 +45,68 @@ if LOGLEVEL:
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
def updateInputField(obj, prop, widget, onBeforeChange=None):
|
||||
'''updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget.
|
||||
The property's value is only assigned if the new value differs from the current value.
|
||||
This prevents onChanged notifications where the value didn't actually change.
|
||||
Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can
|
||||
be of type Quantity or Float.
|
||||
If onBeforeChange is specified it is called before a new value is assigned to the property.
|
||||
Returns True if a new value was assigned, False otherwise (new value is the same as the current).
|
||||
'''
|
||||
The property's value is only assigned if the new value differs from the current value.
|
||||
This prevents onChanged notifications where the value didn't actually change.
|
||||
Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can
|
||||
be of type Quantity or Float.
|
||||
If onBeforeChange is specified it is called before a new value is assigned to the property.
|
||||
Returns True if a new value was assigned, False otherwise (new value is the same as the current).
|
||||
'''
|
||||
value = FreeCAD.Units.Quantity(widget.text()).Value
|
||||
attr = PathUtil.getProperty(obj, prop)
|
||||
attrValue = attr.Value if hasattr(attr, 'Value') else attr
|
||||
|
||||
isDiff = False
|
||||
if not PathGeom.isRoughly(attrValue, value):
|
||||
isDiff = True
|
||||
else:
|
||||
if hasattr(obj, 'ExpressionEngine'):
|
||||
noExpr = True
|
||||
for (prp, expr) in obj.ExpressionEngine:
|
||||
if prp == prop:
|
||||
noExpr = False
|
||||
PathLog.debug('prop = "expression": {} = "{}"'.format(prp, expr))
|
||||
value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value
|
||||
if not PathGeom.isRoughly(attrValue, value):
|
||||
isDiff = True
|
||||
break
|
||||
if noExpr:
|
||||
widget.setReadOnly(False)
|
||||
widget.setStyleSheet("color: black")
|
||||
else:
|
||||
widget.setReadOnly(True)
|
||||
widget.setStyleSheet("color: gray")
|
||||
widget.update()
|
||||
|
||||
if isDiff:
|
||||
PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value))
|
||||
if onBeforeChange:
|
||||
onBeforeChange(obj)
|
||||
PathUtil.setProperty(obj, prop, value)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class QuantitySpinBox:
|
||||
'''Controller class to interface a Gui::QuantitySpinBox.
|
||||
The spin box gets bound to a given property and supports update in both directions.
|
||||
QuatitySpinBox(widget, obj, prop, onBeforeChange=None)
|
||||
widget ... expected to be reference to a Gui::QuantitySpinBox
|
||||
obj ... document object
|
||||
prop ... canonical name of the (sub-) property
|
||||
onBeforeChange ... an optional callback being executed before the value of the property is changed
|
||||
'''
|
||||
The spin box gets bound to a given property and supports update in both directions.
|
||||
QuatitySpinBox(widget, obj, prop, onBeforeChange=None)
|
||||
widget ... expected to be reference to a Gui::QuantitySpinBox
|
||||
obj ... document object
|
||||
prop ... canonical name of the (sub-) property
|
||||
onBeforeChange ... an optional callback being executed before the value of the property is changed
|
||||
'''
|
||||
|
||||
def __init__(self, widget, obj, prop, onBeforeChange=None):
|
||||
self.obj = obj
|
||||
self.widget = widget
|
||||
self.prop = prop
|
||||
self.onBeforeChange = onBeforeChange
|
||||
|
||||
attr = PathUtil.getProperty(self.obj, self.prop)
|
||||
if attr is not None:
|
||||
if hasattr(attr, 'Value'):
|
||||
@@ -95,7 +122,7 @@ The spin box gets bound to a given property and supports update in both directio
|
||||
if self.valid:
|
||||
return self.widget.property('expression')
|
||||
return ''
|
||||
|
||||
|
||||
def setMinimum(self, quantity):
|
||||
if self.valid:
|
||||
value = quantity.Value if hasattr(quantity, 'Value') else quantity
|
||||
@@ -103,8 +130,8 @@ The spin box gets bound to a given property and supports update in both directio
|
||||
|
||||
def updateSpinBox(self, quantity=None):
|
||||
'''updateSpinBox(quantity=None) ... update the display value of the spin box.
|
||||
If no value is provided the value of the bound property is used.
|
||||
quantity can be of type Quantity or Float.'''
|
||||
If no value is provided the value of the bound property is used.
|
||||
quantity can be of type Quantity or Float.'''
|
||||
if self.valid:
|
||||
if quantity is None:
|
||||
quantity = PathUtil.getProperty(self.obj, self.prop)
|
||||
@@ -116,4 +143,3 @@ quantity can be of type Quantity or Float.'''
|
||||
if self.valid:
|
||||
return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange)
|
||||
return None
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ else:
|
||||
|
||||
Processed = False
|
||||
|
||||
|
||||
def Startup():
|
||||
global Processed # pylint: disable=global-statement
|
||||
if not Processed:
|
||||
@@ -71,12 +72,13 @@ def Startup():
|
||||
from PathScripts import PathSimpleCopy
|
||||
from PathScripts import PathSimulatorGui
|
||||
from PathScripts import PathStop
|
||||
# from PathScripts import PathSurfaceGui # Added in initGui.py due to OCL dependency
|
||||
from PathScripts import PathToolController
|
||||
from PathScripts import PathToolControllerGui
|
||||
from PathScripts import PathToolLibraryManager
|
||||
from PathScripts import PathToolLibraryEditor
|
||||
from PathScripts import PathUtilsGui
|
||||
# from PathScripts import PathWaterlineGui # Added in initGui.py due to OCL dependency
|
||||
Processed = True
|
||||
else:
|
||||
PathLog.debug('Skipping PathGui initialisation')
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
'''areaOpShapes(obj) ... return top face'''
|
||||
# Facing is done either against base objects
|
||||
holeShape = None
|
||||
|
||||
|
||||
if obj.Base:
|
||||
PathLog.debug("obj.Base: {}".format(obj.Base))
|
||||
faces = []
|
||||
@@ -147,17 +147,17 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
# 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 == True:
|
||||
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)
|
||||
@@ -170,7 +170,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
elif obj.BoundaryShape == 'Stock':
|
||||
stock = PathUtils.findParentJob(obj).Stock.Shape
|
||||
env = stock
|
||||
|
||||
|
||||
if obj.ExcludeRaisedAreas is True and oneBase[1] is True:
|
||||
includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight)
|
||||
if len(includedFaces) > 0:
|
||||
@@ -269,7 +269,6 @@ def SetupProperties():
|
||||
setup.append("BoundaryShape")
|
||||
setup.append("ExcludeRaisedAreas")
|
||||
setup.append("ClearEdges")
|
||||
|
||||
return setup
|
||||
|
||||
|
||||
@@ -278,5 +277,4 @@ def Create(name, obj=None):
|
||||
if obj is None:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
obj.Proxy = ObjectFace(obj, name)
|
||||
|
||||
return obj
|
||||
|
||||
@@ -157,8 +157,6 @@ class ViewProvider(object):
|
||||
else:
|
||||
return ":/icons/Path-OpActive.svg"
|
||||
|
||||
#return self.OpIcon
|
||||
|
||||
def getTaskPanelOpPage(self, obj):
|
||||
'''getTaskPanelOpPage(obj) ... use the stored information to instantiate the receiver op's page controller.'''
|
||||
mod = importlib.import_module(self.OpPageModule)
|
||||
@@ -190,6 +188,7 @@ class ViewProvider(object):
|
||||
action.triggered.connect(self.setEdit)
|
||||
menu.addAction(action)
|
||||
|
||||
|
||||
class TaskPanelPage(object):
|
||||
'''Base class for all task panel pages.'''
|
||||
|
||||
@@ -377,7 +376,7 @@ class TaskPanelPage(object):
|
||||
combo.clear()
|
||||
combo.addItems(options)
|
||||
combo.blockSignals(False)
|
||||
|
||||
|
||||
if hasattr(obj, 'CoolantMode'):
|
||||
self.selectInComboBox(obj.CoolantMode, combo)
|
||||
|
||||
@@ -704,10 +703,13 @@ class TaskPanelDepthsPage(TaskPanelPage):
|
||||
|
||||
def haveStartDepth(self):
|
||||
return PathOp.FeatureDepths & self.features
|
||||
|
||||
def haveFinalDepth(self):
|
||||
return PathOp.FeatureDepths & self.features and not PathOp.FeatureNoFinalDepth & self.features
|
||||
|
||||
def haveFinishDepth(self):
|
||||
return PathOp.FeatureDepths & self.features and PathOp.FeatureFinishDepth & self.features
|
||||
|
||||
def haveStepDown(self):
|
||||
return PathOp.FeatureStepDown & self. features
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ def orientWire(w, forward=True):
|
||||
PathLog.track('orientWire - ok')
|
||||
return wire
|
||||
|
||||
def offsetWire(wire, base, offset, forward):
|
||||
def offsetWire(wire, base, offset, forward, Side = None):
|
||||
'''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly.
|
||||
The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting
|
||||
happens in the XY plane.
|
||||
@@ -195,8 +195,12 @@ def offsetWire(wire, base, offset, forward):
|
||||
if wire.isClosed():
|
||||
if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset/2, True):
|
||||
PathLog.track('closed - outside')
|
||||
if Side:
|
||||
Side[0] = "Outside"
|
||||
return orientWire(owire, forward)
|
||||
PathLog.track('closed - inside')
|
||||
if Side:
|
||||
Side[0] = "Inside"
|
||||
try:
|
||||
owire = wire.makeOffset2D(-offset)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
|
||||
# * Copyright (c) 2020 Schildkroet *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
@@ -42,7 +41,7 @@ __author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Class and implementation of shape based Pocket operation."
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
@@ -435,7 +434,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
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:
|
||||
@@ -450,11 +449,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
(isLoop, norm, surf) = self.checkForFacesLoop(base, subsList)
|
||||
|
||||
if isLoop is True:
|
||||
PathLog.info("Common Surface.Axis or normalAt() value found for loop faces.")
|
||||
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.info("angle: {}; axis: {}".format(angle, axis))
|
||||
PathLog.debug("angle: {}; axis: {}".format(angle, axis))
|
||||
|
||||
if rtn is True:
|
||||
faceNums = ""
|
||||
@@ -471,15 +470,17 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
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:
|
||||
PathLog.debug("Applying the inverse angle.")
|
||||
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
|
||||
else:
|
||||
PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation."))
|
||||
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
|
||||
PathLog.warning(msg)
|
||||
|
||||
if angle < -180.0:
|
||||
if angle < 0.0:
|
||||
angle += 360.0
|
||||
|
||||
tup = clnBase, subsList, angle, axis, clnStock
|
||||
@@ -518,6 +519,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
|
||||
(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', '')
|
||||
@@ -525,20 +527,31 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
# Verify faces are correctly oriented - InverseAngle might be necessary
|
||||
faceIA = clnBase.Shape.getElement(sub)
|
||||
(norm, surf) = self.getFaceNormAndSurf(faceIA)
|
||||
(rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
|
||||
(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("Face not aligned after initial rotation.")
|
||||
PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
|
||||
if obj.InverseAngle is False:
|
||||
if obj.AttemptInverseAngle is True:
|
||||
PathLog.debug("Applying the inverse angle.")
|
||||
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
|
||||
else:
|
||||
PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation."))
|
||||
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 < -180.0:
|
||||
if angle < 0.0:
|
||||
angle += 360.0
|
||||
|
||||
tup = clnBase, [sub], angle, axis, clnStock
|
||||
@@ -650,8 +663,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
if shpZMin > obj.FinalDepth.Value:
|
||||
afD = shpZMin
|
||||
if sD <= afD:
|
||||
PathLog.error('Start Depth is lower than face depth.')
|
||||
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))
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt tanslation handling
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ __url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Probing operation page controller and command implementation."
|
||||
|
||||
|
||||
# Qt tanslation handling
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
@@ -112,18 +112,23 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
else:
|
||||
PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.'))
|
||||
else:
|
||||
cutWireObjs = False
|
||||
(origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value)
|
||||
cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire)
|
||||
if cutShp is not False:
|
||||
cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp)
|
||||
|
||||
if cutWireObjs is not False:
|
||||
for cW in cutWireObjs:
|
||||
shapes.append((cW, False))
|
||||
self.profileEdgesIsOpen = True
|
||||
if self.JOB.GeometryTolerance.Value == 0.0:
|
||||
msg = self.JOB.Label + '.GeometryTolerance = 0.0.'
|
||||
msg += translate('PathProfileEdges', 'Please set to an acceptable value greater than zero.')
|
||||
PathLog.error(msg)
|
||||
else:
|
||||
PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.'))
|
||||
cutWireObjs = False
|
||||
(origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value)
|
||||
cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire)
|
||||
if cutShp is not False:
|
||||
cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp)
|
||||
|
||||
if cutWireObjs is not False:
|
||||
for cW in cutWireObjs:
|
||||
shapes.append((cW, False))
|
||||
self.profileEdgesIsOpen = True
|
||||
else:
|
||||
PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.'))
|
||||
|
||||
# Delete the temporary objects
|
||||
if PathLog.getLevel(PathLog.thisModule()) != 4:
|
||||
@@ -134,7 +139,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
FreeCADGui.ActiveDocument.getObject(tmpGrpNm).Visibility = False
|
||||
|
||||
|
||||
return shapes
|
||||
|
||||
def _flattenWire(self, obj, wire, trgtDep):
|
||||
@@ -179,13 +184,13 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
|
||||
return (OW, FW)
|
||||
|
||||
# Open-edges methods
|
||||
def _getCutAreaCrossSection(self, obj, base, origWire, flatWireObj):
|
||||
PathLog.debug('_getCutAreaCrossSection()')
|
||||
tmpGrp = self.tmpGrp
|
||||
FCAD = FreeCAD.ActiveDocument
|
||||
tolerance = self.JOB.GeometryTolerance.Value
|
||||
# toolDiam = float(obj.ToolController.Tool.Diameter)
|
||||
toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathprofileBase modules
|
||||
toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules
|
||||
minBfr = toolDiam * 1.25
|
||||
bbBfr = (self.ofstRadius * 2) * 1.25
|
||||
if bbBfr < minBfr:
|
||||
@@ -243,34 +248,33 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
|
||||
# Cut model(selected edges) from extended edges boundbox
|
||||
cutArea = extBndboxEXT.Shape.cut(base.Shape)
|
||||
CA = FCAD.addObject('Part::Feature', 'tmpBndboxCutByBase')
|
||||
CA.Shape = cutArea
|
||||
CA.purgeTouched()
|
||||
tmpGrp.addObject(CA)
|
||||
|
||||
# Get top and bottom faces of cut area (CA), and combine faces when necessary
|
||||
topFc = list()
|
||||
botFc = list()
|
||||
bbZMax = CA.Shape.BoundBox.ZMax
|
||||
bbZMin = CA.Shape.BoundBox.ZMin
|
||||
for f in range(0, len(CA.Shape.Faces)):
|
||||
Fc = CA.Shape.Faces[f]
|
||||
if abs(Fc.BoundBox.ZMax - bbZMax) < tolerance and abs(Fc.BoundBox.ZMin - bbZMax) < tolerance:
|
||||
bbZMax = cutArea.BoundBox.ZMax
|
||||
bbZMin = cutArea.BoundBox.ZMin
|
||||
for f in range(0, len(cutArea.Faces)):
|
||||
FcBB = cutArea.Faces[f].BoundBox
|
||||
if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance:
|
||||
topFc.append(f)
|
||||
if abs(Fc.BoundBox.ZMax - bbZMin) < tolerance and abs(Fc.BoundBox.ZMin - bbZMin) < tolerance:
|
||||
if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance:
|
||||
botFc.append(f)
|
||||
topComp = Part.makeCompound([CA.Shape.Faces[f] for f in topFc])
|
||||
if len(topFc) == 0:
|
||||
PathLog.error('Failed to identify top faces of cut area.')
|
||||
return False
|
||||
topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc])
|
||||
topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth
|
||||
if len(botFc) > 1:
|
||||
PathLog.debug('len(botFc) > 1')
|
||||
bndboxFace = Part.Face(extBndbox.Shape.Wires[0])
|
||||
tmpFace = Part.Face(extBndbox.Shape.Wires[0])
|
||||
for f in botFc:
|
||||
Q = tmpFace.cut(CA.Shape.Faces[f])
|
||||
Q = tmpFace.cut(cutArea.Faces[f])
|
||||
tmpFace = Q
|
||||
botComp = bndboxFace.cut(tmpFace)
|
||||
else:
|
||||
botComp = Part.makeCompound([CA.Shape.Faces[f] for f in botFc])
|
||||
botComp = Part.makeCompound([cutArea.Faces[f] for f in botFc]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc])
|
||||
botComp.translate(FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth
|
||||
|
||||
# Convert compound shapes to FC objects for use in multicommon operation
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
|
||||
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
|
||||
# * Copyright (c) 2020 Schildkroet *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
@@ -43,6 +42,7 @@ __doc__ = "Path Profile operation based on faces."
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
@@ -132,22 +132,42 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
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, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
|
||||
(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.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)
|
||||
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:
|
||||
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
|
||||
PathLog.error(msg)
|
||||
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:
|
||||
@@ -157,21 +177,21 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
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
|
||||
@@ -196,7 +216,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
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:
|
||||
@@ -205,13 +225,12 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
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)
|
||||
|
||||
@@ -230,31 +249,34 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
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
|
||||
custDepthparams = self._customDepthParams(obj, strDep, finDep - 0.5) # only an envelope
|
||||
envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope
|
||||
try:
|
||||
env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=custDepthparams)
|
||||
# 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])
|
||||
# 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, finalDep - 0.5)
|
||||
|
||||
env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams)
|
||||
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)
|
||||
|
||||
@@ -262,11 +284,11 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
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")
|
||||
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):
|
||||
@@ -302,7 +324,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
|
||||
obj.InverseAngle = False
|
||||
obj.AttemptInverseAngle = True
|
||||
obj.LimitDepthToFace = True
|
||||
obj.HandleMultipleFeatures = 'Collectively'
|
||||
obj.HandleMultipleFeatures = 'Individually'
|
||||
|
||||
|
||||
def SetupProperties():
|
||||
@@ -321,6 +343,5 @@ def Create(name, obj=None):
|
||||
'''Create(name) ... Creates and returns a Profile based on faces operation.'''
|
||||
if obj is None:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
|
||||
obj.Proxy = ObjectProfile(obj, name)
|
||||
return obj
|
||||
|
||||
@@ -30,12 +30,14 @@ import PathScripts.PathUtils as PathUtils
|
||||
import math
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class PathBaseGate(object):
|
||||
# pylint: disable=no-init
|
||||
pass
|
||||
|
||||
|
||||
class EGate(PathBaseGate):
|
||||
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
|
||||
return sub and sub[0:4] == 'Edge'
|
||||
@@ -66,6 +68,7 @@ class ENGRAVEGate(PathBaseGate):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class CHAMFERGate(PathBaseGate):
|
||||
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
|
||||
try:
|
||||
@@ -94,7 +97,7 @@ class DRILLGate(PathBaseGate):
|
||||
if hasattr(obj, "Shape") and sub:
|
||||
shape = obj.Shape
|
||||
subobj = shape.getElement(sub)
|
||||
return PathUtils.isDrillable(shape, subobj, includePartials = True)
|
||||
return PathUtils.isDrillable(shape, subobj, includePartials=True)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -159,6 +162,7 @@ class POCKETGate(PathBaseGate):
|
||||
|
||||
return pocketable
|
||||
|
||||
|
||||
class ADAPTIVEGate(PathBaseGate):
|
||||
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
|
||||
|
||||
@@ -170,6 +174,7 @@ class ADAPTIVEGate(PathBaseGate):
|
||||
|
||||
return adaptive
|
||||
|
||||
|
||||
class CONTOURGate(PathBaseGate):
|
||||
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
|
||||
pass
|
||||
@@ -182,34 +187,42 @@ def contourselect():
|
||||
FreeCADGui.Selection.addSelectionGate(CONTOURGate())
|
||||
FreeCAD.Console.PrintWarning("Contour Select Mode\n")
|
||||
|
||||
|
||||
def eselect():
|
||||
FreeCADGui.Selection.addSelectionGate(EGate())
|
||||
FreeCAD.Console.PrintWarning("Edge Select Mode\n")
|
||||
|
||||
|
||||
def drillselect():
|
||||
FreeCADGui.Selection.addSelectionGate(DRILLGate())
|
||||
FreeCAD.Console.PrintWarning("Drilling Select Mode\n")
|
||||
|
||||
|
||||
def engraveselect():
|
||||
FreeCADGui.Selection.addSelectionGate(ENGRAVEGate())
|
||||
FreeCAD.Console.PrintWarning("Engraving Select Mode\n")
|
||||
|
||||
|
||||
def chamferselect():
|
||||
FreeCADGui.Selection.addSelectionGate(CHAMFERGate())
|
||||
FreeCAD.Console.PrintWarning("Deburr Select Mode\n")
|
||||
|
||||
|
||||
def profileselect():
|
||||
FreeCADGui.Selection.addSelectionGate(PROFILEGate())
|
||||
FreeCAD.Console.PrintWarning("Profiling Select Mode\n")
|
||||
|
||||
|
||||
def pocketselect():
|
||||
FreeCADGui.Selection.addSelectionGate(POCKETGate())
|
||||
FreeCAD.Console.PrintWarning("Pocketing Select Mode\n")
|
||||
|
||||
|
||||
def adaptiveselect():
|
||||
FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate())
|
||||
FreeCAD.Console.PrintWarning("Adaptive Select Mode\n")
|
||||
|
||||
|
||||
def surfaceselect():
|
||||
if(MESHGate() is True or PROFILEGate() is True):
|
||||
FreeCADGui.Selection.addSelectionGate(True)
|
||||
@@ -237,10 +250,12 @@ def select(op):
|
||||
opsel['Profile Edges'] = eselect
|
||||
opsel['Profile Faces'] = profileselect
|
||||
opsel['Surface'] = surfaceselect
|
||||
opsel['Waterline'] = surfaceselect
|
||||
opsel['Adaptive'] = adaptiveselect
|
||||
opsel['Probe'] = probeselect
|
||||
return opsel[op]
|
||||
|
||||
|
||||
def clear():
|
||||
FreeCADGui.Selection.removeSelectionGate()
|
||||
FreeCAD.Console.PrintWarning("Free Select\n")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -39,89 +39,120 @@ __doc__ = "Surface operation page controller and command implementation."
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
'''Page controller class for the Surface operation.'''
|
||||
|
||||
def initPage(self, obj):
|
||||
self.setTitle("3D Surface")
|
||||
self.updateVisibility()
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui")
|
||||
|
||||
def getFields(self, obj):
|
||||
'''getFields(obj) ... transfers values from UI to obj's proprties'''
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
|
||||
PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset)
|
||||
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
|
||||
|
||||
if obj.StepOver != self.form.stepOver.value():
|
||||
obj.StepOver = self.form.stepOver.value()
|
||||
|
||||
if obj.Algorithm != str(self.form.algorithmSelect.currentText()):
|
||||
obj.Algorithm = str(self.form.algorithmSelect.currentText())
|
||||
|
||||
if obj.BoundBox != str(self.form.boundBoxSelect.currentText()):
|
||||
obj.BoundBox = str(self.form.boundBoxSelect.currentText())
|
||||
|
||||
if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()):
|
||||
obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText())
|
||||
if obj.ScanType != str(self.form.scanType.currentText()):
|
||||
obj.ScanType = str(self.form.scanType.currentText())
|
||||
|
||||
if obj.StepOver != self.form.stepOver.value():
|
||||
obj.StepOver = self.form.stepOver.value()
|
||||
|
||||
if obj.LayerMode != str(self.form.layerMode.currentText()):
|
||||
obj.LayerMode = str(self.form.layerMode.currentText())
|
||||
|
||||
if obj.CutPattern != str(self.form.cutPattern.currentText()):
|
||||
obj.CutPattern = str(self.form.cutPattern.currentText())
|
||||
|
||||
obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value
|
||||
obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value
|
||||
|
||||
if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()):
|
||||
obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText())
|
||||
|
||||
PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset)
|
||||
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
|
||||
|
||||
if obj.UseStartPoint != self.form.useStartPoint.isChecked():
|
||||
obj.UseStartPoint = self.form.useStartPoint.isChecked()
|
||||
|
||||
if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked():
|
||||
obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked()
|
||||
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
if obj.OptimizeStepOverTransitions != self.form.optimizeStepOverTransitions.isChecked():
|
||||
obj.OptimizeStepOverTransitions = self.form.optimizeStepOverTransitions.isChecked()
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect)
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect)
|
||||
self.selectInComboBox(obj.ScanType, self.form.scanType)
|
||||
self.selectInComboBox(obj.LayerMode, self.form.layerMode)
|
||||
self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
|
||||
self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString)
|
||||
self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString)
|
||||
self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect)
|
||||
|
||||
self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x))
|
||||
self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y))
|
||||
self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString)
|
||||
self.form.sampleInterval.setText(str(obj.SampleInterval))
|
||||
self.form.stepOver.setValue(obj.StepOver)
|
||||
self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString)
|
||||
|
||||
if obj.UseStartPoint:
|
||||
self.form.useStartPoint.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.form.useStartPoint.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
if obj.OptimizeLinearPaths:
|
||||
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
if obj.OptimizeStepOverTransitions:
|
||||
self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
|
||||
signals = []
|
||||
signals.append(self.form.toolController.currentIndexChanged)
|
||||
signals.append(self.form.algorithmSelect.currentIndexChanged)
|
||||
signals.append(self.form.coolantController.currentIndexChanged)
|
||||
signals.append(self.form.boundBoxSelect.currentIndexChanged)
|
||||
signals.append(self.form.dropCutterDirSelect.currentIndexChanged)
|
||||
signals.append(self.form.scanType.currentIndexChanged)
|
||||
signals.append(self.form.layerMode.currentIndexChanged)
|
||||
signals.append(self.form.cutPattern.currentIndexChanged)
|
||||
signals.append(self.form.boundBoxExtraOffsetX.editingFinished)
|
||||
signals.append(self.form.boundBoxExtraOffsetY.editingFinished)
|
||||
signals.append(self.form.sampleInterval.editingFinished)
|
||||
signals.append(self.form.stepOver.editingFinished)
|
||||
signals.append(self.form.dropCutterDirSelect.currentIndexChanged)
|
||||
signals.append(self.form.depthOffset.editingFinished)
|
||||
signals.append(self.form.stepOver.editingFinished)
|
||||
signals.append(self.form.sampleInterval.editingFinished)
|
||||
signals.append(self.form.useStartPoint.stateChanged)
|
||||
signals.append(self.form.optimizeEnabled.stateChanged)
|
||||
signals.append(self.form.coolantController.currentIndexChanged)
|
||||
signals.append(self.form.optimizeStepOverTransitions.stateChanged)
|
||||
|
||||
return signals
|
||||
|
||||
def updateVisibility(self):
|
||||
if self.form.algorithmSelect.currentText() == "OCL Dropcutter":
|
||||
self.form.boundBoxExtraOffsetX.setEnabled(True)
|
||||
self.form.boundBoxExtraOffsetY.setEnabled(True)
|
||||
self.form.boundBoxSelect.setEnabled(True)
|
||||
self.form.dropCutterDirSelect.setEnabled(True)
|
||||
self.form.stepOver.setEnabled(True)
|
||||
else:
|
||||
if self.form.scanType.currentText() == "Planar":
|
||||
self.form.cutPattern.setEnabled(True)
|
||||
self.form.boundBoxExtraOffsetX.setEnabled(False)
|
||||
self.form.boundBoxExtraOffsetY.setEnabled(False)
|
||||
self.form.boundBoxSelect.setEnabled(False)
|
||||
self.form.dropCutterDirSelect.setEnabled(False)
|
||||
self.form.stepOver.setEnabled(False)
|
||||
else:
|
||||
self.form.cutPattern.setEnabled(False)
|
||||
self.form.boundBoxExtraOffsetX.setEnabled(True)
|
||||
self.form.boundBoxExtraOffsetY.setEnabled(True)
|
||||
self.form.dropCutterDirSelect.setEnabled(True)
|
||||
|
||||
def registerSignalHandlers(self, obj):
|
||||
self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility)
|
||||
self.form.scanType.currentIndexChanged.connect(self.updateVisibility)
|
||||
|
||||
|
||||
Command = PathOpGui.SetupOperation('Surface',
|
||||
|
||||
3481
src/Mod/Path/PathScripts/PathWaterline.py
Normal file
3481
src/Mod/Path/PathScripts/PathWaterline.py
Normal file
File diff suppressed because it is too large
Load Diff
138
src/Mod/Path/PathScripts/PathWaterlineGui.py
Normal file
138
src/Mod/Path/PathScripts/PathWaterlineGui.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2020 russ4262 <russ4262@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathScripts.PathWaterline as PathWaterline
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
__title__ = "Path Waterline Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Waterline operation page controller and command implementation."
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
'''Page controller class for the Waterline operation.'''
|
||||
|
||||
def initPage(self, obj):
|
||||
# self.setTitle("Waterline")
|
||||
self.updateVisibility()
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui")
|
||||
|
||||
def getFields(self, obj):
|
||||
'''getFields(obj) ... transfers values from UI to obj's proprties'''
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
|
||||
if obj.Algorithm != str(self.form.algorithmSelect.currentText()):
|
||||
obj.Algorithm = str(self.form.algorithmSelect.currentText())
|
||||
|
||||
if obj.BoundBox != str(self.form.boundBoxSelect.currentText()):
|
||||
obj.BoundBox = str(self.form.boundBoxSelect.currentText())
|
||||
|
||||
if obj.LayerMode != str(self.form.layerMode.currentText()):
|
||||
obj.LayerMode = str(self.form.layerMode.currentText())
|
||||
|
||||
if obj.CutPattern != str(self.form.cutPattern.currentText()):
|
||||
obj.CutPattern = str(self.form.cutPattern.currentText())
|
||||
|
||||
PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment)
|
||||
|
||||
if obj.StepOver != self.form.stepOver.value():
|
||||
obj.StepOver = self.form.stepOver.value()
|
||||
|
||||
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
|
||||
|
||||
if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked():
|
||||
obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked()
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect)
|
||||
self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect)
|
||||
self.selectInComboBox(obj.LayerMode, self.form.layerMode)
|
||||
self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
|
||||
self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString)
|
||||
self.form.stepOver.setValue(obj.StepOver)
|
||||
self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString)
|
||||
|
||||
if obj.OptimizeLinearPaths:
|
||||
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
|
||||
signals = []
|
||||
signals.append(self.form.toolController.currentIndexChanged)
|
||||
signals.append(self.form.algorithmSelect.currentIndexChanged)
|
||||
signals.append(self.form.boundBoxSelect.currentIndexChanged)
|
||||
signals.append(self.form.layerMode.currentIndexChanged)
|
||||
signals.append(self.form.cutPattern.currentIndexChanged)
|
||||
signals.append(self.form.boundaryAdjustment.editingFinished)
|
||||
signals.append(self.form.stepOver.editingFinished)
|
||||
signals.append(self.form.sampleInterval.editingFinished)
|
||||
signals.append(self.form.optimizeEnabled.stateChanged)
|
||||
|
||||
return signals
|
||||
|
||||
def updateVisibility(self):
|
||||
if self.form.algorithmSelect.currentText() == 'OCL Dropcutter':
|
||||
self.form.cutPattern.setEnabled(False)
|
||||
self.form.boundaryAdjustment.setEnabled(False)
|
||||
self.form.stepOver.setEnabled(False)
|
||||
self.form.sampleInterval.setEnabled(True)
|
||||
self.form.optimizeEnabled.setEnabled(True)
|
||||
else:
|
||||
self.form.cutPattern.setEnabled(True)
|
||||
self.form.boundaryAdjustment.setEnabled(True)
|
||||
if self.form.cutPattern.currentText() == 'None':
|
||||
self.form.stepOver.setEnabled(False)
|
||||
else:
|
||||
self.form.stepOver.setEnabled(True)
|
||||
self.form.sampleInterval.setEnabled(False)
|
||||
self.form.optimizeEnabled.setEnabled(False)
|
||||
|
||||
def registerSignalHandlers(self, obj):
|
||||
self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility)
|
||||
self.form.cutPattern.currentIndexChanged.connect(self.updateVisibility)
|
||||
|
||||
|
||||
Command = PathOpGui.SetupOperation('Waterline',
|
||||
PathWaterline.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path-Waterline',
|
||||
QtCore.QT_TRANSLATE_NOOP("Waterline", "Waterline"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Waterline", "Create a Waterline Operation from a model"),
|
||||
PathWaterline.SetupProperties)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n")
|
||||
Reference in New Issue
Block a user