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); diff --git a/src/Gui/SoFCUnifiedSelection.cpp b/src/Gui/SoFCUnifiedSelection.cpp index a3065fc10d..ac4d1e3e45 100644 --- a/src/Gui/SoFCUnifiedSelection.cpp +++ b/src/Gui/SoFCUnifiedSelection.cpp @@ -374,6 +374,28 @@ void SoFCUnifiedSelection::doAction(SoAction *action) currenthighlight->unref(); 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); + 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); } diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 5eeaf197ee..620d2f3d6f 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1488,12 +1488,62 @@ def cut(object1,object2): return obj +def moveVertex(object, vertex_index, vector): + points = object.Points + points[vertex_index] = points[vertex_index].add(vector) + object.Points = points + +def moveEdge(object, edge_index, vector): + moveVertex(object, edge_index, vector) + if isClosedEdge(edge_index, object): + moveVertex(object, 0, vector) + else: + moveVertex(object, edge_index+1, vector) + +def copyMovedEdges(arguments): + copied_edges = [] + for argument in arguments: + copied_edges.append(copyMovedEdge(argument[0], argument[1], argument[2])) + joinWires(copied_edges) + +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) + else: + 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) + 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) 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)) @@ -1651,6 +1701,26 @@ def filterObjectsForModifiers(objects, isCopied=False): filteredObjects.append(object) return filteredObjects +def rotateVertex(object, vertex_index, angle, center, axis): + points = object.Points + 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 @@ -1719,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.''' - 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) - #print(p) - 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) + 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] + 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 8c177425d3..30ef795880 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): @@ -2365,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) @@ -2397,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): @@ -2409,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 ed28fc6d66..5c08273c09 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,106 +2553,166 @@ 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) - 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 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] + self.vector = self.point.sub(last) + for ghost in self.ghosts: + ghost.move(self.vector) + 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_ghosts() + 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.vector = self.point.sub(last) + self.move() + 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 move(self): + if self.ui.isSubelementMode.isChecked(): + self.move_subelements() + else: + self.move_object() + + def move_subelements(self): + try: + 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 = [] + 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.vector))) + command.append('Draft.copyMovedEdges([{}])'.format(','.join(arguments))) + command.append('FreeCAD.ActiveDocument.recompute()') + return command + + 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): + command.append('Draft.moveVertex(FreeCAD.ActiveDocument.{}, {}, {})'.format( + object.ObjectName, + int(object.SubElementNames[index][len("Vertex"):])-1, + DraftVecUtils.toString(self.vector) + )) + elif isinstance(subelement, Part.Edge): + 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): + 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(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" @@ -2671,7 +2731,6 @@ class Move(Modifier): self.move(self.point.sub(last)) self.finish() - class ApplyStyle(Modifier): "The Draft_ApplyStyle FreeCA command definition" @@ -2720,36 +2779,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,[]) @@ -2757,112 +2938,72 @@ 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): + try: + if is_copy: + 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): + 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 + 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))) + 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 + + 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" @@ -2889,7 +3030,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) @@ -3972,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: @@ -4003,63 +4161,105 @@ class Scale(Modifier): msg(translate("draft", "Pick reference distance from base point:")+"\n") self.call = self.view.addEventCallback("SoEvent",self.action) - def finish(self,closed=False,cont=False): - Modifier.finish(self) - if self.ghost: - self.ghost.finalize() + 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: + self.handle_mouse_click_event() - 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 - "moving 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","Copy"), - ['Draft.scale('+sel+',delta='+DraftVecUtils.toString(delta)+',center='+DraftVecUtils.toString(self.node[0])+',copy='+str(copy)+',legacy='+str(legacy)+')', - 'FreeCAD.ActiveDocument.recompute()']) + def handle_mouse_move_event(self, arg): + for ghost in self.ghosts: + ghost.off() + self.point, ctrlPoint, info = getPoint(self, arg, sym=True) + + 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): + 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" 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()']) + def scaleGhost(self,x,y,z,rel): 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" @@ -4072,8 +4272,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: @@ -4089,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" @@ -4209,6 +4414,91 @@ 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) + 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: + 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" @@ -6414,6 +6704,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/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,\ 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"]