Download src/Mod/CAM/Path/Tool/toolbit/ui/editor.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 16.4 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Tool/toolbit/ui/editor.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/CAM/Path/Tool/toolbit/ui/editor.py
-
curl -L -o editor.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Tool/toolbit/ui/editor.py
16.4 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2025 Samuel Abels <knipknap@gmail.com> * | |
| # * * | |
| # * This program is free software; you can redistribute it and/or modify * | |
| # * it under the terms of the GNU Lesser General Public License (LGPL) * | |
| # * as published by the Free Software Foundation; either version 2 of * | |
| # * the License, or (at your option) any later version. * | |
| # * for detail see the LICENCE text file. * | |
| # * * | |
| # * This program is distributed in the hope that it will be useful, * | |
| # * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| # * GNU Library General Public License for more details. * | |
| # * * | |
| # * You should have received a copy of the GNU Library General Public * | |
| # * License along with this program; if not, write to the Free Software * | |
| # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * | |
| # * USA * | |
| # * * | |
| # *************************************************************************** | |
| """Widget for editing a ToolBit object.""" | |
| from typing import Optional | |
| from PySide import QtGui, QtCore | |
| import FreeCAD | |
| import FreeCADGui | |
| from ...shape.ui.shapewidget import ShapeWidget | |
| from ...docobject.ui import DocumentObjectEditorWidget | |
| from ..models.base import ToolBit | |
| from ..util import setToolBitSchema | |
| translate = FreeCAD.Qt.translate | |
| class ToolBitPropertiesWidget(QtGui.QWidget): | |
| """ | |
| A composite widget for editing the properties and shape of a ToolBit. | |
| """ | |
| # Signal emitted when the toolbit data has been modified | |
| toolBitChanged = QtCore.Signal() | |
| toolNoChanged = QtCore.Signal(int) | |
| def __init__( | |
| self, | |
| toolbit: Optional[ToolBit] = None, | |
| tool_no: Optional[int] = None, | |
| parent=None, | |
| icon: bool = True, | |
| ): | |
| super().__init__(parent) | |
| self._toolbit = None | |
| self._show_shape = icon | |
| self._tool_no = tool_no | |
| setToolBitSchema() | |
| # UI Elements | |
| self._label_edit = QtGui.QLineEdit() | |
| self._id_label = QtGui.QLabel() # Read-only ID | |
| self._id_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) | |
| theicon = toolbit.get_icon() if toolbit else None | |
| abbr = theicon.abbreviations if theicon else {} | |
| self._property_editor = DocumentObjectEditorWidget(property_suffixes=abbr) | |
| self._property_editor.setSizePolicy( | |
| QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding | |
| ) | |
| self._shape_widget = None # Will be created in load_toolbit | |
| # Layout | |
| toolbit_group_box = QtGui.QGroupBox(translate("CAM", "Toolbit")) | |
| form_layout = QtGui.QFormLayout(toolbit_group_box) | |
| form_layout.addRow(translate("CAM", "Label:"), self._label_edit) | |
| # form_layout.addRow(translate("CAM", "ID:"), self._id_label) | |
| # Optional tool number edit field. | |
| self._tool_no_edit = QtGui.QSpinBox() | |
| self._tool_no_edit.setMinimum(1) | |
| self._tool_no_edit.setMaximum(99999999) | |
| if tool_no is not None: | |
| form_layout.addRow(translate("CAM", "Tool Number:"), self._tool_no_edit) | |
| main_layout = QtGui.QVBoxLayout(self) | |
| main_layout.addWidget(toolbit_group_box) | |
| properties_group_box = QtGui.QGroupBox(FreeCAD.Qt.translate("CAM", "Properties")) | |
| properties_group_box.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) | |
| properties_layout = QtGui.QVBoxLayout(properties_group_box) | |
| properties_layout.setSpacing(5) | |
| properties_layout.addWidget(self._property_editor) | |
| # Ensure the layout expands horizontally | |
| properties_layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) | |
| # Set stretch factor to make property editor expand | |
| properties_layout.setStretchFactor(self._property_editor, 1) | |
| main_layout.addWidget(properties_group_box) | |
| # Add stretch before shape widget to push it towards the bottom | |
| main_layout.addStretch(1) | |
| # Layout for centering the shape widget (created later) | |
| self._shape_display_layout = QtGui.QHBoxLayout() | |
| self._shape_display_layout.addStretch(1) | |
| # Placeholder for the widget | |
| self._shape_display_layout.addStretch(1) | |
| main_layout.addLayout(self._shape_display_layout) | |
| # Connections | |
| self._label_edit.editingFinished.connect(self._on_label_changed) | |
| self._tool_no_edit.valueChanged.connect(self._on_tool_no_changed) | |
| self._property_editor.propertyChanged.connect(self.toolBitChanged) | |
| if toolbit: | |
| self.load_toolbit(toolbit) | |
| def _on_label_changed(self): | |
| """Update the toolbit's label when the line edit changes.""" | |
| if self._toolbit and self._toolbit.obj: | |
| new_label = self._label_edit.text() | |
| if self._toolbit.obj.Label != new_label: | |
| self._toolbit.obj.Label = new_label | |
| self.toolBitChanged.emit() | |
| def _on_tool_no_changed(self, value): | |
| """Update the tool number when the line edit changes.""" | |
| if self._tool_no != value: | |
| self._tool_no = value | |
| self.toolNoChanged.emit(value) | |
| def load_toolbit(self, toolbit: ToolBit): | |
| """Load a ToolBit object into the editor.""" | |
| # Set schema based on the toolbit's Units property if available | |
| toolbit_units = None | |
| if toolbit and hasattr(toolbit.obj, "Units"): | |
| toolbit_units = getattr(toolbit.obj, "Units", None) | |
| # If Units is an enumeration, get the value | |
| if isinstance(toolbit_units, (list, tuple)) and len(toolbit_units) > 0: | |
| toolbit_units = toolbit_units[0] | |
| if toolbit_units in ("Metric", "Imperial"): | |
| setToolBitSchema(toolbit_units) | |
| elif FreeCAD.ActiveDocument is None: | |
| setToolBitSchema() | |
| self._toolbit = toolbit | |
| if not self._toolbit or not self._toolbit.obj: | |
| # Clear or disable fields if toolbit is invalid | |
| self._label_edit.clear() | |
| self._label_edit.setEnabled(False) | |
| self._id_label.clear() | |
| self._tool_no_edit.clear() | |
| self._property_editor.setObject(None) | |
| # Clear existing shape widget if any | |
| if self._shape_widget: | |
| self._shape_display_layout.removeWidget(self._shape_widget) | |
| self._shape_widget.deleteLater() | |
| self._shape_widget = None | |
| self._tool_no_edit.setValue(1) | |
| self.setEnabled(False) | |
| return | |
| self.setEnabled(True) | |
| self._label_edit.setEnabled(True) | |
| self._label_edit.setText(self._toolbit.obj.Label) | |
| self._id_label.setText(self._toolbit.get_id()) | |
| self._tool_no_edit.setValue(int(self._tool_no or 1)) | |
| # Get properties and suffixes | |
| props_to_show = self._toolbit._get_props(("Shape", "Attributes")) | |
| icon = self._toolbit._tool_bit_shape.get_icon() | |
| suffixes = icon.abbreviations if icon else {} | |
| self._property_editor.setObject(self._toolbit.obj) | |
| self._property_editor.setPropertiesToShow(props_to_show, suffixes) | |
| # Clear old shape widget and create/add new one if shape exists | |
| if self._shape_widget: | |
| self._shape_display_layout.removeWidget(self._shape_widget) | |
| self._shape_widget.deleteLater() | |
| self._shape_widget = None | |
| if self._show_shape and self._toolbit._tool_bit_shape: | |
| self._shape_widget = ShapeWidget(shape=self._toolbit._tool_bit_shape, parent=self) | |
| self._shape_widget.setMinimumSize(200, 150) | |
| # Insert into the middle slot of the HBox layout | |
| self._shape_display_layout.insertWidget(1, self._shape_widget) | |
| def save_toolbit(self): | |
| """ | |
| Applies changes from the editor widgets back to the ToolBit object. | |
| Note: Most changes are applied via signals, but this can be called | |
| for explicit save actions. | |
| """ | |
| # Ensure label is updated if focus is lost without pressing Enter | |
| self._on_label_changed() | |
| # No need to explicitly save the toolbit object itself here, | |
| # as properties were modified directly on toolbit.obj | |
| class ToolBitEditorPanel(QtGui.QWidget): | |
| """ | |
| A widget for editing a ToolBit object, wrapping ToolBitEditorWidget | |
| and providing standard dialog buttons. | |
| """ | |
| # Signals | |
| accepted = QtCore.Signal(ToolBit) | |
| rejected = QtCore.Signal() | |
| toolBitChanged = QtCore.Signal() # Re-emit signal from inner widget | |
| def __init__(self, toolbit: ToolBit | None = None, parent=None): | |
| super().__init__(parent) | |
| # Create the main editor widget | |
| self._editor_widget = ToolBitPropertiesWidget(toolbit, self) | |
| # Create the button box | |
| buttons = QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel | |
| self._button_box = QtGui.QDialogButtonBox(buttons) | |
| # Connect button box signals to custom signals | |
| self._button_box.accepted.connect(self._accepted) | |
| self._button_box.rejected.connect(self.rejected.emit) | |
| # Layout | |
| main_layout = QtGui.QVBoxLayout(self) | |
| main_layout.addWidget(self._editor_widget) | |
| main_layout.addWidget(self._button_box) | |
| # Connect the toolBitChanged signal from the inner widget | |
| self._editor_widget.toolBitChanged.connect(self.toolBitChanged) | |
| def _accepted(self): | |
| self.accepted.emit(self._editor_widget._toolbit) | |
| def load_toolbit(self, toolbit: ToolBit): | |
| """Load a ToolBit object into the editor.""" | |
| self._editor_widget.load_toolbit(toolbit) | |
| def save_toolbit(self): | |
| """Applies changes from the editor widgets back to the ToolBit object.""" | |
| self._editor_widget.save_toolbit() | |
| class ToolBitEditor(QtGui.QWidget): | |
| """ | |
| A widget for editing a ToolBit object, wrapping ToolBitEditorWidget | |
| and providing standard dialog buttons. | |
| """ | |
| # Signals | |
| toolBitChanged = QtCore.Signal() # Re-emit signal from inner widget | |
| def __init__( | |
| self, | |
| toolbit: ToolBit, | |
| tool_no: Optional[int] = None, | |
| parent=None, | |
| icon: bool = False, | |
| ): | |
| super().__init__(parent) | |
| self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitEditor.ui") | |
| self.toolbit = toolbit | |
| self.tool_no = tool_no | |
| self.default_title = self.form.windowTitle() | |
| # Store the original schema to restore on close | |
| self._original_schema = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt( | |
| "UserSchema", 6 | |
| ) | |
| self._tab_closed = False | |
| # Get first tab from the form, add the shape widget at the top. | |
| tool_tab_layout = self.form.toolTabLayout | |
| widget = ShapeWidget(toolbit._tool_bit_shape) | |
| tool_tab_layout.addWidget(widget) | |
| # Add tool properties editor to the same tab. | |
| self._props = ToolBitPropertiesWidget(toolbit, tool_no, self, icon=icon) | |
| self._last_units_value = self._get_units_value(self._props) | |
| self._props.toolBitChanged.connect(self._on_toolbit_changed) | |
| self._props.toolBitChanged.connect(self._update) | |
| self._props.toolNoChanged.connect(self._on_tool_no_changed) | |
| tool_tab_layout.addWidget(self._props) | |
| self.form.tabWidget.setCurrentIndex(0) | |
| self.form.tabWidget.currentChanged.connect(self._on_tab_switched) | |
| # Hide second tab (tool notes) for now. | |
| self.form.tabWidget.setTabVisible(1, False) | |
| # Feeds & Speeds | |
| self.feeds_tab_idx = None | |
| """ | |
| TODO: disabled for now. | |
| if tool.supports_feeds_and_speeds(): | |
| label = translate('CAM', 'Feeds && Speeds') | |
| self.feeds = FeedsAndSpeedsWidget(db, serializer, tool, parent=self) | |
| self.feeds_tab_idx = self.form.tabWidget.insertTab(1, self.feeds, label) | |
| else: | |
| self.feeds = None | |
| self.feeds_tab_idx = None | |
| self.form.lineEditCoating.setText(toolbit.get_coating()) | |
| self.form.lineEditCoating.textChanged.connect(toolbit.set_coating) | |
| self.form.lineEditHardness.setText(toolbit.get_hardness()) | |
| self.form.lineEditHardness.textChanged.connect(toolbit.set_hardness) | |
| self.form.lineEditMaterials.setText(toolbit.get_materials()) | |
| self.form.lineEditMaterials.textChanged.connect(toolbit.set_materials) | |
| self.form.lineEditSupplier.setText(toolbit.get_supplier()) | |
| self.form.lineEditSupplier.textChanged.connect(toolbit.set_supplier) | |
| self.form.plainTextEditNotes.setPlainText(tool.get_notes()) | |
| self.form.plainTextEditNotes.textChanged.connect(self._on_notes_changed) | |
| """ | |
| self._update() | |
| def _get_units_value(self, props): | |
| """ | |
| Helper to extract the Units value from the toolbit properties. | |
| """ | |
| if props and hasattr(props._toolbit.obj, "Units"): | |
| units_value = getattr(props._toolbit.obj, "Units", None) | |
| if isinstance(units_value, (list, tuple)) and len(units_value) > 0: | |
| units_value = units_value[0] | |
| return units_value | |
| return None | |
| def _on_toolbit_changed(self): | |
| """ | |
| Slot called when the toolbit is changed. If the Units value has changed, | |
| refreshes the property editor widget to update the schema and UI. | |
| """ | |
| units_value = self._get_units_value(self._props) | |
| if units_value in ("Metric", "Imperial") and units_value != self._last_units_value: | |
| self._refresh_property_editor() | |
| self._last_units_value = units_value | |
| def _refresh_property_editor(self): | |
| """ | |
| Refreshes the property editor widget in the tab. | |
| Removes the current ToolBitPropertiesWidget, restores the original units schema, | |
| recreates the widget, and reconnects all signals. This ensures the UI and schema | |
| are in sync with the current toolbit's units, and user changes are preserved | |
| because the ToolBit object is always up to date. | |
| """ | |
| # Remove the current property editor widget | |
| tool_tab_layout = self.form.toolTabLayout | |
| tool_tab_layout.removeWidget(self._props) | |
| self._props.deleteLater() | |
| # Restore the original schema | |
| FreeCAD.Units.setSchema(self._original_schema) | |
| # Recreate the property editor with the current toolbit | |
| self._props = ToolBitPropertiesWidget(self.toolbit, self.tool_no, self, icon=False) | |
| self._last_units_value = self._get_units_value(self._props) | |
| self._props.toolBitChanged.connect(self._on_toolbit_changed) | |
| self._props.toolBitChanged.connect(self._update) | |
| self._props.toolNoChanged.connect(self._on_tool_no_changed) | |
| tool_tab_layout.addWidget(self._props) | |
| self.form.tabWidget.setCurrentIndex(0) | |
| def _restore_original_schema(self): | |
| """ | |
| Restores the original units schema that was active before the ToolBit editor was opened. | |
| """ | |
| FreeCAD.Units.setSchema(self._original_schema) | |
| def _update(self): | |
| title = self.default_title | |
| tool_name = self.toolbit.label | |
| if tool_name: | |
| title = "{} - {}".format(tool_name, title) | |
| self.form.setWindowTitle(title) | |
| def _on_tab_switched(self, index): | |
| if index == self.feeds_tab_idx: | |
| self.feeds.update() | |
| def _on_notes_changed(self): | |
| self.toolbit.set_notes(self.form.plainTextEditNotes.toPlainText()) | |
| def _on_tool_no_changed(self, value): | |
| self.tool_no = value | |
| def get_tool_no(self): | |
| return self.tool_no | |
| def show(self): | |
| return self.form.exec_() | |