Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Gauthier
2020-04-11 17:06:31 +02:00
222 changed files with 17310 additions and 6837 deletions

View File

@@ -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):

View File

@@ -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):

View File

@@ -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():

View File

@@ -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):

View File

@@ -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)

View File

@@ -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):

View File

@@ -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

View File

@@ -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')

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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',

File diff suppressed because it is too large Load Diff

View 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")