diff --git a/src/Mod/Draft/drafttaskpanels/task_scale.py b/src/Mod/Draft/drafttaskpanels/task_scale.py index 4f42fd0776..abeeb8bffd 100644 --- a/src/Mod/Draft/drafttaskpanels/task_scale.py +++ b/src/Mod/Draft/drafttaskpanels/task_scale.py @@ -1,6 +1,7 @@ # *************************************************************************** -# * (c) 2009 Yorik van Havre * -# * (c) 2020 Eliud Cabrera Castillo * +# * Copyright (c) 2009 Yorik van Havre * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * Copyright (c) 2025 FreeCAD Project Association * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -36,7 +37,9 @@ import FreeCAD as App import FreeCADGui as Gui import Draft import Draft_rc +from draftguitools import gui_trackers as trackers from draftutils import params +from draftutils import utils from draftutils.translate import translate # So the resource file doesn't trigger errors from code checkers (flake8) @@ -50,9 +53,11 @@ class ScaleTaskPanel: decimals = max(6, params.get_param("Decimals", path="Units")) self.sourceCmd = None self.form = QtWidgets.QWidget() + self.form.setWindowTitle(translate("Draft", "Scale")) self.form.setWindowIcon(QtGui.QIcon(":/icons/Draft_Scale.svg")) layout = QtWidgets.QGridLayout(self.form) self.xLabel = QtWidgets.QLabel() + self.xLabel.setText(translate("Draft", "X factor")) layout.addWidget(self.xLabel, 0, 0, 1, 1) self.xValue = QtWidgets.QDoubleSpinBox() self.xValue.setRange(-1000000.0, 1000000.0) @@ -60,6 +65,7 @@ class ScaleTaskPanel: self.xValue.setValue(1) layout.addWidget(self.xValue,0,1,1,1) self.yLabel = QtWidgets.QLabel() + self.yLabel.setText(translate("Draft", "Y factor")) layout.addWidget(self.yLabel,1,0,1,1) self.yValue = QtWidgets.QDoubleSpinBox() self.yValue.setRange(-1000000.0, 1000000.0) @@ -67,6 +73,7 @@ class ScaleTaskPanel: self.yValue.setValue(1) layout.addWidget(self.yValue,1,1,1,1) self.zLabel = QtWidgets.QLabel() + self.zLabel.setText(translate("Draft", "Z factor")) layout.addWidget(self.zLabel,2,0,1,1) self.zValue = QtWidgets.QDoubleSpinBox() self.zValue.setRange(-1000000.0, 1000000.0) @@ -74,36 +81,60 @@ class ScaleTaskPanel: self.zValue.setValue(1) layout.addWidget(self.zValue,2,1,1,1) self.lock = QtWidgets.QCheckBox() + self.lock.setText(translate("Draft", "Uniform scaling")) self.lock.setChecked(params.get_param("ScaleUniform")) layout.addWidget(self.lock,3,0,1,2) - self.relative = QtWidgets.QCheckBox() - self.relative.setChecked(params.get_param("ScaleRelative")) - layout.addWidget(self.relative,4,0,1,2) - self.isCopy = QtWidgets.QCheckBox() - self.isCopy.setChecked(params.get_param("ScaleCopy")) - layout.addWidget(self.isCopy,5,0,1,2) - self.isSubelementMode = QtWidgets.QCheckBox() - self.isSubelementMode.setChecked(params.get_param("SubelementMode")) - layout.addWidget(self.isSubelementMode,6,0,1,2) - self.isClone = QtWidgets.QCheckBox() - layout.addWidget(self.isClone,7,0,1,2) - self.isClone.setChecked(params.get_param("ScaleClone")) - self.pickrefButton = QtWidgets.QPushButton() - layout.addWidget(self.pickrefButton,8,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) - QtCore.QObject.connect(self.pickrefButton,QtCore.SIGNAL("clicked()"),self.pickRef) QtCore.QObject.connect(self.lock,QtCore.SIGNAL("toggled(bool)"),self.setLock) - QtCore.QObject.connect(self.relative,QtCore.SIGNAL("toggled(bool)"),self.setRelative) - QtCore.QObject.connect(self.isCopy,QtCore.SIGNAL("toggled(bool)"),self.setCopy) - QtCore.QObject.connect(self.isSubelementMode,QtCore.SIGNAL("toggled(bool)"),self.setSubelementMode) - QtCore.QObject.connect(self.isClone,QtCore.SIGNAL("toggled(bool)"),self.setClone) - self.retranslateUi() + + if self.__class__.__name__ != "ScaleTaskPanelEdit": + self.relative = QtWidgets.QCheckBox() + self.relative.setText(translate("Draft", "Working plane orientation")) + self.relative.setChecked(params.get_param("ScaleRelative")) + layout.addWidget(self.relative,4,0,1,2) + self.isCopy = QtWidgets.QCheckBox() + self.isCopy.setText(translate("Draft", "Copy")) + self.isCopy.setChecked(params.get_param("ScaleCopy")) + layout.addWidget(self.isCopy,5,0,1,2) + self.isSubelementMode = QtWidgets.QCheckBox() + self.isSubelementMode.setText(translate("Draft", "Modify subelements")) + self.isSubelementMode.setChecked(params.get_param("SubelementMode")) + layout.addWidget(self.isSubelementMode,6,0,1,2) + self.isClone = QtWidgets.QCheckBox() + self.isClone.setText(translate("Draft", "Create a clone")) + self.isClone.setChecked(params.get_param("ScaleClone")) + layout.addWidget(self.isClone,7,0,1,2) + self.pickrefButton = QtWidgets.QPushButton() + self.pickrefButton.setText(translate("Draft", "Pick from/to points")) + layout.addWidget(self.pickrefButton,8,0,1,2) + + QtCore.QObject.connect(self.relative,QtCore.SIGNAL("toggled(bool)"),self.setRelative) + QtCore.QObject.connect(self.isCopy,QtCore.SIGNAL("toggled(bool)"),self.setCopy) + QtCore.QObject.connect(self.isSubelementMode,QtCore.SIGNAL("toggled(bool)"),self.setSubelementMode) + QtCore.QObject.connect(self.isClone,QtCore.SIGNAL("toggled(bool)"),self.setClone) + QtCore.QObject.connect(self.pickrefButton,QtCore.SIGNAL("clicked()"),self.pickRef) + + def setValue(self, val=None): + """Set the value of the scale factors.""" + if self.lock.isChecked(): + if not self.xValue.hasFocus(): + self.xValue.setValue(val) + if not self.yValue.hasFocus(): + self.yValue.setValue(val) + if not self.zValue.hasFocus(): + self.zValue.setValue(val) + if self.sourceCmd: + # self.sourceCmd is always None for ScaleTaskPanelEdit + self.sourceCmd.scale_ghosts(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) def setLock(self, state): """Set the uniform scaling.""" - params.set_param("ScaleUniform", state) + if self.sourceCmd: + # self.sourceCmd is always None for ScaleTaskPanelEdit + params.set_param("ScaleUniform", state) if state: val = self.xValue.value() self.yValue.setValue(val) @@ -116,12 +147,13 @@ class ScaleTaskPanel: self.sourceCmd.scale_ghosts(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) def setCopy(self, state): - """Set the scale and copy option.""" + """Set the copy option.""" params.set_param("ScaleCopy", state) if state and self.isClone.isChecked(): self.isClone.setChecked(False) def setSubelementMode(self, state): + """Set the subelement option.""" params.set_param("SubelementMode", state) if state and self.isClone.isChecked(): self.isClone.setChecked(False) @@ -130,38 +162,13 @@ class ScaleTaskPanel: self.sourceCmd.scale_ghosts(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) def setClone(self, state): - """Set the clone and scale option.""" + """Set the clone option.""" params.set_param("ScaleClone", state) if state and self.isCopy.isChecked(): self.isCopy.setChecked(False) if state and self.isSubelementMode.isChecked(): self.isSubelementMode.setChecked(False) - def setValue(self, val=None): - """Set the value of the points.""" - if self.lock.isChecked(): - if not self.xValue.hasFocus(): - self.xValue.setValue(val) - if not self.yValue.hasFocus(): - self.yValue.setValue(val) - if not self.zValue.hasFocus(): - self.zValue.setValue(val) - if self.sourceCmd: - self.sourceCmd.scale_ghosts(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) - - def retranslateUi(self, widget=None): - """Translate the various widgets""" - self.form.setWindowTitle(translate("Draft", "Scale")) - self.xLabel.setText(translate("Draft", "X factor")) - self.yLabel.setText(translate("Draft", "Y factor")) - self.zLabel.setText(translate("Draft", "Z factor")) - self.lock.setText(translate("Draft", "Uniform scaling")) - self.relative.setText(translate("Draft", "Working plane orientation")) - self.isCopy.setText(translate("Draft", "Copy")) - self.isSubelementMode.setText(translate("Draft", "Modify subelements")) - self.pickrefButton.setText(translate("Draft", "Pick from/to points")) - self.isClone.setText(translate("Draft", "Create a clone")) - def pickRef(self): """Pick a reference point from the calling class.""" if self.sourceCmd: @@ -181,4 +188,78 @@ class ScaleTaskPanel: Gui.ActiveDocument.resetEdit() return True + +class ScaleTaskPanelEdit(ScaleTaskPanel): + """The task panel to edit the scale of Draft Clones.""" + + def __init__(self, obj): + super().__init__() + self.ghost = None + self.selection = Gui.Selection.getSelectionEx("", 0) + self.obj = obj + self.obj_x, self.obj_y, self.obj_z = self.obj.Scale + self.form.setWindowTitle(translate("Draft", "Edit scale")) + self.form.setWindowIcon(QtGui.QIcon(":/icons/Draft_Clone.svg")) + self.xValue.setValue(self.obj_x) + self.yValue.setValue(self.obj_y) + self.zValue.setValue(self.obj_z) + self.lock.setChecked(self.obj_x == self.obj_y == self.obj_z) + + def setValue(self, val=None): + """Set the value of the scale factors.""" + super().setValue(val) + self.scale_ghost(self.xValue.value(), self.yValue.value(), self.zValue.value()) + + def scale_ghost(self, x, y, z): + """Scale the preview of the object.""" + x = x / (self.obj_x if abs(self.obj_x) > 1e-7 else 1e-7) + y = y / (self.obj_y if abs(self.obj_y) > 1e-7 else 1e-7) + z = z / (self.obj_z if abs(self.obj_z) > 1e-7 else 1e-7) + + if self.ghost is None: + self.set_ghost() + + mtx_scale = App.Matrix() + mtx_scale.scale(x, y, z) + mtx = self.global_place.Matrix * mtx_scale + mtx = mtx * self.global_place.Matrix.inverse() + + delta = self.global_place.inverse().Rotation.multVec(self.global_place.Base) + delta = -App.Vector(delta.x*x, delta.y*y, delta.z*z) + delta = self.global_place.multVec(delta) + + self.ghost.setMatrix(mtx) + self.ghost.move(delta) + # self.ghost.flip_normals(x * y * z < 0) # Does not work properly for Draft_Circles for example. + self.ghost.on() + + def set_ghost(self): + """Set the ghost to display.""" + if self.ghost is not None: + self.ghost.remove() + objs, places, _ = utils._modifiers_process_selection(self.selection, copy=False, scale=True) + self.ghost = trackers.ghostTracker(objs, parent_places=places) + self.global_place = places[0] * self.obj.Placement + + def accept(self): + """Execute when clicking the OK button.""" + self.obj.Scale = (self.xValue.value(), self.yValue.value(), self.zValue.value()) + App.ActiveDocument.recompute() + if self.ghost is not None: + self.ghost.finalize() + Gui.ActiveDocument.resetEdit() + return True + + def reject(self): + """Execute when clicking the Cancel button.""" + if self.ghost is not None: + self.ghost.finalize() + Gui.ActiveDocument.resetEdit() + return True + + def finish(self): + """Called by unsetEdit in view_clone.py.""" + Gui.Control.closeDialog() + return None + ## @} diff --git a/src/Mod/Draft/draftviewproviders/view_clone.py b/src/Mod/Draft/draftviewproviders/view_clone.py index eb7c6a0b62..5c1151c538 100644 --- a/src/Mod/Draft/draftviewproviders/view_clone.py +++ b/src/Mod/Draft/draftviewproviders/view_clone.py @@ -2,6 +2,7 @@ # * Copyright (c) 2009, 2010 Yorik van Havre * # * Copyright (c) 2009, 2010 Ken Cline * # * Copyright (c) 2020 FreeCAD Developers * +# * Copyright (c) 2025 FreeCAD Project Association * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -27,7 +28,12 @@ ## \addtogroup draftviewproviders # @{ +from PySide import QtCore +from PySide import QtGui +import FreeCADGui as Gui +from drafttaskpanels import task_scale +from draftutils.translate import translate class ViewProviderClone: """a view provider that displays a Clone icon instead of a Draft icon""" @@ -35,9 +41,38 @@ class ViewProviderClone: def __init__(self,vobj): vobj.Proxy = self + def attach(self, vobj): + self.Object = vobj.Object + return + def getIcon(self): return ":/icons/Draft_Clone.svg" + def setEdit(self, vobj, mode): + if mode != 0: + return None + + self.task = task_scale.ScaleTaskPanelEdit(self.Object) + Gui.Control.showDialog(self.task) + return True + + def unsetEdit(self, vobj, mode): + if mode != 0: + return None + + self.task.finish() + return True + + def setupContextMenu(self, vobj, menu): + action_edit = QtGui.QAction(translate("draft", "Edit"), menu) + QtCore.QObject.connect(action_edit, + QtCore.SIGNAL("triggered()"), + self.edit) + menu.addAction(action_edit) + + def edit(self): + Gui.ActiveDocument.setEdit(self.Object, 0) + def dumps(self): return None