diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index cd3c112546..d6df197041 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -57,6 +57,7 @@ SET(PathScripts_SRCS PathScripts/PathPreferencesPathDressup.py PathScripts/PathPreferencesPathJob.py PathScripts/PathProfile.py + PathScripts/PathProfileGui.py PathScripts/PathProfileEdges.py PathScripts/PathSanity.py PathScripts/PathSelection.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index b192c40a6d..148d3e7a77 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -66,6 +66,7 @@ panels/PageHeightsEdit.ui panels/PageOpContourEdit.ui panels/PageOpPocketEdit.ui + panels/PageOpProfileEdit.ui panels/PocketEdit.ui panels/PointEdit.ui panels/ProfileEdgesEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpProfileEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpProfileEdit.ui new file mode 100644 index 0000000000..44f8fa3791 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpProfileEdit.ui @@ -0,0 +1,169 @@ + + + Form + + + + 0 + 0 + 446 + 419 + + + + Form + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Tool Controller + + + + + + + + + + + + + + + + Cut Side + + + + + + + + Outside + + + + + Inside + + + + + + + + Direction + + + + + + + + CW + + + + + CCW + + + + + + + + Extra Offset + + + + + + + + 0 + 0 + + + + + + + + + + + + + + Use Start Point + + + + + + + Process Holes + + + + + + + Use Compensation + + + + + + + Process Cirles + + + + + + + Process Perimeter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index be13d90c3f..2ea2bffebc 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -67,7 +67,7 @@ class PathWorkbench (Workbench): from PathScripts import PathPlane from PathScripts import PathPocketGui from PathScripts import PathPost - from PathScripts import PathProfile + from PathScripts import PathProfileGui from PathScripts import PathProfileEdges from PathScripts import PathSanity from PathScripts import PathSimpleCopy diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index 640c814338..a0f0df1ae6 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -44,15 +44,16 @@ PathLog.trackModule() def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -FeatureTool = 0x01 -FeatureDepths = 0x02 -FeatureHeights = 0x04 -FeatureStartPoint = 0x08 -FeatureBaseFaces = 0x10 -FeatureBaseEdges = 0x20 -FeatureFinishDepth = 0x40 +FeatureTool = 0x0001 +FeatureDepths = 0x0002 +FeatureHeights = 0x0004 +FeatureStartPoint = 0x0008 +FeatureFinishDepth = 0x0010 +FeatureBaseFaces = 0x1001 +FeatureBaseEdges = 0x1002 +FeatureBasePanels = 0x1002 -FeatureBaseGeometry = FeatureBaseFaces | FeatureBaseEdges +FeatureBaseGeometry = FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels class ObjectOp(object): @@ -160,13 +161,13 @@ class ObjectOp(object): self.opSetDefaultValues(obj) @waiting_effects - def _buildPathArea(self, obj, baseobject, start=None, getsim=False): + def _buildPathArea(self, obj, baseobject, isHole, start, getsim): PathLog.track() area = Path.Area() area.setPlane(makeWorkplane(baseobject)) area.add(baseobject) - areaParams = self.opAreaParams(obj) + areaParams = self.opAreaParams(obj, isHole) heights = [i for i in self.depthparams] PathLog.debug('depths: {}'.format(heights)) @@ -180,7 +181,7 @@ class ObjectOp(object): shapelist = [sec.getShape() for sec in sections] PathLog.debug("shapelist = %s" % shapelist) - pathParams = self.opPathParams(obj) + pathParams = self.opPathParams(obj, isHole) pathParams['shapes'] = shapelist pathParams['feedrate'] = self.horizFeed pathParams['feedrate_v'] = self.vertFeed @@ -259,9 +260,9 @@ class ObjectOp(object): shapes = self.opShapes(obj, commandlist) sims = [] - for shape in shapes: + for (shape, isHole) in shapes: try: - (pp, sim) = self._buildPathArea(obj, shape, start, getsim) + (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) commandlist.extend(pp.Commands) sims.append(sim) except Exception as e: diff --git a/src/Mod/Path/PathScripts/PathAreaOpGui.py b/src/Mod/Path/PathScripts/PathAreaOpGui.py index 32f0169fea..24385e816d 100644 --- a/src/Mod/Path/PathScripts/PathAreaOpGui.py +++ b/src/Mod/Path/PathScripts/PathAreaOpGui.py @@ -52,9 +52,7 @@ class ViewProvider(object): def __init__(self, vobj): PathLog.track() vobj.Proxy = self - - #for sel in FreeCADGui.Selection.getSelectionEx(): - # if sel[0].HasSubObjects: + self.deleteOnReject = True def attach(self, vobj): PathLog.track() @@ -190,6 +188,9 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): return self.supports & PathAreaOp.FeatureBaseEdges def supportsFaces(self): return self.supports & PathAreaOp.FeatureBaseFaces + def supportsPanels(self): + return self.supports & PathAreaOp.FeatureBasePanels + def featureName(self): if self.supportsEdges() and self.supportsFaces(): return 'features' @@ -200,19 +201,22 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): return 'nothing' def addBaseGeometry(self, selection): + PathLog.track(selection) if len(selection) != 1: PathLog.error(translate("PathProject", "Please select %s from a single solid" % self.featureName())) return False sel = selection[0] - if not sel.HasSubObjects: - PathLog.error(translate("PathProject", "Please select %s of a solid" % self.featureName())) - return False - if not self.supportsEdges() and selection[0].SubObjects[0].ShapeType == "Edge": - PathLog.error(translate("PathProject", "Please select only %s of a solid" % self.featureName())) - return False - if not self.supportsFaces() and selection[0].SubObjects[0].ShapeType == "Face": - PathLog.error(translate("PathProject", "Please select only %s of a solid" % self.featureName())) - return False + if sel.HasSubObjects: + if not self.supportsEdges() and selection[0].SubObjects[0].ShapeType == "Edge": + PathLog.error(translate("PathProject", "Please select only %s of a solid" % self.featureName())) + return False + if not self.supportsFaces() and selection[0].SubObjects[0].ShapeType == "Face": + PathLog.error(translate("PathProject", "Please select only %s of a solid" % self.featureName())) + return False + else: + if not self.supportsPanels() or not 'Panel' in sel.Object.Name: + PathLog.error(translate("PathProject", "Please select %s of a solid" % self.featureName())) + return False for sub in sel.SubElementNames: self.obj.Proxy.addBase(self.obj, sel.Object, sub) @@ -409,7 +413,7 @@ class TaskPanel(object): return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel) def setupUi(self): - PathLog.track() + PathLog.track(self.deleteOnReject) if self.deleteOnReject and PathAreaOp.FeatureBaseGeometry & self.obj.Proxy.opFeatures(self.obj): sel = FreeCADGui.Selection.getSelectionEx() diff --git a/src/Mod/Path/PathScripts/PathContour.py b/src/Mod/Path/PathScripts/PathContour.py index b7104fd285..fa6f01c324 100644 --- a/src/Mod/Path/PathScripts/PathContour.py +++ b/src/Mod/Path/PathScripts/PathContour.py @@ -123,12 +123,12 @@ class ObjectContour(PathAreaOp.ObjectOp): for shape in shapes: f = Part.makeFace([shape], 'Part::FaceMakerSimple') thickness = baseobject.Group[0].Source.Thickness - return [f.extrude(FreeCAD.Vector(0, 0, thickness))] + return [(f.extrude(FreeCAD.Vector(0, 0, thickness)), False)] if hasattr(baseobject, "Shape") and not isPanel: - return [PathUtils.getEnvelope(partshape=baseobject.Shape, subshape=None, depthparams=self.depthparams)] + return [(PathUtils.getEnvelope(partshape=baseobject.Shape, subshape=None, depthparams=self.depthparams), False)] - def opAreaParams(self, obj): + def opAreaParams(self, obj, isHole): params = {'Fill': 0, 'Coplanar': 2} if obj.UseComp is False: @@ -143,7 +143,7 @@ class ObjectContour(PathAreaOp.ObjectOp): params['MiterLimit'] = obj.MiterLimit return params - def opPathParams(self, obj): + def opPathParams(self, obj, isHole): params = {} if obj.Direction == 'CCW': params['orientation'] = 0 diff --git a/src/Mod/Path/PathScripts/PathContourGui.py b/src/Mod/Path/PathScripts/PathContourGui.py index f4311f7fb8..66ac30e36f 100644 --- a/src/Mod/Path/PathScripts/PathContourGui.py +++ b/src/Mod/Path/PathScripts/PathContourGui.py @@ -86,8 +86,6 @@ def Create(name): obj = PathContour.Create(name) vobj = ViewProviderContour(obj.ViewObject) - obj.ViewObject.Proxy.deleteOnReject = True - FreeCAD.ActiveDocument.commitTransaction() obj.ViewObject.startEditing() return obj diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 2de69e9496..664e72ccca 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -75,7 +75,7 @@ class ObjectPocket(PathAreaOp.ObjectOp): PathLog.warning("No job object found (%s), or job has no Base." % job) return None - def opAreaParams(self, obj): + def opAreaParams(self, obj, isHole): params = {} params['Fill'] = 0 params['Coplanar'] = 0 @@ -91,7 +91,7 @@ class ObjectPocket(PathAreaOp.ObjectOp): params['PocketMode'] = Pattern.index(obj.OffsetPattern) + 1 return params - def opPathParams(self, obj): + def opPathParams(self, obj, isHole): params = {} # if MinTravel is turned on, set path sorting to 3DSort @@ -123,13 +123,13 @@ class ObjectPocket(PathAreaOp.ObjectOp): env = PathUtils.getEnvelope(baseobject.Shape, subshape=shape, depthparams=self.depthparams) obj.removalshape = env.cut(baseobject.Shape) - removalshapes.append(obj.removalshape) + removalshapes.append((obj.removalshape, False)) else: # process the job base object as a whole PathLog.debug("processing the whole job base object") env = PathUtils.getEnvelope(baseobject.Shape, subshape=None, depthparams=self.depthparams) obj.removalshape = env.cut(baseobject.Shape) - removalshapes = [obj.removalshape] + removalshapes = [(obj.removalshape, False)] return removalshapes def opSetDefaultValues(self, obj): diff --git a/src/Mod/Path/PathScripts/PathPocketGui.py b/src/Mod/Path/PathScripts/PathPocketGui.py index aeb7ccbbbb..277c0c58f1 100644 --- a/src/Mod/Path/PathScripts/PathPocketGui.py +++ b/src/Mod/Path/PathScripts/PathPocketGui.py @@ -92,8 +92,6 @@ def Create(name): obj = PathPocket.Create(name) vobj = ViewProviderPocket(obj.ViewObject) - obj.ViewObject.Proxy.deleteOnReject = True - FreeCAD.ActiveDocument.commitTransaction() obj.ViewObject.startEditing() return obj diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 8738166f5d..5d9db8d8f6 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -22,24 +22,20 @@ # * * # *************************************************************************** -import FreeCAD -import Path -import numpy import ArchPanel +import FreeCAD import Part - -from PathScripts import PathUtils -from PathScripts.PathUtils import depth_params +import Path +import PathScripts.PathAreaOp as PathAreaOp import PathScripts.PathLog as PathLog +import PathScripts.PathUtils as PathUtils +import numpy -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule('PathProfile') -#FreeCAD.setLogLevel('Path.Area', 0) - -if FreeCAD.GuiUp: - import FreeCADGui - from PySide import QtCore, QtGui +from PathScripts.PathUtils import depth_params +from PySide import QtCore +PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) # Qt tanslation handling def translate(context, text, disambig=None): @@ -52,27 +48,9 @@ __url__ = "http://www.freecadweb.org" """Path Profile object and FreeCAD command""" -class ObjectProfile: +class ObjectProfile(PathAreaOp.ObjectOp): - def __init__(self, obj): - obj.addProperty("App::PropertyLinkSubList", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base geometry of this toolpath")) - 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")) - - # 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")) - - # Depth Properties - obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "The height needed to clear clamps and obstructions")) - obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Rapid Safety Height between locations.")) - obj.addProperty("App::PropertyDistance", "StepDown", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Incremental Step Down of Tool")) - obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Starting Depth of Tool- first cut depth in Z")) - obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Final Depth of Tool- lowest value in Z")) - - # 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")) + def initOperation(self, obj): # Profile Properties obj.addProperty("App::PropertyEnumeration", "Side", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Side of edge that tool should cut")) @@ -88,44 +66,35 @@ class ObjectProfile: obj.JoinType = ['Round', 'Square', 'Miter'] # this is the direction that the Contour runs obj.addProperty("App::PropertyFloat", "MiterLimit", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Maximum distance before a miter join is truncated")) - # Debug Parameters - obj.addProperty("App::PropertyString", "AreaParams", "Path") - obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyString", "PathParams", "Path") - obj.setEditorMode('PathParams', 2) # hide - obj.addProperty("Part::PropertyPartShape", "removalshape", "Path") - obj.setEditorMode('removalshape', 2) # hide - - if FreeCAD.GuiUp: - _ViewProviderProfile(obj.ViewObject) - obj.Proxy = self - def __getstate__(self): - return None + def opSetDefaultValues(self, obj): + obj.Side = "Outside" + obj.OffsetExtra = 0.0 + obj.Direction = "CW" + obj.UseComp = True + obj.processHoles = False + obj.processPerimeter = True + obj.JoinType = "Round" + obj.MiterLimit = 0.1 - def __setstate__(self, state): - return None - - def onChanged(self, obj, prop): + def onOpChanged(self, obj, prop): if prop == "UseComp": if not obj.UseComp: obj.setEditorMode('Side', 2) else: obj.setEditorMode('Side', 0) - if prop in ['AreaParams', 'PathParams', 'removalshape']: - obj.setEditorMode(prop, 2) - obj.setEditorMode('MiterLimit', 2) - if obj.JoinType == 'Miter': - obj.setEditorMode('MiterLimit', 0) + if prop == "JoinType": + obj.setEditorMode('MiterLimit', 2) + if obj.JoinType == 'Miter': + obj.setEditorMode('MiterLimit', 0) - def addprofilebase(self, obj, ss, sub=""): - baselist = obj.Base - if len(baselist) == 0: # When adding the first base object, guess at heights + if prop == 'Base' and len(obj.Base) == 1: try: - bb = ss.Shape.BoundBox # parent boundbox - subobj = ss.Shape.getElement(sub) + (base, sub) = obj.Base[0] + bb = base.BoundBox # parent boundbox + subobj = base.getElement(sub) fbb = subobj.BoundBox # feature boundbox obj.StartDepth = bb.ZMax obj.ClearanceHeight = bb.ZMax + 5.0 @@ -141,39 +110,44 @@ class ObjectProfile: obj.FinalDepth = fbb.ZMin else: # catch all obj.FinalDepth = bb.ZMin - except: + + if bb.XLength == fbb.XLength and bb.YLength == fbb.YLength: + obj.Side = "Outside" + else: + obj.Side = "Inside" + + except Exception as e: + PathLog.error(translate("PathPocket", "Error in calculating depths: %s" % e)) obj.StartDepth = 5.0 obj.ClearanceHeight = 10.0 obj.SafeHeight = 8.0 - - if bb.XLength == fbb.XLength and bb.YLength == fbb.YLength: obj.Side = "Outside" - else: - obj.Side = "Inside" - item = (ss, sub) - if item in baselist: - FreeCAD.Console.PrintWarning("this object already in the list" + "\n") - else: - baselist.append(item) - obj.Base = baselist - self.execute(obj) + def opFeatures(self, obj): + return PathAreaOp.FeatureTool | PathAreaOp.FeatureDepths | PathAreaOp.FeatureHeights | PathAreaOp.FeatureStartPoint | PathAreaOp.FeatureBaseFaces - def _buildPathArea(self, obj, baseobject, isHole=False, start=None, getsim=False): - PathLog.track() - profile = Path.Area() - profile.setPlane(Part.makeCircle(10)) - profile.add(baseobject) + def opUseProjection(self, obj): + return True - profileparams = {'Fill': 0, - 'Coplanar': 2, - 'Offset': 0.0, - 'SectionCount': -1} + def opShapeForDepths(self, obj): + job = PathUtils.findParentJob(obj) + if job and job.Base: + PathLog.debug("job=%s base=%s shape=%s" % (job, job.Base, job.Base.Shape)) + return job.Base.Shape + PathLog.warning("No job object found (%s), or job has no Base." % job) + return None + + def opAreaParams(self, obj, isHole): + params = {} + params['Fill'] = 0 + params['Coplanar'] = 2 + params['Offset'] = 0.0 + params['SectionCount'] = -1 offsetval = 0 if obj.UseComp: - offsetval = self.radius+obj.OffsetExtra.Value + offsetval = self.radius + obj.OffsetExtra.Value if obj.Side == 'Inside': offsetval = 0 - offsetval @@ -181,31 +155,17 @@ class ObjectProfile: if isHole: offsetval = 0 - offsetval - profileparams['Offset'] = offsetval + params['Offset'] = offsetval jointype = ['Round', 'Square', 'Miter'] - profileparams['JoinType'] = jointype.index(obj.JoinType) + params['JoinType'] = jointype.index(obj.JoinType) if obj.JoinType == 'Miter': - profileparams['MiterLimit'] = obj.MiterLimit + params['MiterLimit'] = obj.MiterLimit + return params - profile.setParams(**profileparams) - obj.AreaParams = str(profile.getParams()) - - # PathLog.debug("About to profile with params: {}".format(profileparams)) - PathLog.debug("About to profile with params: {}".format(profile.getParams())) - - heights = [i for i in self.depthparams] - - sections = profile.makeSections(mode=0, project=True, heights=heights) - shapelist = [sec.getShape() for sec in sections] - - params = {'shapes': shapelist, - 'feedrate': self.horizFeed, - 'feedrate_v': self.vertFeed, - 'verbose': True, - 'resume_height': obj.StepDown.Value, - 'retraction': obj.ClearanceHeight.Value} + def opPathParams(self, obj, isHole): + params = {} # Reverse the direction for holes if isHole: @@ -217,62 +177,10 @@ class ObjectProfile: params['orientation'] = 0 else: params['orientation'] = 1 + return params - if obj.UseStartPoint is True and obj.StartPoint is not None: - params['start'] = obj.StartPoint - - pp = Path.fromShapes(**params) - - obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'}) - - PathLog.debug("Generating Path with params: {}".format(params)) - PathLog.debug(pp) - - simobj = None - if getsim: - profileparams['Thicken'] = True - profileparams['ToolRadius'] = self.radius - self.radius * .005 - profile.setParams(**profileparams) - sec = profile.makeSections(mode=0, project=False, heights=heights)[-1].getShape() - simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) - - return pp, simobj - - def execute(self, obj, getsim=False): - import Part - - if not obj.Active: - path = Path.Path("(inactive operation)") - obj.Path = path - obj.ViewObject.Visibility = False - return - - self.depthparams = depth_params( - clearance_height=obj.ClearanceHeight.Value, - safe_height=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) - - 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 - else: - self.vertFeed = toolLoad.VertFeed.Value - self.horizFeed = toolLoad.HorizFeed.Value - self.vertRapid = toolLoad.VertRapid.Value - self.horizRapid = toolLoad.HorizRapid.Value - 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 + def opShapes(self, obj, commandlist): commandlist.append(Path.Command("(" + obj.Label + ")")) if obj.UseComp: @@ -280,13 +188,13 @@ class ObjectProfile: else: commandlist.append(Path.Command("(Uncompensated Tool Path)")) - parentJob = PathUtils.findParentJob(obj) - if parentJob is None: - return - baseobject = parentJob.Base - if baseobject is None: + job = PathUtils.findParentJob(obj) + if not job or not job.Base: return + baseobject = job.Base + shapes = [] + if obj.Base: # The user has selected subobjects from the base. Process each. holes = [] faces = [] @@ -306,408 +214,37 @@ class ObjectProfile: drillable = PathUtils.isDrillable(baseobject.Shape, wire) if (drillable and obj.processCircles) or (not drillable and obj.processHoles): env = PathUtils.getEnvelope(baseobject.Shape, subshape=f, depthparams=self.depthparams) - try: - (pp, sim) = self._buildPathArea(obj, baseobject=env, isHole=True, start=None, 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.") + shapes.append((env, True)) if len(faces) > 0: profileshape = Part.makeCompound(faces) if obj.processPerimeter: env = PathUtils.getEnvelope(baseobject.Shape, subshape=profileshape, depthparams=self.depthparams) - try: - (pp, sim) = self._buildPathArea(obj, baseobject=env, start=None, 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.") + shapes.append((env, False)) else: # Try to build targets from the job base if hasattr(baseobject, "Proxy"): if isinstance(baseobject.Proxy, ArchPanel.PanelSheet): # process the sheet if obj.processCircles or obj.processHoles: - shapes = baseobject.Proxy.getHoles(baseobject, transform=True) - for shape in shapes: + for shape in baseobject.Proxy.getHoles(baseobject, transform=True): for wire in shape.Wires: drillable = PathUtils.isDrillable(baseobject.Proxy, wire) if (drillable and obj.processCircles) or (not drillable and obj.processHoles): f = Part.makeFace(wire, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope(baseobject.Shape, subshape=f, depthparams=self.depthparams) - try: - (pp, sim) = self._buildPathArea(obj, baseobject=env, isHole=True, start=None, 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.") + shapes.append((env, True)) if obj.processPerimeter: - shapes = baseobject.Proxy.getOutlines(baseobject, transform=True) - for shape in shapes: + for shape in baseobject.Proxy.getOutlines(baseobject, transform=True): for wire in shape.Wires: f = Part.makeFace(wire, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope(baseobject.Shape, subshape=f, depthparams=self.depthparams) - try: - (pp, sim) = self._buildPathArea(obj, baseobject=env, isHole=False, start=None, 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.") + shapes.append((env, False)) - # Let's finish by rapid to clearance...just for safety - commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) + return shapes - path = Path.Path(commandlist) - obj.Path = path - - -class _ViewProviderProfile: - - def __init__(self, vobj): - vobj.Proxy = self - - def attach(self, vobj): - self.Object = vobj.Object - return - - def deleteObjectsOnReject(self): - return hasattr(self, 'deleteOnReject') and self.deleteOnReject - - def setEdit(self, vobj, mode=0): - FreeCADGui.Control.closeDialog() - taskd = TaskPanel(vobj.Object, self.deleteObjectsOnReject()) - taskd.obj = vobj.Object - FreeCADGui.Control.showDialog(taskd) - taskd.setupUi() - self.deleteOnReject = False - return True - - def getIcon(self): - return ":/icons/Path-Profile-Face.svg" - - def __getstate__(self): - return None - - def __setstate__(self, state): - return None - - -class _CommandSetStartPoint: - def GetResources(self): - return {'Pixmap': 'Path-StartPoint', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Pick Start Point"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Profile", "Pick Start Point")} - - def IsActive(self): - return FreeCAD.ActiveDocument is not None - - def setpoint(self, point, o): - obj = FreeCADGui.Selection.getSelection()[0] - obj.StartPoint.x = point.x - obj.StartPoint.y = point.y - - def Activated(self): - - FreeCADGui.Snapper.getPoint(callback=self.setpoint) - - -class CommandPathProfile: - def GetResources(self): - return {'Pixmap': 'Path-Profile-Face', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathProfile", "Face Profile"), - 'Accel': "P, F", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile based on face or faces")} - - def IsActive(self): - if FreeCAD.ActiveDocument is not None: - for o in FreeCAD.ActiveDocument.Objects: - if o.Name[:3] == "Job": - return True - return False - - def Activated(self): - ztop = 10.0 - zbottom = 0.0 - - FreeCAD.ActiveDocument.openTransaction(translate("Path", "Create a Profile")) - FreeCADGui.addModule("PathScripts.PathProfile") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Profile")') - FreeCADGui.doCommand('PathScripts.PathProfile.ObjectProfile(obj)') - - FreeCADGui.doCommand('obj.Active = True') - - FreeCADGui.doCommand('obj.ClearanceHeight = ' + str(ztop + 10.0)) - FreeCADGui.doCommand('obj.StepDown = 1.0') - FreeCADGui.doCommand('obj.StartDepth= ' + str(ztop)) - FreeCADGui.doCommand('obj.FinalDepth=' + str(zbottom)) - - FreeCADGui.doCommand('obj.SafeHeight = ' + str(ztop + 2.0)) - FreeCADGui.doCommand('obj.Side = "Outside"') - FreeCADGui.doCommand('obj.OffsetExtra = 0.0') - FreeCADGui.doCommand('obj.Direction = "CW"') - FreeCADGui.doCommand('obj.UseComp = True') - FreeCADGui.doCommand('obj.processHoles = False') - FreeCADGui.doCommand('obj.processPerimeter = True') - FreeCADGui.doCommand('obj.JoinType = "Round"') - FreeCADGui.doCommand('obj.MiterLimit =' + str(0.1)) - - FreeCADGui.doCommand('obj.ViewObject.Proxy.deleteOnReject = True') - FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') - FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)') - - FreeCAD.ActiveDocument.commitTransaction() - FreeCADGui.doCommand('obj.ViewObject.startEditing()') - - -class TaskPanel: - def __init__(self, obj, deleteOnReject): - FreeCAD.ActiveDocument.openTransaction(translate("Path_Profile", "Profile Operation")) - self.form = FreeCADGui.PySideUic.loadUi(":/panels/ProfileEdit.ui") - self.isDirty = True - self.deleteOnReject = deleteOnReject - self.obj = obj - - def accept(self): - FreeCADGui.Control.closeDialog() - FreeCADGui.ActiveDocument.resetEdit() - FreeCAD.ActiveDocument.commitTransaction() - FreeCADGui.Selection.removeObserver(self.s) - if self.isDirty: - FreeCAD.ActiveDocument.recompute() - - def reject(self): - FreeCADGui.Control.closeDialog() - FreeCADGui.ActiveDocument.resetEdit() - FreeCAD.ActiveDocument.abortTransaction() - FreeCADGui.Selection.removeObserver(self.s) - if self.deleteOnReject: - FreeCAD.ActiveDocument.openTransaction(translate("Path_Profile", "Uncreate Profile Operation")) - FreeCAD.ActiveDocument.removeObject(self.obj.Name) - FreeCAD.ActiveDocument.commitTransaction() - FreeCAD.ActiveDocument.recompute() - - def clicked(self, button): - if button == QtGui.QDialogButtonBox.Apply: - self.getFields() - self.obj.Proxy.execute(self.obj) - self.isDirty = False - - def getFields(self): - if self.obj: - - if hasattr(self.obj, "StartDepth"): - self.obj.StartDepth = FreeCAD.Units.Quantity(self.form.startDepth.text()).Value - if hasattr(self.obj, "FinalDepth"): - self.obj.FinalDepth = FreeCAD.Units.Quantity(self.form.finalDepth.text()).Value - if hasattr(self.obj, "StepDown"): - self.obj.StepDown = FreeCAD.Units.Quantity(self.form.stepDown.text()).Value - if hasattr(self.obj, "SafeHeight"): - self.obj.SafeHeight = FreeCAD.Units.Quantity(self.form.safeHeight.text()).Value - if hasattr(self.obj, "ClearanceHeight"): - self.obj.ClearanceHeight = FreeCAD.Units.Quantity(self.form.clearanceHeight.text()).Value - if hasattr(self.obj, "OffsetExtra"): - self.obj.OffsetExtra = FreeCAD.Units.Quantity(self.form.extraOffset.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, "Side"): - self.obj.Side = str(self.form.cutSide.currentText()) - if hasattr(self.obj, "Direction"): - self.obj.Direction = str(self.form.direction.currentText()) - if hasattr(self.obj, "processHoles"): - self.obj.processHoles = self.form.processHoles.isChecked() - if hasattr(self.obj, "processPerimeter"): - self.obj.processPerimeter = self.form.processPerimeter.isChecked() - if hasattr(self.obj, "processCircles"): - self.obj.processCircles = self.form.processCircles.isChecked() - if hasattr(self.obj, "ToolController"): - PathLog.debug("name: {}".format(self.form.uiToolController.currentText())) - tc = PathUtils.findToolController(self.obj, self.form.uiToolController.currentText()) - self.obj.ToolController = tc - - self.isDirty = True - - def setFields(self): - self.form.startDepth.setText(FreeCAD.Units.Quantity(self.obj.StartDepth.Value, FreeCAD.Units.Length).UserString) - self.form.finalDepth.setText(FreeCAD.Units.Quantity(self.obj.FinalDepth.Value, FreeCAD.Units.Length).UserString) - self.form.stepDown.setText(FreeCAD.Units.Quantity(self.obj.StepDown.Value, FreeCAD.Units.Length).UserString) - self.form.safeHeight.setText(FreeCAD.Units.Quantity(self.obj.SafeHeight.Value, FreeCAD.Units.Length).UserString) - self.form.clearanceHeight.setText(FreeCAD.Units.Quantity(self.obj.ClearanceHeight.Value, FreeCAD.Units.Length).UserString) - self.form.extraOffset.setText(FreeCAD.Units.Quantity(self.obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString) - self.form.useCompensation.setChecked(self.obj.UseComp) - self.form.useStartPoint.setChecked(self.obj.UseStartPoint) - self.form.processHoles.setChecked(self.obj.processHoles) - self.form.processPerimeter.setChecked(self.obj.processPerimeter) - self.form.processCircles.setChecked(self.obj.processCircles) - - index = self.form.cutSide.findText( - self.obj.Side, QtCore.Qt.MatchFixedString) - if index >= 0: - self.form.cutSide.blockSignals(True) - self.form.cutSide.setCurrentIndex(index) - self.form.cutSide.blockSignals(False) - - index = self.form.direction.findText( - self.obj.Direction, QtCore.Qt.MatchFixedString) - if index >= 0: - self.form.direction.blockSignals(True) - self.form.direction.setCurrentIndex(index) - self.form.direction.blockSignals(False) - - controllers = PathUtils.getToolControllers(self.obj) - labels = [c.Label for c in controllers] - 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) - PathLog.debug("searching for TC label {}. Found Index: {}".format(self.obj.ToolController.Label, index)) - if index >= 0: - self.form.uiToolController.blockSignals(True) - self.form.uiToolController.setCurrentIndex(index) - self.form.uiToolController.blockSignals(False) - else: - self.obj.ToolController = PathUtils.findToolController(self.obj) - - self.form.baseList.blockSignals(True) - for i in self.obj.Base: - for sub in i[1]: - self.form.baseList.addItem(i[0].Name + "." + sub) - self.form.baseList.blockSignals(False) - - self.form.update() - - def open(self): - self.s = SelObserver() - # install the function mode resident - FreeCADGui.Selection.addObserver(self.s) - - def addBase(self): - # check that the selection contains exactly what we want - selection = FreeCADGui.Selection.getSelectionEx() - - if len(selection) != 1: - FreeCAD.Console.PrintError(translate("PathProject", "Please select only faces from one solid\n")) - return - sel = selection[0] - if not sel.HasSubObjects: - FreeCAD.Console.PrintError(translate("PathProject", "Please select faces from one solid\n")) - return - if not selection[0].SubObjects[0].ShapeType == "Face": - FreeCAD.Console.PrintError(translate("PathProject", "Please select faces from one solid\n")) - return - - for i in sel.SubElementNames: - self.obj.Proxy.addprofilebase(self.obj, sel.Object, i) - - self.setFields() # defaults may have changed. Reload. - self.form.baseList.clear() - - for i in self.obj.Base: - for sub in i[1]: - self.form.baseList.addItem(i[0].Name + "." + sub) - - def deleteBase(self): - dlist = self.form.baseList.selectedItems() - newlist = [] - for d in dlist: - for i in self.obj.Base: - if i[0].Name != d.text().partition(".")[0] or i[1] != d.text().partition(".")[2]: - newlist.append(i) - self.form.baseList.takeItem(self.form.baseList.row(d)) - self.obj.Base = newlist - self.obj.Proxy.execute(self.obj) - FreeCAD.ActiveDocument.recompute() - - def itemActivated(self): - FreeCADGui.Selection.clearSelection() - slist = self.form.baseList.selectedItems() - for i in slist: - objstring = i.text().partition(".") - obj = FreeCAD.ActiveDocument.getObject(objstring[0]) - if objstring[2] != "": - FreeCADGui.Selection.addSelection(obj, objstring[2]) - else: - FreeCADGui.Selection.addSelection(obj) - - FreeCADGui.updateGui() - - def reorderBase(self): - newlist = [] - for i in range(self.form.baseList.count()): - s = self.form.baseList.item(i).text() - objstring = s.partition(".") - - obj = FreeCAD.ActiveDocument.getObject(objstring[0]) - item = (obj, str(objstring[2])) - newlist.append(item) - self.obj.Base = newlist - - self.obj.Proxy.execute(self.obj) - FreeCAD.ActiveDocument.recompute() - - def getStandardButtons(self): - return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel) - - def setupUi(self): - - # Connect Signals and Slots - # Base Controls - self.form.baseList.itemSelectionChanged.connect(self.itemActivated) - self.form.addBase.clicked.connect(self.addBase) - self.form.deleteBase.clicked.connect(self.deleteBase) - self.form.reorderBase.clicked.connect(self.reorderBase) - self.form.uiToolController.currentIndexChanged.connect(self.getFields) - - # Depths - self.form.startDepth.editingFinished.connect(self.getFields) - self.form.finalDepth.editingFinished.connect(self.getFields) - self.form.stepDown.editingFinished.connect(self.getFields) - - # Heights - self.form.safeHeight.editingFinished.connect(self.getFields) - self.form.clearanceHeight.editingFinished.connect(self.getFields) - - # operation - self.form.cutSide.currentIndexChanged.connect(self.getFields) - self.form.uiToolController.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.extraOffset.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) - - self.setFields() - - sel = FreeCADGui.Selection.getSelectionEx() - if len(sel) != 0 and sel[0].HasSubObjects: - self.addBase() - - -class SelObserver: - def __init__(self): - import PathScripts.PathSelection as PST - PST.profileselect() - - def __del__(self): - import PathScripts.PathSelection as PST - PST.clear() - - def addSelection(self, doc, obj, sub, pnt): - FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') - FreeCADGui.updateGui() - - -if FreeCAD.GuiUp: - # register the FreeCAD command - FreeCADGui.addCommand('Path_Profile', CommandPathProfile()) - FreeCADGui.addCommand('Set_StartPoint', _CommandSetStartPoint()) - -FreeCAD.Console.PrintLog("Loading PathProfile... done\n") +def Create(name): + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + proxy = ObjectProfile(obj) + return obj diff --git a/src/Mod/Path/PathScripts/PathProfileGui.py b/src/Mod/Path/PathScripts/PathProfileGui.py new file mode 100644 index 0000000000..c34b750741 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathProfileGui.py @@ -0,0 +1,120 @@ +# -*- 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 FreeCADGui +import Path +import PathScripts.PathAreaOpGui as PathAreaOpGui +import PathScripts.PathLog as PathLog +import PathScripts.PathProfile as PathProfile +import PathScripts.PathSelection as PathSelection + +from PathScripts import PathUtils +from PySide import QtCore, QtGui + +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + +class TaskPanelOpPage(PathAreaOpGui.TaskPanelPage): + + def getForm(self): + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpProfileEdit.ui") + + def getFields(self, obj): + self.obj.OffsetExtra = FreeCAD.Units.Quantity(self.form.extraOffset.text()).Value + self.obj.UseComp = self.form.useCompensation.isChecked() + self.obj.UseStartPoint = self.form.useStartPoint.isChecked() + self.obj.Side = str(self.form.cutSide.currentText()) + self.obj.Direction = str(self.form.direction.currentText()) + self.obj.processHoles = self.form.processHoles.isChecked() + self.obj.processPerimeter = self.form.processPerimeter.isChecked() + self.obj.processCircles = self.form.processCircles.isChecked() + + tc = PathUtils.findToolController(self.obj, self.form.toolController.currentText()) + self.obj.ToolController = tc + + def setFields(self, obj): + self.form.extraOffset.setText(FreeCAD.Units.Quantity(self.obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString) + self.form.useCompensation.setChecked(self.obj.UseComp) + self.form.useStartPoint.setChecked(self.obj.UseStartPoint) + self.form.processHoles.setChecked(self.obj.processHoles) + self.form.processPerimeter.setChecked(self.obj.processPerimeter) + self.form.processCircles.setChecked(self.obj.processCircles) + + self.selectInComboBox(self.obj.Side, self.form.cutSide) + self.selectInComboBox(self.obj.Direction, self.form.direction) + self.setupToolController(self.obj, self.form.toolController) + + def getSignalsForUpdate(self, obj): + signals = [] + signals.append(self.form.cutSide.currentIndexChanged) + signals.append(self.form.direction.currentIndexChanged) + signals.append(self.form.useCompensation.clicked) + signals.append(self.form.useStartPoint.clicked) + signals.append(self.form.extraOffset.editingFinished) + signals.append(self.form.processHoles.clicked) + signals.append(self.form.processPerimeter.clicked) + signals.append(self.form.processCircles.clicked) + return signals + +class ViewProviderProfile(PathAreaOpGui.ViewProvider): + + def getTaskPanelOpPage(self, obj): + return TaskPanelOpPage(obj) + + def getIcon(self): + return ":/icons/Path-Profile-Face.svg" + + def getSelectionFactory(self): + return PathSelection.profileselect + + +def Create(name): + FreeCAD.ActiveDocument.openTransaction(translate("Path", "Create a Profile")) + obj = PathProfile.Create(name) + vobj = ViewProviderProfile(obj.ViewObject) + + FreeCAD.ActiveDocument.commitTransaction() + obj.ViewObject.startEditing() + return obj + +class CommandPathProfile: + def GetResources(self): + return {'Pixmap': 'Path-Profile-Face', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathProfile", "Face Profile"), + 'Accel': "P, F", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathProfile", "Profile based on face or faces")} + + def IsActive(self): + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False + + def Activated(self): + return Create('Profile') + +FreeCADGui.addCommand('Path_Profile', CommandPathProfile()) +FreeCAD.Console.PrintLog("Loading PathProfileGui... done\n")