From 4c04d8ee948e7d3e1d0007e0b89766f867197129 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 17 Feb 2019 22:55:57 +1100 Subject: [PATCH 01/18] Add new edit tool icon, shortcut, and basic modes. * It allows you to select multiple objects to edit instead of just one. * It highlights the object lines and the points in red. * It stays in the mode and allows you to run other modifiers. * A very hackish hook into the move modifier is added as a proof of concept. --- src/Mod/Draft/DraftTools.py | 83 ++++++++++++++++++++++++++++++++++++- src/Mod/Draft/InitGui.py | 2 +- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index ed28fc6d66..35eaaa220a 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2563,7 +2563,16 @@ class Move(Modifier): def Activated(self): self.name = translate("draft","Move", utf8_decode=True) - Modifier.Activated(self,self.name) + if not isinstance(FreeCAD.activeDraftCommand, EditImproved): + Modifier.Activated(self,self.name) + else: + self.ui = FreeCADGui.draftToolBar + self.view = Draft.get3DView() + self.call = None + self.node = [] + self.extendedCopy = False + self.featureName = "Edit_Improved" + self.planetrack = None self.ghost = None if self.ui: if not FreeCADGui.Selection.getSelection(): @@ -4209,6 +4218,77 @@ class ToggleDisplayMode(): if "Flat Lines" in obj.ViewObject.listDisplayModes(): obj.ViewObject.DisplayMode = "Flat Lines" +class EditImproved(Modifier): + "The Draft_Edit_Improved FreeCAD command definition" + + def __init__(self): + self.is_running = False + self.editable_objects = [] + self.original_view_settings = {} + + def GetResources(self): + return {'Pixmap' : 'Draft_Edit', + 'Accel' : "D, E", + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Edit_Improved", "Edit Improved"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Edit_Improved", "Edits the selected objects")} + + def Activated(self): + if self.is_running: + return self.finish() + self.is_running = True + Modifier.Activated(self,"Edit_Improved") + self.get_selection() + + def proceed(self): + self.remove_view_callback() + self.get_editable_objects_from_selection() + if not self.editable_objects: + return self.finish() + self.call = self.view.addEventCallback("SoEvent", self.action) + self.highlight_editable_objects() + + def finish(self): + Modifier.finish(self) + self.remove_view_callback() + self.restore_editable_objects_graphics() + self.__init__() + + def action(self, event): + if event["Type"] == "SoKeyboardEvent" and event["Key"] == "ESCAPE": + self.finish() + + def get_selection(self): + if not FreeCADGui.Selection.getSelection() and self.ui: + msg(translate("Draft_Edit_Improved", "Select an object to edit")+"\n") + self.call = self.view.addEventCallback("SoEvent", selectObject) + else: + self.proceed() + + def remove_view_callback(self): + if self.call: + self.view.removeEventCallback("SoEvent",self.call) + + def get_editable_objects_from_selection(self): + for object in FreeCADGui.Selection.getSelection(): + if object.isDerivedFrom("Part::Part2DObject"): + self.editable_objects.append(object) + + def highlight_editable_objects(self): + for object in self.editable_objects: + self.original_view_settings[object.Name] = { + 'PointSize': object.ViewObject.PointSize, + 'PointColor': object.ViewObject.PointColor, + 'LineColor': object.ViewObject.LineColor + } + object.ViewObject.PointSize = 10 + object.ViewObject.PointColor = (1., 0., 0.) + object.ViewObject.LineColor = (1., 0., 0.) + + def restore_editable_objects_graphics(self): + for object in self.editable_objects: + for attribute, value in self.original_view_settings[object.Name].items(): + setattr(object.ViewObject, attribute, value) + class Edit(Modifier): "The Draft_Edit FreeCAD command definition" @@ -6414,6 +6494,7 @@ FreeCADGui.addCommand('Draft_Trimex',Trimex()) FreeCADGui.addCommand('Draft_Scale',Scale()) FreeCADGui.addCommand('Draft_Drawing',Drawing()) FreeCADGui.addCommand('Draft_Edit',Edit()) +FreeCADGui.addCommand('Draft_Edit_Improved',EditImproved()) FreeCADGui.addCommand('Draft_AddPoint',AddPoint()) FreeCADGui.addCommand('Draft_DelPoint',DelPoint()) FreeCADGui.addCommand('Draft_WireToBSpline',WireToBSpline()) diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index 26f1668780..31221b6367 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -74,7 +74,7 @@ class DraftWorkbench (Workbench): "Draft_ShapeString","Draft_Facebinder","Draft_BezierTools","Draft_Label"] self.modList = ["Draft_Move","Draft_Rotate","Draft_Offset", "Draft_Trimex", "Draft_Join", "Draft_Split", "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale", - "Draft_Edit","Draft_WireToBSpline","Draft_AddPoint", + "Draft_Edit","Draft_Edit_Improved","Draft_WireToBSpline","Draft_AddPoint", "Draft_DelPoint","Draft_Shape2DView","Draft_Draft2Sketch","Draft_Array", "Draft_PathArray", "Draft_PointArray","Draft_Clone", "Draft_Drawing","Draft_Mirror","Draft_Stretch"] From 9e619d0e4b8fbd0c2eff7713514c92a7e4c729c0 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 20 Feb 2019 22:15:54 +1100 Subject: [PATCH 02/18] Make ghostTracker support showing nodes as well --- src/Mod/Draft/DraftTrackers.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Mod/Draft/DraftTrackers.py b/src/Mod/Draft/DraftTrackers.py index ccd908a3c0..5ed4e805ac 100644 --- a/src/Mod/Draft/DraftTrackers.py +++ b/src/Mod/Draft/DraftTrackers.py @@ -583,7 +583,23 @@ class ghostTracker(Tracker): if not isinstance(sel,list): sel = [sel] for obj in sel: - rootsep.addChild(self.getNode(obj)) + import Part + if not isinstance(obj, Part.Vertex): + rootsep.addChild(self.getNode(obj)) + else: + self.coords = coin.SoCoordinate3() + self.coords.point.setValue((obj.X,obj.Y,obj.Z)) + color = coin.SoBaseColor() + color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") + self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker.markerIndex = FreeCADGui.getMarkerIndex("quad", 9) + node = coin.SoAnnotation() + selnode = coin.SoSeparator() + selnode.addChild(self.coords) + selnode.addChild(color) + selnode.addChild(self.marker) + node.addChild(selnode) + rootsep.addChild(node) self.children.append(rootsep) Tracker.__init__(self,dotted,scolor,swidth,children=self.children,name="ghostTracker") @@ -674,7 +690,6 @@ class ghostTracker(Tracker): matrix.A41,matrix.A42,matrix.A43,matrix.A44) self.trans.setMatrix(m) - class editTracker(Tracker): "A node edit tracker" def __init__(self,pos=Vector(0,0,0),name=None,idx=0,objcol=None,\ From d392daefd29a3dce48d00c5ab4739d2741615dc6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 20 Feb 2019 22:16:12 +1100 Subject: [PATCH 03/18] Fix typo --- src/Mod/Draft/Draft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 5eeaf197ee..bab72e7430 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1493,7 +1493,7 @@ def move(objectslist,vector,copy=False): in objects (that can be an object or a list of objects) in the direction and distance indicated by the given vector. If copy is True, the actual objects are not moved, but copies - are created instead.he objects (or their copies) are returned.''' + are created instead. The objects (or their copies) are returned.''' typecheck([(vector,Vector), (copy,bool)], "move") if not isinstance(objectslist,list): objectslist = [objectslist] objectslist.extend(getMovableChildren(objectslist)) From 6e6bdc04a07c42d775a504692664bdeaf6537acb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 20 Feb 2019 22:16:26 +1100 Subject: [PATCH 04/18] Create functions for moving vertex and edges --- src/Mod/Draft/Draft.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index bab72e7430..c7904dc5e2 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1488,6 +1488,16 @@ def cut(object1,object2): return obj +def moveVertex(object, vertex_index, vertex, vector): + points = object.Points + points[vertex_index] = object.Placement.inverse().multVec(vertex).add(vector) + object.Points = points + FreeCAD.ActiveDocument.recompute() + +def moveEdge(object, edge_index, edge, vector): + moveVertex(object, edge_index, object.Placement.multVec(object.Points[edge_index]), vector) + moveVertex(object, edge_index+1, object.Placement.multVec(object.Points[edge_index+1]), vector) + def move(objectslist,vector,copy=False): '''move(objects,vector,[copy]): Moves the objects contained in objects (that can be an object or a list of objects) From 60fa857b4923165e0be16501a0c2c3b8de8fa618 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 20 Feb 2019 22:16:53 +1100 Subject: [PATCH 05/18] Add subelement mode to draft move command to allow movement of vertices and nodes --- src/Mod/Draft/DraftGui.py | 9 ++ src/Mod/Draft/DraftTools.py | 194 ++++++++++++++++++++---------------- 2 files changed, 115 insertions(+), 88 deletions(-) diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index 8c177425d3..bc4c58eb10 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -109,6 +109,7 @@ inCommandShortcuts = { "Continue": ["T",translate("draft","Continue"), "continueCmd"], "Close": ["O",translate("draft","Close"), "closeButton"], "Copy": ["P",translate("draft","Copy"), "isCopy"], + "SubelementMode": ["D",translate("draft","Subelement mode"), "isSubelementMode"], "Fill": ["L",translate("draft","Fill"), "hasFill"], "Exit": ["A",translate("draft","Exit"), "finishButton"], "Snap": ["S",translate("draft","Snap On/Off"), None], @@ -606,6 +607,7 @@ class DraftToolBar: self.currentViewButton = self._pushbutton("view", self.layout,icon="view-isometric") self.resetPlaneButton = self._pushbutton("none", self.layout,icon="view-axonometric") self.isCopy = self._checkbox("isCopy",self.layout,checked=False) + self.isSubelementMode = self._checkbox("isSubelementMode",self.layout,checked=False) gl = QtGui.QHBoxLayout() self.layout.addLayout(gl) self.gridLabel = self._label("gridLabel", gl) @@ -830,6 +832,8 @@ class DraftToolBar: self.resetPlaneButton.setToolTip(translate("draft", "Do not project points to a drawing plane")) self.isCopy.setText(translate("draft", "Copy")+" ("+inCommandShortcuts["Copy"][0]+")") self.isCopy.setToolTip(translate("draft", "If checked, objects will be copied instead of moved. Preferences -> Draft -> Global copy mode to keep this mode in next commands")) + self.isSubelementMode.setText(translate("draft", "Modify subelements")+" ("+inCommandShortcuts["SubelementMode"][0]+")") + self.isSubelementMode.setToolTip(translate("draft", "If checked, subelements will be modified instead of entire objects")) self.SStringValue.setToolTip(translate("draft", "Text string to draw")) self.labelSString.setText(translate("draft", "String")) self.SSizeValue.setToolTip(translate("draft", "Height of text")) @@ -1243,6 +1247,7 @@ class DraftToolBar: def modUi(self): self.isCopy.show() + self.isSubelementMode.show() p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") if p.GetBool("copymode",True): self.isCopy.setChecked(p.GetBool("copymodeValue",False)) @@ -1727,6 +1732,10 @@ class DraftToolBar: if self.isCopy.isVisible(): self.isCopy.setChecked(not self.isCopy.isChecked()) spec = True + elif txt.upper().endswith(inCommandShortcuts["SubelementMode"][0]): + if self.isSubelementMode.isVisible(): + self.isSubelementMode.setChecked(not self.isSubelementMode.isChecked()) + spec = True if spec: for i,k in enumerate([self.xValue,self.yValue,self.zValue,self.lengthValue,self.angleValue]): if (k.property("text") == txt): diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 35eaaa220a..893a2d5597 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -217,8 +217,8 @@ class DraftTool: else: return False - def Activated(self,name="None",noplanesetup=False): - if FreeCAD.activeDraftCommand: + def Activated(self, name="None", noplanesetup=False, is_subtool=False): + if FreeCAD.activeDraftCommand and not is_subtool: FreeCAD.activeDraftCommand.finish() global Part, DraftGeomUtils @@ -2553,115 +2553,134 @@ class Move(Modifier): def __init__(self): Modifier.__init__(self) - self.copymode = False def GetResources(self): return {'Pixmap' : 'Draft_Move', 'Accel' : "M, V", 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Move", "Move"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Move", "Moves the selected objects between 2 points. CTRL to snap, SHIFT to constrain, ALT to copy")} + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Move", "Moves the selected objects between 2 points. CTRL to snap, SHIFT to constrain")} def Activated(self): self.name = translate("draft","Move", utf8_decode=True) - if not isinstance(FreeCAD.activeDraftCommand, EditImproved): - Modifier.Activated(self,self.name) - else: - self.ui = FreeCADGui.draftToolBar - self.view = Draft.get3DView() - self.call = None - self.node = [] - self.extendedCopy = False - self.featureName = "Edit_Improved" - self.planetrack = None - self.ghost = None - if self.ui: - if not FreeCADGui.Selection.getSelection(): - self.ui.selectUi() - msg(translate("draft", "Select an object to move")+"\n") - self.call = self.view.addEventCallback("SoEvent",selectObject) - else: - self.proceed() + Modifier.Activated(self, self.name, is_subtool=isinstance(FreeCAD.activeDraftCommand, EditImproved)) + if not self.ui: + return + self.ghosts = [] + self.get_object_selection() + + def get_object_selection(self): + if FreeCADGui.Selection.getSelectionEx(): + return self.proceed() + self.ui.selectUi() + msg(translate("draft", "Select an object to move")+"\n") + self.call = self.view.addEventCallback("SoEvent", selectObject) def proceed(self): - if self.call: self.view.removeEventCallback("SoEvent",self.call) - self.sel = FreeCADGui.Selection.getSelection() - self.sel = Draft.getGroupContents(self.sel,addgroups=True,spaces=True,noarchchild=True) + if self.call: + self.view.removeEventCallback("SoEvent",self.call) + self.selected_objects = FreeCADGui.Selection.getSelection() + self.selected_objects = Draft.getGroupContents(self.selected_objects, addgroups=True, spaces=True, noarchchild=True) + self.selected_subelements = FreeCADGui.Selection.getSelectionEx() self.ui.pointUi(self.name) self.ui.modUi() - if self.copymode: - self.ui.isCopy.setChecked(True) self.ui.xValue.setFocus() self.ui.xValue.selectAll() - self.ghost = ghostTracker(self.sel) - self.call = self.view.addEventCallback("SoEvent",self.action) + self.call = self.view.addEventCallback("SoEvent", self.action) msg(translate("draft", "Pick start point:")+"\n") def finish(self,closed=False,cont=False): - if self.ghost: - self.ghost.finalize() + for ghost in self.ghosts: + ghost.finalize() if cont and self.ui: if self.ui.continueMode: todo.delayAfter(self.Activated,[]) Modifier.finish(self) - def move(self,delta,copy=False): - "moving the real shapes" - sel = '[' - for o in self.sel: - if len(sel) > 1: - sel += ',' - sel += 'FreeCAD.ActiveDocument.'+o.Name - sel += ']' - FreeCADGui.addModule("Draft") - if copy: - self.commit(translate("draft","Copy"), - ['Draft.move('+sel+','+DraftVecUtils.toString(delta)+',copy='+str(copy)+')', - 'FreeCAD.ActiveDocument.recompute()']) - else: - self.commit(translate("draft","Move"), - ['Draft.move('+sel+','+DraftVecUtils.toString(delta)+',copy='+str(copy)+')', - '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 - if self.ghost: - self.ghost.off() - self.point,ctrlPoint,info = getPoint(self,arg) - if (len(self.node) > 0): - last = self.node[len(self.node)-1] - delta = self.point.sub(last) - if self.ghost: - self.ghost.move(delta) - self.ghost.on() - if self.extendedCopy: - if not hasMod(arg,MODALT): self.finish() - redraw3DView() - 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:")+"\n") - if self.planetrack: - self.planetrack.set(self.point) - else: - last = self.node[0] - if self.ui.isCopy.isChecked() or hasMod(arg,MODALT): - self.move(self.point.sub(last),True) - else: - self.move(self.point.sub(last)) - if hasMod(arg,MODALT): - self.extendedCopy = True - else: - self.finish(cont=True) + if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": + self.finish() + elif arg["Type"] == "SoLocation2Event": + self.handle_mouse_move_event(arg) + elif arg["Type"] == "SoMouseButtonEvent" \ + and arg["State"] == "DOWN" \ + and arg["Button"] == "BUTTON1": + self.handle_mouse_click_event(arg) + + def handle_mouse_move_event(self, arg): + for ghost in self.ghosts: + ghost.off() + self.point, ctrlPoint, info = getPoint(self,arg) + if (len(self.node) > 0): + last = self.node[len(self.node)-1] + delta = self.point.sub(last) + for ghost in self.ghosts: + ghost.move(delta) + ghost.on() + if self.extendedCopy: + if not hasMod(arg,MODALT): self.finish() + redraw3DView() + + def handle_mouse_click_event(self, arg): + if not self.ghosts: + self.set_ghost() + if not self.point: + return + self.ui.redraw() + if self.node == []: + self.node.append(self.point) + self.ui.isRelative.show() + for ghost in self.ghosts: + ghost.on() + msg(translate("draft", "Pick end point:")+"\n") + if self.planetrack: + self.planetrack.set(self.point) + else: + last = self.node[0] + self.move(self.point.sub(last)) + if hasMod(arg,MODALT): + self.extendedCopy = True + else: + self.finish(cont=True) + + def set_ghost(self): + if self.ui.isSubelementMode.isChecked(): + return self.set_subelement_ghosts() + self.ghosts = [ghostTracker(self.selected_objects)] + + def set_subelement_ghosts(self): + import Part + for object in self.selected_subelements: + for subelement in object.SubObjects: + if isinstance(subelement, Part.Vertex) \ + or isinstance(subelement, Part.Edge): + ghost = ghostTracker(subelement) + ghost.on() + self.ghosts.append(ghost) + + def move(self, delta): + if self.ui.isSubelementMode.isChecked(): + self.move_subelements(delta) + else: + self.move_object(delta) + + def move_subelements(self, delta): + for object in self.selected_subelements: + for index, subelement in enumerate(object.SubObjects): + if isinstance(subelement, Part.Vertex): + Draft.moveVertex(getattr(FreeCAD.ActiveDocument, object.ObjectName), + int(object.SubElementNames[index][len("Vertex"):])-1, + subelement.Point, delta) + elif isinstance(subelement, Part.Edge): + Draft.moveEdge(getattr(FreeCAD.ActiveDocument, object.ObjectName), + int(object.SubElementNames[index][len("Edge"):])-1, + subelement, delta) + + def move_object(self, delta): + objects = '[' + ','.join(['FreeCAD.ActiveDocument.' + object.Name for object in self.selected_objects]) + ']' + FreeCADGui.addModule("Draft") + self.commit(translate("draft","Copy" if self.ui.isCopy.isChecked() else "Move"), + ['Draft.move('+objects+','+DraftVecUtils.toString(delta)+',copy='+str(self.ui.isCopy.isChecked())+')', 'FreeCAD.ActiveDocument.recompute()']) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" @@ -2680,7 +2699,6 @@ class Move(Modifier): self.move(self.point.sub(last)) self.finish() - class ApplyStyle(Modifier): "The Draft_ApplyStyle FreeCA command definition" From ba1f045065be5b2b78f80617c1c3c8e7ce55c3c8 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 21 Feb 2019 21:36:10 +1100 Subject: [PATCH 06/18] Make movement vector a class variable of the move command, show error for unmovable elements, and move action into a commit to allow undo/redo. --- src/Mod/Draft/Draft.py | 3 +-- src/Mod/Draft/DraftTools.py | 46 +++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index c7904dc5e2..6b55644cc3 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1492,9 +1492,8 @@ def moveVertex(object, vertex_index, vertex, vector): points = object.Points points[vertex_index] = object.Placement.inverse().multVec(vertex).add(vector) object.Points = points - FreeCAD.ActiveDocument.recompute() -def moveEdge(object, edge_index, edge, vector): +def moveEdge(object, edge_index, vector): moveVertex(object, edge_index, object.Placement.multVec(object.Points[edge_index]), vector) moveVertex(object, edge_index+1, object.Placement.multVec(object.Points[edge_index+1]), vector) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 893a2d5597..aca8d1fcd5 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2613,9 +2613,9 @@ class Move(Modifier): self.point, ctrlPoint, info = getPoint(self,arg) if (len(self.node) > 0): last = self.node[len(self.node)-1] - delta = self.point.sub(last) + self.vector = self.point.sub(last) for ghost in self.ghosts: - ghost.move(delta) + ghost.move(self.vector) ghost.on() if self.extendedCopy: if not hasMod(arg,MODALT): self.finish() @@ -2637,7 +2637,8 @@ class Move(Modifier): self.planetrack.set(self.point) else: last = self.node[0] - self.move(self.point.sub(last)) + self.vector = self.point.sub(last) + self.move() if hasMod(arg,MODALT): self.extendedCopy = True else: @@ -2658,29 +2659,44 @@ class Move(Modifier): ghost.on() self.ghosts.append(ghost) - def move(self, delta): + def move(self): if self.ui.isSubelementMode.isChecked(): - self.move_subelements(delta) + self.move_subelements() else: - self.move_object(delta) + self.move_object() - def move_subelements(self, delta): + def move_subelements(self): + try: + self.commit(translate("draft", "Move"), self.build_move_subelements_command()) + except: + FreeCAD.Console.PrintError(translate("draft", "Some subelements could not be moved.")) + + def build_move_subelements_command(self): + import Part + command = [] for object in self.selected_subelements: for index, subelement in enumerate(object.SubObjects): if isinstance(subelement, Part.Vertex): - Draft.moveVertex(getattr(FreeCAD.ActiveDocument, object.ObjectName), - int(object.SubElementNames[index][len("Vertex"):])-1, - subelement.Point, delta) + command.append('Draft.moveVertex(FreeCAD.ActiveDocument.{}, {}, {}, {})'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Vertex"):])-1, + DraftVecUtils.toString(subelement.Point), + DraftVecUtils.toString(self.vector) + )) elif isinstance(subelement, Part.Edge): - Draft.moveEdge(getattr(FreeCAD.ActiveDocument, object.ObjectName), - int(object.SubElementNames[index][len("Edge"):])-1, - subelement, delta) + command.append('Draft.moveEdge(FreeCAD.ActiveDocument.{}, {}, {})'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Edge"):])-1, + DraftVecUtils.toString(self.vector) + )) + command.append('FreeCAD.ActiveDocument.recompute()') + return command - def move_object(self, delta): + def move_object(self): objects = '[' + ','.join(['FreeCAD.ActiveDocument.' + object.Name for object in self.selected_objects]) + ']' FreeCADGui.addModule("Draft") self.commit(translate("draft","Copy" if self.ui.isCopy.isChecked() else "Move"), - ['Draft.move('+objects+','+DraftVecUtils.toString(delta)+',copy='+str(self.ui.isCopy.isChecked())+')', 'FreeCAD.ActiveDocument.recompute()']) + ['Draft.move('+objects+','+DraftVecUtils.toString(self.vector)+',copy='+str(self.ui.isCopy.isChecked())+')', 'FreeCAD.ActiveDocument.recompute()']) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" From 7ecc9cfb21af64f1b9d2aabd814fadc98690a020 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 23 Feb 2019 22:26:38 +1100 Subject: [PATCH 07/18] Add python binding for setPreselect function via Selection.SetPreselection() --- src/Gui/Selection.cpp | 25 +++++++++++++++++++++++++ src/Gui/Selection.h | 1 + 2 files changed, 26 insertions(+) diff --git a/src/Gui/Selection.cpp b/src/Gui/Selection.cpp index 44b6b7e581..d11a35d4bb 100644 --- a/src/Gui/Selection.cpp +++ b/src/Gui/Selection.cpp @@ -1077,6 +1077,8 @@ PyMethodDef SelectionSingleton::Methods[] = { "given the complete selection is cleared."}, {"isSelected", (PyCFunction) SelectionSingleton::sIsSelected, METH_VARARGS, "isSelected(object) -- Check if a given object is selected"}, + {"setPreselection", (PyCFunction) SelectionSingleton::sSetPreselection, METH_VARARGS, + "setPreselection() -- Set preselected object"}, {"getPreselection", (PyCFunction) SelectionSingleton::sGetPreselection, METH_VARARGS, "getPreselection() -- Get preselected object"}, {"clearPreselection", (PyCFunction) SelectionSingleton::sRemPreselection, METH_VARARGS, @@ -1263,6 +1265,29 @@ PyObject *SelectionSingleton::sGetSelection(PyObject * /*self*/, PyObject *args) } } +PyObject *SelectionSingleton::sSetPreselection(PyObject * /*self*/, PyObject *args) +{ + PyObject *object; + char* subname=0; + float x=0,y=0,z=0; + if (PyArg_ParseTuple(args, "O!|sfff", &(App::DocumentObjectPy::Type),&object,&subname,&x,&y,&z)) { + App::DocumentObjectPy* docObjPy = static_cast(object); + App::DocumentObject* docObj = docObjPy->getDocumentObjectPtr(); + if (!docObj || !docObj->getNameInDocument()) { + PyErr_SetString(Base::BaseExceptionFreeCADError, "Cannot check invalid object"); + return NULL; + } + + Selection().setPreselect(docObj->getDocument()->getName(), + docObj->getNameInDocument(), + subname,x,y,z); + Py_Return; + } + + PyErr_SetString(PyExc_ValueError, "type must be 'DocumentObject[,subname[,x,y,z]]'"); + return 0; +} + PyObject *SelectionSingleton::sGetPreselection(PyObject * /*self*/, PyObject *args) { if (!PyArg_ParseTuple(args, "")) diff --git a/src/Gui/Selection.h b/src/Gui/Selection.h index f5f69391ec..2a8c87e731 100644 --- a/src/Gui/Selection.h +++ b/src/Gui/Selection.h @@ -341,6 +341,7 @@ protected: static PyObject *sIsSelected (PyObject *self,PyObject *args); static PyObject *sCountObjectsOfType (PyObject *self,PyObject *args); static PyObject *sGetSelection (PyObject *self,PyObject *args); + static PyObject *sSetPreselection (PyObject *self,PyObject *args); static PyObject *sGetPreselection (PyObject *self,PyObject *args); static PyObject *sRemPreselection (PyObject *self,PyObject *args); static PyObject *sGetCompleteSelection(PyObject *self,PyObject *args); From 7da48b33071d53442abf28d4a5edc485838f656d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 23 Feb 2019 22:27:33 +1100 Subject: [PATCH 08/18] Actually highlight preselected objects upon the selectionchange event, otherwise it only triggers on mousemove event --- src/Gui/SoFCUnifiedSelection.cpp | 16 ++++++++++++++++ src/Gui/View3DInventorViewer.cpp | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Gui/SoFCUnifiedSelection.cpp b/src/Gui/SoFCUnifiedSelection.cpp index a3065fc10d..e0cbaf5260 100644 --- a/src/Gui/SoFCUnifiedSelection.cpp +++ b/src/Gui/SoFCUnifiedSelection.cpp @@ -374,6 +374,22 @@ void SoFCUnifiedSelection::doAction(SoAction *action) currenthighlight->unref(); currenthighlight = 0; } + } else if (hilaction->SelChange.Type == SelectionChanges::SetPreselect) { + App::Document* doc = App::GetApplication().getDocument(hilaction->SelChange.pDocName); + App::DocumentObject* obj = doc->getObject(hilaction->SelChange.pObjectName); + ViewProvider*vp = Application::Instance->getViewProvider(obj); + SoDetail* detail = vp->getDetail(hilaction->SelChange.pSubName); + SoHighlightElementAction action; + action.setHighlighted(true); + action.setColor(this->colorHighlight.getValue()); + action.setElement(detail); + action.apply(vp->getRoot()); + delete detail; + SoSearchAction sa; + sa.setNode(vp->getRoot()); + sa.apply(vp->getRoot()); + currenthighlight = static_cast(sa.getPath()->copy()); + currenthighlight->ref(); } } diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index 3798afb28b..61add44527 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -643,7 +643,8 @@ void View3DInventorViewer::OnChange(Gui::SelectionSingleton::SubjectType& rCalle SoFCSelectionAction cAct(Reason); cAct.apply(pcViewProviderRoot); } - else if (Reason.Type == SelectionChanges::RmvPreselect) { + else if (Reason.Type == SelectionChanges::RmvPreselect || + Reason.Type == SelectionChanges::SetPreselect) { SoFCHighlightAction cAct(Reason); cAct.apply(pcViewProviderRoot); } From 985dbaead892e7ab0e1343b46bf95c38d435ab19 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 24 Feb 2019 10:22:48 +1100 Subject: [PATCH 09/18] Clear existing preselects when setting a new preselect --- src/Gui/SoFCUnifiedSelection.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Gui/SoFCUnifiedSelection.cpp b/src/Gui/SoFCUnifiedSelection.cpp index e0cbaf5260..ac4d1e3e45 100644 --- a/src/Gui/SoFCUnifiedSelection.cpp +++ b/src/Gui/SoFCUnifiedSelection.cpp @@ -375,6 +375,12 @@ void SoFCUnifiedSelection::doAction(SoAction *action) currenthighlight = 0; } } else if (hilaction->SelChange.Type == SelectionChanges::SetPreselect) { + if (currenthighlight) { + SoHighlightElementAction action; + action.apply(currenthighlight); + currenthighlight->unref(); + currenthighlight = 0; + } App::Document* doc = App::GetApplication().getDocument(hilaction->SelChange.pDocName); App::DocumentObject* obj = doc->getObject(hilaction->SelChange.pObjectName); ViewProvider*vp = Application::Instance->getViewProvider(obj); From be65fbf8ffdb671734897680706dc7457fd9675e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 24 Feb 2019 10:23:52 +1100 Subject: [PATCH 10/18] Fix move edge to work on closed wires --- src/Mod/Draft/Draft.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 6b55644cc3..1bace3522a 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1495,7 +1495,13 @@ def moveVertex(object, vertex_index, vertex, vector): def moveEdge(object, edge_index, vector): moveVertex(object, edge_index, object.Placement.multVec(object.Points[edge_index]), vector) - moveVertex(object, edge_index+1, object.Placement.multVec(object.Points[edge_index+1]), vector) + if isClosedEdge(edge_index, object): + moveVertex(object, 0, object.Placement.multVec(object.Points[0]), vector) + else: + moveVertex(object, edge_index+1, object.Placement.multVec(object.Points[edge_index+1]), vector) + +def isClosedEdge(edge_index, object): + return edge_index + 1 >= len(object.Points) def move(objectslist,vector,copy=False): '''move(objects,vector,[copy]): Moves the objects contained From 69646518df929d791a6003131c1155cd5ff592c5 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 24 Feb 2019 12:20:33 +1100 Subject: [PATCH 11/18] Show draft objects as an x-ray view, and edit bases if they exist. --- src/Mod/Draft/DraftTools.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index aca8d1fcd5..f8f07db8fd 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -4306,22 +4306,34 @@ class EditImproved(Modifier): for object in FreeCADGui.Selection.getSelection(): if object.isDerivedFrom("Part::Part2DObject"): self.editable_objects.append(object) + elif hasattr(object, "Base") and object.Base.isDerivedFrom("Part::Part2DObject"): + self.editable_objects.append(object.Base) def highlight_editable_objects(self): for object in self.editable_objects: self.original_view_settings[object.Name] = { + 'Visibility': object.ViewObject.Visibility, 'PointSize': object.ViewObject.PointSize, 'PointColor': object.ViewObject.PointColor, 'LineColor': object.ViewObject.LineColor } + object.ViewObject.Visibility = True object.ViewObject.PointSize = 10 object.ViewObject.PointColor = (1., 0., 0.) object.ViewObject.LineColor = (1., 0., 0.) + xray = coin.SoAnnotation() + xray.addChild(object.ViewObject.RootNode.getChild(2).getChild(0)) + xray.setName("xray") + object.ViewObject.RootNode.addChild(xray) def restore_editable_objects_graphics(self): for object in self.editable_objects: + if not object.Name: + continue for attribute, value in self.original_view_settings[object.Name].items(): - setattr(object.ViewObject, attribute, value) + view_object = object.ViewObject + setattr(view_object, attribute, value) + view_object.RootNode.removeChild(view_object.RootNode.getByName("xray")) class Edit(Modifier): "The Draft_Edit FreeCAD command definition" From 547851ccf6fa0daed9f5c1b2df295d8ecbf32620 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 24 Feb 2019 19:38:12 +1100 Subject: [PATCH 12/18] If wires are merged, deleted, or otherwise had graph changing operations while edit mode is active, don't complain --- src/Mod/Draft/DraftTools.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index f8f07db8fd..59d67f18f0 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -4328,12 +4328,14 @@ class EditImproved(Modifier): def restore_editable_objects_graphics(self): for object in self.editable_objects: - if not object.Name: - continue - for attribute, value in self.original_view_settings[object.Name].items(): - view_object = object.ViewObject - setattr(view_object, attribute, value) - view_object.RootNode.removeChild(view_object.RootNode.getByName("xray")) + try: + for attribute, value in self.original_view_settings[object.Name].items(): + view_object = object.ViewObject + setattr(view_object, attribute, value) + view_object.RootNode.removeChild(view_object.RootNode.getByName("xray")) + except: + # This can occur if objects have had graph changing operations + pass class Edit(Modifier): "The Draft_Edit FreeCAD command definition" From 850137711938fc8cdca4cbb8d5036f3871e601e3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 24 Feb 2019 22:09:44 +1100 Subject: [PATCH 13/18] Support copying subelements --- src/Mod/Draft/Draft.py | 14 ++++++++++++++ src/Mod/Draft/DraftTools.py | 22 +++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 1bace3522a..dd1ccddcbc 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1500,6 +1500,20 @@ def moveEdge(object, edge_index, vector): else: moveVertex(object, edge_index+1, object.Placement.multVec(object.Points[edge_index+1]), vector) +def copyEdges(copy_edge_arguments): + copied_edges = [] + for argument in copy_edge_arguments: + copied_edges.append(copyEdge(argument[0], argument[1], argument[2])) + joinWires(copied_edges) + +def copyEdge(object, edge_index, vector): + vertex1 = object.Placement.multVec(object.Points[edge_index]).add(vector) + if isClosedEdge(edge_index, object): + vertex2 = object.Placement.multVec(object.Points[0]).add(vector) + else: + vertex2 = object.Placement.multVec(object.Points[edge_index+1]).add(vector) + return makeLine(vertex1, vertex2) + def isClosedEdge(edge_index, object): return edge_index + 1 >= len(object.Points) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 59d67f18f0..ec03ec286d 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2667,10 +2667,30 @@ class Move(Modifier): def move_subelements(self): try: - self.commit(translate("draft", "Move"), self.build_move_subelements_command()) + if self.ui.isCopy.isChecked(): + self.commit(translate("draft", "Copy"), self.build_copy_subelements_command()) + else: + self.commit(translate("draft", "Move"), self.build_move_subelements_command()) except: FreeCAD.Console.PrintError(translate("draft", "Some subelements could not be moved.")) + def build_copy_subelements_command(self): + import Part + command = [] + copy_edge_arguments = [] + for object in self.selected_subelements: + for index, subelement in enumerate(object.SubObjects): + if not isinstance(subelement, Part.Edge): + continue + copy_edge_arguments.append('[FreeCAD.ActiveDocument.{}, {}, {}]'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Edge"):])-1, + DraftVecUtils.toString(self.vector) + )) + command.append('Draft.copyEdges([{}])'.format(','.join(copy_edge_arguments))) + command.append('FreeCAD.ActiveDocument.recompute()') + return command + def build_move_subelements_command(self): import Part command = [] From bc3af1afbc3a8125c73f9f902730d4dbbb4bc08e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 25 Feb 2019 21:44:15 +1100 Subject: [PATCH 14/18] Code simplification of move subelements command --- src/Mod/Draft/Draft.py | 10 +++++----- src/Mod/Draft/DraftTools.py | 11 ++++------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index dd1ccddcbc..666a32db1e 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1488,17 +1488,17 @@ def cut(object1,object2): return obj -def moveVertex(object, vertex_index, vertex, vector): +def moveVertex(object, vertex_index, vector): points = object.Points - points[vertex_index] = object.Placement.inverse().multVec(vertex).add(vector) + points[vertex_index] = points[vertex_index].add(vector) object.Points = points def moveEdge(object, edge_index, vector): - moveVertex(object, edge_index, object.Placement.multVec(object.Points[edge_index]), vector) + moveVertex(object, edge_index, vector) if isClosedEdge(edge_index, object): - moveVertex(object, 0, object.Placement.multVec(object.Points[0]), vector) + moveVertex(object, 0, vector) else: - moveVertex(object, edge_index+1, object.Placement.multVec(object.Points[edge_index+1]), vector) + moveVertex(object, edge_index+1, vector) def copyEdges(copy_edge_arguments): copied_edges = [] diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index ec03ec286d..869fe30b52 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2623,7 +2623,7 @@ class Move(Modifier): def handle_mouse_click_event(self, arg): if not self.ghosts: - self.set_ghost() + self.set_ghosts() if not self.point: return self.ui.redraw() @@ -2644,7 +2644,7 @@ class Move(Modifier): else: self.finish(cont=True) - def set_ghost(self): + def set_ghosts(self): if self.ui.isSubelementMode.isChecked(): return self.set_subelement_ghosts() self.ghosts = [ghostTracker(self.selected_objects)] @@ -2655,9 +2655,7 @@ class Move(Modifier): for subelement in object.SubObjects: if isinstance(subelement, Part.Vertex) \ or isinstance(subelement, Part.Edge): - ghost = ghostTracker(subelement) - ghost.on() - self.ghosts.append(ghost) + self.ghosts.append(ghostTracker(subelement)) def move(self): if self.ui.isSubelementMode.isChecked(): @@ -2697,10 +2695,9 @@ class Move(Modifier): for object in self.selected_subelements: for index, subelement in enumerate(object.SubObjects): if isinstance(subelement, Part.Vertex): - command.append('Draft.moveVertex(FreeCAD.ActiveDocument.{}, {}, {}, {})'.format( + command.append('Draft.moveVertex(FreeCAD.ActiveDocument.{}, {}, {})'.format( object.ObjectName, int(object.SubElementNames[index][len("Vertex"):])-1, - DraftVecUtils.toString(subelement.Point), DraftVecUtils.toString(self.vector) )) elif isinstance(subelement, Part.Edge): From 91438c1688fb0c5c0438e94f44affddd32413ffa Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 25 Feb 2019 21:51:14 +1100 Subject: [PATCH 15/18] Add subelement support for rotating individual vertices --- src/Mod/Draft/Draft.py | 9 ++ src/Mod/Draft/DraftTools.py | 300 ++++++++++++++++++++++-------------- 2 files changed, 190 insertions(+), 119 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 666a32db1e..e26737159a 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1680,6 +1680,15 @@ def filterObjectsForModifiers(objects, isCopied=False): filteredObjects.append(object) return filteredObjects +def rotateVertex(object, vertex_index, angle, center, axis): + points = object.Points + v = object.Placement.multVec(points[vertex_index]) + rv = v.sub(center) + rv = DraftVecUtils.rotate(rv, math.radians(angle), axis) + v = center.add(rv) + points[vertex_index] = object.Placement.inverse().multVec(v) + object.Points = points + def rotate(objectslist,angle,center=Vector(0,0,0),axis=Vector(0,0,1),copy=False): '''rotate(objects,angle,[center,axis,copy]): Rotates the objects contained in objects (that can be a list of objects or an object) of the given angle diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 869fe30b52..d64727c067 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2780,36 +2780,158 @@ class Rotate(Modifier): def Activated(self): Modifier.Activated(self,"Rotate") - self.ghost = None + if not self.ui: + return + self.ghosts = [] self.arctrack = None - if self.ui: - if not FreeCADGui.Selection.getSelection(): - self.ui.selectUi() - msg(translate("draft", "Select an object to rotate")+"\n") - self.call = self.view.addEventCallback("SoEvent",selectObject) - else: - self.proceed() + self.get_object_selection() + + def get_object_selection(self): + if FreeCADGui.Selection.getSelection(): + return self.proceed() + self.ui.selectUi() + msg(translate("draft", "Select an object to rotate")+"\n") + self.call = self.view.addEventCallback("SoEvent", selectObject) def proceed(self): - if self.call: self.view.removeEventCallback("SoEvent",self.call) - self.sel = FreeCADGui.Selection.getSelection() - self.sel = Draft.getGroupContents(self.sel,addgroups=True,spaces=True,noarchchild=True) + if self.call: + self.view.removeEventCallback("SoEvent", self.call) + self.selected_objects = FreeCADGui.Selection.getSelection() + self.selected_objects = Draft.getGroupContents(self.selected_objects, addgroups=True, spaces=True, noarchchild=True) + self.selected_subelements = FreeCADGui.Selection.getSelectionEx() self.step = 0 self.center = None self.ui.arcUi() self.ui.modUi() self.ui.setTitle("Rotate") self.arctrack = arcTracker() - self.ghost = ghostTracker(self.sel) self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick rotation center:")+"\n") - def finish(self,closed=False,cont=False): + def action(self, arg): + "scene event handler" + if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": + self.finish() + elif arg["Type"] == "SoLocation2Event": + self.handle_mouse_move_event(arg) + elif arg["Type"] == "SoMouseButtonEvent" \ + and arg["State"] == "DOWN" \ + and arg["Button"] == "BUTTON1": + self.handle_mouse_click_event(arg) + + def handle_mouse_move_event(self, arg): + for ghost in self.ghosts: + ghost.off() + self.point,ctrlPoint,info = getPoint(self,arg) + # this is to make sure radius is what you see on screen + if self.center and DraftVecUtils.dist(self.point,self.center): + viewdelta = DraftVecUtils.project(self.point.sub(self.center), plane.axis) + if not DraftVecUtils.isNull(viewdelta): + self.point = self.point.add(viewdelta.negative()) + if self.extendedCopy: + if not hasMod(arg,MODALT): + self.step = 3 + self.finish() + if (self.step == 0): + pass + elif (self.step == 1): + currentrad = DraftVecUtils.dist(self.point,self.center) + if (currentrad != 0): + angle = DraftVecUtils.angle(plane.u, self.point.sub(self.center), plane.axis) + else: angle = 0 + self.ui.setRadiusValue(math.degrees(angle),unit="Angle") + self.firstangle = angle + self.ui.radiusValue.setFocus() + self.ui.radiusValue.selectAll() + elif (self.step == 2): + currentrad = DraftVecUtils.dist(self.point,self.center) + if (currentrad != 0): + angle = DraftVecUtils.angle(plane.u, self.point.sub(self.center), plane.axis) + else: angle = 0 + if (angle < self.firstangle): + sweep = (2*math.pi-self.firstangle)+angle + else: + sweep = angle - self.firstangle + self.arctrack.setApertureAngle(sweep) + for ghost in self.ghosts: + ghost.rotate(plane.axis,sweep) + ghost.on() + self.ui.setRadiusValue(math.degrees(sweep), 'Angle') + self.ui.radiusValue.setFocus() + self.ui.radiusValue.selectAll() + redraw3DView() + + def handle_mouse_click_event(self, arg): + if not self.point: + return + if self.step == 0: + self.set_center() + elif self.step == 1: + self.set_start_point() + else: + self.set_rotation_angle(arg) + + def set_center(self): + if not self.ghosts: + self.set_ghosts() + self.center = self.point + self.node = [self.point] + self.ui.radiusUi() + self.ui.radiusValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Angle).UserString) + self.ui.hasFill.hide() + self.ui.labelRadius.setText("Base angle") + self.arctrack.setCenter(self.center) + for ghost in self.ghosts: + ghost.center(self.center) + self.step = 1 + msg(translate("draft", "Pick base angle:")+"\n") + if self.planetrack: + self.planetrack.set(self.point) + + def set_start_point(self): + self.ui.labelRadius.setText("Rotation") + self.rad = DraftVecUtils.dist(self.point,self.center) + self.arctrack.on() + self.arctrack.setStartPoint(self.point) + for ghost in self.ghosts: + ghost.on() + self.step = 2 + msg(translate("draft", "Pick rotation angle:")+"\n") + + def set_rotation_angle(self, arg): + currentrad = DraftVecUtils.dist(self.point,self.center) + angle = self.point.sub(self.center).getAngle(plane.u) + if DraftVecUtils.project(self.point.sub(self.center), plane.v).getAngle(plane.v) > 1: + angle = -angle + if (angle < self.firstangle): + self.angle = (2*math.pi-self.firstangle)+angle + else: + self.angle = angle - self.firstangle + self.rotate(self.ui.isCopy.isChecked() or hasMod(arg,MODALT)) + if hasMod(arg,MODALT): + self.extendedCopy = True + else: + self.finish(cont=True) + + def set_ghosts(self): + if self.ui.isSubelementMode.isChecked(): + return self.set_subelement_ghosts() + self.ghosts = [ghostTracker(self.selected_objects)] + + def set_subelement_ghosts(self): + import Part + for object in self.selected_subelements: + for subelement in object.SubObjects: + if isinstance(subelement, Part.Vertex) \ + or isinstance(subelement, Part.Edge): + self.ghosts.append(ghostTracker(subelement)) + + def finish(self, closed=False, cont=False): "finishes the arc" if self.arctrack: self.arctrack.finalize() - if self.ghost: - self.ghost.finalize() + for ghost in self.ghosts: + ghost.finalize() if cont and self.ui: if self.ui.continueMode: todo.delayAfter(self.Activated,[]) @@ -2817,112 +2939,52 @@ class Rotate(Modifier): if self.doc: self.doc.recompute() - def rot (self,angle,copy=False): - "rotating the real shapes" - sel = '[' - for o in self.sel: - if len(sel) > 1: - sel += ',' - sel += 'FreeCAD.ActiveDocument.'+o.Name - sel += ']' - FreeCADGui.addModule("Draft") - if copy: - self.commit(translate("draft","Copy"), - ['Draft.rotate('+sel+','+str(math.degrees(angle))+','+DraftVecUtils.toString(self.center)+',axis='+DraftVecUtils.toString(plane.axis)+',copy='+str(copy)+')']) + def rotate(self, is_copy=False): + if self.ui.isSubelementMode.isChecked(): + self.rotate_subelements(is_copy) else: - self.commit(translate("draft","Rotate"), - ['Draft.rotate('+sel+','+str(math.degrees(angle))+','+DraftVecUtils.toString(self.center)+',axis='+DraftVecUtils.toString(plane.axis)+',copy='+str(copy)+')']) + self.rotate_object(is_copy) - def action(self,arg): - "scene event handler" - if arg["Type"] == "SoKeyboardEvent": - if arg["Key"] == "ESCAPE": - self.finish() - elif arg["Type"] == "SoLocation2Event": - if self.ghost: - self.ghost.off() - self.point,ctrlPoint,info = getPoint(self,arg) - # this is to make sure radius is what you see on screen - if self.center and DraftVecUtils.dist(self.point,self.center): - viewdelta = DraftVecUtils.project(self.point.sub(self.center), plane.axis) - if not DraftVecUtils.isNull(viewdelta): - self.point = self.point.add(viewdelta.negative()) - if self.extendedCopy: - if not hasMod(arg,MODALT): - self.step = 3 - self.finish() - if (self.step == 0): - pass - elif (self.step == 1): - currentrad = DraftVecUtils.dist(self.point,self.center) - if (currentrad != 0): - angle = DraftVecUtils.angle(plane.u, self.point.sub(self.center), plane.axis) - else: angle = 0 - self.ui.setRadiusValue(math.degrees(angle),unit="Angle") - self.firstangle = angle - self.ui.radiusValue.setFocus() - self.ui.radiusValue.selectAll() - elif (self.step == 2): - currentrad = DraftVecUtils.dist(self.point,self.center) - if (currentrad != 0): - angle = DraftVecUtils.angle(plane.u, self.point.sub(self.center), plane.axis) - else: angle = 0 - if (angle < self.firstangle): - sweep = (2*math.pi-self.firstangle)+angle - else: - sweep = angle - self.firstangle - self.arctrack.setApertureAngle(sweep) - if self.ghost: - self.ghost.rotate(plane.axis,sweep) - self.ghost.on() - self.ui.setRadiusValue(math.degrees(sweep), 'Angle') - self.ui.radiusValue.setFocus() - self.ui.radiusValue.selectAll() - redraw3DView() + def rotate_subelements(self, is_copy): + self.commit(translate("draft", "Rotate"), self.build_rotate_subelements_command()) + return + try: + if self.ui.isCopy.isChecked(): + self.commit(translate("draft", "Copy"), self.build_copy_subelements_command()) + else: + self.commit(translate("draft", "Rotate"), self.build_rotate_subelements_command()) + except: + FreeCAD.Console.PrintError(translate("draft", "Some subelements could not be moved.")) - elif arg["Type"] == "SoMouseButtonEvent": - if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - if self.point: - if (self.step == 0): - self.center = self.point - self.node = [self.point] - self.ui.radiusUi() - self.ui.radiusValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Angle).UserString) - self.ui.hasFill.hide() - self.ui.labelRadius.setText("Base angle") - self.arctrack.setCenter(self.center) - if self.ghost: - self.ghost.center(self.center) - self.step = 1 - msg(translate("draft", "Pick base angle:")+"\n") - if self.planetrack: - self.planetrack.set(self.point) - elif (self.step == 1): - self.ui.labelRadius.setText("Rotation") - self.rad = DraftVecUtils.dist(self.point,self.center) - self.arctrack.on() - self.arctrack.setStartPoint(self.point) - if self.ghost: - self.ghost.on() - self.step = 2 - msg(translate("draft", "Pick rotation angle:")+"\n") - else: - currentrad = DraftVecUtils.dist(self.point,self.center) - angle = self.point.sub(self.center).getAngle(plane.u) - if DraftVecUtils.project(self.point.sub(self.center), plane.v).getAngle(plane.v) > 1: - angle = -angle - if (angle < self.firstangle): - sweep = (2*math.pi-self.firstangle)+angle - else: - sweep = angle - self.firstangle - if self.ui.isCopy.isChecked() or hasMod(arg,MODALT): - self.rot(sweep,True) - else: - self.rot(sweep) - if hasMod(arg,MODALT): - self.extendedCopy = True - else: - self.finish(cont=True) + def build_copy_subelements_command(self): + pass + + def build_rotate_subelements_command(self): + import Part + command = [] + for object in self.selected_subelements: + for index, subelement in enumerate(object.SubObjects): + if isinstance(subelement, Part.Vertex): + command.append('Draft.rotateVertex(FreeCAD.ActiveDocument.{}, {}, {}, {}, {})'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Vertex"):])-1, + math.degrees(self.angle), + DraftVecUtils.toString(self.center), + DraftVecUtils.toString(plane.axis))) + command.append('FreeCAD.ActiveDocument.recompute()') + return command + + def rotate_object(self, is_copy): + objects = '[' + ','.join(['FreeCAD.ActiveDocument.' + object.Name for object in self.selected_objects]) + ']' + FreeCADGui.addModule("Draft") + self.commit(translate("draft","Copy" if is_copy else "Rotate"), + ['Draft.rotate({},{},{},axis={},copy={})'.format( + objects, + math.degrees(self.angle), + DraftVecUtils.toString(self.center), + DraftVecUtils.toString(plane.axis), + is_copy + )]) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z have been entered there" @@ -2949,7 +3011,7 @@ class Rotate(Modifier): self.step = 2 msg(translate("draft", "Pick rotation angle:")+"\n") else: - self.rot(math.radians(rad),self.ui.isCopy.isChecked()) + self.rotate(math.radians(rad),self.ui.isCopy.isChecked()) self.finish(cont=True) From fd4992cb9af011676549c9f5485357c5bc0b8306 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 26 Feb 2019 21:55:19 +1100 Subject: [PATCH 16/18] Implement rotation with optional copy of draft subelements --- src/Mod/Draft/Draft.py | 50 ++++++++++++++++++++++++++++++------- src/Mod/Draft/DraftTools.py | 37 ++++++++++++++++++++------- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index e26737159a..61b5a805f9 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1500,13 +1500,13 @@ def moveEdge(object, edge_index, vector): else: moveVertex(object, edge_index+1, vector) -def copyEdges(copy_edge_arguments): +def copyMovedEdges(arguments): copied_edges = [] - for argument in copy_edge_arguments: - copied_edges.append(copyEdge(argument[0], argument[1], argument[2])) + for argument in arguments: + copied_edges.append(copyMovedEdge(argument[0], argument[1], argument[2])) joinWires(copied_edges) -def copyEdge(object, edge_index, vector): +def copyMovedEdge(object, edge_index, vector): vertex1 = object.Placement.multVec(object.Points[edge_index]).add(vector) if isClosedEdge(edge_index, object): vertex2 = object.Placement.multVec(object.Points[0]).add(vector) @@ -1514,6 +1514,27 @@ def copyEdge(object, edge_index, vector): vertex2 = object.Placement.multVec(object.Points[edge_index+1]).add(vector) return makeLine(vertex1, vertex2) +def copyRotatedEdges(arguments): + copied_edges = [] + for argument in arguments: + copied_edges.append(copyRotatedEdge(argument[0], argument[1], + argument[2], argument[3], argument[4])) + joinWires(copied_edges) + +def copyRotatedEdge(object, edge_index, angle, center, axis): + vertex1 = rotateVectorFromCenter( + object.Placement.multVec(object.Points[edge_index]), + angle, axis, center) + if isClosedEdge(edge_index, object): + vertex2 = rotateVectorFromCenter( + object.Placement.multVec(object.Points[0]), + angle, axis, center) + else: + vertex2 = rotateVectorFromCenter( + object.Placement.multVec(object.Points[edge_index+1]), + angle, axis, center) + return makeLine(vertex1, vertex2) + def isClosedEdge(edge_index, object): return edge_index + 1 >= len(object.Points) @@ -1682,13 +1703,24 @@ def filterObjectsForModifiers(objects, isCopied=False): def rotateVertex(object, vertex_index, angle, center, axis): points = object.Points - v = object.Placement.multVec(points[vertex_index]) - rv = v.sub(center) - rv = DraftVecUtils.rotate(rv, math.radians(angle), axis) - v = center.add(rv) - points[vertex_index] = object.Placement.inverse().multVec(v) + points[vertex_index] = object.Placement.inverse().multVec( + rotateVectorFromCenter( + object.Placement.multVec(points[vertex_index]), + angle, axis, center)) object.Points = points +def rotateVectorFromCenter(vector, angle, axis, center): + rv = vector.sub(center) + rv = DraftVecUtils.rotate(rv, math.radians(angle), axis) + return center.add(rv) + +def rotateEdge(object, edge_index, angle, center, axis): + rotateVertex(object, edge_index, angle, center, axis) + if isClosedEdge(edge_index, object): + rotateVertex(object, 0, angle, center, axis) + else: + rotateVertex(object, edge_index+1, angle, center, axis) + def rotate(objectslist,angle,center=Vector(0,0,0),axis=Vector(0,0,1),copy=False): '''rotate(objects,angle,[center,axis,copy]): Rotates the objects contained in objects (that can be a list of objects or an object) of the given angle diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index d64727c067..63685c14b0 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2675,17 +2675,16 @@ class Move(Modifier): def build_copy_subelements_command(self): import Part command = [] - copy_edge_arguments = [] + arguments = [] for object in self.selected_subelements: for index, subelement in enumerate(object.SubObjects): if not isinstance(subelement, Part.Edge): continue - copy_edge_arguments.append('[FreeCAD.ActiveDocument.{}, {}, {}]'.format( + arguments.append('[FreeCAD.ActiveDocument.{}, {}, {}]'.format( object.ObjectName, int(object.SubElementNames[index][len("Edge"):])-1, - DraftVecUtils.toString(self.vector) - )) - command.append('Draft.copyEdges([{}])'.format(','.join(copy_edge_arguments))) + DraftVecUtils.toString(self.vector))) + command.append('Draft.copyMovedEdges([{}])'.format(','.join(arguments))) command.append('FreeCAD.ActiveDocument.recompute()') return command @@ -2946,10 +2945,8 @@ class Rotate(Modifier): self.rotate_object(is_copy) def rotate_subelements(self, is_copy): - self.commit(translate("draft", "Rotate"), self.build_rotate_subelements_command()) - return try: - if self.ui.isCopy.isChecked(): + if is_copy: self.commit(translate("draft", "Copy"), self.build_copy_subelements_command()) else: self.commit(translate("draft", "Rotate"), self.build_rotate_subelements_command()) @@ -2957,7 +2954,22 @@ class Rotate(Modifier): FreeCAD.Console.PrintError(translate("draft", "Some subelements could not be moved.")) def build_copy_subelements_command(self): - pass + import Part + command = [] + arguments = [] + for object in self.selected_subelements: + for index, subelement in enumerate(object.SubObjects): + if not isinstance(subelement, Part.Edge): + continue + arguments.append('[FreeCAD.ActiveDocument.{}, {}, {}, {}, {}]'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Edge"):])-1, + math.degrees(self.angle), + DraftVecUtils.toString(self.center), + DraftVecUtils.toString(plane.axis))) + command.append('Draft.copyRotatedEdges([{}])'.format(','.join(arguments))) + command.append('FreeCAD.ActiveDocument.recompute()') + return command def build_rotate_subelements_command(self): import Part @@ -2971,6 +2983,13 @@ class Rotate(Modifier): math.degrees(self.angle), DraftVecUtils.toString(self.center), DraftVecUtils.toString(plane.axis))) + elif isinstance(subelement, Part.Edge): + command.append('Draft.rotateEdge(FreeCAD.ActiveDocument.{}, {}, {}, {}, {})'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Edge"):])-1, + math.degrees(self.angle), + DraftVecUtils.toString(self.center), + DraftVecUtils.toString(plane.axis))) command.append('FreeCAD.ActiveDocument.recompute()') return command From fc24bf14462054596763cbe1ecc70ac9165a9345 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 2 Mar 2019 22:00:36 +1100 Subject: [PATCH 17/18] Add subelement ghosts in scale command, and minor code cleanup I suspect the scale command itself is a little broken, so I need to fix that before adding subelement support --- src/Mod/Draft/Draft.py | 4 +- src/Mod/Draft/DraftTools.py | 103 +++++++++++++++++++++--------------- 2 files changed, 62 insertions(+), 45 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 61b5a805f9..64d62fb748 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1796,7 +1796,8 @@ def scale(objectslist,delta=Vector(1,1,1),center=Vector(0,0,0),copy=False,legacy given center. If legacy is True, direct (old) mode is used, otherwise a parametric copy is made. If copy is True, the actual objects are not moved, but copies are created instead. The objects (or their copies) are returned.''' - if not isinstance(objectslist,list): objectslist = [objectslist] + if not isinstance(objectslist, list): + objectslist = [objectslist] if legacy: newobjlist = [] for obj in objectslist: @@ -1834,7 +1835,6 @@ def scale(objectslist,delta=Vector(1,1,1),center=Vector(0,0,0),copy=False,legacy elif getType(obj) == "Wire": p = [] for v in sh.Vertexes: p.append(v.Point) - #print(p) newobj.Points = p elif getType(obj) == "BSpline": p = [] diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 63685c14b0..d674b1acdd 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -4113,30 +4113,47 @@ class Scale(Modifier): def Activated(self): self.name = translate("draft","Scale", utf8_decode=True) 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 scale")+"\n") - self.call = self.view.addEventCallback("SoEvent",selectObject) - else: - self.proceed() + if not self.ui: + return + self.ghosts = [] + self.get_object_selection() + + def get_object_selection(self): + if FreeCADGui.Selection.getSelection(): + return self.proceed() + self.ui.selectUi() + msg(translate("draft", "Select an object to scale")+"\n") + self.call = self.view.addEventCallback("SoEvent",selectObject) def proceed(self): - if self.call: self.view.removeEventCallback("SoEvent",self.call) - self.sel = FreeCADGui.Selection.getSelection() - self.sel = Draft.getGroupContents(self.sel) + if self.call: + self.view.removeEventCallback("SoEvent", self.call) + self.selected_objects = FreeCADGui.Selection.getSelection() + self.selected_objects = Draft.getGroupContents(self.selected_objects) + self.selected_subelements = FreeCADGui.Selection.getSelectionEx() self.refs = [] self.ui.pointUi(self.name) self.ui.modUi() self.ui.xValue.setFocus() self.ui.xValue.selectAll() - self.ghost = ghostTracker(self.sel) self.pickmode = False self.task = None - self.call = self.view.addEventCallback("SoEvent",self.action) + self.call = self.view.addEventCallback("SoEvent", self.action) msg(translate("draft", "Pick base point:")+"\n") + def set_ghosts(self): + if self.ui.isSubelementMode.isChecked(): + return self.set_subelement_ghosts() + self.ghosts = [ghostTracker(self.selected_objects)] + + def set_subelement_ghosts(self): + import Part + for object in self.selected_subelements: + for subelement in object.SubObjects: + if isinstance(subelement, Part.Vertex) \ + or isinstance(subelement, Part.Edge): + self.ghosts.append(ghostTracker(subelement)) + def pickRef(self): self.pickmode = True if self.node: @@ -4144,10 +4161,29 @@ class Scale(Modifier): msg(translate("draft", "Pick reference distance from base point:")+"\n") self.call = self.view.addEventCallback("SoEvent",self.action) + def action(self,arg): + "scene event handler" + if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": + self.finish() + elif arg["Type"] == "SoLocation2Event": + self.handle_mouse_move_event(arg) + elif arg["Type"] == "SoMouseButtonEvent" \ + and arg["State"] == "DOWN" \ + and (arg["Button"] == "BUTTON1") \ + and self.point: + if not self.ghosts: + self.set_ghosts() + self.numericInput(self.point.x, self.point.y, self.point.z) + + def handle_mouse_move_event(self, arg): + for ghost in self.ghosts: + ghost.off() + self.point, ctrlPoint, info = getPoint(self, arg, sym=True) + def finish(self,closed=False,cont=False): Modifier.finish(self) - if self.ghost: - self.ghost.finalize() + for ghost in self.ghosts: + ghost.finalize() def scale(self,x,y,z,rel,mode): delta = Vector(x,y,z) @@ -4162,16 +4198,10 @@ class Scale(Modifier): elif mode == 2: copy = True legacy = True - "moving the real shapes" - sel = '[' - for o in self.sel: - if len(sel) > 1: - sel += ',' - sel += 'FreeCAD.ActiveDocument.'+o.Name - sel += ']' + objects = '[' + ','.join(['FreeCAD.ActiveDocument.' + object.Name for object in self.selected_objects]) + ']' FreeCADGui.addModule("Draft") self.commit(translate("draft","Copy"), - ['Draft.scale('+sel+',delta='+DraftVecUtils.toString(delta)+',center='+DraftVecUtils.toString(self.node[0])+',copy='+str(copy)+',legacy='+str(legacy)+')', + ['Draft.scale('+objects+',delta='+DraftVecUtils.toString(delta)+',center='+DraftVecUtils.toString(self.node[0])+',copy='+str(copy)+',legacy='+str(legacy)+')', 'FreeCAD.ActiveDocument.recompute()']) self.finish() @@ -4179,28 +4209,15 @@ class Scale(Modifier): delta = Vector(x,y,z) if rel: delta = FreeCAD.DraftWorkingPlane.getGlobalCoords(delta) - self.ghost.scale(delta) + for ghost in self.ghosts: + ghost.scale(delta) # calculate a correction factor depending on the scaling center corr = Vector(self.node[0].x,self.node[0].y,self.node[0].z) corr.scale(delta.x,delta.y,delta.z) corr = (corr.sub(self.node[0])).negative() - self.ghost.move(corr) - self.ghost.on() - - def action(self,arg): - "scene event handler" - if arg["Type"] == "SoKeyboardEvent": - if arg["Key"] == "ESCAPE": - self.finish() - elif arg["Type"] == "SoLocation2Event": #mouse movement detection - if self.ghost: - self.ghost.off() - self.point,ctrlPoint,info = getPoint(self,arg,sym=True) - elif arg["Type"] == "SoMouseButtonEvent": - if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - if self.point: - #self.ui.redraw() - self.numericInput(self.point.x,self.point.y,self.point.z) + for ghost in self.ghosts: + ghost.move(corr) + ghost.on() def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when a valid base point has been entered" @@ -4213,8 +4230,8 @@ class Scale(Modifier): self.task = DraftGui.ScaleTaskPanel() self.task.sourceCmd = self DraftGui.todo.delay(FreeCADGui.Control.showDialog,self.task) - if self.ghost: - self.ghost.on() + for ghost in self.ghosts: + ghost.on() elif len(self.node) == 2: msg(translate("draft", "Pick new distance from base point:")+"\n") elif len(self.node) == 3: From dc284e4d4df5b4812ba65c1c62dc90dcb516c5ca Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 3 Mar 2019 17:37:02 +1100 Subject: [PATCH 18/18] Fix scale command and simplify UI, add support for scale subelements --- src/Mod/Draft/Draft.py | 174 +++++++++++++++++++----------------- src/Mod/Draft/DraftGui.py | 33 ++----- src/Mod/Draft/DraftTools.py | 93 ++++++++++++++----- 3 files changed, 170 insertions(+), 130 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 64d62fb748..620d2f3d6f 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1789,95 +1789,105 @@ def rotate(objectslist,angle,center=Vector(0,0,0),axis=Vector(0,0,1),copy=False) if len(newobjlist) == 1: return newobjlist[0] return newobjlist -def scale(objectslist,delta=Vector(1,1,1),center=Vector(0,0,0),copy=False,legacy=False): +def scaleVectorFromCenter(vector, scale, center): + return vector.sub(center).scale(scale.x, scale.y, scale.z).add(center) + +def scaleVertex(object, vertex_index, scale, center): + points = object.Points + points[vertex_index] = object.Placement.inverse().multVec( + scaleVectorFromCenter( + object.Placement.multVec(points[vertex_index]), + scale, center)) + object.Points = points + +def scaleEdge(object, edge_index, scale, center): + scaleVertex(object, edge_index, scale, center) + if isClosedEdge(edge_index, object): + scaleVertex(object, 0, scale, center) + else: + scaleVertex(object, edge_index+1, scale, center) + +def copyScaledEdges(arguments): + copied_edges = [] + for argument in arguments: + copied_edges.append(copyScaledEdge(argument[0], argument[1], + argument[2], argument[3])) + joinWires(copied_edges) + +def copyScaledEdge(object, edge_index, scale, center): + vertex1 = scaleVectorFromCenter( + object.Placement.multVec(object.Points[edge_index]), + scale, center) + if isClosedEdge(edge_index, object): + vertex2 = scaleVectorFromCenter( + object.Placement.multVec(object.Points[0]), + scale, center) + else: + vertex2 = scaleVectorFromCenter( + object.Placement.multVec(object.Points[edge_index+1]), + scale, center) + return makeLine(vertex1, vertex2) + +def scale(objectslist,scale=Vector(1,1,1),center=Vector(0,0,0),copy=False): '''scale(objects,vector,[center,copy,legacy]): Scales the objects contained in objects (that can be a list of objects or an object) of the given scale factors defined by the given vector (in X, Y and Z directions) around - given center. If legacy is True, direct (old) mode is used, otherwise - a parametric copy is made. If copy is True, the actual objects are not moved, - but copies are created instead. The objects (or their copies) are returned.''' + given center. If copy is True, the actual objects are not moved, but copies + are created instead. The objects (or their copies) are returned.''' if not isinstance(objectslist, list): objectslist = [objectslist] - if legacy: - newobjlist = [] - for obj in objectslist: - if copy: - newobj = makeCopy(obj) - else: - newobj = obj - if obj.isDerivedFrom("Part::Feature"): - sh = obj.Shape.copy() - m = FreeCAD.Matrix() - m.scale(delta) - sh = sh.transformGeometry(m) - corr = Vector(center.x,center.y,center.z) - corr.scale(delta.x,delta.y,delta.z) - corr = (corr.sub(center)).negative() - sh.translate(corr) - if getType(obj) == "Rectangle": - p = [] - for v in sh.Vertexes: p.append(v.Point) - pl = obj.Placement.copy() - pl.Base = p[0] - diag = p[2].sub(p[0]) - bb = p[1].sub(p[0]) - bh = p[3].sub(p[0]) - nb = DraftVecUtils.project(diag,bb) - nh = DraftVecUtils.project(diag,bh) - if obj.Length < 0: l = -nb.Length - else: l = nb.Length - if obj.Height < 0: h = -nh.Length - else: h = nh.Length - newobj.Length = l - newobj.Height = h - tr = p[0].sub(obj.Shape.Vertexes[0].Point) - newobj.Placement = pl - elif getType(obj) == "Wire": - p = [] - for v in sh.Vertexes: p.append(v.Point) - newobj.Points = p - elif getType(obj) == "BSpline": - p = [] - for p1 in obj.Points: - p2 = p1.sub(center) - p2.scale(delta.x,delta.y,delta.z) - p.append(p2) - newobj.Points = p - elif (obj.isDerivedFrom("Part::Feature")): - newobj.Shape = sh - elif (obj.TypeId == "App::Annotation"): - factor = delta.y * obj.ViewObject.FontSize - newobj.ViewObject.FontSize = factor - d = obj.Position.sub(center) - newobj.Position = center.add(Vector(d.x*delta.x,d.y*delta.y,d.z*delta.z)) - if copy: - formatObject(newobj,obj) - newobjlist.append(newobj) - if copy and getParam("selectBaseObjects",False): - select(objectslist) + newobjlist = [] + for obj in objectslist: + if copy: + newobj = makeCopy(obj) else: - select(newobjlist) - if len(newobjlist) == 1: return newobjlist[0] - return newobjlist + newobj = obj + if obj.isDerivedFrom("Part::Feature"): + scaled_shape = obj.Shape.copy() + m = FreeCAD.Matrix() + m.move(obj.Placement.Base.negative()) + m.move(center.negative()) + m.multiply(scale) + m.move(center) + m.move(obj.Placement.Base) + scaled_shape = scaled_shape.transformGeometry(m) + if getType(obj) == "Rectangled": + p = [] + for v in scaled_shape.Vertexes: p.append(v.Point) + pl = obj.Placement.copy() + pl.Base = p[0] + diag = p[2].sub(p[0]) + bb = p[1].sub(p[0]) + bh = p[3].sub(p[0]) + nb = DraftVecUtils.project(diag,bb) + nh = DraftVecUtils.project(diag,bh) + if obj.Length < 0: l = -nb.Length + else: l = nb.Length + if obj.Height < 0: h = -nh.Length + else: h = nh.Length + newobj.Length = l + newobj.Height = h + tr = p[0].sub(obj.Shape.Vertexes[0].Point) + newobj.Placement = pl + elif getType(obj) == "Wire" or getType(obj) == "BSpline": + for index, point in enumerate(newobj.Points): + scaleVertex(newobj, index, scale, center) + elif (obj.isDerivedFrom("Part::Feature")): + newobj.Shape = scaled_shape + elif (obj.TypeId == "App::Annotation"): + factor = scale.y * obj.ViewObject.FontSize + newobj.ViewObject.FontSize = factor + d = obj.Position.sub(center) + newobj.Position = center.add(Vector(d.x*scale.x,d.y*scale.y,d.z*scale.z)) + if copy: + formatObject(newobj,obj) + newobjlist.append(newobj) + if copy and getParam("selectBaseObjects",False): + select(objectslist) else: - obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Scale") - _Clone(obj) - obj.Objects = objectslist - obj.Scale = delta - corr = Vector(center.x,center.y,center.z) - corr.scale(delta.x,delta.y,delta.z) - corr = (corr.sub(center)).negative() - p = obj.Placement - p.move(corr) - obj.Placement = p - if not copy: - for o in objectslist: - o.ViewObject.hide() - if gui: - _ViewProviderClone(obj.ViewObject) - formatObject(obj,objectslist[-1]) - select(obj) - return obj + select(newobjlist) + if len(newobjlist) == 1: return newobjlist[0] + return newobjlist def offset(obj,delta,copy=False,bind=False,sym=False,occ=False): '''offset(object,delta,[copymode],[bind]): offsets the given wire by diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index bc4c58eb10..30ef795880 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -2374,17 +2374,12 @@ class ScaleTaskPanel: layout.addWidget(self.lock,3,0,1,2) self.relative = QtGui.QCheckBox() layout.addWidget(self.relative,4,0,1,2) - self.rLabel = QtGui.QLabel() - layout.addWidget(self.rLabel,5,0,1,2) - self.isClone = QtGui.QRadioButton() - layout.addWidget(self.isClone,6,0,1,2) - self.isClone.setChecked(True) - self.isOriginal = QtGui.QRadioButton() - layout.addWidget(self.isOriginal,7,0,1,2) - self.isCopy = QtGui.QRadioButton() - layout.addWidget(self.isCopy,8,0,1,2) + self.isCopy = QtGui.QCheckBox() + layout.addWidget(self.isCopy,5,0,1,2) + self.isSubelementMode = QtGui.QCheckBox() + layout.addWidget(self.isSubelementMode,6,0,1,2) self.pickrefButton = QtGui.QPushButton() - layout.addWidget(self.pickrefButton,9,0,1,2) + layout.addWidget(self.pickrefButton,7,0,1,2) QtCore.QObject.connect(self.xValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) QtCore.QObject.connect(self.yValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) QtCore.QObject.connect(self.zValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) @@ -2406,10 +2401,8 @@ class ScaleTaskPanel: self.zLabel.setText(QtGui.QApplication.translate("Draft", "Z factor", None)) self.lock.setText(QtGui.QApplication.translate("Draft", "Uniform scaling", None)) self.relative.setText(QtGui.QApplication.translate("Draft", "Working plane orientation", None)) - self.rLabel.setText(QtGui.QApplication.translate("Draft", "Result", None)) - self.isClone.setText(QtGui.QApplication.translate("Draft", "Create a clone", None)) - self.isOriginal.setText(QtGui.QApplication.translate("Draft", "Modify original", None)) - self.isCopy.setText(QtGui.QApplication.translate("Draft", "Create a copy", None)) + self.isCopy.setText(QtGui.QApplication.translate("draft", "Copy")) + self.isSubelementMode.setText(QtGui.QApplication.translate("draft", "Modify subelements")) self.pickrefButton.setText(QtGui.QApplication.translate("Draft", "Pick from/to points", None)) def pickRef(self): @@ -2418,17 +2411,7 @@ class ScaleTaskPanel: def accept(self): if self.sourceCmd: - x = self.xValue.value() - y = self.yValue.value() - z = self.zValue.value() - rel = self.relative.isChecked() - if self.isClone.isChecked(): - mod = 0 - elif self.isOriginal.isChecked(): - mod = 1 - else: - mod = 2 - self.sourceCmd.scale(x,y,z,rel,mod) + self.sourceCmd.scale() FreeCADGui.ActiveDocument.resetEdit() return True diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index d674b1acdd..5c08273c09 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -4171,39 +4171,81 @@ class Scale(Modifier): and arg["State"] == "DOWN" \ and (arg["Button"] == "BUTTON1") \ and self.point: - if not self.ghosts: - self.set_ghosts() - self.numericInput(self.point.x, self.point.y, self.point.z) + self.handle_mouse_click_event() def handle_mouse_move_event(self, arg): for ghost in self.ghosts: ghost.off() self.point, ctrlPoint, info = getPoint(self, arg, sym=True) - def finish(self,closed=False,cont=False): - Modifier.finish(self) - for ghost in self.ghosts: - ghost.finalize() + def handle_mouse_click_event(self): + if not self.ghosts: + self.set_ghosts() + self.numericInput(self.point.x, self.point.y, self.point.z) - def scale(self,x,y,z,rel,mode): - delta = Vector(x,y,z) - if rel: - delta = FreeCAD.DraftWorkingPlane.getGlobalCoords(delta) - if mode == 0: - copy = False - legacy = False - elif mode == 1: - copy = False - legacy = True - elif mode == 2: - copy = True - legacy = True + def scale(self): + self.delta = Vector(self.task.xValue.value(), self.task.yValue.value(), self.task.zValue.value()) + self.center = self.node[0] + if self.task.isSubelementMode.isChecked(): + self.scale_subelements() + else: + self.scale_object() + self.finish() + + def scale_subelements(self): + try: + if self.task.isCopy.isChecked(): + self.commit(translate("draft", "Copy"), self.build_copy_subelements_command()) + else: + self.commit(translate("draft", "Scale"), self.build_scale_subelements_command()) + except: + FreeCAD.Console.PrintError(translate("draft", "Some subelements could not be scaled.")) + + def build_copy_subelements_command(self): + import Part + command = [] + arguments = [] + for object in self.selected_subelements: + for index, subelement in enumerate(object.SubObjects): + if not isinstance(subelement, Part.Edge): + continue + arguments.append('[FreeCAD.ActiveDocument.{}, {}, {}, {}]'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Edge"):])-1, + DraftVecUtils.toString(self.delta), + DraftVecUtils.toString(self.center))) + command.append('Draft.copyScaledEdges([{}])'.format(','.join(arguments))) + command.append('FreeCAD.ActiveDocument.recompute()') + return command + + def build_scale_subelements_command(self): + import Part + command = [] + for object in self.selected_subelements: + for index, subelement in enumerate(object.SubObjects): + if isinstance(subelement, Part.Vertex): + command.append('Draft.scaleVertex(FreeCAD.ActiveDocument.{}, {}, {}, {})'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Vertex"):])-1, + DraftVecUtils.toString(self.delta), + DraftVecUtils.toString(self.center))) + elif isinstance(subelement, Part.Edge): + command.append('Draft.scaleEdge(FreeCAD.ActiveDocument.{}, {}, {}, {})'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Edge"):])-1, + DraftVecUtils.toString(self.delta), + DraftVecUtils.toString(self.center))) + command.append('FreeCAD.ActiveDocument.recompute()') + return command + + def scale_object(self): + if self.task.relative.isChecked(): + self.delta = FreeCAD.DraftWorkingPlane.getGlobalCoords(self.delta) objects = '[' + ','.join(['FreeCAD.ActiveDocument.' + object.Name for object in self.selected_objects]) + ']' FreeCADGui.addModule("Draft") - self.commit(translate("draft","Copy"), - ['Draft.scale('+objects+',delta='+DraftVecUtils.toString(delta)+',center='+DraftVecUtils.toString(self.node[0])+',copy='+str(copy)+',legacy='+str(legacy)+')', + self.commit(translate("draft","Copy" if self.task.isCopy.isChecked() else "Scale"), + ['Draft.scale('+objects+',scale='+DraftVecUtils.toString(self.delta)+',center='+DraftVecUtils.toString(self.center)+',copy='+str(self.task.isCopy.isChecked())+')', 'FreeCAD.ActiveDocument.recompute()']) - self.finish() def scaleGhost(self,x,y,z,rel): delta = Vector(x,y,z) @@ -4247,6 +4289,11 @@ class Scale(Modifier): self.task.lock.setChecked(True) self.task.setValue(d2/d1) + def finish(self,closed=False,cont=False): + Modifier.finish(self) + for ghost in self.ghosts: + ghost.finalize() + class ToggleConstructionMode(): "The Draft_ToggleConstructionMode FreeCAD command definition"