Skip to main content
Glama
tools_widget_compact.py31.4 kB
"""Compact Tools Widget - Clean and organized GUI for all FreeCAD tools""" import os import sys # Ensure the addon directory is in the Python path addon_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if addon_dir not in sys.path: sys.path.insert(0, addon_dir) import logging import FreeCAD from PySide2 import QtCore, QtWidgets class CompactToolButton(QtWidgets.QPushButton): """Ultra-compact tool button with icon.""" def __init__(self, icon, text, tooltip, parent=None): super().__init__(parent) self.setText(icon) self.setToolTip(f"<b>{text}</b><br>{tooltip}") self.setFixedSize(40, 30) # Even smaller self.setStyleSheet( """ QPushButton { font-size: 14px; border: 1px solid #ddd; border-radius: 4px; background-color: #fff; margin: 1px; } QPushButton:hover { background-color: #e3f2fd; border: 1px solid #2196F3; } QPushButton:pressed { background-color: #bbdefb; } """ ) class ToolsWidget(QtWidgets.QWidget): """Ultra-compact tools widget with icons.""" tool_executed = QtCore.Signal(str, dict, dict) def __init__(self, parent=None): super().__init__(parent) self.logger = logging.getLogger(__name__) self.tools = {} self.current_tool_dialog = None self._setup_tools() self._setup_ui() def _setup_tools(self): """Setup all tool instances.""" try: # Try absolute imports first try: from tools.advanced_operations import AdvancedOperationsTool from tools.advanced_primitives import AdvancedPrimitivesTool from tools.export_import import ExportImportTool from tools.measurements import MeasurementsTool from tools.operations import OperationsTool from tools.primitives import PrimitivesTool from tools.surface_modification import SurfaceModificationTool except ImportError: # Try relative imports from ..tools.advanced_operations import AdvancedOperationsTool from ..tools.advanced_primitives import AdvancedPrimitivesTool from ..tools.export_import import ExportImportTool from ..tools.measurements import MeasurementsTool from ..tools.operations import OperationsTool from ..tools.primitives import PrimitivesTool from ..tools.surface_modification import SurfaceModificationTool self.tools = { "primitives": PrimitivesTool(), "operations": OperationsTool(), "measurements": MeasurementsTool(), "export_import": ExportImportTool(), "advanced_primitives": AdvancedPrimitivesTool(), "advanced_operations": AdvancedOperationsTool(), "surface_modification": SurfaceModificationTool(), } self.logger.info("All tools loaded successfully") except ImportError as e: self.logger.error(f"Failed to import some tools: {e}") # Try to load basic tools only try: try: from tools.export_import import ExportImportTool from tools.measurements import MeasurementsTool from tools.operations import OperationsTool from tools.primitives import PrimitivesTool except ImportError: from ..tools.export_import import ExportImportTool from ..tools.measurements import MeasurementsTool from ..tools.operations import OperationsTool from ..tools.primitives import PrimitivesTool self.tools = { "primitives": PrimitivesTool(), "operations": OperationsTool(), "measurements": MeasurementsTool(), "export_import": ExportImportTool(), } self.logger.warning("Only basic tools loaded") except Exception as e2: self.tools = {} self.logger.error(f"No tools could be loaded: {e2}") def _setup_ui(self): """Setup the user interface.""" layout = QtWidgets.QVBoxLayout(self) layout.setSpacing(10) layout.setContentsMargins(10, 10, 10, 10) # Standard header with consistent styling header_group = QtWidgets.QGroupBox("FreeCAD Tools") header_layout = QtWidgets.QHBoxLayout(header_group) # Search box with standard styling self.search_box = QtWidgets.QLineEdit() self.search_box.setPlaceholderText("Search tools...") self.search_box.setMaximumWidth(200) self.search_box.setStyleSheet( """ QLineEdit { padding: 8px; border: 2px solid #ddd; border-radius: 4px; font-size: 12px; } QLineEdit:focus { border-color: #2196F3; } """ ) header_layout.addWidget(QtWidgets.QLabel("Search:")) header_layout.addWidget(self.search_box) header_layout.addStretch() layout.addWidget(header_group) # Create scroll area with standard spacing scroll = QtWidgets.QScrollArea() scroll.setWidgetResizable(True) scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) scroll.setStyleSheet("QScrollArea { border: 1px solid #ddd; border-radius: 4px; }") scroll_widget = QtWidgets.QWidget() scroll_layout = QtWidgets.QVBoxLayout(scroll_widget) scroll_layout.setSpacing(10) scroll_layout.setContentsMargins(10, 10, 10, 10) # Tool categories with icons self._create_basic_primitives(scroll_layout) self._create_basic_operations(scroll_layout) self._create_measurements(scroll_layout) self._create_import_export(scroll_layout) if "advanced_primitives" in self.tools: self._create_advanced_primitives(scroll_layout) if "advanced_operations" in self.tools: self._create_advanced_operations(scroll_layout) if "surface_modification" in self.tools: self._create_surface_modification(scroll_layout) scroll_layout.addStretch() scroll.setWidget(scroll_widget) layout.addWidget(scroll) # Standard status with consistent styling status_layout = QtWidgets.QHBoxLayout() status_layout.addWidget(QtWidgets.QLabel("Status:")) self.status_label = QtWidgets.QLabel("Ready") self.status_label.setStyleSheet( "padding: 8px 12px; background-color: #4CAF50; color: white; border-radius: 4px; font-size: 12px; font-weight: bold;" ) status_layout.addWidget(self.status_label) status_layout.addStretch() layout.addLayout(status_layout) def _create_basic_primitives(self, layout): """Create basic primitives section.""" category = QtWidgets.QGroupBox("□ Basic Shapes") category_layout = QtWidgets.QGridLayout(category) category_layout.setSpacing(5) category_layout.setContentsMargins(10, 10, 10, 10) tools = [ ("□", "Box", "Create a box/cube", "create_box"), ("○", "Cylinder", "Create a cylinder", "create_cylinder"), ("●", "Sphere", "Create a sphere", "create_sphere"), ("△", "Cone", "Create a cone", "create_cone"), ("◯", "Torus", "Create a torus", "create_torus"), ("▬", "Plane", "Create a plane", "create_plane"), ] def create_handler(method_name): return lambda checked: self._execute_tool("primitives", method_name) for i, (icon, name, tooltip, method) in enumerate(tools): btn = CompactToolButton(icon, name, tooltip) btn.clicked.connect(create_handler(method)) category_layout.addWidget(btn, i // 6, i % 6) layout.addWidget(category) def _create_basic_operations(self, layout): """Create basic operations section.""" category = QtWidgets.QGroupBox("⚙ Operations") category_layout = QtWidgets.QGridLayout(category) category_layout.setSpacing(5) category_layout.setContentsMargins(10, 10, 10, 10) tools = [ ("∪", "Union", "Boolean union", "boolean_union"), ("−", "Cut", "Boolean cut", "boolean_cut"), ("∩", "Intersect", "Boolean intersection", "boolean_intersection"), ("→", "Move", "Move object", "move_object"), ("↻", "Rotate", "Rotate object", "rotate_object"), ("⤢", "Scale", "Scale object", "scale_object"), ("⟷", "Mirror", "Mirror object", "mirror_object"), ("⋮⋮", "Array", "Create array", "create_array"), ] def create_handler(method_name): return lambda checked: self._execute_tool("operations", method_name) for i, (icon, name, tooltip, method) in enumerate(tools): btn = CompactToolButton(icon, name, tooltip) btn.clicked.connect(create_handler(method)) category_layout.addWidget(btn, i // 6, i % 6) layout.addWidget(category) def _create_measurements(self, layout): """Create measurements section.""" category = QtWidgets.QGroupBox("📏 Measure") category_layout = QtWidgets.QGridLayout(category) category_layout.setSpacing(5) category_layout.setContentsMargins(10, 10, 10, 10) tools = [ ("↔", "Distance", "Measure distance", "measure_distance"), ("∠", "Angle", "Measure angle", "measure_angle"), ("³", "Volume", "Measure volume", "measure_volume"), ("²", "Area", "Measure area", "measure_area"), ("━", "Length", "Measure length", "measure_length"), ("⌀", "Radius", "Measure radius", "measure_radius"), ("▭", "BBox", "Bounding box", "measure_bounding_box"), ("⊕", "CoG", "Center of gravity", "measure_center_of_gravity"), ] def create_handler(method_name): return lambda checked: self._execute_tool("measurements", method_name) for i, (icon, name, tooltip, method) in enumerate(tools): btn = CompactToolButton(icon, name, tooltip) btn.clicked.connect(create_handler(method)) category_layout.addWidget(btn, i // 6, i % 6) layout.addWidget(category) def _create_import_export(self, layout): """Create import/export section.""" category = QtWidgets.QGroupBox("📁 Import/Export") category_layout = QtWidgets.QGridLayout(category) category_layout.setSpacing(5) category_layout.setContentsMargins(10, 10, 10, 10) tools = [ ("▼S", "Import STL", "Import STL", "import_stl"), ("▲S", "Export STL", "Export STL", "export_stl"), ("▼P", "Import STEP", "Import STEP", "import_step"), ("▲P", "Export STEP", "Export STEP", "export_step"), ("▼I", "Import IGES", "Import IGES", "import_iges"), ("▲I", "Export IGES", "Export IGES", "export_iges"), ("▼O", "Import OBJ", "Import OBJ", "import_obj"), ("▲O", "Export OBJ", "Export OBJ", "export_obj"), ] def create_handler(method_name): return lambda checked: self._execute_tool("export_import", method_name) for i, (icon, name, tooltip, method) in enumerate(tools): btn = CompactToolButton(icon, name, tooltip) btn.clicked.connect(create_handler(method)) category_layout.addWidget(btn, i // 6, i % 6) layout.addWidget(category) def _create_advanced_primitives(self, layout): """Create advanced primitives section.""" category = QtWidgets.QGroupBox("◈ Advanced Shapes") category_layout = QtWidgets.QGridLayout(category) category_layout.setSpacing(5) category_layout.setContentsMargins(10, 10, 10, 10) tools = [ ("◎", "Tube", "Hollow cylinder", "create_tube"), ("⬟", "Prism", "N-sided prism", "create_prism"), ("◢", "Wedge", "Wedge shape", "create_wedge"), ("⬭", "Ellipsoid", "Ellipsoid", "create_ellipsoid"), ("∿", "Spring", "Spring/helix", "create_spring"), ("⚙", "Gear", "Create gear", "create_gear"), ("⟳", "Thread", "Create thread", "create_thread"), ("A", "Text", "3D text", "create_text"), ] def create_handler(method_name): return lambda checked: self._execute_tool( "advanced_primitives", method_name ) for i, (icon, name, tooltip, method) in enumerate(tools): btn = CompactToolButton(icon, name, tooltip) btn.clicked.connect(create_handler(method)) category_layout.addWidget(btn, i // 6, i % 6) layout.addWidget(category) def _create_advanced_operations(self, layout): """Create advanced operations section.""" category = QtWidgets.QGroupBox("⚡ Advanced Ops") category_layout = QtWidgets.QGridLayout(category) category_layout.setSpacing(5) category_layout.setContentsMargins(10, 10, 10, 10) tools = [ ("⬆", "Extrude", "Extrude profile", "extrude_profile"), ("⟲", "Revolve", "Revolve profile", "revolve_profile"), ("⌒", "Loft", "Loft profiles", "loft_profiles"), ("⟿", "Sweep", "Sweep along path", "sweep_profile"), ("⟳", "Helix", "Create helix", "create_helix"), ("⋮⋮", "Pattern", "Create pattern", "create_pattern"), ("⊼", "Project", "Project to surface", "project_to_surface"), ("⊕", "Wrap", "Wrap to surface", "wrap_to_surface"), ] def create_handler(method_name): return lambda checked: self._execute_tool( "advanced_operations", method_name ) for i, (icon, name, tooltip, method) in enumerate(tools): btn = CompactToolButton(icon, name, tooltip) btn.clicked.connect(create_handler(method)) category_layout.addWidget(btn, i // 6, i % 6) layout.addWidget(category) def _create_surface_modification(self, layout): """Create surface modification section.""" category = QtWidgets.QGroupBox("✨ Surface Mods") category_layout = QtWidgets.QGridLayout(category) category_layout.setSpacing(5) category_layout.setContentsMargins(10, 10, 10, 10) tools = [ ("╭", "Fillet", "Round edges", "fillet_edges"), ("╱", "Chamfer", "Chamfer edges", "chamfer_edges"), ("◣", "Draft", "Draft angle", "draft_faces"), ("⬚", "Shell", "Shell/hollow", "create_thickness"), ("⟷", "Offset", "Offset surface", "offset_surface"), ("∼", "Blend", "Blend surfaces", "blend_surfaces"), ("◫", "Patch", "Create patch", "create_patch"), ("≈", "Simplify", "Simplify shape", "simplify_shape"), ] def create_handler(method_name): return lambda checked: self._execute_tool( "surface_modification", method_name ) for i, (icon, name, tooltip, method) in enumerate(tools): btn = CompactToolButton(icon, name, tooltip) btn.clicked.connect(create_handler(method)) category_layout.addWidget(btn, i // 6, i % 6) layout.addWidget(category) def _execute_tool(self, category, method): """Execute a tool with parameter dialog.""" if category not in self.tools: self.status_label.setText(f"❌ {category} not available") self.status_label.setStyleSheet( "padding: 5px 10px; background-color: #ffcdd2; border-radius: 4px; font-size: 12px;" ) return tool = self.tools[category] if not hasattr(tool, method): self.status_label.setText(f"❌ {method} not found") self.status_label.setStyleSheet( "padding: 5px 10px; background-color: #ffcdd2; border-radius: 4px; font-size: 12px;" ) return # Create parameter dialog dialog = ToolParameterDialog(category, method, self) if dialog.exec_(): params = dialog.get_parameters() try: # Execute the tool result = getattr(tool, method)(**params) # Update status if result.get("success"): self.status_label.setText(f"✅ {result.get('message', 'Success')}") self.status_label.setStyleSheet( "padding: 5px 10px; background-color: #c8e6c9; border-radius: 4px; font-size: 12px;" ) else: self.status_label.setText(f"❌ {result.get('message', 'Failed')}") self.status_label.setStyleSheet( "padding: 5px 10px; background-color: #ffcdd2; border-radius: 4px; font-size: 12px;" ) # Emit signal self.tool_executed.emit(f"{category}.{method}", params, result) except Exception as e: self.logger.error(f"Error executing {category}.{method}: {e}") self.status_label.setText(f"❌ Error: {str(e)[:30]}...") self.status_label.setStyleSheet( "padding: 5px 10px; background-color: #ffcdd2; border-radius: 4px; font-size: 12px;" ) class ToolParameterDialog(QtWidgets.QDialog): """Compact dialog for tool parameters.""" # Parameter configurations for all tools PARAM_CONFIGS = { # Basic Primitives "primitives.create_box": [ ("length", "float", 10.0, "Length", "mm"), ("width", "float", 10.0, "Width", "mm"), ("height", "float", 10.0, "Height", "mm"), ("name", "string", "", "Name (optional)", ""), ], "primitives.create_cylinder": [ ("radius", "float", 5.0, "Radius", "mm"), ("height", "float", 10.0, "Height", "mm"), ("name", "string", "", "Name (optional)", ""), ], "primitives.create_sphere": [ ("radius", "float", 5.0, "Radius", "mm"), ("name", "string", "", "Name (optional)", ""), ], "primitives.create_cone": [ ("radius1", "float", 5.0, "Bottom Radius", "mm"), ("radius2", "float", 0.0, "Top Radius", "mm"), ("height", "float", 10.0, "Height", "mm"), ("name", "string", "", "Name (optional)", ""), ], "primitives.create_torus": [ ("radius1", "float", 10.0, "Major Radius", "mm"), ("radius2", "float", 2.0, "Minor Radius", "mm"), ("name", "string", "", "Name (optional)", ""), ], "primitives.create_plane": [ ("length", "float", 10.0, "Length", "mm"), ("width", "float", 10.0, "Width", "mm"), ("name", "string", "", "Name (optional)", ""), ], # Basic Operations "operations.boolean_union": [ ("obj1_name", "object", "", "First Object", ""), ("obj2_name", "object", "", "Second Object", ""), ("keep_originals", "bool", False, "Keep Originals", ""), ], "operations.boolean_cut": [ ("obj1_name", "object", "", "Base Object", ""), ("obj2_name", "object", "", "Tool Object", ""), ("keep_originals", "bool", False, "Keep Originals", ""), ], "operations.boolean_intersection": [ ("obj1_name", "object", "", "First Object", ""), ("obj2_name", "object", "", "Second Object", ""), ("keep_originals", "bool", False, "Keep Originals", ""), ], "operations.move_object": [ ("obj_name", "object", "", "Object", ""), ("x", "float", 0.0, "X", "mm"), ("y", "float", 0.0, "Y", "mm"), ("z", "float", 0.0, "Z", "mm"), ("relative", "bool", True, "Relative", ""), ], "operations.rotate_object": [ ("obj_name", "object", "", "Object", ""), ("angle", "float", 90.0, "Angle", "°"), ("axis", "choice", "z", "Axis", ["x", "y", "z"]), ("center", "point", "", "Center (optional)", ""), ], "operations.scale_object": [ ("obj_name", "object", "", "Object", ""), ("scale_x", "float", 1.0, "Scale X", ""), ("scale_y", "float", 1.0, "Scale Y", ""), ("scale_z", "float", 1.0, "Scale Z", ""), ], # Measurements "measurements.measure_distance": [ ("point1", "point", "", "First Point/Object", ""), ("point2", "point", "", "Second Point/Object", ""), ], "measurements.measure_angle": [ ("obj1_name", "object", "", "First Object/Edge", ""), ("obj2_name", "object", "", "Second Object/Edge", ""), ], "measurements.measure_volume": [("obj_name", "object", "", "Object", "")], "measurements.measure_area": [("obj_name", "object", "", "Object/Face", "")], # Import/Export "export_import.export_stl": [ ("filepath", "file_save", "model.stl", "Output File", ""), ("object_names", "objects", "", "Objects (empty=all)", ""), ("ascii", "bool", False, "ASCII Format", ""), ], "export_import.import_stl": [ ("filepath", "file_open", "", "STL File", ""), ("object_name", "string", "", "Name (optional)", ""), ], # Advanced Primitives "advanced_primitives.create_tube": [ ("outer_radius", "float", 10.0, "Outer Radius", "mm"), ("inner_radius", "float", 5.0, "Inner Radius", "mm"), ("height", "float", 10.0, "Height", "mm"), ("name", "string", "", "Name (optional)", ""), ], "advanced_primitives.create_prism": [ ("sides", "int", 6, "Number of Sides", ""), ("radius", "float", 5.0, "Radius", "mm"), ("height", "float", 10.0, "Height", "mm"), ("name", "string", "", "Name (optional)", ""), ], # Advanced Operations "advanced_operations.extrude_profile": [ ("profile_name", "object", "", "Profile Object", ""), ("distance", "float", 10.0, "Distance", "mm"), ("direction", "vector", "[0,0,1]", "Direction", ""), ("name", "string", "", "Name (optional)", ""), ], "advanced_operations.revolve_profile": [ ("profile_name", "object", "", "Profile Object", ""), ("angle", "float", 360.0, "Angle", "°"), ("axis", "vector", "[0,0,1]", "Axis", ""), ("name", "string", "", "Name (optional)", ""), ], # Surface Modification "surface_modification.fillet_edges": [ ("obj_name", "object", "", "Object", ""), ("edge_indices", "indices", "", "Edge Indices", ""), ("radius", "float", 1.0, "Radius", "mm"), ("name", "string", "", "Name (optional)", ""), ], "surface_modification.chamfer_edges": [ ("obj_name", "object", "", "Object", ""), ("edge_indices", "indices", "", "Edge Indices", ""), ("distance", "float", 1.0, "Distance", "mm"), ("name", "string", "", "Name (optional)", ""), ], } def __init__(self, category, method, parent=None): super().__init__(parent) self.category = category self.method = method self.inputs = {} self.setWindowTitle(f"{category}.{method}") self.setModal(True) self.setMinimumWidth(300) self._setup_ui() def _setup_ui(self): """Setup the parameter dialog UI.""" layout = QtWidgets.QVBoxLayout(self) # Title title = QtWidgets.QLabel(self.method.replace("_", " ").title()) title.setStyleSheet("font-size: 14px; font-weight: bold; margin-bottom: 10px;") layout.addWidget(title) # Parameters param_key = f"{self.category}.{self.method}" if param_key in self.PARAM_CONFIGS: form = QtWidgets.QFormLayout() for param_name, param_type, default, label, unit in self.PARAM_CONFIGS[ param_key ]: widget = self._create_input_widget( param_name, param_type, default, unit ) if widget: form.addRow(f"{label}:", widget) self.inputs[param_name] = widget layout.addLayout(form) else: # Generic message for unconfigured tools msg = QtWidgets.QLabel( "This tool requires no parameters or is not yet configured." ) msg.setWordWrap(True) layout.addWidget(msg) # Buttons buttons = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel ) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def _create_input_widget(self, name, param_type, default, unit): """Create appropriate input widget for parameter type.""" if param_type == "float": widget = QtWidgets.QDoubleSpinBox() widget.setRange(-10000, 10000) widget.setValue(default) widget.setDecimals(2) if unit: widget.setSuffix(f" {unit}") return widget elif param_type == "int": widget = QtWidgets.QSpinBox() widget.setRange(-10000, 10000) widget.setValue(default) return widget elif param_type == "bool": widget = QtWidgets.QCheckBox() widget.setChecked(default) return widget elif param_type == "string": widget = QtWidgets.QLineEdit() widget.setText(default) widget.setPlaceholderText("Optional" if not default else "") return widget elif param_type == "object": widget = QtWidgets.QComboBox() widget.setEditable(True) # Populate with current objects if FreeCAD.ActiveDocument: for obj in FreeCAD.ActiveDocument.Objects: widget.addItem(obj.Name) return widget elif param_type == "objects": widget = QtWidgets.QLineEdit() widget.setPlaceholderText("Obj1,Obj2,... or empty for all") return widget elif param_type == "point": widget = QtWidgets.QLineEdit() widget.setPlaceholderText("[x,y,z] or ObjectName") return widget elif param_type == "vector": widget = QtWidgets.QLineEdit() widget.setText(str(default)) widget.setPlaceholderText("[x,y,z]") return widget elif param_type == "indices": widget = QtWidgets.QLineEdit() widget.setPlaceholderText("0,1,2,... or empty for all") return widget elif param_type == "choice": widget = QtWidgets.QComboBox() if isinstance(unit, list): # Choices passed in unit field widget.addItems(unit) widget.setCurrentText(str(default)) return widget elif param_type == "file_save": widget = FileSelectWidget(default, True) return widget elif param_type == "file_open": widget = FileSelectWidget(default, False) return widget return None def get_parameters(self): """Extract parameters from dialog.""" params = {} for name, widget in self.inputs.items(): if isinstance(widget, QtWidgets.QDoubleSpinBox): params[name] = widget.value() elif isinstance(widget, QtWidgets.QSpinBox): params[name] = widget.value() elif isinstance(widget, QtWidgets.QCheckBox): params[name] = widget.isChecked() elif isinstance(widget, QtWidgets.QComboBox): params[name] = widget.currentText() elif isinstance(widget, QtWidgets.QLineEdit): text = widget.text().strip() if text: # Handle special cases if name == "object_names" and text: params[name] = [s.strip() for s in text.split(",") if s.strip()] elif name == "edge_indices" and text: params[name] = [ int(s.strip()) for s in text.split(",") if s.strip() ] elif ( name.startswith("point") or name == "direction" or name == "axis" ) and text.startswith("["): try: params[name] = eval(text) except: params[name] = text else: params[name] = text elif isinstance(widget, FileSelectWidget): params[name] = widget.get_path() return params class FileSelectWidget(QtWidgets.QWidget): """Widget for file selection.""" def __init__(self, default="", save_mode=False, parent=None): super().__init__(parent) self.save_mode = save_mode layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self.path_edit = QtWidgets.QLineEdit() self.path_edit.setText(default) self.browse_btn = QtWidgets.QPushButton("...") self.browse_btn.setMaximumWidth(30) self.browse_btn.clicked.connect(self._browse) layout.addWidget(self.path_edit) layout.addWidget(self.browse_btn) def _browse(self): """Open file dialog.""" if self.save_mode: path, _ = QtWidgets.QFileDialog.getSaveFileName( self, "Save File", self.path_edit.text() ) else: path, _ = QtWidgets.QFileDialog.getOpenFileName( self, "Open File", self.path_edit.text() ) if path: self.path_edit.setText(path) def get_path(self): """Get the selected path.""" return self.path_edit.text()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/mcp-freecad'

If you have feedback or need assistance with the MCP directory API, please join our Discord server