From ad52e41ece6046c66a93c61c68c9c0ac92acc14f Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 16 Jun 2017 11:06:02 -0500 Subject: [PATCH] Path: cleanup and add collision --- src/Mod/Path/PathScripts/PathCollision.py | 132 +++++++++++++++++++++ src/Mod/Path/PathScripts/PathContour.py | 46 ++++---- src/Mod/Path/PathScripts/PathSanity.py | 134 +++++++++++++++------- src/Mod/Path/PathScripts/PathUtils.py | 40 ------- 4 files changed, 247 insertions(+), 105 deletions(-) create mode 100644 src/Mod/Path/PathScripts/PathCollision.py diff --git a/src/Mod/Path/PathScripts/PathCollision.py b/src/Mod/Path/PathScripts/PathCollision.py new file mode 100644 index 0000000000..29fffb5b88 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathCollision.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * 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 + + + diff --git a/src/Mod/Path/PathScripts/PathContour.py b/src/Mod/Path/PathScripts/PathContour.py index 655886a1d8..774c1b8dbb 100644 --- a/src/Mod/Path/PathScripts/PathContour.py +++ b/src/Mod/Path/PathScripts/PathContour.py @@ -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: diff --git a/src/Mod/Path/PathScripts/PathSanity.py b/src/Mod/Path/PathScripts/PathSanity.py index 77fb662381..6da87c69ff 100644 --- a/src/Mod/Path/PathScripts/PathSanity.py +++ b/src/Mod/Path/PathScripts/PathSanity.py @@ -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 diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index a03408720c..0626af8c61 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -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