diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 64d62fb748a8..620d2f3d6f68 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 bc4c58eb108b..30ef79588020 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -2374,17 +2374,12 @@ def __init__(self): 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 @@ def retranslateUi(self,widget=None): 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 @@ def pickRef(self): 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 d674b1acdd9a..5c08273c0932 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -4171,39 +4171,81 @@ def action(self,arg): 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 @@ def numericInput(self,numx,numy,numz): 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"