Path: remove Pocket OCC algorithm

This commit is contained in:
sliptonic
2017-05-15 19:23:02 -05:00
committed by Yorik van Havre
parent 936bbedaeb
commit c97724ef30
5 changed files with 423 additions and 655 deletions

View File

@@ -26,10 +26,10 @@
<string>Pocket</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<item row="0" column="1">
<widget class="QToolBox" name="toolBox">
<property name="currentIndex">
<number>5</number>
<number>3</number>
</property>
<widget class="QWidget" name="Geometry">
<property name="enabled">
@@ -40,7 +40,7 @@
<x>0</x>
<y>0</y>
<width>334</width>
<height>331</height>
<height>387</height>
</rect>
</property>
<attribute name="icon">
@@ -118,7 +118,7 @@
<x>0</x>
<y>0</y>
<width>334</width>
<height>331</height>
<height>387</height>
</rect>
</property>
<attribute name="icon">
@@ -196,7 +196,7 @@
<x>0</x>
<y>0</y>
<width>334</width>
<height>331</height>
<height>387</height>
</rect>
</property>
<attribute name="icon">
@@ -240,108 +240,13 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="Entry">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>331</height>
</rect>
</property>
<attribute name="label">
<string>Entry</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3"/>
</widget>
<widget class="QWidget" name="Pattern">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>331</height>
</rect>
</property>
<attribute name="label">
<string>Pattern</string>
</attribute>
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QSpinBox" name="stepOverPercent">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Step Over Percent</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout_4">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="useZigZag">
<property name="text">
<string>Use ZigZag</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="zigZagUnidirectional">
<property name="text">
<string>ZigZag Unidirectional</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_10">
<property name="text">
<string>ZigZag Angle</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="Gui::InputField" name="zigZagAngle">
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>331</height>
<height>387</height>
</rect>
</property>
<attribute name="icon">
@@ -383,48 +288,6 @@
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_4">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>Algorithm</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="algorithmSelect">
<item>
<property name="text">
<string>OCC Native</string>
</property>
</item>
<item>
<property name="text">
<string>libarea</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_3">
<property name="frameShape">
@@ -440,54 +303,27 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>Cut Mode</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cutMode">
<item>
<property name="text">
<string>Climb</string>
</property>
</item>
<item>
<property name="text">
<string>Conventional</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QFormLayout" name="formLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="useStartPoint">
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Use Start Point</string>
<string>Pattern</string>
</property>
</widget>
</item>
<item row="1" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Material Allowance</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="4" column="1">
<widget class="Gui::InputField" name="extraOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@@ -506,9 +342,113 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Cut Mode</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="stepOverPercent">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Step Over Percent</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="cutMode">
<item>
<property name="text">
<string>Climb</string>
</property>
</item>
<item>
<property name="text">
<string>Conventional</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="offsetpattern">
<item>
<property name="text">
<string>ZigZag</string>
</property>
</item>
<item>
<property name="text">
<string>Offset</string>
</property>
</item>
<item>
<property name="text">
<string>Spiral</string>
</property>
</item>
<item>
<property name="text">
<string>ZigZagOffset</string>
</property>
</item>
<item>
<property name="text">
<string>Line</string>
</property>
</item>
<item>
<property name="text">
<string>Grid</string>
</property>
</item>
<item>
<property name="text">
<string>Triangle</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>ZigZag Angle</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::InputField" name="zigZagAngle">
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useStartPoint">
<property name="text">
<string>Use Start Point</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

View File

@@ -27,6 +27,8 @@ import Path
from PySide import QtCore, QtGui
from PathScripts import PathUtils
import PathScripts.PathLog as PathLog
from PathScripts.PathUtils import waiting_effects, depth_params
import Part
FreeCADGui = None
if FreeCAD.GuiUp:
@@ -35,8 +37,9 @@ if FreeCAD.GuiUp:
"""Path Pocket object and FreeCAD command"""
LOG_MODULE = 'PathPocket'
PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE)
# PathLog.trackModule('PathPocket')
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
PathLog.trackModule('PathPocket')
FreeCAD.setLogLevel('Path.Area',0)
# Qt tanslation handling
def translate(context, text, disambig=None):
@@ -50,9 +53,6 @@ class ObjectPocket:
obj.addProperty("App::PropertyString", "Comment", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "An optional comment for this profile"))
obj.addProperty("App::PropertyString", "UserLabel", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "User Assigned Label"))
obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The library to use to generate the path"))
obj.Algorithm = ['OCC Native', 'libarea']
# Tool Properties
obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path"))
@@ -71,19 +71,10 @@ class ObjectPocket:
obj.addProperty("App::PropertyEnumeration", "StartAt", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start pocketing at center or boundary"))
obj.StartAt = ['Center', 'Edge']
obj.addProperty("App::PropertyPercent", "StepOver", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Percent of cutter diameter to step over on each pass"))
obj.addProperty("App::PropertyBool", "KeepToolDown", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Attempts to avoid unnecessary retractions."))
obj.addProperty("App::PropertyBool", "ZigUnidirectional", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Lifts tool at the end of each pass to respect cut mode."))
obj.addProperty("App::PropertyBool", "UseZigZag", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Use Zig Zag pattern to clear area."))
obj.addProperty("App::PropertyFloat", "ZigZagAngle", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Angle of the zigzag pattern"))
obj.addProperty("App::PropertyEnumeration", "OffsetPattern", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "clearing pattern to use"))
obj.OffsetPattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle']
# Entry Properties
obj.addProperty("App::PropertyBool", "UseEntry", "Entry", QtCore.QT_TRANSLATE_NOOP("App::Property", "Allow Cutter enter material with a straight plunge."))
obj.addProperty("App::PropertyFloatConstraint", "RampSize", "Entry", QtCore.QT_TRANSLATE_NOOP("App::Property", "The minimum fraction of tool diameter to use for ramp length"))
obj.RampSize = (0.0, 0.01, 100.0, 0.5)
obj.addProperty("App::PropertyFloatConstraint", "HelixSize", "Entry", QtCore.QT_TRANSLATE_NOOP("App::Property", "The fraction of tool diameter to use for calculating helix size."))
obj.HelixSize = (0.0, 0.01, 100.0, 0.5)
obj.addProperty("App::PropertyFloatConstraint", "RampAngle", "Entry", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Angle of the ramp entry."))
obj.RampAngle = (0.0, 0.01, 100.0, 0.5)
# Start Point Properties
obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path"))
@@ -92,15 +83,7 @@ class ObjectPocket:
obj.Proxy = self
def onChanged(self, obj, prop):
if prop == "UseEntry":
if obj.UseEntry:
obj.setEditorMode('HelixSize', 0) # make this visible
obj.setEditorMode('RampAngle', 0) # make this visible
obj.setEditorMode('RampSize', 0) # make this visible
else:
obj.setEditorMode('HelixSize', 2) # make this hidden
obj.setEditorMode('RampAngle', 2) # make this hidden
obj.setEditorMode('RampSize', 2) # make this hidden
pass
def __getstate__(self):
return None
@@ -108,7 +91,29 @@ class ObjectPocket:
def __setstate__(self, state):
return None
def setDepths(proxy, obj):
PathLog.track()
parentJob = PathUtils.findParentJob(obj)
if parentJob is None:
return
baseobject = parentJob.Base
if baseobject is None:
return
try:
bb = baseobject.Shape.BoundBox # parent boundbox
obj.StartDepth = bb.ZMax
obj.ClearanceHeight = bb.ZMax + 5.0
obj.SafeHeight = bb.ZMax + 3.0
obj.FinalDepth = bb.ZMin
except:
obj.StartDepth = 5.0
obj.ClearanceHeight = 10.0
obj.SafeHeight = 8.0
def addpocketbase(self, obj, ss, sub=""):
PathLog.track()
baselist = obj.Base
if baselist is None:
baselist = []
@@ -143,7 +148,6 @@ class ObjectPocket:
else:
baselist.append(item)
obj.Base = baselist
print("this base is: " + str(baselist))
self.execute(obj)
def getStock(self, obj):
@@ -158,191 +162,68 @@ class ObjectPocket:
return self.getStock(o)
return None
def buildpathlibarea(self, obj, a):
"""Build the pocket path using libarea algorithm"""
import PathScripts.PathAreaUtils as PathAreaUtils
from PathScripts.PathUtils import depth_params
PathLog.debug("Generating toolpath with libarea offsets.\n")
@waiting_effects
def _buildPathArea(self, obj, baseobject):
PathLog.track()
pocket = Path.Area()
pocket.setPlane(Part.makeCircle(10))
pocket.add(baseobject)
depthparams = depth_params(
obj.ClearanceHeight.Value,
obj.SafeHeight.Value,
obj.StartDepth.Value,
obj.StepDown,
obj.FinishDepth.Value,
obj.FinalDepth.Value)
extraoffset = obj.MaterialAllowance.Value
stepover = (self.radius * 2) * (float(obj.StepOver)/100)
use_zig_zag = obj.UseZigZag
zig_angle = obj.ZigZagAngle
from_center = (obj.StartAt == "Center")
keep_tool_down = obj.KeepToolDown
zig_unidirectional = obj.ZigUnidirectional
start_point = None
cut_mode = obj.CutMode
PathAreaUtils.flush_nc()
PathAreaUtils.output('mem')
PathAreaUtils.feedrate_hv(self.horizFeed, self.vertFeed)
if obj.UseStartPoint:
start_point = (obj.StartPoint.x, obj.StartPoint.y)
pocketparams = {'Fill': 0,
'Coplanar': 0,
'PocketMode': 1,
'SectionCount': -1,
'Angle': obj.ZigZagAngle,
'FromCenter': (obj.StartAt == "Center"),
'PocketStepover': stepover,
'PocketExtraOffset': obj.MaterialAllowance.Value}
# print "a," + str(self.radius) + "," + str(extraoffset) + "," + str(stepover) + ",depthparams, " + str(from_center) + "," + str(keep_tool_down) + "," + str(use_zig_zag) + "," + str(zig_angle) + "," + str(zig_unidirectional) + "," + str(start_point) + "," + str(cut_mode)
Pattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle']
pocketparams['PocketMode'] = Pattern.index(obj.OffsetPattern) + 1
PathAreaUtils.pocket(
a,
self.radius,
extraoffset,
stepover,
depthparams,
from_center,
keep_tool_down,
use_zig_zag,
zig_angle,
zig_unidirectional,
start_point,
cut_mode)
return PathAreaUtils.retrieve_gcode()
def buildpathocc(self, obj, shape):
"""Build pocket Path using Native OCC algorithm."""
import Part
import DraftGeomUtils
from PathScripts.PathUtils import fmt, helicalPlunge, rampPlunge, depth_params
PathLog.debug("Generating toolpath with OCC native offsets.\n")
extraoffset = obj.MaterialAllowance.Value
# Build up the offset loops
output = ""
if obj.Comment != "":
output += '(' + str(obj.Comment)+')\n'
output += 'G0 Z' + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n"
offsets = []
nextradius = self.radius + extraoffset
result = DraftGeomUtils.pocket2d(shape, nextradius)
while result:
offsets.extend(result)
nextradius += (self.radius * 2) * (float(obj.StepOver)/100)
result = DraftGeomUtils.pocket2d(shape, nextradius)
# revert the list so we start with the outer wires
if obj.StartAt != 'Edge':
offsets.reverse()
plungePos = None
rampEdge = None
if obj.UseEntry:
# Try to find an entry location
toold = self.radius*2
helixBounds = DraftGeomUtils.pocket2d(shape, self.radius * (1 + obj.HelixSize))
if helixBounds:
rampD = obj.RampSize
if obj.StartAt == 'Edge':
plungePos = helixBounds[0].Edges[0].Vertexes[0].Point
else:
plungePos = offsets[0].Edges[0].Vertexes[0].Point
# If it turns out this is invalid for some reason, nuke plungePos
[perp, idx] = DraftGeomUtils.findPerpendicular(plungePos, shape.Edges)
if not perp or perp.Length < self.radius * (1 + obj.HelixSize):
plungePos = None
FreeCAD.Console.PrintError(translate("PathPocket", "Helical Entry location not found.\n"))
# FIXME: Really need to do a point-in-polygon operation to make sure this is within helixBounds
# Or some math to prove that it has to be (doubt that's true)
# Maybe reverse helixBounds and pick off that?
if plungePos is None: # If we didn't find a place to helix, how about a ramp?
FreeCAD.Console.PrintMessage(translate("PathPocket", "Attempting ramp entry.\n"))
if (offsets[0].Edges[0].Length >= toold * rampD) and not (isinstance(offsets[0].Edges[0].Curve, Part.Circle)):
rampEdge = offsets[0].Edges[0]
# The last edge also connects with the starting location- try that
elif (offsets[0].Edges[-1].Length >= toold * rampD) and not (isinstance(offsets[0].Edges[-1].Curve, Part.Circle)):
rampEdge = offsets[0].Edges[-1]
else:
FreeCAD.Console.PrintError(translate("PathPocket", "Ramp Entry location not found.\n"))
# print "Neither edge works: " + str(offsets[0].Edges[0]) + ", " + str(offsets[0].Edges[-1])
# FIXME: There's got to be a smarter way to find a place to ramp
# For helix-ing/ramping, know where we were last time
# FIXME: Can probably get this from the "machine"?
lastZ = obj.ClearanceHeight.Value
startPoint = None
pocket.setParams(**pocketparams)
PathLog.debug("Pocketing with params: {}".format(pocket.getParams()))
depthparams = depth_params(
obj.ClearanceHeight.Value,
obj.SafeHeight.Value,
obj.StartDepth.Value,
obj.StepDown,
obj.FinishDepth.Value,
obj.FinalDepth.Value)
clearance_height=obj.ClearanceHeight.Value,
rapid_safety_space=obj.SafeHeight.Value,
start_depth=obj.StartDepth.Value,
step_down=obj.StepDown.Value,
z_finish_step=0.0,
final_depth=obj.FinalDepth.Value,
user_depths=None)
for vpos in depthparams.get_depths():
sections = pocket.makeSections(mode=0, project=False, heights=depthparams.get_depths())
shapelist = [sec.getShape() for sec in sections]
first = True
# loop over successive wires
for currentWire in offsets:
last = None
for edge in currentWire.Edges:
if not last:
# we set the base GO to our fast move to our starting pos
if first:
# If we can helix, do so
if plungePos:
output += helicalPlunge(plungePos, obj.RampAngle, vpos, lastZ, self.radius*2, obj.HelixSize, self.horizFeed)
lastZ = vpos
# Otherwise, see if we can ramp
# FIXME: This could be a LOT smarter (eg, searching for a longer leg of the edge to ramp along)
elif rampEdge:
output += rampPlunge(rampEdge, obj.RampAngle, vpos, lastZ)
lastZ = vpos
# Otherwise, straight plunge... Don't want to, but sometimes you might not have a choice.
# FIXME: At least not with the lazy ramp programming above...
else:
print("WARNING: Straight-plunging... probably not good, but we didn't find a place to helix or ramp")
startPoint = edge.Vertexes[0].Point
output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n"
output += "G0 X" + fmt(startPoint.x) + " Y" + fmt(startPoint.y) +\
" Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.horizRapid) + "\n"
first = False
# then move slow down to our starting point for our profile
last = edge.Vertexes[0].Point
output += "G1 X" + fmt(last.x) + " Y" + fmt(last.y) + " Z" + fmt(vpos) + " F" + fmt(self.vertFeed) + "\n"
if DraftGeomUtils.geomType(edge) == "Circle":
point = edge.Vertexes[-1].Point
if point == last: # edges can come flipped
point = edge.Vertexes[0].Point
center = edge.Curve.Center
relcenter = center.sub(last)
v1 = last.sub(center)
v2 = point.sub(center)
if v1.cross(v2).z < 0:
output += "G2"
else:
output += "G3"
output += " X" + fmt(point.x) + " Y" + fmt(point.y) + " Z" + fmt(vpos)
output += " I" + fmt(relcenter.x) + " J" + fmt(relcenter.y) + " K" + fmt(relcenter.z) + " F" + fmt(self.horizFeed)
output += "\n"
last = point
else:
point = edge.Vertexes[-1].Point
if point == last: # edges can come flipped
point = edge.Vertexes[0].Point
output += "G1 X" + fmt(point.x) + " Y" + fmt(point.y) + " Z" + fmt(vpos) + " F" + fmt(self.horizFeed) + "\n"
last = point
params = {'shapes': shapelist,
'feedrate': self.horizFeed,
'feedrate_v': self.vertFeed,
'verbose': True,
'resume_height': obj.StepDown.Value,
'retraction': obj.ClearanceHeight.Value}
# if obj.UseStartPoint is True and obj.StartPoint is not None:
# params['start'] = obj.StartPoint
pp = Path.fromShapes(**params)
PathLog.debug("Generating Path with params: {}".format(params))
PathLog.debug(pp)
return pp
# move back up
output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n"
return output
# To reload this from FreeCAD, use: import PathScripts.PathPocket; reload(PathScripts.PathPocket)
def execute(self, obj):
output = ""
PathLog.track()
commandlist = []
commandlist.append(Path.Command("(" + obj.Label + ")"))
if not obj.Active:
path = Path.Path("(inactive operation)")
obj.Path = path
obj.ViewObject.Visibility = False
return
toolLoad = obj.ToolController
if toolLoad is None or toolLoad.ToolNumber == 0:
@@ -360,51 +241,40 @@ class ObjectPocket:
self.radius = tool.Diameter/2
if obj.Base:
PathLog.debug("base items exist. Processing...")
for b in obj.Base:
PathLog.debug("Base item: {}".format(b))
for sub in b[1]:
import Part
import PathScripts.PathKurveUtils
if "Face" in sub:
shape = getattr(b[0].Shape, sub)
wire = shape.OuterWire
edges = wire.Edges
else:
edges = [getattr(b[0].Shape, sub) for sub in b[1]]
wire = Part.Wire(edges)
shape = None
shape = Part.makeFace(edges, 'Part::FaceMakerSimple')
# output = ""
if obj.Algorithm == "OCC Native":
if shape is None:
shape = wire
output += self.buildpathocc(obj, shape)
else:
try:
import area
except:
FreeCAD.Console.PrintError(translate("PathKurve", "libarea needs to be installed for this command to work.\n"))
return
env = PathUtils.getEnvelope(shape, obj.StartDepth)
try:
commandlist.extend(self._buildPathArea(obj, env.cut(b[0].Shape)).Commands)
except Exception as e:
print(e)
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a pocket path. Check project and tool config.")
else: # process the job base object as a whole
PathLog.debug("processing the whole job base object")
parentJob = PathUtils.findParentJob(obj)
if parentJob is None:
return
baseobject = parentJob.Base
if baseobject is None:
return
env = PathUtils.getEnvelope(baseobject.Shape, obj.StartDepth)
try:
commandlist.extend(self._buildPathArea(obj, env.cut(baseobject.Shape)).Commands)
except Exception as e:
print(e)
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a pocket path. Check project and tool config.")
a = area.Area()
if shape is None:
c = PathScripts.PathKurveUtils.makeAreaCurve(wire.Edges, 'CW')
a.append(c)
else:
for w in shape.Wires:
c = PathScripts.PathKurveUtils.makeAreaCurve(w.Edges, 'CW')
a.append(c)
a.Reorder()
output += self.buildpathlibarea(obj, a)
if obj.Active:
path = Path.Path(output)
obj.Path = path
obj.ViewObject.Visibility = True
else:
path = Path.Path("(inactive operation)")
obj.Path = path
obj.ViewObject.Visibility = False
path = Path.Path(commandlist)
obj.Path = path
obj.ViewObject.Visibility = True
class _CommandSetPocketStartPoint:
@@ -468,6 +338,7 @@ class CommandPathPocket:
return False
def Activated(self):
PathLog.track()
zbottom = 0.0
ztop = 10.0
@@ -480,22 +351,18 @@ class CommandPathPocket:
FreeCADGui.doCommand('obj.Active = True')
FreeCADGui.doCommand('PathScripts.PathPocket.ViewProviderPocket(obj.ViewObject)')
FreeCADGui.doCommand('from PathScripts import PathUtils')
FreeCADGui.doCommand('obj.Algorithm = "libarea"')
FreeCADGui.doCommand('obj.StepOver = 100')
FreeCADGui.doCommand('obj.ClearanceHeight = 10') # + str(bb.ZMax + 2.0))
FreeCADGui.doCommand('obj.StepDown = 1.0')
FreeCADGui.doCommand('obj.StartDepth = ' + str(ztop))
FreeCADGui.doCommand('obj.FinalDepth =' + str(zbottom))
FreeCADGui.doCommand('obj.ZigZagAngle = 45')
FreeCADGui.doCommand('obj.UseEntry = False')
FreeCADGui.doCommand('obj.RampAngle = 3.0')
FreeCADGui.doCommand('obj.RampSize = 0.75')
FreeCADGui.doCommand('obj.HelixSize = 0.75')
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
FreeCADGui.doCommand('PathScripts.PathPocket.ObjectPocket.setDepths(obj.Proxy, obj)')
FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)')
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
#FreeCAD.ActiveDocument.recompute()
FreeCADGui.doCommand('obj.ViewObject.startEditing()')
@@ -534,14 +401,8 @@ class TaskPanel:
self.obj.MaterialAllowance = FreeCAD.Units.Quantity(self.form.extraOffset.text()).Value
if hasattr(self.obj, "UseStartPoint"):
self.obj.UseStartPoint = self.form.useStartPoint.isChecked()
if hasattr(self.obj, "Algorithm"):
self.obj.Algorithm = str(self.form.algorithmSelect.currentText())
if hasattr(self.obj, "CutMode"):
self.obj.CutMode = str(self.form.cutMode.currentText())
if hasattr(self.obj, "UseZigZag"):
self.obj.UseZigZag = self.form.useZigZag.isChecked()
if hasattr(self.obj, "ZigUnidirectional"):
self.obj.ZigUnidirectional = self.form.zigZagUnidirectional.isChecked()
if hasattr(self.obj, "ZigZagAngle"):
self.obj.ZigZagAngle = FreeCAD.Units.Quantity(self.form.zigZagAngle.text()).Value
if hasattr(self.obj, "StepOver"):
@@ -561,16 +422,14 @@ class TaskPanel:
self.form.stepDown.setText(FreeCAD.Units.Quantity(self.obj.StepDown.Value, FreeCAD.Units.Length).UserString)
self.form.extraOffset.setText(FreeCAD.Units.Quantity(self.obj.MaterialAllowance, FreeCAD.Units.Length).UserString)
self.form.useStartPoint.setChecked(self.obj.UseStartPoint)
self.form.useZigZag.setChecked(self.obj.UseZigZag)
self.form.zigZagUnidirectional.setChecked(self.obj.ZigUnidirectional)
self.form.zigZagAngle.setText(FreeCAD.Units.Quantity(self.obj.ZigZagAngle, FreeCAD.Units.Angle).UserString)
self.form.stepOverPercent.setValue(self.obj.StepOver)
index = self.form.algorithmSelect.findText(self.obj.Algorithm, QtCore.Qt.MatchFixedString)
if index >= 0:
self.form.algorithmSelect.blockSignals(True)
self.form.algorithmSelect.setCurrentIndex(index)
self.form.algorithmSelect.blockSignals(False)
# index = self.form.algorithmSelect.findText(self.obj.Algorithm, QtCore.Qt.MatchFixedString)
# if index >= 0:
# self.form.algorithmSelect.blockSignals(True)
# self.form.algorithmSelect.setCurrentIndex(index)
# self.form.algorithmSelect.blockSignals(False)
index = self.form.cutMode.findText(
self.obj.CutMode, QtCore.Qt.MatchFixedString)
@@ -579,8 +438,6 @@ class TaskPanel:
self.form.cutMode.setCurrentIndex(index)
self.form.cutMode.blockSignals(False)
# for i in self.obj.Base:
# self.form.baseList.addItem(i[0].Name + "." + i[1][0])
self.form.baseList.blockSignals(True)
for i in self.obj.Base:
for sub in i[1]:
@@ -632,9 +489,6 @@ class TaskPanel:
for sub in i[1]:
self.form.baseList.addItem(i[0].Name + "." + sub)
# for i in self.obj.Base:
# self.form.baseList.addItem(i[0].Name + "." + i[1][0])
def deleteBase(self):
dlist = self.form.baseList.selectedItems()
newlist = []
@@ -701,15 +555,12 @@ class TaskPanel:
self.form.clearanceHeight.editingFinished.connect(self.getFields)
# operation
self.form.algorithmSelect.currentIndexChanged.connect(self.getFields)
self.form.cutMode.currentIndexChanged.connect(self.getFields)
self.form.useStartPoint.clicked.connect(self.getFields)
self.form.extraOffset.editingFinished.connect(self.getFields)
# Pattern
self.form.stepOverPercent.editingFinished.connect(self.getFields)
self.form.useZigZag.clicked.connect(self.getFields)
self.form.zigZagUnidirectional.clicked.connect(self.getFields)
self.form.zigZagAngle.editingFinished.connect(self.getFields)
self.setFields()

View File

@@ -25,17 +25,17 @@
import FreeCAD
import Path
import numpy
import TechDraw
import ArchPanel
import Part
from FreeCAD import Vector
from PathScripts import PathUtils
from PathScripts.PathUtils import depth_params
import PathScripts.PathLog as PathLog
LOG_MODULE = 'PathProfile'
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE)
# PathLog.trackModule('PathProfile')
FreeCAD.setLogLevel('Path.Area', 0)
if FreeCAD.GuiUp:
import FreeCADGui
@@ -73,14 +73,6 @@ class ObjectProfile:
# Start Point Properties
obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path"))
obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point"))
obj.addProperty("App::PropertyLength", "ExtendAtStart", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "extra length of tool path before start of part edge"))
obj.addProperty("App::PropertyLength", "LeadInLineLen", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "length of straight segment of toolpath that comes in at angle to first part edge"))
# End Point Properties
obj.addProperty("App::PropertyBool", "UseEndPoint", "End Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying an End Point"))
obj.addProperty("App::PropertyLength", "ExtendAtEnd", "End Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "extra length of tool path after end of part edge"))
obj.addProperty("App::PropertyLength", "LeadOutLineLen", "End Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "length of straight segment of toolpath that comes in at angle to last part edge"))
obj.addProperty("App::PropertyVector", "EndPoint", "End Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The end point of this path"))
# Profile Properties
obj.addProperty("App::PropertyEnumeration", "Side", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Side of edge that tool should cut"))
@@ -88,8 +80,6 @@ class ObjectProfile:
obj.addProperty("App::PropertyEnumeration", "Direction", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW"))
obj.Direction = ['CW', 'CCW'] # this is the direction that the profile runs
obj.addProperty("App::PropertyBool", "UseComp", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if using Cutter Radius Compensation"))
obj.addProperty("App::PropertyDistance", "RollRadius", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Radius at start and end"))
obj.addProperty("App::PropertyDistance", "OffsetExtra", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final profile- good for roughing toolpath"))
obj.addProperty("App::PropertyBool", "processHoles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile holes as well as the outline"))
obj.addProperty("App::PropertyBool", "processPerimeter", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the outline"))
@@ -145,29 +135,45 @@ class ObjectProfile:
obj.Base = baselist
self.execute(obj)
def _buildPathLibarea(self, obj, edgelist, isHole):
import PathScripts.PathKurveUtils as PathKurveUtils
# import math
# import area
def _buildPathArea(self, obj, baseobject, isHole, start=None):
PathLog.track()
profile = Path.Area()
profile.setPlane(Part.makeCircle(10))
profile.add(baseobject)
PathLog.track("edgelist: {} \n".format(edgelist))
profileparams = {'Fill': 0,
'Coplanar': 0,
'Offset': 0.0,
'SectionCount': -1}
output = ""
if obj.Comment != "":
output += '(' + str(obj.Comment)+')\n'
if obj.UseComp:
if isHole:
profileparams['Offset'] = 0 - self.radius+obj.OffsetExtra.Value
else:
profileparams['Offset'] = self.radius+obj.OffsetExtra.Value
if obj.StartPoint and obj.UseStartPoint:
startpoint = obj.StartPoint
else:
startpoint = None
profile.setParams(**profileparams)
# PathLog.debug("About to profile with params: {}".format(profileparams))
PathLog.debug("About to profile with params: {}".format(profile.getParams()))
if obj.EndPoint and obj.UseEndPoint:
endpoint = obj.EndPoint
else:
endpoint = None
depthparams = depth_params(
clearance_height=obj.ClearanceHeight.Value,
rapid_safety_space=obj.SafeHeight.Value,
start_depth=obj.StartDepth.Value,
step_down=obj.StepDown.Value,
z_finish_step=0.0,
final_depth=obj.FinalDepth.Value,
user_depths=None)
PathKurveUtils.output('mem')
PathKurveUtils.feedrate_hv(self.horizFeed, self.vertFeed)
sections = profile.makeSections(mode=0, project=True, heights=depthparams.get_depths())
shapelist = [sec.getShape() for sec in sections]
params = {'shapes': shapelist,
'feedrate': self.horizFeed,
'feedrate_v': self.vertFeed,
'verbose': False,
'resume_height': obj.StepDown.Value,
'retraction': obj.ClearanceHeight.Value}
# Reverse the direction for holes
if isHole:
@@ -175,50 +181,28 @@ class ObjectProfile:
else:
direction = obj.Direction
output = ""
output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n"
curve = PathKurveUtils.makeAreaCurve(edgelist, direction, startpoint, endpoint)
if direction == 'CCW':
params['orientation'] = 1
else:
params['orientation'] = 0
'''The following line uses a profile function written for use with FreeCAD. It's clean but incomplete. It doesn't handle
print "x = " + str(point.x)
print "y - " + str(point.y)
holding tags
start location
CRC
or probably other features in heekscnc'''
if obj.UseStartPoint is True and obj.StartPoint is not None:
params['start'] = obj.StartPoint
'''The following calls the original procedure from h
toolLoad = obj.activeTCeekscnc profile function. This, in turn, calls many other procedures to modify the profile.
This procedure is hacked together from heekscnc and has not been thoroughly reviewed or understood for FreeCAD. It can probably be
thoroughly optimized and improved but it'll take a smarter mind than mine to do it. -sliptonic Feb16'''
roll_radius = 2.0
extend_at_start = 0.0
extend_at_end = 0.0
lead_in_line_len = 0.0
lead_out_line_len = 0.0
pp = Path.fromShapes(**params)
PathLog.debug("Generating Path with params: {}".format(params))
PathLog.debug(pp)
depthparams = depth_params(
obj.ClearanceHeight.Value,
obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0,
obj.FinalDepth.Value, None)
PathKurveUtils.profile2(
curve, obj.Side, self.radius, self.vertFeed, self.horizFeed,
self.vertRapid, self.horizRapid, obj.OffsetExtra.Value, roll_radius,
None, None, depthparams, extend_at_start, extend_at_end,
lead_in_line_len, lead_out_line_len)
output += PathKurveUtils.retrieve_gcode()
return output
return pp
def execute(self, obj):
import Part # math #DraftGeomUtils
output = ""
import Part
commandlist = []
toolLoad = obj.ToolController
if toolLoad is None or toolLoad.ToolNumber == 0:
FreeCAD.Console.PrintError("No Tool Controller is selected. We need a tool to build a Path.")
#return
return
else:
self.vertFeed = toolLoad.VertFeed.Value
self.horizFeed = toolLoad.HorizFeed.Value
@@ -231,11 +215,12 @@ print "y - " + str(point.y)
else:
self.radius = tool.Diameter/2
output += "(" + obj.Label + ")"
commandlist.append(Path.Command("(" + obj.Label + ")"))
if obj.UseComp:
output += "(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")"
commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")"))
else:
output += "(Uncompensated Tool Path)"
commandlist.append(Path.Command("(Uncompensated Tool Path)"))
if obj.Base:
holes = []
@@ -247,25 +232,40 @@ print "y - " + str(point.y)
faces.append(shape)
if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face
holes += shape.Wires[1:]
else:
print ("found a base object which is not a face. Can't continue.")
return
profileshape = Part.makeCompound(faces)
profilewire = TechDraw.findShapeOutline(profileshape, 1, Vector(0, 0, 1))
if obj.processHoles:
for wire in holes:
edgelist = wire.Edges
edgelist = Part.__sortEdges__(edgelist)
output += self._buildPathLibarea(obj, edgelist, True)
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
# shift the compound to the bottom of the base object for
# proper sectioning
zShift = b[0].Shape.BoundBox.ZMin - f.BoundBox.ZMin
newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation)
f.Placement = newPlace
env = PathUtils.getEnvelope(f, obj.StartDepth)
try:
commandlist.extend(self._buildPathArea(obj, baseobject=env, isHole=True, start=None).Commands)
except Exception as e:
print(e)
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
profileshape = Part.makeCompound(faces)
zShift = b[0].Shape.BoundBox.ZMin - profileshape.BoundBox.ZMin
newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), profileshape.Placement.Rotation)
profileshape.Placement = newPlace
if obj.processPerimeter:
edgelist = profilewire.Edges
edgelist = Part.__sortEdges__(edgelist)
output += self._buildPathLibarea(obj, edgelist, False)
env = PathUtils.getEnvelope(profileshape, obj.StartDepth)
try:
commandlist.extend(self._buildPathArea(obj, baseobject=env, isHole=False, start=None).Commands)
except Exception as e:
print(e)
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
else: #Try to build targets frorm the job base
else: # Try to build targets frorm the job base
parentJob = PathUtils.findParentJob(obj)
if parentJob is None:
return
@@ -279,28 +279,29 @@ print "y - " + str(point.y)
shapes = baseobject.Proxy.getOutlines(baseobject, transform=True)
for shape in shapes:
for wire in shape.Wires:
edgelist = wire.Edges
edgelist = Part.__sortEdges__(edgelist)
PathLog.debug("Processing panel perimeter. edges found: {}".format(len(edgelist)))
try:
output += self._buildPathLibarea(obj, edgelist, isHole=False)
except:
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
env = PathUtils.getEnvelope(f, obj.StartDepth)
try:
commandlist.extend(self._buildPathArea(obj, baseobject=env, isHole=False, start=None).Commands)
except Exception as e:
print(e)
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
shapes = baseobject.Proxy.getHoles(baseobject, transform=True)
for shape in shapes:
for wire in shape.Wires:
drillable = PathUtils.isDrillable(baseobject.Proxy, wire)
if (drillable and obj.processCircles) or (not drillable and obj.processHoles):
edgelist = wire.Edges
edgelist = Part.__sortEdges__(edgelist)
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
env = PathUtils.getEnvelope(f, obj.StartDepth)
try:
output += self._buildPathLibarea(obj, edgelist, isHole=True)
except:
commandlist.extend(self._buildPathArea(obj, baseobject=env, isHole=True, start=None).Commands)
except Exception as e:
print(e)
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
if obj.Active:
path = Path.Path(output)
path = Path.Path(commandlist)
obj.Path = path
obj.ViewObject.Visibility = True
@@ -356,25 +357,6 @@ class _CommandSetStartPoint:
FreeCADGui.Snapper.getPoint(callback=self.setpoint)
class _CommandSetEndPoint:
def GetResources(self):
return {'Pixmap': 'Path-EndPoint',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Pick End Point"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Pick End Point")}
def IsActive(self):
return FreeCAD.ActiveDocument is not None
def setpoint(self, point, o):
obj = FreeCADGui.Selection.getSelection()[0]
obj.EndPoint.x = point.x
obj.EndPoint.y = point.y
def Activated(self):
FreeCADGui.Snapper.getPoint(callback=self.setpoint)
class CommandPathProfile:
def GetResources(self):
return {'Pixmap': 'Path-Profile-Face',
@@ -454,14 +436,10 @@ class TaskPanel:
self.obj.StepDown = FreeCAD.Units.Quantity(self.form.stepDown.text()).Value
if hasattr(self.obj, "OffsetExtra"):
self.obj.OffsetExtra = FreeCAD.Units.Quantity(self.form.extraOffset.text()).Value
if hasattr(self.obj, "RollRadius"):
self.obj.RollRadius = FreeCAD.Units.Quantity(self.form.rollRadius.text()).Value
if hasattr(self.obj, "UseComp"):
self.obj.UseComp = self.form.useCompensation.isChecked()
if hasattr(self.obj, "UseStartPoint"):
self.obj.UseStartPoint = self.form.useStartPoint.isChecked()
if hasattr(self.obj, "UseEndPoint"):
self.obj.UseEndPoint = self.form.useEndPoint.isChecked()
if hasattr(self.obj, "Side"):
self.obj.Side = str(self.form.cutSide.currentText())
if hasattr(self.obj, "Direction"):
@@ -486,10 +464,8 @@ class TaskPanel:
self.form.clearanceHeight.setText(FreeCAD.Units.Quantity(self.obj.ClearanceHeight.Value, FreeCAD.Units.Length).UserString)
self.form.stepDown.setText(FreeCAD.Units.Quantity(self.obj.StepDown.Value, FreeCAD.Units.Length).UserString)
self.form.extraOffset.setText(FreeCAD.Units.Quantity(self.obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString)
self.form.rollRadius.setText(FreeCAD.Units.Quantity(self.obj.RollRadius.Value, FreeCAD.Units.Length).UserString)
self.form.useCompensation.setChecked(self.obj.UseComp)
self.form.useStartPoint.setChecked(self.obj.UseStartPoint)
self.form.useEndPoint.setChecked(self.obj.UseEndPoint)
self.form.processHoles.setChecked(self.obj.processHoles)
self.form.processPerimeter.setChecked(self.obj.processPerimeter)
self.form.processCircles.setChecked(self.obj.processCircles)
@@ -513,6 +489,7 @@ class TaskPanel:
self.form.uiToolController.blockSignals(True)
self.form.uiToolController.addItems(labels)
self.form.uiToolController.blockSignals(False)
if self.obj.ToolController is not None:
index = self.form.uiToolController.findText(
self.obj.ToolController.Label, QtCore.Qt.MatchFixedString)
@@ -628,9 +605,7 @@ class TaskPanel:
self.form.direction.currentIndexChanged.connect(self.getFields)
self.form.useCompensation.clicked.connect(self.getFields)
self.form.useStartPoint.clicked.connect(self.getFields)
self.form.useEndPoint.clicked.connect(self.getFields)
self.form.extraOffset.editingFinished.connect(self.getFields)
self.form.rollRadius.editingFinished.connect(self.getFields)
self.form.processHoles.clicked.connect(self.getFields)
self.form.processPerimeter.clicked.connect(self.getFields)
self.form.processCircles.clicked.connect(self.getFields)
@@ -660,6 +635,5 @@ if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Profile', CommandPathProfile())
FreeCADGui.addCommand('Set_StartPoint', _CommandSetStartPoint())
FreeCADGui.addCommand('Set_EndPoint', _CommandSetEndPoint())
FreeCAD.Console.PrintLog("Loading PathProfile... done\n")

View File

@@ -24,6 +24,7 @@
import FreeCAD
import Path
import Part
from PathScripts import PathUtils
from PathScripts.PathUtils import depth_params
from DraftGeomUtils import findWires
@@ -33,7 +34,7 @@ from PathScripts.PathUtils import waiting_effects
"""Path Profile from Edges Object and Command"""
LOG_MODULE = 'PathProfileEdges'
PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE)
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
# PathLog.trackModule('PathProfileEdges')
if FreeCAD.GuiUp:
@@ -72,23 +73,14 @@ class ObjectProfile:
# Start Point Properties
obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path"))
obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point"))
obj.addProperty("App::PropertyLength", "ExtendAtStart", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "extra length of tool path before start of part edge"))
obj.addProperty("App::PropertyLength", "LeadInLineLen", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "length of straight segment of toolpath that comes in at angle to first part edge"))
# End Point Properties
obj.addProperty("App::PropertyBool", "UseEndPoint", "End Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying an End Point"))
obj.addProperty("App::PropertyLength", "ExtendAtEnd", "End Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "extra length of tool path after end of part edge"))
obj.addProperty("App::PropertyLength", "LeadOutLineLen", "End Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "length of straight segment of toolpath that comes in at angle to last part edge"))
obj.addProperty("App::PropertyVector", "EndPoint", "End Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The end point of this path"))
# Profile Properties
obj.addProperty("App::PropertyEnumeration", "Side", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Side of edge that tool should cut"))
obj.Side = ['Left', 'Right', 'On'] # side of profile that cutter is on in relation to direction of profile
obj.Side = ['Left', 'Right'] # side of profile that cutter is on in relation to direction of profile
obj.addProperty("App::PropertyEnumeration", "Direction", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW"))
obj.Direction = ['CW', 'CCW'] # this is the direction that the profile runs
obj.addProperty("App::PropertyBool", "UseComp", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if using Cutter Radius Compensation"))
obj.addProperty("App::PropertyDistance", "RollRadius", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Radius at start and end"))
obj.addProperty("App::PropertyDistance", "OffsetExtra", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final profile- good for roughing toolpath"))
obj.Proxy = self
@@ -134,70 +126,82 @@ class ObjectProfile:
else:
baselist.append(item)
obj.Base = baselist
self.execute(obj)
#self.execute(obj)
@waiting_effects
def _buildPathLibarea(self, obj, edgelist):
import PathScripts.PathKurveUtils as PathKurveUtils
# import math
# import area
output = ""
if obj.Comment != "":
output += '(' + str(obj.Comment)+')\n'
def _buildPathArea(self, obj, baseobject, start=None):
PathLog.track()
profile = Path.Area()
profile.setPlane(Part.makeCircle(10))
profile.add(baseobject)
if obj.StartPoint and obj.UseStartPoint:
startpoint = obj.StartPoint
else:
startpoint = None
profileparams = {'Fill': 0,
'Coplanar': 0,
'Offset': 0.0,
'SectionCount': -1}
if obj.EndPoint and obj.UseEndPoint:
endpoint = obj.EndPoint
else:
endpoint = None
if obj.UseComp:
if obj.Side == 'Right':
profileparams['Offset'] = 0 - self.radius+obj.OffsetExtra.Value
else:
profileparams['Offset'] = self.radius+obj.OffsetExtra.Value
PathKurveUtils.output('mem')
PathKurveUtils.feedrate_hv(self.horizFeed, self.vertFeed)
output = ""
output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n"
curve = PathKurveUtils.makeAreaCurve(edgelist, obj.Direction, startpoint, endpoint)
'''The following line uses a profile function written for use with FreeCAD. It's clean but incomplete. It doesn't handle
print("x = " + str(point.x))
print("y - " + str(point.y))
holding tags
start location
CRC
or probably other features in heekscnc'''
# output += PathKurveUtils.profile(curve, side, radius, vf, hf, offset_extra, rapid_safety_space, clearance, start_depth, step_down, final_depth, use_CRC)
'''The following calls the original procedure from h
toolLoad = obj.activeTCeekscnc profile function. This, in turn, calls many other procedures to modify the profile.
This procedure is hacked together from heekscnc and has not been thoroughly reviewed or understood for FreeCAD. It can probably be
thoroughly optimized and improved but it'll take a smarter mind than mine to do it. -sliptonic Feb16'''
roll_radius = 2.0
extend_at_start = 0.0
extend_at_end = 0.0
lead_in_line_len = 0.0
lead_out_line_len = 0.0
profile.setParams(**profileparams)
# PathLog.debug("About to profile with params: {}".format(profileparams))
PathLog.debug("About to profile with params: {}".format(profile.getParams()))
depthparams = depth_params(
obj.ClearanceHeight.Value,
obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0,
obj.FinalDepth.Value, None)
clearance_height=obj.ClearanceHeight.Value,
rapid_safety_space=obj.SafeHeight.Value,
start_depth=obj.StartDepth.Value,
step_down=obj.StepDown.Value,
z_finish_step=0.0,
final_depth=obj.FinalDepth.Value,
user_depths=None)
PathKurveUtils.profile2(
curve, obj.Side, self.radius, self.vertFeed, self.horizFeed,
self.vertRapid, self.horizRapid, obj.OffsetExtra.Value, roll_radius,
None, None, depthparams, extend_at_start, extend_at_end,
lead_in_line_len, lead_out_line_len)
sections = profile.makeSections(mode=0, project=True, heights=depthparams.get_depths())
shapelist = [sec.getShape() for sec in sections]
params = {'shapes': shapelist,
'feedrate': self.horizFeed,
'feedrate_v': self.vertFeed,
'verbose': False,
'resume_height': obj.StepDown.Value,
'retraction': obj.ClearanceHeight.Value}
# Reverse the direction for holes
# if isHole:
# direction = "CW" if obj.Direction == "CCW" else "CCW"
# else:
# direction = obj.Direction
if obj.Direction == 'CCW':
params['orientation'] = 0
else:
params['orientation'] = 1
if obj.UseStartPoint is True and obj.StartPoint is not None:
params['start'] = obj.StartPoint
pp = Path.fromShapes(**params)
PathLog.debug("Generating Path with params: {}".format(params))
PathLog.debug(pp)
return pp
output += PathKurveUtils.retrieve_gcode()
return output
def execute(self, obj):
import Part # math #DraftGeomUtils
output = ""
# import Part # math #DraftGeomUtils
commandlist = []
if not obj.Active:
path = Path.Path("(inactive operation)")
obj.Path = path
obj.ViewObject.Visibility = False
return
toolLoad = obj.ToolController
if toolLoad is None or toolLoad.ToolNumber == 0:
@@ -214,12 +218,13 @@ print("y - " + str(point.y))
else:
self.radius = tool.Diameter/2
output += "(" + obj.Label + ")"
commandlist.append(Path.Command("(" + obj.Label + ")"))
if obj.UseComp:
output += "(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")"
commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")"))
else:
output += "(Uncompensated Tool Path)"
commandlist.append(Path.Command("(Uncompensated Tool Path)"))
if obj.Base:
wires = []
@@ -230,19 +235,26 @@ print("y - " + str(point.y))
wires.extend(findWires(edgelist))
for wire in wires:
edgelist = wire.Edges
edgelist = Part.__sortEdges__(edgelist)
output += self._buildPathLibarea(obj, edgelist)
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
if obj.Active:
path = Path.Path(output)
obj.Path = path
obj.ViewObject.Visibility = True
# shift the compound to the bottom of the base object for
# proper sectioning
zShift = b[0].Shape.BoundBox.ZMin - f.BoundBox.ZMin
newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation)
f.Placement = newPlace
env = PathUtils.getEnvelope(f, obj.StartDepth)
else:
path = Path.Path("(inactive operation)")
obj.Path = path
obj.ViewObject.Visibility = False
try:
# commandlist.extend(self._buildPathArea(obj, wire).Commands)
commandlist.extend(self._buildPathArea(obj, baseobject=env, start=None).Commands)
except Exception as e:
print(e)
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
path = Path.Path(commandlist)
obj.Path = path
obj.ViewObject.Visibility = True
class _ViewProviderProfile:
@@ -342,7 +354,7 @@ class CommandPathProfileEdges:
FreeCADGui.doCommand('obj.FinalDepth=' + str(zbottom))
FreeCADGui.doCommand('obj.SafeHeight = ' + str(ztop + 2.0))
FreeCADGui.doCommand('obj.Side = "On"')
FreeCADGui.doCommand('obj.Side = "Right"')
FreeCADGui.doCommand('obj.OffsetExtra = 0.0')
FreeCADGui.doCommand('obj.Direction = "CW"')
FreeCADGui.doCommand('obj.UseComp = True')
@@ -390,14 +402,8 @@ class TaskPanel:
self.obj.StepDown = FreeCAD.Units.Quantity(self.form.stepDown.text()).Value
if hasattr(self.obj, "OffsetExtra"):
self.obj.OffsetExtra = FreeCAD.Units.Quantity(self.form.extraOffset.text()).Value
if hasattr(self.obj, "RollRadius"):
self.obj.RollRadius = FreeCAD.Units.Quantity(self.form.rollRadius.text()).Value
if hasattr(self.obj, "UseComp"):
self.obj.UseComp = self.form.useCompensation.isChecked()
if hasattr(self.obj, "UseStartPoint"):
self.obj.UseStartPoint = self.form.useStartPoint.isChecked()
if hasattr(self.obj, "UseEndPoint"):
self.obj.UseEndPoint = self.form.useEndPoint.isChecked()
if hasattr(self.obj, "Side"):
self.obj.Side = str(self.form.cutSide.currentText())
if hasattr(self.obj, "Direction"):
@@ -416,10 +422,7 @@ class TaskPanel:
self.form.clearanceHeight.setText(FreeCAD.Units.Quantity(self.obj.ClearanceHeight.Value, FreeCAD.Units.Length).UserString)
self.form.stepDown.setText(FreeCAD.Units.Quantity(self.obj.StepDown.Value, FreeCAD.Units.Length).UserString)
self.form.extraOffset.setText(FreeCAD.Units.Quantity(self.obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString)
self.form.rollRadius.setText(FreeCAD.Units.Quantity(self.obj.RollRadius.Value, FreeCAD.Units.Length).UserString)
self.form.useCompensation.setChecked(self.obj.UseComp)
self.form.useStartPoint.setChecked(self.obj.UseStartPoint)
self.form.useEndPoint.setChecked(self.obj.UseEndPoint)
controllers = PathUtils.getToolControllers(self.obj)
labels = [c.Label for c in controllers]
@@ -549,10 +552,7 @@ class TaskPanel:
self.form.cutSide.currentIndexChanged.connect(self.getFields)
self.form.direction.currentIndexChanged.connect(self.getFields)
self.form.useCompensation.clicked.connect(self.getFields)
self.form.useStartPoint.clicked.connect(self.getFields)
self.form.useEndPoint.clicked.connect(self.getFields)
self.form.extraOffset.editingFinished.connect(self.getFields)
self.form.rollRadius.editingFinished.connect(self.getFields)
self.setFields()
sel = FreeCADGui.Selection.getSelectionEx()
@@ -577,7 +577,5 @@ class SelObserver:
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Profile_Edges', CommandPathProfileEdges())
FreeCADGui.addCommand('Set_StartPoint', _CommandSetStartPoint())
FreeCADGui.addCommand('Set_EndPoint', _CommandSetEndPoint())
FreeCAD.Console.PrintLog("Loading PathProfileEdges... done\n")

View File

@@ -38,10 +38,9 @@ from PySide import QtGui
LOG_MODULE = 'PathUtils'
PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE)
# PathLog.trackModule('PathUtils')
PathLog.trackModule('PathUtils')
FreeCAD.setLogLevel('Path.Area', 0)
def waiting_effects(function):
def new_function(*args, **kwargs):
if not FreeCAD.GuiUp:
@@ -253,10 +252,16 @@ def getEnvelope(partshape, stockheight=None):
# partshape.BoundBox.ZMin)
area.setPlane(makeWorkplane(partshape))
sec = area.makeSections(heights=[1.0], project=True)[0].getShape()
if stockheight is not None:
return sec.extrude(FreeCAD.Vector(0, 0, stockheight))
eLength = float(stockheight)-partshape.BoundBox.ZMin
envelopeshape = sec.extrude(FreeCAD.Vector(0, 0, eLength))
else:
return sec.extrude(FreeCAD.Vector(0, 0, partshape.BoundBox.ZLength))
envelopeshape = sec.extrude(FreeCAD.Vector(0, 0, partshape.BoundBox.ZLength))
if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG:
removalshape=FreeCAD.ActiveDocument.addObject("Part::Feature","RemovedMaterial")
removalshape.Shape = envelopeshape
return envelopeshape
def reverseEdge(e):