Path: cleanup and add collision
This commit is contained in:
132
src/Mod/Path/PathScripts/PathCollision.py
Normal file
132
src/Mod/Path/PathScripts/PathCollision.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 sliptonic <shopinthewoods@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 PathScripts.PathLog as PathLog
|
||||
from PySide import QtCore
|
||||
from PathScripts.PathUtils import waiting_effects
|
||||
|
||||
LOG_MODULE = 'PathCollision'
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
|
||||
PathLog.trackModule('PathCollision')
|
||||
FreeCAD.setLogLevel('Path.Area', 0)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
# from PySide import QtGui
|
||||
|
||||
|
||||
# Qt tanslation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
__title__ = "Path Collision Utility"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
|
||||
"""Path Collision object and FreeCAD command"""
|
||||
|
||||
|
||||
class _CollisionSim:
|
||||
def __init__(self, obj):
|
||||
#obj.addProperty("App::PropertyLink", "Original", "reference", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base object this collision refers to"))
|
||||
obj.Proxy = self
|
||||
|
||||
def execute(self, fp):
|
||||
"'''Do something when doing a recomputation, this method is mandatory'''"
|
||||
pass
|
||||
|
||||
|
||||
class _ViewProviderCollisionSim:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
vobj.addProperty("App::PropertyLink", "Original", "reference", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base object this collision refers to"))
|
||||
|
||||
def attach(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
return
|
||||
|
||||
def setEdit(self, vobj, mode=0):
|
||||
return True
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/Path-Contour.svg"
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
def onDelete(self, feature, subelements):
|
||||
feature.Original.ViewObject.Visibility = True
|
||||
return True
|
||||
|
||||
|
||||
def __compareBBSpace(bb1, bb2):
|
||||
if (bb1.XMin == bb2.XMin and
|
||||
bb1.XMax == bb2.XMax and
|
||||
bb1.YMin == bb2.YMin and
|
||||
bb1.YMax == bb2.YMax and
|
||||
bb1.ZMin == bb2.ZMin and
|
||||
bb1.ZMax == bb2.ZMax):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@waiting_effects
|
||||
def getCollisionObject(baseobject, simobject):
|
||||
result = None
|
||||
cVol = baseobject.Shape.common(simobject)
|
||||
if cVol.Volume > 1e-12:
|
||||
baseColor = (0.800000011920929, 0.800000011920929, 0.800000011920929, 00.0)
|
||||
intersecColor = (1.0, 0.0, 0.0, 0.0)
|
||||
colorassignment = []
|
||||
gougedShape = baseobject.Shape.cut(simobject)
|
||||
|
||||
for idx, i in enumerate(gougedShape.Faces):
|
||||
match = False
|
||||
for jdx, j in enumerate(cVol.Faces):
|
||||
if __compareBBSpace(i.BoundBox, j.BoundBox):
|
||||
match = True
|
||||
if match is True:
|
||||
# print ("Need to highlight Face{}".format(idx+1))
|
||||
colorassignment.append(intersecColor)
|
||||
else:
|
||||
colorassignment.append(baseColor)
|
||||
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Collision")
|
||||
obj.Shape = gougedShape
|
||||
_CollisionSim(obj)
|
||||
_ViewProviderCollisionSim(obj.ViewObject)
|
||||
|
||||
obj.ViewObject.DiffuseColor = colorassignment
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
baseobject.ViewObject.Visibility = False
|
||||
obj.ViewObject.Original = baseobject
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class ObjectContour:
|
||||
obj.SafeHeight = 8.0
|
||||
|
||||
@waiting_effects
|
||||
def _buildPathArea(self, obj, baseobject, start=None):
|
||||
def _buildPathArea(self, obj, baseobject, start=None, getsim=False):
|
||||
PathLog.track()
|
||||
profile = Path.Area()
|
||||
profile.setPlane(makeWorkplane(baseobject))
|
||||
@@ -173,26 +173,27 @@ class ObjectContour:
|
||||
PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector))
|
||||
self.endVector = end_vector
|
||||
|
||||
# if True:
|
||||
# from PathScripts.PathUtils import CollisionTester
|
||||
# parentJob = PathUtils.findParentJob(obj)
|
||||
# if parentJob is None:
|
||||
# pass
|
||||
# base = parentJob.Base
|
||||
# if base is None:
|
||||
# pass
|
||||
simobj = None
|
||||
if getsim:
|
||||
#from PathScripts.PathUtils import CollisionTester
|
||||
parentJob = PathUtils.findParentJob(obj)
|
||||
if parentJob is None:
|
||||
pass
|
||||
base = parentJob.Base
|
||||
if base is None:
|
||||
pass
|
||||
|
||||
# profileparams['Thicken'] = True #{'Fill':0, 'Coplanar':0, 'Project':True, 'SectionMode':2, 'Thicken':True}
|
||||
# profileparams['ToolRadius']= self.radius - self.radius *.005
|
||||
# profile.setParams(**profileparams)
|
||||
# sec = profile.makeSections(heights=[0.0])[0].getShape()
|
||||
# cutPath = sec.extrude(FreeCAD.Vector(0,0,baseobject.BoundBox.ZMax))
|
||||
# c = CollisionTester()
|
||||
# c.getCollisionSim(base.Shape, cutPath)
|
||||
profileparams['Thicken'] = True #{'Fill':0, 'Coplanar':0, 'Project':True, 'SectionMode':2, 'Thicken':True}
|
||||
profileparams['ToolRadius']= self.radius - self.radius *.005
|
||||
profile.setParams(**profileparams)
|
||||
sec = profile.makeSections(heights=[0.0])[0].getShape()
|
||||
simobj = sec.extrude(FreeCAD.Vector(0,0,baseobject.BoundBox.ZMax))
|
||||
#c = CollisionTester()
|
||||
#simobj = c.getCollisionSim(base.Shape, cutPath)
|
||||
|
||||
return pp
|
||||
return pp, simobj
|
||||
|
||||
def execute(self, obj):
|
||||
def execute(self, obj, getsim=False):
|
||||
PathLog.track()
|
||||
self.endVector = None
|
||||
|
||||
@@ -249,7 +250,8 @@ class ObjectContour:
|
||||
thickness = baseobject.Group[0].Source.Thickness
|
||||
contourshape = f.extrude(FreeCAD.Vector(0, 0, thickness))
|
||||
try:
|
||||
commandlist.extend(self._buildPathArea(obj, contourshape, start=obj.StartPoint).Commands)
|
||||
(pp, sim) = self._buildPathArea(obj, contourshape, start=obj.StartPoint, getsim=getsim)
|
||||
commandlist.extend(pp.Commands)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(e)
|
||||
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
|
||||
@@ -258,7 +260,8 @@ class ObjectContour:
|
||||
bb = baseobject.Shape.BoundBox
|
||||
env = PathUtils.getEnvelope(partshape=baseobject.Shape, subshape=None, stockheight=bb.ZLength + (obj.StartDepth.Value-bb.ZMax))
|
||||
try:
|
||||
commandlist.extend(self._buildPathArea(obj, env, start=obj.StartPoint).Commands)
|
||||
(pp, sim) = self._buildPathArea(obj, env, start=obj.StartPoint,getsim=getsim)
|
||||
commandlist.extend(pp.Commands)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(e)
|
||||
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
|
||||
@@ -268,8 +271,9 @@ class ObjectContour:
|
||||
|
||||
path = Path.Path(commandlist)
|
||||
obj.Path = path
|
||||
if obj.ViewObject:
|
||||
if obj.ViewObject:
|
||||
obj.ViewObject.Visibility = True
|
||||
return sim
|
||||
|
||||
|
||||
class _ViewProviderContour:
|
||||
|
||||
@@ -29,40 +29,16 @@ from __future__ import print_function
|
||||
from PySide import QtCore, QtGui
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import PathScripts.PathUtils as PU
|
||||
import PathScripts
|
||||
import PathScripts.PathCollision as PC
|
||||
# Qt tanslation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
def review(obj):
|
||||
"checks the selected job for common errors"
|
||||
toolcontrolcount = 0
|
||||
|
||||
for item in obj.Group:
|
||||
print("Checking: " + item.Label)
|
||||
if hasattr(item, 'Tool') and hasattr(item, 'SpindleDir'):
|
||||
toolcontrolcount += 1
|
||||
if item.ToolNumber == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " is using ID 0 which the undefined default. Please set a real tool.\n"))
|
||||
else:
|
||||
tool = item.Tool
|
||||
if tool is None:
|
||||
FreeCAD.Console.PrintError(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " is using tool: " + str(item.ToolNumber) + " which is invalid\n"))
|
||||
elif tool.Diameter == 0:
|
||||
FreeCAD.Console.PrintError(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " is using tool: " + str(item.ToolNumber) + " which has a zero diameter\n"))
|
||||
if item.HorizFeed == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the Horizontal feed rate\n"))
|
||||
if item.VertFeed == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the Vertical feed rate\n"))
|
||||
if item.SpindleSpeed == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the spindle speed\n"))
|
||||
|
||||
if toolcontrolcount == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "A Tool Controller was not found. Default values are used which is dangerous. Please add a Tool Controller.\n"))
|
||||
|
||||
|
||||
class CommandPathSanity:
|
||||
baseobj=None
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap' : 'Path-Sanity',
|
||||
@@ -70,26 +46,96 @@ class CommandPathSanity:
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Sanity","Check the Path Project for common errors")}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.Name[:3] == "Job":
|
||||
return True
|
||||
obj = FreeCADGui.Selection.getSelectionEx()[0].Object
|
||||
if (obj.TypeId == "Path::FeatureCompoundPython"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __review(self, obj):
|
||||
"checks the selected job for common errors"
|
||||
toolcontrolcount = 0
|
||||
operationcount = 0
|
||||
#global baseobj
|
||||
|
||||
# if obj.X_Max == obj.X_Min or obj.Y_Max == obj.Y_Min:
|
||||
# FreeCAD.Console.PrintWarning(translate("Path_Sanity", "It appears the machine limits haven't been set. Not able to check path extents.\n"))
|
||||
|
||||
if obj.PostProcessor == '':
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "A Postprocessor has not been selected.\n"))
|
||||
|
||||
if obj.PostProcessorOutputFile == '':
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "No output file is named. You'll be prompted during postprocessing.\n"))
|
||||
|
||||
for item in obj.Group:
|
||||
print("Checking: " + item.Label)
|
||||
if isinstance(item.Proxy, PathScripts.PathLoadTool.LoadTool):
|
||||
toolcontrolcount += 1
|
||||
self.__checkTC(item)
|
||||
|
||||
if isinstance(item.Proxy, PathScripts.PathContour.ObjectContour):
|
||||
if item.Active:
|
||||
operationcount +=1
|
||||
simobj = item.Proxy.execute(item, getsim=True)
|
||||
if simobj is not None:
|
||||
print ('collision detected')
|
||||
PC.getCollisionObject(self.baseobj, simobj)
|
||||
#r.original = self.baseobj
|
||||
|
||||
|
||||
if isinstance(item.Proxy, PathScripts.PathProfile.ObjectProfile):
|
||||
if item.Active:
|
||||
operationcount +=1
|
||||
|
||||
if isinstance(item.Proxy, PathScripts.PathProfileEdges.ObjectProfile):
|
||||
if item.Active:
|
||||
operationcount +=1
|
||||
|
||||
if isinstance(item.Proxy, PathScripts.PathPocket.ObjectPocket):
|
||||
if item.Active:
|
||||
operationcount +=1
|
||||
|
||||
if isinstance(item.Proxy, PathScripts.PathDrilling.ObjectDrilling):
|
||||
if item.Active:
|
||||
operationcount +=1
|
||||
|
||||
if isinstance(item.Proxy, PathScripts.PathMillFace.ObjectFace):
|
||||
if item.Active:
|
||||
operationcount +=1
|
||||
|
||||
if isinstance(item.Proxy, PathScripts.PathHelix.ObjectPathHelix):
|
||||
if item.Active:
|
||||
operationcount +=1
|
||||
|
||||
if isinstance(item.Proxy, PathScripts.PathSurface.ObjectSurface):
|
||||
if item.Active:
|
||||
operationcount +=1
|
||||
|
||||
if operationcount == 0: #no active operations
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "A Tool Controller was not found. Default values are used which is dangerous. Please add a Tool Controller.\n"))
|
||||
|
||||
if toolcontrolcount == 0: #need at least one active TC
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "A Tool Controller was not found. Default values are used which is dangerous. Please add a Tool Controller.\n"))
|
||||
|
||||
def __checkTC(self, item):
|
||||
if item.ToolNumber == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " is using ID 0 which the undefined default. Please set a real tool.\n"))
|
||||
if item.HorizFeed == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the Horizontal feed rate\n"))
|
||||
if item.VertFeed == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the Vertical feed rate\n"))
|
||||
if item.SpindleSpeed == 0:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the spindle speed\n"))
|
||||
|
||||
|
||||
def Activated(self):
|
||||
# check that the selection contains exactly what we want
|
||||
selection = FreeCADGui.Selection.getSelection()
|
||||
if len(selection) != 1:
|
||||
FreeCAD.Console.PrintError(translate("Path_Sanity", "Please select a path Project to check\n"))
|
||||
return
|
||||
if not(selection[0].TypeId == "Path::FeatureCompoundPython"):
|
||||
FreeCAD.Console.PrintError(translate("Path_Sanity", "Please select a path project to check\n"))
|
||||
return
|
||||
|
||||
#global baseobj
|
||||
# if everything is ok, execute
|
||||
FreeCADGui.addModule("PathScripts.PathSanity")
|
||||
FreeCADGui.doCommand('PathScripts.PathSanity.review(FreeCAD.ActiveDocument.' + selection[0].Name + ')')
|
||||
|
||||
obj = FreeCADGui.Selection.getSelectionEx()[0].Object
|
||||
self.baseobj = obj.Base
|
||||
if self.baseobj is None:
|
||||
FreeCAD.Console.PrintWarning(translate("Path_Sanity", "The Job has no selected Base object.\n"))
|
||||
return
|
||||
self.__review(obj)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
|
||||
@@ -754,43 +754,3 @@ class depth_params:
|
||||
return [stop] + depths.tolist()
|
||||
|
||||
|
||||
class CollisionTester:
|
||||
|
||||
def compareBBSpace(self, bb1, bb2):
|
||||
if (bb1.XMin == bb2.XMin and
|
||||
bb1.XMax == bb2.XMax and
|
||||
bb1.YMin == bb2.YMin and
|
||||
bb1.YMax == bb2.YMax and
|
||||
bb1.ZMin == bb2.ZMin and
|
||||
bb1.ZMax == bb2.ZMax):
|
||||
return True
|
||||
return False
|
||||
|
||||
def getCollisionSim(self, baseobject, cutterPath):
|
||||
|
||||
baseColor = (0.800000011920929, 0.800000011920929, 0.800000011920929, 00.0)
|
||||
intersecColor = (1.0, 0.0, 0.0, 0.0)
|
||||
cVol = baseobject.common(cutterPath)
|
||||
|
||||
if cVol.Volume > 1e-12:
|
||||
colorassignment = []
|
||||
gougedShape = baseobject.cut(cutterPath)
|
||||
# gougeSim = FreeCAD.ActiveDocument.addObject("Part::Feature","Gouge")
|
||||
# gougeSim.Shape = gougedShape
|
||||
|
||||
for idx, i in enumerate(gougedShape.Faces):
|
||||
match = False
|
||||
for jdx, j in enumerate(cVol.Faces):
|
||||
if self.compareBBSpace(i.BoundBox, j.BoundBox):
|
||||
match = True
|
||||
if match is True:
|
||||
# print ("Need to highlight Face{}".format(idx+1))
|
||||
colorassignment.append(intersecColor)
|
||||
else:
|
||||
colorassignment.append(baseColor)
|
||||
collisionSim = FreeCAD.ActiveDocument.addObject("Part::Feature", "Collision")
|
||||
collisionSim.Shape = gougedShape
|
||||
collisionSim.ViewObject.DiffuseColor = colorassignment
|
||||
return collisionSim
|
||||
else:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user