# -*- coding: utf-8 -*- # *************************************************************************** # * * # * Copyright (c) 2014 Yorik van Havre * # * * # * 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 ArchPanel import DraftGeomUtils import FreeCAD import Part import Path import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathUtils as PathUtils import TechDraw import traceback from PySide import QtCore __doc__ = "Class and implementation of Path Engrave operation" if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) class ObjectEngrave(PathOp.ObjectOp): '''Proxy class for Engrave operation.''' def opFeatures(self, obj): '''opFeatures(obj) ... return all standard features and edges based geomtries''' return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureBaseEdges; def initOperation(self, obj): '''initOperation(obj) ... create engraving specific properties.''' obj.addProperty("App::PropertyInteger", "StartVertex", "Path", QtCore.QT_TRANSLATE_NOOP("PathEngrave", "The vertex index to start the path from")) obj.addProperty("App::PropertyLinkList", "BaseShapes", "Path", QtCore.QT_TRANSLATE_NOOP("PathEngrave", "Additional base objects to be engraved")) def opExecute(self, obj): '''opExecute(obj) ... process engraving operation''' PathLog.track() zValues = [] if obj.StepDown.Value != 0: z = obj.StartDepth.Value - obj.StepDown.Value while z > obj.FinalDepth.Value: zValues.append(z) z -= obj.StepDown.Value zValues.append(obj.FinalDepth.Value) self.zValues = zValues output = '' try: if self.baseobject.isDerivedFrom('Sketcher::SketchObject') or \ self.baseobject.isDerivedFrom('Part::Part2DObject') or \ hasattr(self.baseobject, 'ArrayType'): output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) # we only consider the outer wire if this is a Face wires = [] for w in self.baseobject.Shape.Wires: tempedges = PathUtils.cleanedges(w.Edges, 0.5) wires.append(Part.Wire(tempedges)) output += self.buildpathocc(obj, wires, zValues) self.wires = wires elif isinstance(self.baseobject.Proxy, ArchPanel.PanelSheet): # process the sheet wires = [] for tag in self.baseobject.Proxy.getTags(self.baseobject, transform=True): output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) tagWires = [] for w in tag.Wires: tempedges = PathUtils.cleanedges(w.Edges, 0.5) tagWires.append(Part.Wire(tempedges)) output += self.buildpathocc(obj, tagWires, zValues) wires.extend(tagWires) self.wires = wires elif obj.Base: wires = [] for base, subs in obj.Base: edges = [] for sub in subs: edges.extend(base.Shape.getElement(sub).Edges) wires.extend(TechDraw.edgeWalker(edges)) output += self.buildpathocc(obj, wires, zValues) self.wires = wires elif not obj.BaseShapes: PathLog.info("base object: %s" % (obj.Base)) raise ValueError('Unknown baseobject type for engraving') if obj.BaseShapes: job = PathUtils.findParentJob(obj) wires = [] for shape in obj.BaseShapes: shapeWires = shape.Shape.copy().Wires if hasattr(shape, 'MapMode') and 'Deactivated' != shape.MapMode: if hasattr(shape, 'Support') and 1 == len(shape.Support) and 1 == len(shape.Support[0][1]): pmntShape = shape.Placement pmntSupport = shape.Support[0][0].getGlobalPlacement() pmntBase = job.Base.Placement for w in shapeWires: w.Placement = pmntBase.multiply(pmntSupport.inverse().multiply(pmntShape)) else: PathLog.warning(translate("PathEngrave", "Attachment not supported by engraver")) else: PathLog.info("MapMode: %s" % (shape.MapMode if hasattr(shape, 'MapMode') else 'hugo')) output += self.buildpathocc(obj, shapeWires, zValues) wires.extend(shapeWires) self.wires = wires output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) except Exception as e: #PathLog.error("Exception: %s" % e) #traceback.print_exc() PathLog.error(translate("PathEngrave", "The Job Base Object has no engraveable element. Engraving operation will produce no output.")) def buildpathocc(self, obj, wires, zValues): '''buildpathocc(obj, wires, zValues) ... internal helper function to generate engraving commands.''' PathLog.track() output = "" for wire in wires: offset = wire # reorder the wire offset = DraftGeomUtils.rebaseWire(offset, obj.StartVertex) last = None for z in zValues: if last: self.commandlist.append(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed})) for edge in offset.Edges: if not last: # we set the first move to our first point last = edge.Vertexes[0].Point output += "G0" + " X" + PathUtils.fmt(last.x) + " Y" + PathUtils.fmt(last.y) + " Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.horizRapid) # Rapid to starting position self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'Z': obj.ClearanceHeight.Value, 'F': self.horizRapid})) output += "G0" + " X" + PathUtils.fmt(last.x) + " Y" + PathUtils.fmt(last.y) + " Z" + PathUtils.fmt(obj.SafeHeight.Value) + "F " + PathUtils.fmt(self.horizRapid) # Rapid to safe height self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) output += "G1" + " X" + PathUtils.fmt(last.x) + " Y" + PathUtils.fmt(last.y) + " Z" + PathUtils.fmt(z) + "F " + PathUtils.fmt(self.vertFeed) + "\n" # Vertical feed to depth self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed})) if isinstance(edge.Curve, Part.Circle): point = edge.Vertexes[-1].Point if point == last: # edges can come flipped point = edge.Vertexes[0].Point center = edge.Curve.Center relcenter = center.sub(last) v1 = last.sub(center) v2 = point.sub(center) if v1.cross(v2).z < 0: output += "G2" else: output += "G3" output += " X" + PathUtils.fmt(point.x) + " Y" + PathUtils.fmt(point.y) + " Z" + PathUtils.fmt(z) output += " I" + PathUtils.fmt(relcenter.x) + " J" + PathUtils.fmt(relcenter.y) + " K" + PathUtils.fmt(relcenter.z) output += " F " + PathUtils.fmt(self.horizFeed) output += "\n" cmd = 'G2' if v1.cross(v2).z < 0 else 'G3' args = {} args['X'] = point.x args['Y'] = point.y args['Z'] = z args['I'] = relcenter.x args['J'] = relcenter.y args['K'] = relcenter.z args['F'] = self.horizFeed self.commandlist.append(Path.Command(cmd, args)) last = point else: point = edge.Vertexes[-1].Point if point == last: # edges can come flipped point = edge.Vertexes[0].Point output += "G1 X" + PathUtils.fmt(point.x) + " Y" + PathUtils.fmt(point.y) + " Z" + PathUtils.fmt(z) output += " F " + PathUtils.fmt(self.horizFeed) output += "\n" self.commandlist.append(Path.Command('G1', {'X': point.x, 'Y': point.y, 'Z': z, 'F': self.horizFeed})) last = point output += "G0 Z " + PathUtils.fmt(obj.ClearanceHeight.Value) self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) return output def opSetDefaultValues(self, obj): '''opSetDefaultValues(obj) ... set depths for engraving''' job = PathUtils.findParentJob(obj) if job and job.Base: bb = job.Base.Shape.BoundBox obj.OpStartDepth = bb.ZMax obj.OpFinalDepth = bb.ZMin else: obj.OpFinalDepth = -0.1 def Create(name): '''Create(name) ... Creates and returns a Engrave operation.''' obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectEngrave(obj) return obj