Path: fix drilling panel
Path: Make drilling work with local tc tools also make hole identification more robust
This commit is contained in:
committed by
Yorik van Havre
parent
fa65446ff0
commit
5282fc5961
@@ -82,12 +82,12 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="reorderBase">
|
||||
<widget class="QPushButton" name="uiFindAllHoles">
|
||||
<property name="toolTip">
|
||||
<string>Update the path with the removed and reordered items.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update</string>
|
||||
<string>Find All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -24,11 +24,19 @@
|
||||
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Vector
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import Part
|
||||
from PySide import QtCore, QtGui
|
||||
from PathScripts import PathUtils
|
||||
from PathScripts.PathUtils import fmt
|
||||
from math import pi
|
||||
|
||||
|
||||
LOG_MODULE = 'PathDrilling'
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
|
||||
PathLog.trackModule('PathDrilling')
|
||||
|
||||
FreeCADGui = None
|
||||
if FreeCAD.GuiUp:
|
||||
@@ -50,25 +58,29 @@ except AttributeError:
|
||||
class ObjectDrilling:
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty("App::PropertyLinkSubList", "Base","Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The base geometry of this toolpath"))
|
||||
# Base & location
|
||||
obj.addProperty("App::PropertyLinkSubList", "Base","Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base geometry of this toolpath"))
|
||||
#obj.addProperty("App::PropertyVectorList", "Positions", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Locations of insterted holding tags"))
|
||||
obj.addProperty("App::PropertyIntegerList", "Disabled", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Ids of disabled holding tags"))
|
||||
|
||||
# General Properties
|
||||
obj.addProperty("App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Make False, to prevent operation from generating code"))
|
||||
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::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"))
|
||||
|
||||
# Drilling Properties
|
||||
obj.addProperty("App::PropertyLength", "PeckDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Incremental Drill depth before retracting to clear chips"))
|
||||
obj.addProperty("App::PropertyLength", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Starting Depth of Tool- first cut depth in Z"))
|
||||
obj.addProperty("App::PropertyFloat", "DwellTime", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "The time to dwell between peck cycles"))
|
||||
|
||||
# Heights & Depths
|
||||
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "The height needed to clear clamps and obstructions"))
|
||||
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Final Depth of Tool- lowest value in Z"))
|
||||
obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Height to clear top of materil"))
|
||||
obj.addProperty("App::PropertyDistance", "RetractHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "The height where feed starts and height during retract tool when path is finished"))
|
||||
|
||||
obj.addProperty("App::PropertyLength", "PeckDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","Incremental Drill depth before retracting to clear chips"))
|
||||
obj.addProperty("App::PropertyLength", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","Starting Depth of Tool- first cut depth in Z"))
|
||||
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","The height needed to clear clamps and obstructions"))
|
||||
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","Final Depth of Tool- lowest value in Z"))
|
||||
obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","Height to clear top of materil"))
|
||||
obj.addProperty("App::PropertyDistance", "RetractHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","The height where feed starts and height during retract tool when path is finished"))
|
||||
obj.addProperty("App::PropertyFloat", "DwellTime", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","The time to dwell between peck cycles"))
|
||||
# Tool Properties
|
||||
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", QtCore.QT_TRANSLATE_NOOP("App::Property","The tool number in use"))
|
||||
obj.ToolNumber = (0, 0, 1000, 1)
|
||||
obj.setEditorMode('ToolNumber', 1) # make this read only
|
||||
obj.addProperty("App::PropertyString", "ToolDescription", "Tool", QtCore.QT_TRANSLATE_NOOP("App::Property","The description of the tool "))
|
||||
obj.setEditorMode('ToolDescription', 1) # make this read onlyt
|
||||
|
||||
obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path"))
|
||||
|
||||
obj.Proxy = self
|
||||
|
||||
@@ -83,36 +95,37 @@ class ObjectDrilling:
|
||||
obj.Label = obj.UserLabel + " :" + obj.ToolDescription
|
||||
|
||||
def execute(self, obj):
|
||||
PathLog.track()
|
||||
output = ""
|
||||
if obj.Comment != "":
|
||||
output += '(' + str(obj.Comment)+')\n'
|
||||
|
||||
toolLoad = PathUtils.getLastToolLoad(obj)
|
||||
toolLoad = obj.ToolController
|
||||
if toolLoad is None or toolLoad.ToolNumber == 0:
|
||||
self.vertFeed = 100
|
||||
self.horizFeed = 100
|
||||
self.vertRapid = 100
|
||||
self.horizRapid = 100
|
||||
self.radius = 0.25
|
||||
obj.ToolNumber = 0
|
||||
obj.ToolDescription = "UNDEFINED"
|
||||
FreeCAD.Console.PrintError("No Tool Controller is selected. We need a tool to build a Path.")
|
||||
#return
|
||||
else:
|
||||
self.vertFeed = toolLoad.VertFeed.Value
|
||||
self.horizFeed = toolLoad.HorizFeed.Value
|
||||
self.vertRapid = toolLoad.VertRapid.Value
|
||||
self.horizRapid = toolLoad.HorizRapid.Value
|
||||
tool = PathUtils.getTool(obj, toolLoad.ToolNumber)
|
||||
if tool.Diameter == 0:
|
||||
self.radius = 0.25
|
||||
tool = toolLoad.Proxy.getTool(toolLoad)
|
||||
if not tool or tool.Diameter == 0:
|
||||
FreeCAD.Console.PrintError("No Tool found or diameter is zero. We need a tool to build a Path.")
|
||||
return
|
||||
else:
|
||||
self.radius = tool.Diameter/2
|
||||
obj.ToolNumber = toolLoad.ToolNumber
|
||||
obj.ToolDescription = toolLoad.Name
|
||||
|
||||
if obj.UserLabel == "":
|
||||
obj.Label = obj.Name + " :" + obj.ToolDescription
|
||||
else:
|
||||
obj.Label = obj.UserLabel + " :" + obj.ToolDescription
|
||||
if not obj.Base:
|
||||
parentJob = PathUtils.findParentJob(obj)
|
||||
if parentJob is None:
|
||||
return
|
||||
baseobject = parentJob.Base
|
||||
if baseobject is None:
|
||||
return
|
||||
holes = self.findHoles(baseobject.Shape)
|
||||
for hole in holes:
|
||||
self.addDrillableLocation(obj, baseobject, hole[0])
|
||||
|
||||
locations = []
|
||||
output = "(Begin Drilling)\n"
|
||||
@@ -142,7 +155,6 @@ class ObjectDrilling:
|
||||
Z = s.CenterOfMass.z
|
||||
locations.append(FreeCAD.Vector(X, Y, Z))
|
||||
|
||||
|
||||
output += "G90 G98\n"
|
||||
# rapid to clearance height
|
||||
output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n"
|
||||
@@ -171,9 +183,6 @@ class ObjectDrilling:
|
||||
|
||||
output += "G80\n"
|
||||
|
||||
# path = Path.Path(output)
|
||||
# obj.Path = path
|
||||
|
||||
if obj.Active:
|
||||
path = Path.Path(output)
|
||||
obj.Path = path
|
||||
@@ -184,40 +193,67 @@ class ObjectDrilling:
|
||||
obj.Path = path
|
||||
obj.ViewObject.Visibility = False
|
||||
|
||||
|
||||
|
||||
def checkdrillable(self, obj, sub):
|
||||
def _isDrillable(self, obj, candidate):
|
||||
PathLog.track()
|
||||
drillable = False
|
||||
if obj.ShapeType == 'Vertex':
|
||||
drillable = True
|
||||
elif obj.ShapeType in['Solid', 'Compound']:
|
||||
if sub[0:4] == 'Face':
|
||||
subobj = obj.getElement(sub)
|
||||
if isinstance(subobj.Edges[0].Curve, Part.Circle):
|
||||
drillable = True
|
||||
if str(subobj.Surface) == "<Cylinder object>":
|
||||
drillable = subobj.isClosed()
|
||||
if len(subobj.Edges) == 3:
|
||||
cedge = []
|
||||
ledge = []
|
||||
for e in subobj.Edges:
|
||||
if isinstance (e.Curve, Part.Circle):
|
||||
cedge.append(e)
|
||||
elif isinstance (e.Curve, Part.LineSegment):
|
||||
ledge.append(e)
|
||||
if len(cedge) == 2 and len(ledge) == 1:
|
||||
drillable = True
|
||||
else:
|
||||
#if len(subobj.Edges[0].Vertexes) > 1:
|
||||
if candidate.BoundBox.ZLength > 0:
|
||||
face = candidate
|
||||
# eliminate flat faces
|
||||
if (round(face.ParameterRange[0], 8) == 0.0) and (round(face.ParameterRange[1], 8) == round(pi * 2, 8)):
|
||||
for edge in face.Edges: # Find seam edge and check if aligned to Z axis.
|
||||
if (isinstance(edge.Curve, Part.Line)):
|
||||
v0 = edge.Vertexes[0].Point
|
||||
v1 = edge.Vertexes[1].Point
|
||||
if (v1.sub(v0).x == 0) and (v1.sub(v0).y == 0):
|
||||
# vector of top center
|
||||
lsp = Vector(face.BoundBox.Center.x,
|
||||
face.BoundBox.Center.y, face.BoundBox.ZMax)
|
||||
# vector of bottom center
|
||||
lep = Vector(face.BoundBox.Center.x,
|
||||
face.BoundBox.Center.y, face.BoundBox.ZMin)
|
||||
if obj.isInside(lsp, 0, False) or obj.isInside(lep, 0, False):
|
||||
drillable = False
|
||||
# eliminate elliptical holes
|
||||
elif abs(face.BoundBox.XLength - face.BoundBox.YLength) > 0.05:
|
||||
drillable = False
|
||||
else:
|
||||
drillable = True
|
||||
else:
|
||||
for edge in candidate.Edges:
|
||||
if (isinstance(edge.Curve, Part.Circle)):
|
||||
if abs(edge.BoundBox.XLength - edge.BoundBox.YLength) > 0.05:
|
||||
drillable = False
|
||||
if sub[0:4] == 'Edge':
|
||||
o = obj.getElement(sub)
|
||||
if isinstance(o.Curve, Part.Circle):
|
||||
drillable = True
|
||||
|
||||
else:
|
||||
drillable = True
|
||||
return drillable
|
||||
|
||||
def findHoles(self, obj):
|
||||
PathLog.track()
|
||||
holelist = []
|
||||
if obj.BoundBox.ZLength == 0:
|
||||
for i in range(len(obj.Edges)):
|
||||
candidateEdgeName = "Edge" + str(i +1)
|
||||
e = obj.getElement(candidateEdgeName)
|
||||
if self._isDrillable(obj, e):
|
||||
x = e.BoundBox.Center.x
|
||||
y = e.BoundBox.Center.y
|
||||
diameter = e.BoundBox.XLength
|
||||
holelist.append((candidateEdgeName, e, x, y, diameter))
|
||||
else:
|
||||
for i in range(len(obj.Faces)):
|
||||
candidateFaceName = "Face" + str(i + 1)
|
||||
f = obj.getElement(candidateFaceName)
|
||||
if self._isDrillable(obj, f):
|
||||
x = f.BoundBox.Center.x
|
||||
y = f.BoundBox.Center.y
|
||||
diameter = f.BoundBox.XLength
|
||||
holelist.append((candidateFaceName, f, x, y, diameter))
|
||||
|
||||
PathLog.debug("holes found: {}".format(holelist))
|
||||
return holelist
|
||||
|
||||
def addDrillableLocation(self, obj, ss, sub=""):
|
||||
PathLog.track('ss: {} sub: {}'.format(ss.Label, sub))
|
||||
baselist = obj.Base
|
||||
item = (ss, sub)
|
||||
if len(baselist) == 0: # When adding the first base object, guess at heights
|
||||
@@ -239,73 +275,8 @@ class ObjectDrilling:
|
||||
obj.ClearanceHeight = 10.0
|
||||
obj.SafeHeight = 8.0
|
||||
obj.RetractHeight = 6.0
|
||||
baselist.append(item)
|
||||
|
||||
if not self.checkdrillable(ss.Shape,sub):
|
||||
FreeCAD.Console.PrintError("Selected element is not a drillable location" + "\n")
|
||||
return
|
||||
|
||||
if sub[0:4] == 'Face':
|
||||
# Check for other drillable faces and give user the option
|
||||
drillableFaces = []
|
||||
|
||||
for i in range(len(ss.Shape.Faces)):
|
||||
if self.checkdrillable(ss.Shape, "Face" + str(i+1)):
|
||||
drillableFaces.append("Face" + str(i+1))
|
||||
if len(drillableFaces) > 1:
|
||||
reply = QtGui.QMessageBox.question(None,"","Multiple drillable faces found. Drill them all?",
|
||||
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
for i in drillableFaces:
|
||||
if i in baselist:
|
||||
FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n")
|
||||
continue
|
||||
else:
|
||||
newitem = (ss, i)
|
||||
baselist.append(newitem)
|
||||
else:
|
||||
if item in baselist:
|
||||
FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n")
|
||||
else:
|
||||
baselist.append(item)
|
||||
else:
|
||||
if item in baselist:
|
||||
FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n")
|
||||
else:
|
||||
baselist.append(item)
|
||||
|
||||
|
||||
if sub[0:4] == 'Edge':
|
||||
drillableEdges = []
|
||||
o = ss.Shape.getElement(sub)
|
||||
|
||||
for i in range(len(ss.Shape.Edges)):
|
||||
candidateedge = ss.Shape.getElement("Edge" + str(i+1))
|
||||
if self.checkdrillable(ss.Shape, "Edge" + str(i+1)):
|
||||
if candidateedge.Curve.Radius == o.Curve.Radius and candidateedge.Curve.Center.z == o.Curve.Center.z:
|
||||
drillableEdges.append("Edge" + str(i+1))
|
||||
if len(drillableEdges) > 1:
|
||||
reply = QtGui.QMessageBox.question(None,"","Multiple drillable edges found. Drill them all?",
|
||||
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
for i in drillableEdges:
|
||||
if i in baselist:
|
||||
FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n")
|
||||
continue
|
||||
else:
|
||||
newitem = (ss, i)
|
||||
baselist.append(newitem)
|
||||
else:
|
||||
if item in baselist:
|
||||
FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n")
|
||||
else:
|
||||
baselist.append(item)
|
||||
else:
|
||||
if item in baselist:
|
||||
FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n")
|
||||
else:
|
||||
baselist.append(item)
|
||||
|
||||
print(baselist)
|
||||
obj.Base = baselist
|
||||
self.execute(obj)
|
||||
|
||||
@@ -375,6 +346,7 @@ class CommandPathDrilling:
|
||||
FreeCADGui.doCommand('obj.RetractHeight= ' + str(ztop))
|
||||
FreeCADGui.doCommand('obj.FinalDepth=' + str(zbottom))
|
||||
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
|
||||
FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)')
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
@@ -412,7 +384,9 @@ class TaskPanel:
|
||||
self.obj.ClearanceHeight = FreeCAD.Units.Quantity(self.form.clearanceHeight.text()).Value
|
||||
if hasattr(self.obj, "RetractHeight"):
|
||||
self.obj.RetractHeight = FreeCAD.Units.Quantity(self.form.retractHeight.text()).Value
|
||||
|
||||
if hasattr(self.obj, "ToolController"):
|
||||
tc = PathUtils.findToolController(self.obj, self.form.uiToolController.currentText())
|
||||
self.obj.ToolController = tc
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
|
||||
def setFields(self):
|
||||
@@ -428,6 +402,14 @@ class TaskPanel:
|
||||
for sub in i[1]:
|
||||
self.form.baseList.addItem(i[0].Name + "." + sub)
|
||||
|
||||
controllers = PathUtils.getToolControllers(self.obj)
|
||||
labels = [c.Label for c in controllers]
|
||||
self.form.uiToolController.addItems(labels)
|
||||
if self.obj.ToolController is not None:
|
||||
index = self.form.uiToolController.findText(
|
||||
self.obj.ToolController.Label, QtCore.Qt.MatchFixedString)
|
||||
if index >= 0:
|
||||
self.form.uiToolController.setCurrentIndex(index)
|
||||
|
||||
def open(self):
|
||||
self.s = SelObserver()
|
||||
@@ -510,6 +492,7 @@ class TaskPanel:
|
||||
self.form.reorderBase.clicked.connect(self.reorderBase)
|
||||
|
||||
self.form.baseList.itemSelectionChanged.connect(self.itemActivated)
|
||||
self.form.uiToolController.currentIndexChanged.connect(self.getFields)
|
||||
|
||||
sel = FreeCADGui.Selection.getSelectionEx()
|
||||
if len(sel) != 0 and sel[0].HasSubObjects:
|
||||
|
||||
@@ -26,18 +26,18 @@ import FreeCAD
|
||||
import FreeCADGui
|
||||
import Part
|
||||
import math
|
||||
# import Draft
|
||||
# import Path
|
||||
# import TechDraw
|
||||
from DraftGeomUtils import geomType
|
||||
# from DraftGeomUtils import findWires
|
||||
# import DraftVecUtils
|
||||
import PathScripts
|
||||
from PathScripts import PathJob
|
||||
# import itertools
|
||||
import numpy
|
||||
import PathLog
|
||||
|
||||
LOG_MODULE = 'PathUtils'
|
||||
PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE)
|
||||
PathLog.trackModule('PathUtils')
|
||||
|
||||
def cleanedges(splines, precision):
|
||||
PathLog.track()
|
||||
'''cleanedges([splines],precision). Convert BSpline curves, Beziers, to arcs that can be used for cnc paths.
|
||||
Returns Lines as is. Filters Circle and Arcs for over 180 degrees. Discretizes Ellipses. Ignores other geometry. '''
|
||||
edges = []
|
||||
@@ -72,9 +72,9 @@ def cleanedges(splines, precision):
|
||||
|
||||
return edges
|
||||
|
||||
|
||||
def curvetowire(obj, steps):
|
||||
'''adapted from DraftGeomUtils, because the discretize function changed a bit '''
|
||||
PathLog.track()
|
||||
points = obj.copy().discretize(Distance=eval('steps'))
|
||||
p0 = points[0]
|
||||
edgelist = []
|
||||
@@ -88,96 +88,20 @@ def curvetowire(obj, steps):
|
||||
# fixme set at 4 decimal places for testing
|
||||
def fmt(val): return format(val, '.4f')
|
||||
|
||||
|
||||
# def getProjected(shape,direction):
|
||||
# "returns projected edges from a shape and a direction"
|
||||
# import Part,Drawing
|
||||
# edges = []
|
||||
# groups = Drawing.projectEx(shape,direction)
|
||||
# for g in groups[0:5]:
|
||||
# if g:
|
||||
# edges.append(g)
|
||||
# # if hasattr(obj,"Tessellation") and obj.Tessellation:
|
||||
# # return DraftGeomUtils.cleanProjection(Part.makeCompound(edges),obj.Tessellation,obj.SegmentLength)
|
||||
# # else:
|
||||
# return Part.makeCompound(edges)
|
||||
|
||||
|
||||
# def silhouette(obj):
|
||||
# from FreeCAD import Vector
|
||||
# s = getProjected(obj.Shape, Vector(0,0,1))
|
||||
# w = TechDraw.findOuterWire(s.Edges)
|
||||
# return w
|
||||
|
||||
# def isSameEdge(e1, e2):
|
||||
# """isSameEdge(e1,e2): return True if the 2 edges are both lines or arcs/circles and have the same
|
||||
# points - inspired by Yorik's function isSameLine"""
|
||||
# if not (isinstance(e1.Curve, Part.Line) or isinstance(e1.Curve, Part.Circle)):
|
||||
# return False
|
||||
# if not (isinstance(e2.Curve, Part.Line) or isinstance(e2.Curve, Part.Circle)):
|
||||
# return False
|
||||
# if type(e1.Curve) != type(e2.Curve):
|
||||
# return False
|
||||
# if isinstance(e1.Curve, Part.Line):
|
||||
# if (DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[0].Point)) and \
|
||||
# (DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[-1].Point)):
|
||||
# return True
|
||||
# elif (DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[0].Point)) and \
|
||||
# (DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[-1].Point)):
|
||||
# return True
|
||||
# if isinstance(e1.Curve, Part.Circle):
|
||||
# center = False
|
||||
# radius = False
|
||||
# endpts = False
|
||||
# if e1.Curve.Center == e2.Curve.Center:
|
||||
# center = True
|
||||
# if e1.Curve.Radius == e2.Curve.Radius:
|
||||
# radius = True
|
||||
# if (DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[0].Point)) and \
|
||||
# (DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[-1].Point)):
|
||||
# endpts = True
|
||||
# elif (DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[0].Point)) and \
|
||||
# (DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[-1].Point)):
|
||||
# endpts = True
|
||||
# if (center and radius and endpts):
|
||||
# return True
|
||||
# return False
|
||||
|
||||
|
||||
def segments(poly):
|
||||
''' A sequence of (x,y) numeric coordinates pairs '''
|
||||
return zip(poly, poly[1:] + [poly[0]])
|
||||
|
||||
# def is_clockwise(obj):
|
||||
# '''tests if a wire or Path is clockwise'''
|
||||
# sum = 0
|
||||
# if isinstance(obj, Part.Wire):
|
||||
# for first, second in itertools.izip(obj.Edges, obj.Edges[1:]):
|
||||
# sum = (second.Vertexes[0].X - first.Vertexes[0].X) * (second.Vertexes[0].Y + first.Vertexes[0].Y)
|
||||
# sum += (obj.Edges[0].Vertexes[0].X - obj.Edges[-1].Vertexes[0].X) * (obj.Edges[0].Vertexes[0].Y + obj.Edges[-1].Vertexes[0].Y)
|
||||
# elif isinstance(obj, Path.Path):
|
||||
# movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03']
|
||||
|
||||
# lastLocation = {'Y': 0, 'X': 0, 'Z': 0.0}
|
||||
# currLocation = {'Y': 0, 'X': 0, 'Z': 0.0}
|
||||
# sum = 0
|
||||
|
||||
# for curCommand in obj.Commands:
|
||||
|
||||
# if curCommand.Name in movecommands:
|
||||
# lastLocation.update(currLocation)
|
||||
# currLocation.update(curCommand.Parameters)
|
||||
# sum += (currLocation["X"] - lastLocation["X"]) * (currLocation["Y"] + lastLocation["Y"])
|
||||
# sum += (0 - lastLocation["X"]) * (0 + lastLocation["Y"])
|
||||
|
||||
# return sum >= 0
|
||||
|
||||
def loopdetect(obj, edge1, edge2):
|
||||
'''
|
||||
Returns a loop wire that includes the two edges.
|
||||
Useful for detecting boundaries of negative space features ie 'holes'
|
||||
If a unique loop is not found, returns None
|
||||
edge1 = edge
|
||||
edge2 = edge
|
||||
'''
|
||||
|
||||
PathLog.track()
|
||||
candidates = []
|
||||
for wire in obj.Shape.Wires:
|
||||
for e in wire.Edges:
|
||||
@@ -191,18 +115,6 @@ def loopdetect(obj, edge1, edge2):
|
||||
loopwire = next(x for x in loop)[1]
|
||||
return loopwire
|
||||
|
||||
|
||||
# def check_clockwise(poly):
|
||||
# '''
|
||||
# check_clockwise(poly) a function for returning a boolean if the selected wire is clockwise or counter clockwise
|
||||
# based on point order. poly = [(x1,y1),(x2,y2),(x3,y3)]
|
||||
# '''
|
||||
# clockwise = False
|
||||
# if (sum(x0 * y1 - x1 * y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:
|
||||
# clockwise = not clockwise
|
||||
# return clockwise
|
||||
|
||||
|
||||
def filterArcs(arcEdge):
|
||||
'''filterArcs(Edge) -used to split arcs that over 180 degrees. Returns list '''
|
||||
s = arcEdge
|
||||
@@ -254,240 +166,6 @@ def reverseEdge(e):
|
||||
|
||||
return newedge
|
||||
|
||||
# def edge_to_path(lastpt, edge, Z, hf=2.0):
|
||||
# if isinstance(edge.Curve, Part.Circle):
|
||||
# # FreeCAD.Console.PrintMessage("arc\n")
|
||||
# arcstartpt = edge.valueAt(edge.FirstParameter)
|
||||
# midpt = edge.valueAt(
|
||||
# (edge.FirstParameter + edge.LastParameter) * 0.5)
|
||||
# arcendpt = edge.valueAt(edge.LastParameter)
|
||||
# # arcchkpt = edge.valueAt(edge.LastParameter * .99)
|
||||
|
||||
# if DraftVecUtils.equals(lastpt, arcstartpt):
|
||||
# startpt = arcstartpt
|
||||
# endpt = arcendpt
|
||||
# else:
|
||||
# startpt = arcendpt
|
||||
# endpt = arcstartpt
|
||||
# center = edge.Curve.Center
|
||||
# relcenter = center.sub(lastpt)
|
||||
# # FreeCAD.Console.PrintMessage("arc startpt= " + str(startpt)+ "\n")
|
||||
# # FreeCAD.Console.PrintMessage("arc midpt= " + str(midpt)+ "\n")
|
||||
# # FreeCAD.Console.PrintMessage("arc endpt= " + str(endpt)+ "\n")
|
||||
# arc_cw = check_clockwise(
|
||||
# [(startpt.x, startpt.y), (midpt.x, midpt.y), (endpt.x, endpt.y)])
|
||||
# # FreeCAD.Console.PrintMessage("arc_cw="+ str(arc_cw)+"\n")
|
||||
# if arc_cw:
|
||||
# output = "G2"
|
||||
# else:
|
||||
# output = "G3"
|
||||
# output += " X" + str(fmt(endpt.x)) + " Y" + \
|
||||
# str(fmt(endpt.y)) + " Z" + str(fmt(Z)) + " F" + str(hf)
|
||||
# output += " I" + str(fmt(relcenter.x)) + " J" + \
|
||||
# str(fmt(relcenter.y)) + " K" + str(fmt(relcenter.z))
|
||||
# output += "\n"
|
||||
# lastpt = endpt
|
||||
# # FreeCAD.Console.PrintMessage("last pt arc= " + str(lastpt)+ "\n")
|
||||
# else:
|
||||
# point = edge.Vertexes[-1].Point
|
||||
# if DraftVecUtils.equals(point, lastpt): # edges can come flipped
|
||||
# point = edge.Vertexes[0].Point
|
||||
# output = "G1 X" + str(fmt(point.x)) + " Y" + str(fmt(point.y)) + \
|
||||
# " Z" + str(fmt(Z)) + " F" + str(hf) + "\n"
|
||||
# lastpt = point
|
||||
# # FreeCAD.Console.PrintMessage("line\n")
|
||||
# # FreeCAD.Console.PrintMessage("last pt line= " + str(lastpt)+ "\n")
|
||||
# return lastpt, output
|
||||
|
||||
|
||||
# def convert(toolpath, Z=0.0, PlungeAngle=90.0, Zprevious=None, StopLength=None, vf=1.0, hf=2.0) :
|
||||
# '''convert(toolpath,Z=0.0,vf=1.0,hf=2.0,PlungeAngle=90.0,Zprevious=None,StopLength=None) Converts lines and arcs to G1,G2,G3 moves. Returns a string.'''
|
||||
|
||||
# if PlungeAngle != 90.0:
|
||||
# if Zprevious is None:
|
||||
# raise Exception("Cannot use PlungeAngle != 90.0 degrees without parameter Zprevious")
|
||||
# tanA = math.tan(math.pi * PlungeAngle / 180.0)
|
||||
# minA = (Zprevious - Z) / sum(edge.Length for edge in toolpath)
|
||||
# if tanA < minA:
|
||||
# tanA = minA
|
||||
# #FreeCAD.Console.PrintMessage('Increasing ramp angle to {0} degrees, to be able to make a full round\n'.format(math.atan(tanA) * 180.0 / math.pi))
|
||||
# else:
|
||||
# Zprevious = Z
|
||||
|
||||
# lastpt = None
|
||||
# output = ""
|
||||
# path_length = 0.0
|
||||
# Z_cur = Zprevious
|
||||
|
||||
# # create the path from the offset shape
|
||||
# for edge in toolpath:
|
||||
# if not lastpt:
|
||||
# # set the first point
|
||||
# lastpt = edge.Vertexes[0].Point
|
||||
# # FreeCAD.Console.PrintMessage("last pt= " + str(lastpt)+ "\n")
|
||||
# output += "G1 X" + str(fmt(lastpt.x)) + " Y" + str(fmt(lastpt.y)) + \
|
||||
# " Z" + str(fmt(Z_cur)) + " F" + str(vf) + "\n"
|
||||
|
||||
# if StopLength:
|
||||
# if path_length + edge.Length > StopLength:
|
||||
# # have to split current edge in two
|
||||
# t0 = edge.FirstParameter
|
||||
# t1 = edge.LastParameter
|
||||
# dL = StopLength - path_length
|
||||
# t = t0 + (t1 - t0) * dL / edge.Length
|
||||
# assert(t0 < t < t1)
|
||||
# edge = edge.split(t).Edges[0]
|
||||
# path_length = StopLength
|
||||
# else:
|
||||
# path_length += edge.Length
|
||||
# else:
|
||||
# path_length += edge.Length
|
||||
|
||||
# if Z_cur > Z:
|
||||
# Z_next = Zprevious - path_length * tanA
|
||||
# if Z_next < Z:
|
||||
# # have to split current edge in two
|
||||
# t0 = edge.FirstParameter
|
||||
# t1 = edge.LastParameter
|
||||
# dZ = Z_cur - Z
|
||||
# t = t0 + (t1 - t0) * (dZ / tanA) / edge.Length
|
||||
# assert(t0 < t < t1)
|
||||
# subwire = edge.split(t)
|
||||
# assert(len(subwire.Edges) == 2)
|
||||
# Z_cur = Z
|
||||
# lastpt, codes = edge_to_path(lastpt, subwire.Edges[0], Z_cur, hf)
|
||||
# output += codes
|
||||
# edge = subwire.Edges[1]
|
||||
# else:
|
||||
# Z_cur = Z_next
|
||||
|
||||
# lastpt, codes = edge_to_path(lastpt, edge, Z_cur, hf)
|
||||
# output += codes
|
||||
|
||||
# if StopLength:
|
||||
# if path_length >= StopLength:
|
||||
# break
|
||||
|
||||
# return output
|
||||
|
||||
# def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5):
|
||||
# '''SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5) Sorts the wire and reverses it, if needed. Splits arcs over 180 degrees in two. Returns the reordered offset of the wire. '''
|
||||
# if firstedge:
|
||||
# edgelist = wire.Edges[:]
|
||||
# if wire.isClosed():
|
||||
# elindex = None
|
||||
# n = 0
|
||||
# for e in edgelist:
|
||||
# if isSameEdge(e, firstedge):
|
||||
# # FreeCAD.Console.PrintMessage('found first edge\n')
|
||||
# elindex = n
|
||||
# n = n + 1
|
||||
# l1 = edgelist[:elindex]
|
||||
# l2 = edgelist[elindex:]
|
||||
# newedgelist = l2 + l1
|
||||
|
||||
# if clockwise:
|
||||
# newedgelist.reverse()
|
||||
# last = newedgelist.pop(-1)
|
||||
# newedgelist.insert(0, last)
|
||||
|
||||
# preoffset = []
|
||||
# for e in newedgelist:
|
||||
# if clockwise:
|
||||
# r = reverseEdge(e)
|
||||
# preoffset.append(r)
|
||||
# else:
|
||||
# preoffset.append(e)
|
||||
|
||||
# sortedpreoff = Part.__sortEdges__(preoffset)
|
||||
# wire = Part.Wire(sortedpreoff)
|
||||
# #wire = findWires(sortedpreoff)[0]
|
||||
# else:
|
||||
# sortedpreoff = Part.__sortEdges__(edgelist)
|
||||
# wire = Part.Wire(sortedpreoff)
|
||||
# #wire = findWires(sortedpreoff)[0]
|
||||
|
||||
# edgelist = []
|
||||
# for e in wire.Edges:
|
||||
# if geomType(e) == "Circle":
|
||||
# arclist = filterArcs(e)
|
||||
# for a in arclist:
|
||||
# edgelist.append(a)
|
||||
# elif geomType(e) == "LineSegment":
|
||||
# edgelist.append(e)
|
||||
# elif geomType(e) == "BSplineCurve" or \
|
||||
# geomType(e) == "BezierCurve" or \
|
||||
# geomType(e) == "Ellipse":
|
||||
# edgelist.append(Part.Wire(curvetowire(e, (SegLen))))
|
||||
# #newwire = Part.Wire(edgelist)
|
||||
# sortededges = Part.__sortEdges__(edgelist)
|
||||
# newwire = findWires(sortededges)[0]
|
||||
|
||||
# if is_clockwise(newwire) is not clockwise:
|
||||
# newwire.reverse()
|
||||
|
||||
# if Side == 'Left':
|
||||
# # we use the OCC offset feature
|
||||
# offset = newwire.makeOffset(radius) # tool is outside line
|
||||
# elif Side == 'Right':
|
||||
# offset = newwire.makeOffset(-radius) # tool is inside line
|
||||
# else:
|
||||
# if wire.isClosed():
|
||||
# offset = newwire.makeOffset(0.0)
|
||||
# else:
|
||||
# offset = newwire
|
||||
# offset.reverse()
|
||||
|
||||
# return offset
|
||||
|
||||
|
||||
# def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart,
|
||||
# ZFinalDepth, firstedge=None, PathClosed=True, SegLen=0.5,
|
||||
# VertFeed=1.0, HorizFeed=2.0, VertJog=1.0, HorizJog = 2.0, PlungeAngle=90.0):
|
||||
# ''' makes the path - just a simple profile for now '''
|
||||
# offset = SortPath(wire, Side, radius, clockwise, firstedge, SegLen=SegLen)
|
||||
# if len(offset.Edges) == 0:
|
||||
# return ""
|
||||
|
||||
# toolpath = offset.Edges[:]
|
||||
# paths = ""
|
||||
# paths += "G0 Z" + str(ZClearance) + "F " + fmt(VertJog) + "\n"
|
||||
# first = toolpath[0].Vertexes[0].Point
|
||||
# paths += "G0 X" + str(fmt(first.x)) + "Y" + str(fmt(first.y)) + "F " + fmt(HorizJog) + "\n"
|
||||
# Zprevious = ZStart
|
||||
# ZCurrent = ZStart - StepDown
|
||||
|
||||
# while ZCurrent > ZFinalDepth:
|
||||
# paths += convert(toolpath, Z=ZCurrent, Zprevious=Zprevious, PlungeAngle=PlungeAngle,
|
||||
# vf=VertFeed, hf=HorizFeed)
|
||||
# if not PathClosed:
|
||||
# paths += "G0 Z" + str(ZClearance) + "F " + fmt(VertJog)
|
||||
# paths += "G0 X" + str(fmt(first.x)) + "Y" + \
|
||||
# str(fmt(first.y)) + "F " + fmt(HorizJog) + "\n"
|
||||
# Zprevious = ZCurrent
|
||||
# ZCurrent = ZCurrent - abs(StepDown)
|
||||
|
||||
# # do the final Z value
|
||||
# paths += convert(toolpath, Z=ZFinalDepth, Zprevious=Zprevious, PlungeAngle=PlungeAngle,
|
||||
# vf=VertFeed, hf=HorizFeed)
|
||||
|
||||
# # when plunging with != 90 degree we have to do one last pass to clear the remaining ramp
|
||||
# if PlungeAngle != 90.0:
|
||||
# tanA = math.tan(math.pi * PlungeAngle / 180.0)
|
||||
# if tanA <= 0.0:
|
||||
# StopLength=None
|
||||
# else:
|
||||
# StopLength=abs(StepDown/tanA)
|
||||
# paths += convert(toolpath, Z=ZFinalDepth, Zprevious=Zprevious, StopLength=StopLength,
|
||||
# vf=VertFeed, hf=HorizFeed)
|
||||
|
||||
# paths += "G0 Z" + str(ZClearance) + "F " + fmt(VertJog) + "\n"
|
||||
# return paths
|
||||
|
||||
# the next two functions are for automatically populating tool
|
||||
# numbers/height offset numbers based on previously active toolnumbers
|
||||
|
||||
|
||||
def changeTool(obj, job):
|
||||
tlnum = 0
|
||||
for p in job.Group:
|
||||
@@ -503,52 +181,10 @@ def changeTool(obj, job):
|
||||
if g == obj:
|
||||
return tlnum
|
||||
|
||||
|
||||
def getLastToolLoad(obj):
|
||||
# This walks up the hierarchy and tries to find the closest preceding
|
||||
# toolchange.
|
||||
|
||||
import PathScripts
|
||||
tc = None
|
||||
lastfound = None
|
||||
|
||||
try:
|
||||
child = obj
|
||||
parent = obj.InList[0]
|
||||
except:
|
||||
parent = None
|
||||
|
||||
while parent is not None:
|
||||
if hasattr(parent, 'Group'):
|
||||
sibs = parent.Group
|
||||
for g in sibs:
|
||||
if hasattr(g, 'Proxy'):
|
||||
if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool):
|
||||
lastfound = g
|
||||
if g == child:
|
||||
tc = lastfound
|
||||
|
||||
if tc is None:
|
||||
try:
|
||||
child = parent
|
||||
parent = parent.InList[0]
|
||||
except:
|
||||
parent = None
|
||||
else:
|
||||
return tc
|
||||
|
||||
if tc is None:
|
||||
for g in FreeCAD.ActiveDocument.Objects: # top level object
|
||||
try:
|
||||
if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool):
|
||||
lastfound = g
|
||||
if g == obj:
|
||||
tc = lastfound
|
||||
except:
|
||||
continue
|
||||
return tc
|
||||
|
||||
def getToolControllers(obj):
|
||||
'''returns all the tool controllers'''
|
||||
|
||||
PathLog.track()
|
||||
controllers = []
|
||||
try:
|
||||
parent = obj.InList[0]
|
||||
@@ -567,6 +203,7 @@ def findToolController(obj, name=""):
|
||||
If no name is specified, returns the first controller.
|
||||
if no controller is found, returns None'''
|
||||
|
||||
PathLog.track()
|
||||
controllers = getToolControllers(obj)
|
||||
for c in controllers:
|
||||
if c.Label == name:
|
||||
@@ -578,6 +215,7 @@ def findToolController(obj, name=""):
|
||||
|
||||
def findParentJob(obj):
|
||||
'''retrieves a parent job object for an operation or other Path object'''
|
||||
PathLog.track()
|
||||
for i in obj.InList:
|
||||
if isinstance(i.Proxy, PathScripts.PathJob.ObjectPathJob):
|
||||
return i
|
||||
@@ -587,15 +225,9 @@ def findParentJob(obj):
|
||||
return grandParent
|
||||
return None
|
||||
|
||||
def getTool(obj, number=0):
|
||||
"retrieves a tool from a hosting object with a tooltable, if any"
|
||||
job = findParentJob(obj)
|
||||
if job is not None:
|
||||
return job.Tooltable.getTool(number)
|
||||
return None
|
||||
|
||||
def GetJobs(jobname = None):
|
||||
'''returns all jobs in the current document. If name is given, returns that job'''
|
||||
PathLog.track()
|
||||
jobs = []
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if "Proxy" in o.PropertiesList:
|
||||
@@ -608,6 +240,10 @@ def GetJobs(jobname = None):
|
||||
return jobs
|
||||
|
||||
def addToJob(obj, jobname = None):
|
||||
'''adds a path object to a job
|
||||
obj = obj
|
||||
jobname = None'''
|
||||
PathLog.track()
|
||||
if jobname is not None:
|
||||
jobs = GetJobs(jobname)
|
||||
if len(jobs) == 1:
|
||||
@@ -615,7 +251,6 @@ def addToJob(obj, jobname = None):
|
||||
else:
|
||||
FreeCAD.Console.PrintError("Didn't find the job")
|
||||
return None
|
||||
|
||||
else:
|
||||
jobs = GetJobs()
|
||||
if len(jobs) == 0:
|
||||
@@ -640,16 +275,6 @@ def addToJob(obj, jobname = None):
|
||||
job.Group = g
|
||||
return job
|
||||
|
||||
# def getLastZ(obj):
|
||||
# ''' find the last z value in the job '''
|
||||
# lastZ = ""
|
||||
# for g in obj.Group:
|
||||
# for c in g.Path.Commands:
|
||||
# for n in c.Parameters:
|
||||
# if n == 'Z':
|
||||
# lastZ = c.Parameters['Z']
|
||||
# return lastZ
|
||||
|
||||
def rapid(x=None, y=None, z=None):
|
||||
""" Returns gcode string to perform a rapid move."""
|
||||
retstr = "G00"
|
||||
|
||||
Reference in New Issue
Block a user