From 2e91633da9eb19dd9d05108690e6e676ab17ccf6 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sat, 5 Dec 2015 14:19:35 -0200 Subject: [PATCH] Draft: Added mirror tool (more graphical version of Part Mirror) - fixes #2215 --- src/Mod/Arch/InitGui.py | 3 +- src/Mod/Draft/Draft.py | 35 +++++ src/Mod/Draft/DraftTools.py | 124 +++++++++++++++ src/Mod/Draft/DraftTrackers.py | 21 +++ src/Mod/Draft/InitGui.py | 2 +- src/Mod/Draft/Resources/Draft.qrc | 1 + .../Draft/Resources/icons/Draft_Mirror.svg | 147 ++++++++++++++++++ 7 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 src/Mod/Draft/Resources/icons/Draft_Mirror.svg diff --git a/src/Mod/Arch/InitGui.py b/src/Mod/Arch/InitGui.py index 268f5a87e..bbf20effd 100644 --- a/src/Mod/Arch/InitGui.py +++ b/src/Mod/Arch/InitGui.py @@ -91,7 +91,8 @@ class ArchWorkbench(Workbench): "Draft_Trimex", "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale", "Draft_Shape2DView","Draft_Draft2Sketch","Draft_Array", "Draft_Clone"] - self.draftextratools = ["Draft_WireToBSpline","Draft_AddPoint","Draft_DelPoint","Draft_ShapeString","Draft_PathArray"] + self.draftextratools = ["Draft_WireToBSpline","Draft_AddPoint","Draft_DelPoint","Draft_ShapeString", + "Draft_PathArray","Draft_Mirror"] self.draftcontexttools = ["Draft_ApplyStyle","Draft_ToggleDisplayMode","Draft_AddToGroup", "Draft_SelectGroup","Draft_SelectPlane", "Draft_ShowSnapBar","Draft_ToggleGrid","Draft_UndoLine", diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index c2d877ab1..384cc8780 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -2456,6 +2456,41 @@ def getCloneBase(obj,strict=False): return False return obj + +def mirror(objlist,p1,p2): + '''mirror(objlist,p1,p2,[clone]): creates a mirrored version of the given object(s) + along an axis that passes through the two vectors p1 and p2.''' + + if not objlist: + FreeCAD.Console.PrintError(translate("draft","No object given\n")) + return + if p1 == p2: + FreeCAD.Console.PrintError(translate("draft","The two points are coincident\n")) + return + if not isinstance(objlist,list): + objlist = [objlist] + + result = [] + + for obj in objlist: + mir = FreeCAD.ActiveDocument.addObject("Part::Mirroring","mirror") + mir.Label = "Mirror of "+obj.Label + mir.Source = obj + if gui: + norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative() + else: + norm = FreeCAD.Vector(0,0,1) + pnorm = p2.sub(p1).cross(norm).normalize() + mir.Base = p1 + mir.Normal = pnorm + formatObject(mir,obj) + result.append(mir) + + if len(result) == 1: + result = result[0] + return result + + def heal(objlist=None,delete=True,reparent=True): '''heal([objlist],[delete],[reparent]) - recreates Draft objects that are damaged, for example if created from an earlier version. If delete is True, diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index f1afe0914..f915486cf 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -4409,6 +4409,129 @@ class VisGroup(): FreeCAD.ActiveDocument.recompute() +class Mirror(Modifier): + "The Draft_Mirror FreeCAD command definition" + + def GetResources(self): + return {'Pixmap' : 'Draft_Mirror', + 'Accel' : "M, I", + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Mirror", "Mirror"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Mirror", "Mirrors the selected objects along a line defined by two points")} + + def Activated(self): + self.name = translate("draft","Mirror").decode("utf8") + Modifier.Activated(self,self.name) + self.ghost = None + if self.ui: + if not FreeCADGui.Selection.getSelection(): + self.ui.selectUi() + msg(translate("draft", "Select an object to mirror\n")) + self.call = self.view.addEventCallback("SoEvent",selectObject) + else: + self.proceed() + + def proceed(self): + if self.call: self.view.removeEventCallback("SoEvent",self.call) + self.sel = FreeCADGui.Selection.getSelection() + self.ui.pointUi(self.name) + self.ui.modUi() + self.ui.xValue.setFocus() + self.ui.xValue.selectAll() + #self.ghost = ghostTracker(self.sel) TODO: solve this (see below) + self.call = self.view.addEventCallback("SoEvent",self.action) + msg(translate("draft", "Pick start point of mirror line:\n")) + self.ui.isCopy.hide() + + def finish(self,closed=False,cont=False): + if self.ghost: + self.ghost.finalize() + Modifier.finish(self) + if cont and self.ui: + if self.ui.continueMode: + FreeCADGui.Selection.clearSelection() + self.Activated() + + def mirror(self,p1,p2,copy=False): + "mirroring the real shapes" + sel = '[' + for o in self.sel: + if len(sel) > 1: + sel += ',' + sel += 'FreeCAD.ActiveDocument.'+o.Name + sel += ']' + FreeCADGui.addModule("Draft") + self.commit(translate("draft","Mirror"), + ['Draft.mirror('+sel+','+DraftVecUtils.toString(p1)+','+DraftVecUtils.toString(p2)+')', + 'FreeCAD.ActiveDocument.recompute()']) + + def action(self,arg): + "scene event handler" + if arg["Type"] == "SoKeyboardEvent": + if arg["Key"] == "ESCAPE": + self.finish() + elif arg["Type"] == "SoLocation2Event": #mouse movement detection + self.point,ctrlPoint,info = getPoint(self,arg) + if (len(self.node) > 0): + last = self.node[-1] + if self.ghost: + if self.point != last: + # TODO : the following doesn't work at the moment + mu = self.point.sub(last).normalize() + if FreeCAD.GuiUp: + mv = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative() + else: + mv = FreeCAD.Vector(0,0,1) + mw = mv.cross(mu) + import WorkingPlane + tm = WorkingPlane.plane(u=mu,v=mv,w=mw,pos=last).getPlacement().toMatrix() + m = self.ghost.getMatrix() + m = m.multiply(tm.inverse()) + m.scale(FreeCAD.Vector(1,1,-1)) + m = m.multiply(tm) + m.scale(FreeCAD.Vector(-1,1,1)) + self.ghost.setMatrix(m) + if self.extendedCopy: + if not hasMod(arg,MODALT): self.finish() + elif arg["Type"] == "SoMouseButtonEvent": + if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): + if self.point: + self.ui.redraw() + if (self.node == []): + self.node.append(self.point) + self.ui.isRelative.show() + if self.ghost: + self.ghost.on() + msg(translate("draft", "Pick end point of mirror line:\n")) + if self.planetrack: + self.planetrack.set(self.point) + else: + last = self.node[0] + if self.ui.isCopy.isChecked() or hasMod(arg,MODALT): + self.mirror(last,self.point,True) + else: + self.mirror(last,self.point) + if hasMod(arg,MODALT): + self.extendedCopy = True + else: + self.finish(cont=True) + + def numericInput(self,numx,numy,numz): + "this function gets called by the toolbar when valid x, y, and z have been entered there" + self.point = Vector(numx,numy,numz) + if not self.node: + self.node.append(self.point) + if self.ghost: + self.ghost.on() + msg(translate("draft", "Pick end point of mirror line:\n")) + else: + last = self.node[-1] + if self.ui.isCopy.isChecked(): + self.mirror(last,self.point,True) + else: + self.mirror(last,self.point) + self.finish() + + #--------------------------------------------------------------------------- # Snap tools #--------------------------------------------------------------------------- @@ -4620,6 +4743,7 @@ FreeCADGui.addCommand('Draft_Clone',Draft_Clone()) FreeCADGui.addCommand('Draft_PathArray',PathArray()) FreeCADGui.addCommand('Draft_Heal',Heal()) FreeCADGui.addCommand('Draft_VisGroup',VisGroup()) +FreeCADGui.addCommand('Draft_Mirror',Mirror()) # context commands FreeCADGui.addCommand('Draft_FinishLine',FinishLine()) diff --git a/src/Mod/Draft/DraftTrackers.py b/src/Mod/Draft/DraftTrackers.py index 313e5dc7c..638fc9cfb 100644 --- a/src/Mod/Draft/DraftTrackers.py +++ b/src/Mod/Draft/DraftTrackers.py @@ -611,6 +611,27 @@ class ghostTracker(Tracker): except: print("Error retrieving coin node") return sep + + def getMatrix(self): + r = FreeCADGui.ActiveDocument.ActiveView.getViewer().getSoRenderManager().getViewportRegion() + v = coin.SoGetMatrixAction(r) + m = self.trans.getMatrix(v) + if m: + m = m.getValue() + return FreeCAD.Matrix(m[0][0],m[0][1],m[0][2],m[0][3], + m[1][0],m[1][1],m[1][2],m[1][3], + m[2][0],m[2][1],m[2][2],m[2][3], + m[3][0],m[3][1],m[3][2],m[3][3]) + else: + return FreeCAD.Matrix() + + def setMatrix(self,matrix): + m = coin.SbMatrix(matrix.A11,matrix.A12,matrix.A13,matrix.A14, + matrix.A21,matrix.A22,matrix.A23,matrix.A24, + matrix.A31,matrix.A32,matrix.A33,matrix.A34, + matrix.A41,matrix.A42,matrix.A43,matrix.A44) + self.trans.setMatrix(m) + class editTracker(Tracker): "A node edit tracker" diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index e5d72db29..a9e1e525c 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -112,7 +112,7 @@ class DraftWorkbench (Workbench): "Draft_Trimex", "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale", "Draft_Edit","Draft_WireToBSpline","Draft_AddPoint", "Draft_DelPoint","Draft_Shape2DView","Draft_Draft2Sketch","Draft_Array", - "Draft_PathArray","Draft_Clone","Draft_Drawing"] + "Draft_PathArray","Draft_Clone","Draft_Drawing","Draft_Mirror"] self.treecmdList = ["Draft_ApplyStyle","Draft_ToggleDisplayMode","Draft_AddToGroup", "Draft_SelectGroup","Draft_SelectPlane", "Draft_ShowSnapBar","Draft_ToggleGrid"] diff --git a/src/Mod/Draft/Resources/Draft.qrc b/src/Mod/Draft/Resources/Draft.qrc index 20c7a7b08..d2edc859f 100644 --- a/src/Mod/Draft/Resources/Draft.qrc +++ b/src/Mod/Draft/Resources/Draft.qrc @@ -66,6 +66,7 @@ icons/Draft_ShapeString.svg icons/Draft_Facebinder.svg icons/Draft_FlipDimension.svg + icons/Draft_Mirror.svg patterns/concrete.svg patterns/cross.svg patterns/line.svg diff --git a/src/Mod/Draft/Resources/icons/Draft_Mirror.svg b/src/Mod/Draft/Resources/icons/Draft_Mirror.svg new file mode 100644 index 000000000..6421b5efa --- /dev/null +++ b/src/Mod/Draft/Resources/icons/Draft_Mirror.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + +