diff --git a/src/Mod/Arch/InitGui.py b/src/Mod/Arch/InitGui.py
index 268f5a87e9..bbf20effd0 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 c2d877ab17..384cc87809 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 f1afe09140..f915486cf0 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 313e5dc7c9..638fc9cfb8 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 e5d72db297..a9e1e525c2 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 20c7a7b083..d2edc859f2 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 0000000000..6421b5efa2
--- /dev/null
+++ b/src/Mod/Draft/Resources/icons/Draft_Mirror.svg
@@ -0,0 +1,147 @@
+
+
+
+