Files
create/src/Mod/CAM/Path/Tool/toolbit/ui/editor.py

285 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# ***************************************************************************
# * 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 functools import partial
import FreeCAD
import FreeCADGui
from PySide import QtGui, QtCore
from ..models.base import ToolBit
from ...shape.ui.shapewidget import ShapeWidget
from ...ui.docobject import DocumentObjectEditorWidget
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()
def __init__(self, toolbit: ToolBit | None = None, parent=None, icon: bool = True):
super().__init__(parent)
self._toolbit = None
self._show_shape = icon
# 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(FreeCAD.Qt.translate("CAM", "Tool Bit"))
form_layout = QtGui.QFormLayout(toolbit_group_box)
form_layout.addRow("Label:", self._label_edit)
form_layout.addRow("ID:", self._id_label)
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._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 load_toolbit(self, toolbit: ToolBit):
"""Load a ToolBit object into the editor."""
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._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.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())
# 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, parent=None):
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()
# 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.
props = ToolBitPropertiesWidget(toolbit, self, icon=False)
props.toolBitChanged.connect(self._update)
# props.toolNoChanged.connect(self._on_tool_no_changed)
tool_tab_layout.addWidget(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)
"""
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 show(self):
return self.form.exec_()