All skills
github avatar

/freecad-scripts

@c037695 official
by githubgithub/awesome-copilot40k stars
5,040

Expert skill for writing FreeCAD Python scripts, macros, and automation. Use when asked to create FreeCAD models, parametric objects, Part/Mesh/Sketcher scripts, workbench tools, GUI dialogs with PySide, Coin3D scenegraph manipulation, or any FreeCAD Python API task. Covers FreeCAD scripting basics, geometry creation, FeaturePython objects, interface tools, and macro development.

Use this Skill: https://skilld.dev/gh/github/awesome-copilot/freecad-scripts

This session only. Nothing lands on disk.

referencesgui-and-interface.md

≈2.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

FreeCAD GUI and Interface

Reference guide for building FreeCAD user interfaces: PySide/Qt dialogs, task panels, Gui Commands, Coin3D scenegraph via Pivy.

Official Wiki References

Gui Command

The standard way to add toolbar buttons and menu items in FreeCAD:

import FreeCAD
import FreeCADGui

class MyCommand:
    """A registered FreeCAD command."""

    def GetResources(self):
        return {
            "Pixmap": ":/icons/Part_Box.svg",    # Icon (built-in or custom path)
            "MenuText": "My Command",
            "ToolTip": "Does something useful",
            "Accel": "Ctrl+Shift+M",             # Keyboard shortcut
            "CmdType": "ForEdit"                  # Optional: ForEdit, Alter, etc.
        }

    def IsActive(self):
        """Return True if command should be enabled."""
        return FreeCAD.ActiveDocument is not None

    def Activated(self):
        """Called when the command is triggered."""
        FreeCAD.Console.PrintMessage("Command activated!\n")
        # Open a task panel:
        panel = MyTaskPanel()
        FreeCADGui.Control.showDialog(panel)

# Register the command (name must be unique)
FreeCADGui.addCommand("My_Command", MyCommand())

Task Panel (Sidebar Integration)

Task panels appear in FreeCAD's left sidebar — the preferred way to build interactive tools:

import FreeCAD
import FreeCADGui
from PySide2 import QtWidgets, QtCore

class MyTaskPanel:
    """Task panel for the sidebar."""

    def __init__(self):
        # Build the widget
        self.form = QtWidgets.QWidget()
        self.form.setWindowTitle("My Tool")
        layout = QtWidgets.QVBoxLayout(self.form)

        # Input widgets
        self.length_spin = QtWidgets.QDoubleSpinBox()
        self.length_spin.setRange(0.1, 10000.0)
        self.length_spin.setValue(10.0)
        self.length_spin.setSuffix(" mm")
        self.length_spin.setDecimals(2)

        self.width_spin = QtWidgets.QDoubleSpinBox()
        self.width_spin.setRange(0.1, 10000.0)
        self.width_spin.setValue(10.0)
        self.width_spin.setSuffix(" mm")

        self.height_spin = QtWidgets.QDoubleSpinBox()
        self.height_spin.setRange(0.1, 10000.0)
        self.height_spin.setValue(5.0)
        self.height_spin.setSuffix(" mm")

        self.fillet_check = QtWidgets.QCheckBox("Apply fillet")

        # Form layout
        form_layout = QtWidgets.QFormLayout()
        form_layout.addRow("Length:", self.length_spin)
        form_layout.addRow("Width:", self.width_spin)
        form_layout.addRow("Height:", self.height_spin)
        form_layout.addRow(self.fillet_check)
        layout.addLayout(form_layout)

        # Live preview on value change
        self.length_spin.valueChanged.connect(self._preview)
        self.width_spin.valueChanged.connect(self._preview)
        self.height_spin.valueChanged.connect(self._preview)

    def _preview(self):
        """Update preview in 3D view."""
        pass  # Build and display temporary shape

    def accept(self):
        """Called when user clicks OK."""
        import Part
        doc = FreeCAD.ActiveDocument
        shape = Part.makeBox(
            self.length_spin.value(),
            self.width_spin.value(),
            self.height_spin.value()
        )
        Part.show(shape, "MyBox")
        doc.recompute()
        FreeCADGui.Control.closeDialog()
        return True

    def reject(self):
        """Called when user clicks Cancel."""
        FreeCADGui.Control.closeDialog()
        return True

    def getStandardButtons(self):
        """Which buttons to show."""
        return int(QtWidgets.QDialogButtonBox.Ok |
                   QtWidgets.QDialogButtonBox.Cancel)

    def isAllowedAlterSelection(self):
        return True

    def isAllowedAlterView(self):
        return True

    def isAllowedAlterDocument(self):
        return True

# Show:
# FreeCADGui.Control.showDialog(MyTaskPanel())

Task Panel with Multiple Widgets (Multi-Form)

class MultiFormPanel:
    def __init__(self):
        self.form = [self._buildPage1(), self._buildPage2()]

    def _buildPage1(self):
        w = QtWidgets.QWidget()
        w.setWindowTitle("Page 1")
        # ... add widgets ...
        return w

    def _buildPage2(self):
        w = QtWidgets.QWidget()
        w.setWindowTitle("Page 2")
        # ... add widgets ...
        return w

Standalone PySide Dialogs

import FreeCAD
import FreeCADGui
from PySide2 import QtWidgets, QtCore, QtGui

class MyDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent or (FreeCADGui.getMainWindow() if FreeCAD.GuiUp else None))
        self.setWindowTitle("My Dialog")
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)

        layout = QtWidgets.QVBoxLayout(self)

        # Combo box
        self.combo = QtWidgets.QComboBox()
        self.combo.addItems(["Option A", "Option B", "Option C"])
        layout.addWidget(self.combo)

        # Slider
        self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        self.slider.setRange(1, 100)
        self.slider.setValue(50)
        layout.addWidget(self.slider)

        # Text input
        self.line_edit = QtWidgets.QLineEdit()
        self.line_edit.setPlaceholderText("Enter a name...")
        layout.addWidget(self.line_edit)

        # Button box
        buttons = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)

Loading a .ui File

import os
from PySide2 import QtWidgets, QtUiTools, QtCore

def loadUiFile(ui_path):
    """Load a Qt Designer .ui file."""
    loader = QtUiTools.QUiLoader()
    file = QtCore.QFile(ui_path)
    file.open(QtCore.QFile.ReadOnly)
    widget = loader.load(file)
    file.close()
    return widget

# In a task panel:
class UiTaskPanel:
    def __init__(self):
        ui_path = os.path.join(os.path.dirname(__file__), "panel.ui")
        self.form = loadUiFile(ui_path)
        # Access widgets by objectName set in Qt Designer
        self.form.myButton.clicked.connect(self._onButton)

File Dialogs

# Open file
path, _ = QtWidgets.QFileDialog.getOpenFileName(
    FreeCADGui.getMainWindow(),
    "Open File",
    "",
    "STEP files (*.step *.stp);;All files (*)"
)

# Save file
path, _ = QtWidgets.QFileDialog.getSaveFileName(
    FreeCADGui.getMainWindow(),
    "Save File",
    "",
    "STL files (*.stl);;All files (*)"
)

# Select directory
path = QtWidgets.QFileDialog.getExistingDirectory(
    FreeCADGui.getMainWindow(),
    "Select Directory"
)

Message Boxes

QtWidgets.QMessageBox.information(None, "Info", "Operation completed.")
QtWidgets.QMessageBox.warning(None, "Warning", "Something may be wrong.")
QtWidgets.QMessageBox.critical(None, "Error", "An error occurred.")

result = QtWidgets.QMessageBox.question(
    None, "Confirm", "Are you sure?",
    QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
)
if result == QtWidgets.QMessageBox.Yes:
    pass  # proceed

Input Dialogs

text, ok = QtWidgets.QInputDialog.getText(None, "Input", "Enter name:")
value, ok = QtWidgets.QInputDialog.getDouble(None, "Input", "Value:", 10.0, 0, 1000, 2)
choice, ok = QtWidgets.QInputDialog.getItem(None, "Choose", "Select:", ["A","B","C"], 0, False)

Coin3D / Pivy Scenegraph

FreeCAD's 3D view uses Coin3D (Open Inventor). Pivy provides Python bindings.

from pivy import coin
import FreeCADGui

# Get the scenegraph root
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()

# --- Basic shapes ---
sep = coin.SoSeparator()

# Material (color)
mat = coin.SoMaterial()
mat.diffuseColor.setValue(0.0, 0.8, 0.2)  # RGB 0-1
mat.transparency.setValue(0.3)             # 0=opaque, 1=invisible

# Transform
transform = coin.SoTransform()
transform.translation.setValue(10, 0, 0)
transform.rotation.setValue(coin.SbVec3f(0,0,1), 0.785)  # axis, angle(rad)
transform.scaleFactor.setValue(2, 2, 2)

# Shapes
sphere = coin.SoSphere()
sphere.radius.setValue(3.0)

cube = coin.SoCube()
cube.width.setValue(5)
cube.height.setValue(5)
cube.depth.setValue(5)

cylinder = coin.SoCylinder()
cylinder.radius.setValue(2)
cylinder.height.setValue(10)

# Assemble
sep.addChild(mat)
sep.addChild(transform)
sep.addChild(sphere)
sg.addChild(sep)

# --- Lines ---
line_sep = coin.SoSeparator()
coords = coin.SoCoordinate3()
coords.point.setValues(0, 3, [[0,0,0], [10,0,0], [10,10,0]])
line_set = coin.SoLineSet()
line_set.numVertices.setValue(3)
line_sep.addChild(coords)
line_sep.addChild(line_set)
sg.addChild(line_sep)

# --- Points ---
point_sep = coin.SoSeparator()
style = coin.SoDrawStyle()
style.pointSize.setValue(5)
coords = coin.SoCoordinate3()
coords.point.setValues(0, 3, [[0,0,0], [5,5,0], [10,0,0]])
points = coin.SoPointSet()
point_sep.addChild(style)
point_sep.addChild(coords)
point_sep.addChild(points)
sg.addChild(point_sep)

# --- Text ---
text_sep = coin.SoSeparator()
trans = coin.SoTranslation()
trans.translation.setValue(0, 0, 5)
font = coin.SoFont()
font.name.setValue("Arial")
font.size.setValue(16)
text = coin.SoText2()       # 2D screen-aligned text
text.string.setValue("Hello")
text_sep.addChild(trans)
text_sep.addChild(font)
text_sep.addChild(text)
sg.addChild(text_sep)

# --- Cleanup ---
sg.removeChild(sep)
sg.removeChild(line_sep)

View Manipulation

view = FreeCADGui.ActiveDocument.ActiveView

# Camera operations
view.viewIsometric()
view.viewFront()
view.viewTop()
view.viewRight()
view.fitAll()
view.setCameraOrientation(FreeCAD.Rotation(0, 0, 0))
view.setCameraType("Perspective")   # or "Orthographic"

# Save image
view.saveImage("/path/to/screenshot.png", 1920, 1080, "White")

# Get camera info
cam = view.getCameraNode()

Source: SKILL.md on GitHub

No alerts16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill is a technical reference for FreeCAD Python scripting. It provides code templates and API documentation for 3D modeling, GUI creation, and automation. No malicious patterns or security risks were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at c037695. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 6 months ago
  • Python
  • freecad
  • cad
  • modeling
  • parametric
  • macros
  • geometry
  • part-design
  • sketcher
  • pyside

README badge

README badge for github/awesome-copilot/freecad-scripts

Writes Python scripts for FreeCAD's embedded interpreter, generating geometry creation, parametric FeaturePython objects, sketches, macros, and GUI tools using the FreeCAD Python API. Covers Part/Mesh/Sketcher workbenches, Coin3D scenegraph manipulation via Pivy, PySide dialogs, and custom workbench commands.

Generated from the current SKILL.md.

Does this skill work with all FreeCAD versions?
FreeCAD 0.19+ is recommended; version 0.21+/1.0+ is needed for the latest API features. The skill covers the core Python API stable across these versions.
Can I use this skill to create GUI dialogs?
Yes. The skill covers PySide2/Qt GUI tools within FreeCAD, which is bundled with FreeCAD installations.
Does this skill support mesh-to-solid conversion?
Yes. The skill includes examples of converting between mesh and Part shapes using MeshPart.meshFromShape() and Part.makeShapeFromMesh().
Can I create custom workbenches with this skill?
Yes. The skill covers creating custom workbenches, Gui Commands, and FeaturePython objects for parametric design.

Generated from the current SKILL.md. These answers refresh after source changes.