# -*- coding: utf-8 -*- # *************************************************************************** # * * # * Copyright (c) 2018 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 math import PathScripts.PathGeom as PathGeom import PathScripts.PathUtils as PathUtils from PySide import QtCore, QtGui """Axis remapping Dressup object and FreeCAD command. This dressup remaps one axis of motion to another. For example, you can re-map the Y axis to A to control a 4th axis rotary.""" # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03'] rapidcommands = ['G0', 'G00'] arccommands = ['G2', 'G3', 'G02', 'G03'] class ObjectDressup: def __init__(self, obj): maplist = ["X->A", "Y->A", "X->B", "Y->B", "X->C", "Y->C"] obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The base path to modify")) obj.addProperty("App::PropertyEnumeration", "AxisMap", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The input mapping axis")) obj.addProperty("App::PropertyDistance", "Radius", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The radius of the wrapped axis")) obj.AxisMap = maplist obj.AxisMap = "Y->A" obj.Proxy = self def __getstate__(self): return None def __setstate__(self, state): return None def _linear2angular(self, radius, length): '''returns an angular distance in degrees to achieve a linear move of a given lenth''' circum = 2 * math.pi * float(radius) return 360 * (float(length) / circum) def _stripArcs(self, path, d): '''converts all G2/G3 commands into G1 commands''' newcommandlist = [] currLocation = {'X':0,'Y':0,'Z':0, 'F': 0} for p in path: if p.Name in arccommands: curVec = FreeCAD.Vector(currLocation['X'], currLocation['Y'], currLocation['Z']) arcwire = PathGeom.edgeForCmd(p, curVec) pointlist = arcwire.discretize(Deflection=d) for point in pointlist: newcommand = Path.Command("G1", {'X':point.x, 'Y':point.y, 'Z':point.z}) newcommandlist.append(newcommand) currLocation.update(newcommand.Parameters) else: newcommandlist.append(p) currLocation.update(p.Parameters) return newcommandlist def execute(self, obj): inAxis = obj.AxisMap[0] outAxis = obj.AxisMap[3] d = 0.1 if obj.Base: if obj.Base.isDerivedFrom("Path::Feature"): if obj.Base.Path: if obj.Base.Path.Commands: pp = obj.Base.Path.Commands if len([i for i in pp if i.Name in arccommands]) == 0: pathlist = pp else: pathlist = self._stripArcs(pp, d) newcommandlist = [] currLocation = {'X':0,'Y':0,'Z':0, 'F': 0} for c in pathlist: #obj.Base.Path.Commands: newparams = dict(c.Parameters) remapvar = newparams.pop(inAxis, None) if remapvar is not None: newparams[outAxis] = self._linear2angular(obj.Radius, remapvar) locdiff = dict(set(newparams.items()) - set(currLocation.items())) if len(locdiff) == 1 and outAxis in locdiff: #pure rotation. Calculate rotational feed rate if 'F' in c.Parameters: feed = c.Parameters['F'] else: feed = currLocation['F'] newparams.update({"F":self._linear2angular(obj.Radius, feed)}) newcommand = Path.Command(c.Name, newparams) newcommandlist.append(newcommand) currLocation.update(newparams) else: newcommandlist.append(c) currLocation.update(c.Parameters) path = Path.Path(newcommandlist) path.Center = self.center(obj) obj.Path = path def onChanged(self, obj, prop): if not 'Restore' in obj.State and prop == "Radius": job = PathUtils.findParentJob(obj) if job: job.Proxy.setCenterOfRotation(self.center(obj)) def center(self, obj): return FreeCAD.Vector(0, 0, 0 - obj.Radius.Value) class ViewProviderDressup: def __init__(self, vobj): vobj.Proxy = self def attach(self, vobj): self.obj = vobj.Object if self.obj and self.obj.Base: for i in self.obj.Base.InList: if hasattr(i, "Group"): group = i.Group for g in group: if g.Name == self.obj.Base.Name: group.remove(g) i.Group = group # FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False return def claimChildren(self): return [self.obj.Base] def __getstate__(self): return None def __setstate__(self, state): return None def onDelete(self, arg1=None, arg2=None): '''this makes sure that the base operation is added back to the project and visible''' FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True job = PathUtils.findParentJob(arg1.Object) job.Proxy.addOperation(arg1.Object.Base, arg1.Object) arg1.Object.Base = None return True class CommandPathDressup: def GetResources(self): return {'Pixmap': 'Path-Dressup', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "Axis Map Dress-up"), 'Accel': "", 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "Remap one axis to another.")} 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): # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select one path object\n")) return if not selection[0].isDerivedFrom("Path::Feature"): FreeCAD.Console.PrintError(translate("Path_Dressup", "The selected object is not a path\n")) return if selection[0].isDerivedFrom("Path::FeatureCompoundPython"): FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object")) return # everything ok! FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupAxisMap", "Create Dress-up")) FreeCADGui.addModule("PathScripts.PathDressupAxisMap") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "AxisMapDressup")') FreeCADGui.doCommand('PathScripts.PathDressupAxisMap.ObjectDressup(obj)') FreeCADGui.doCommand('base = FreeCAD.ActiveDocument.' + selection[0].Name) FreeCADGui.doCommand('job = PathScripts.PathUtils.findParentJob(base)') FreeCADGui.doCommand('obj.Base = base') FreeCADGui.doCommand('obj.Radius = 45') FreeCADGui.doCommand('job.Proxy.addOperation(obj, base)') FreeCADGui.doCommand('PathScripts.PathDressupAxisMap.ViewProviderDressup(obj.ViewObject)') FreeCADGui.doCommand('Gui.ActiveDocument.getObject(base.Name).Visibility = False') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand('Path_DressupAxisMap', CommandPathDressup()) FreeCAD.Console.PrintLog("Loading PathDressup... done\n")