Freecad Scripts
GitHubOfficialFirst committed by jhaugaDistributed by GitHub
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides comprehensive support for the FreeCAD Python API, enabling users to automate 3D modeling, parametric design, and GUI development. It covers the entire FreeCAD ecosystem, including the Part, Mesh, and Sketcher workbenches, as well as advanced topics like FeaturePython objects, Coin3D scenegraph manipulation via Pivy, and custom PySide interface tools. It is designed to translate natural language requests into production-ready Python code for FreeCAD.
What it does
- Generates Python scripts for automated 3D geometry creation and manipulation.
- Assists in building parametric FeaturePython objects with custom properties.
- Develops custom GUI dialogs and task panels using PySide and Qt.
- Provides code for manipulating the Coin3D scenegraph via Pivy.
- Automates repetitive CAD tasks through macro development.
- Facilitates the creation of custom workbenches and GUI commands.
When to use it
- When you need to automate repetitive 3D modeling operations in FreeCAD.
- When creating custom parametric parts that require specific logic or properties.
- When developing custom tools or workbenches to extend FreeCAD functionality.
- When you need to perform complex geometric operations or conversions between mesh and solid formats.
- When building interactive GUI interfaces for CAD workflows.
Advantages
- Deep integration with FreeCAD's internal Python interpreter and API.
- Supports both headless automation and interactive GUI-based tool development.
- Provides standardized patterns for parametric object design and recomputation.
- Includes best practices for macro structure and selection handling.
- Simplifies complex OpenCASCADE-based BRep operations.
Usage examples
- “Write a FreeCAD script to create a 10x10x10 box and fillet its edges by 1mm.”
- The agent generates a Python script using Part.makeBox and the .makeFillet method, followed by a doc.recompute() call to update the document.
- “Create a custom parametric object in FreeCAD that acts as a cylinder with a configurable radius.”
- The agent provides a FeaturePython class implementation with an App::PropertyLength property for the radius and an execute method to regenerate the cylinder shape.
- “How do I create a task panel in FreeCAD to input dimensions for a sketch?”
- The agent provides a class structure inheriting from QWidget, including a layout with QDoubleSpinBox inputs and the necessary methods to integrate with FreeCADGui.Control.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/github/freecad-scripts/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip freecad-scripts.zip -d .agents/skills
Documentation by jhauga
FreeCAD Scripts
Expert skill for generating production-quality Python scripts for the FreeCAD CAD application. Interprets shorthand, quasi-code, and natural language descriptions of 3D modeling tasks and translates them into correct FreeCAD Python API calls.
When to Use This Skill
Writing Python scripts for FreeCAD's built-in console or macro system
Creating or manipulating 3D geometry (Part, Mesh, Sketcher, Path, FEM)
Building parametric FeaturePython objects with custom properties
Developing GUI tools using PySide/Qt within FreeCAD
Manipulating the Coin3D scenegraph via Pivy
Creating custom workbenches or Gui Commands
Automating repetitive CAD operations with macros
Converting between mesh and solid representations
Scripting FEM analyses, raytracing, or drawing exports
Prerequisites
FreeCAD installed (0.19+ recommended; 0.21+/1.0+ for latest API)
Python 3.x (bundled with FreeCAD)
For GUI work: PySide2 (bundled with FreeCAD)
For scenegraph: Pivy (bundled with FreeCAD)
FreeCAD Python Environment
FreeCAD embeds a Python interpreter. Scripts run in an environment where these key modules are available:
import FreeCAD # Core module (also aliased as 'App')
import FreeCADGui # GUI module (also aliased as 'Gui') — only in GUI mode
import Part # Part workbench — BRep/OpenCASCADE shapes
import Mesh # Mesh workbench — triangulated meshes
import Sketcher # Sketcher workbench — 2D constrained sketches
import Draft # Draft workbench — 2D drawing tools
import Arch # Arch/BIM workbench
import Path # Path/CAM workbench
import FEM # FEM workbench
import TechDraw # TechDraw workbench (replaces Drawing)
import BOPTools # Boolean operations
import CompoundTools # Compound shape utilitiesThe FreeCAD Document Model
# Create or access a document
doc = FreeCAD.newDocument("MyDoc")
doc = FreeCAD.ActiveDocument
# Add objects
box = doc.addObject("Part::Box", "MyBox")
box.Length = 10.0
box.Width = 10.0
box.Height = 10.0
# Recompute
doc.recompute()
# Access objects
obj = doc.getObject("MyBox")
obj = doc.MyBox # Attribute access also works
# Remove objects
doc.removeObject("MyBox")Core Concepts
Vectors and Placements
import FreeCAD
# Vectors
v1 = FreeCAD.Vector(1, 0, 0)
v2 = FreeCAD.Vector(0, 1, 0)
v3 = v1.cross(v2) # Cross product
d = v1.dot(v2) # Dot product
v4 = v1 + v2 # Addition
length = v1.Length # Magnitude
v_norm = FreeCAD.Vector(v1)
v_norm.normalize() # In-place normalize
# Rotations
rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 45) # axis, angle(deg)
rot = FreeCAD.Rotation(0, 0, 45) # Euler angles (yaw, pitch, roll)
# Placements (position + orientation)
placement = FreeCAD.Placement(
FreeCAD.Vector(10, 20, 0), # translation
FreeCAD.Rotation(0, 0, 45), # rotation
FreeCAD.Vector(0, 0, 0) # center of rotation
)
obj.Placement = placement
# Matrix (4x4 transformation)
import math
mat = FreeCAD.Matrix()
mat.move(FreeCAD.Vector(10, 0, 0))
mat.rotateZ(math.radians(45))Creating and Manipulating Geometry (Part Module)
The Part module wraps OpenCASCADE and provides BRep solid modeling:
import FreeCAD
import Part
# --- Primitive Shapes ---
box = Part.makeBox(10, 10, 10) # length, width, height
cyl = Part.makeCylinder(5, 20) # radius, height
sphere = Part.makeSphere(10) # radius
cone = Part.makeCone(5, 2, 10) # r1, r2, height
torus = Part.makeTorus(10, 2) # major_r, minor_r
# --- Wires and Edges ---
edge1 = Part.makeLine((0, 0, 0), (10, 0, 0))
edge2 = Part.makeLine((10, 0, 0), (10, 10, 0))
edge3 = Part.makeLine((10, 10, 0), (0, 0, 0))
wire = Part.Wire([edge1, edge2, edge3])
# Circles and arcs
circle = Part.makeCircle(5) # radius
arc = Part.makeCircle(5, FreeCAD.Vector(0, 0, 0),
FreeCAD.Vector(0, 0, 1), 0, 180) # start/end angle
# --- Faces ---
face = Part.Face(wire) # From a closed wire
# --- Solids from Faces/Wires ---
extrusion = face.extrude(FreeCAD.Vector(0, 0, 10)) # Extrude
revolved = face.revolve(FreeCAD.Vector(0, 0, 0),
FreeCAD.Vector(0, 0, 1), 360) # Revolve
# --- Boolean Operations ---
fused = box.fuse(cyl) # Union
cut = box.cut(cyl) # Subtraction
common = box.common(cyl) # Intersection
fused_clean = fused.removeSplitter() # Clean up seams
# --- Fillets and Chamfers ---
filleted = box.makeFillet(1.0, box.Edges) # radius, edges
chamfered = box.makeChamfer(1.0, box.Edges) # dist, edges
# --- Loft and Sweep ---
loft = Part.makeLoft([wire1, wire2], True) # wires, solid
swept = Part.Wire([path_edge]).makePipeShell([profile_wire],
True, False) # solid, frenet
# --- BSpline Curves ---
from FreeCAD import Vector
points = [Vector(0,0,0), Vector(1,2,0), Vector(3,1,0), Vector(4,3,0)]
bspline = Part.BSplineCurve()
bspline.interpolate(points)
edge = bspline.toShape()
# --- Show in document ---
Part.show(box, "MyBox") # Quick display (adds to active doc)
# Or explicitly:
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument()
obj = doc.addObject("Part::Feature", "MyShape")
obj.Shape = box
doc.recompute()Topological Exploration
shape = obj.Shape
# Access sub-elements
shape.Vertexes # List of Vertex objects
shape.Edges # List of Edge objects
shape.Wires # List of Wire objects
shape.Faces # List of Face objects
shape.Shells # List of Shell objects
shape.Solids # List of Solid objects
# Bounding box
bb = shape.BoundBox
print(bb.XMin, bb.XMax, bb.YMin, bb.YMax, bb.ZMin, bb.ZMax)
print(bb.Center)
# Properties
shape.Volume
shape.Area
shape.Length # For edges/wires
face.Surface # Underlying geometric surface
edge.Curve # Underlying geometric curve
# Shape type
shape.ShapeType # "Solid", "Shell", "Face", "Wire", "Edge", "Vertex", "Compound"Mesh Module
import Mesh
# Create mesh from vertices and facets
mesh = Mesh.Mesh()
mesh.addFacet(
0.0, 0.0, 0.0, # vertex 1
1.0, 0.0, 0.0, # vertex 2
0.0, 1.0, 0.0 # vertex 3
)
# Import/Export
mesh = Mesh.Mesh("/path/to/file.stl")
mesh.write("/path/to/output.stl")
# Convert Part shape to Mesh
import Part
import MeshPart
shape = Part.makeBox(1, 1, 1)
mesh = MeshPart.meshFromShape(Shape=shape, LinearDeflection=0.1,
AngularDeflection=0.5)
# Convert Mesh to Part shape
shape = Part.Shape()
shape.makeShapeFromMesh(mesh.Topology, 0.05) # tolerance
solid = Part.makeSolid(shape)Sketcher Module
Create a sketch on XY plane
sketch = doc.addObject("Sketcher::SketchObject", "MySketch") sketch.Placement = FreeCAD.Placement( FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 0, 0, 1) )
Add geometry (returns geometry index)
idx_line = sketch.addGeometry(Part.LineSegment( FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(10, 0, 0))) idx_circle = sketch.addGeometry(Part.Circle( FreeCAD.Vector(5, 5, 0), FreeCAD.Vector(0, 0, 1), 3))
Add constraints
sketch.addConstraint(Sketcher.Constraint("Coincident", 0, 2, 1, 1)) sketch.addConstraint(Sketcher.Constraint("Horizontal", 0)) sketch.addConstraint(Sketcher.Constraint("DistanceX", 0, 1, 0, 2, 10.0)) sketch.addConstraint(Sketcher.Constraint("Radius", 1, 3.0)) sketch.addConstraint(Sketcher.Constraint("Fixed", 0, 1))
Constraint types: Coincident, Horizontal, Vertical, Parallel, Perpendicular,
Tangent, Equal, Symmetric, Distance, DistanceX, DistanceY, Radius, Angle,
Fixed (Block), InternalAlignment
doc.recompute()
### Draft Module
```python
import Draft
import FreeCAD
# 2D shapes
line = Draft.makeLine(FreeCAD.Vector(0,0,0), FreeCAD.Vector(10,0,0))
circle = Draft.makeCircle(5)
rect = Draft.makeRectangle(10, 5)
poly = Draft.makePolygon(6, radius=5) # hexagon
# Operations
moved = Draft.move(obj, FreeCAD.Vector(10, 0, 0), copy=True)
rotated = Draft.rotate(obj, 45, FreeCAD.Vector(0,0,0),
axis=FreeCAD.Vector(0,0,1), copy=True)
scaled = Draft.scale(obj, FreeCAD.Vector(2,2,2), center=FreeCAD.Vector(0,0,0),
copy=True)
offset = Draft.offset(obj, FreeCAD.Vector(1,0,0))
array = Draft.makeArray(obj, FreeCAD.Vector(15,0,0),
FreeCAD.Vector(0,15,0), 3, 3)Creating Parametric Objects (FeaturePython)
FeaturePython objects are custom parametric objects with properties that trigger recomputation:
import FreeCAD
import Part
class MyBox:
"""A custom parametric box."""
def __init__(self, obj):
obj.Proxy = self
obj.addProperty("App::PropertyLength", "Length", "Dimensions",
"Box length").Length = 10.0
obj.addProperty("App::PropertyLength", "Width", "Dimensions",
"Box width").Width = 10.0
obj.addProperty("App::PropertyLength", "Height", "Dimensions",
"Box height").Height = 10.0
def execute(self, obj):
"""Called on document recompute."""
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)
def onChanged(self, obj, prop):
"""Called when a property changes."""
pass
def __getstate__(self):
return None
def __setstate__(self, state):
return None
class ViewProviderMyBox:
"""View provider for custom icon and display settings."""
def __init__(self, vobj):
vobj.Proxy = self
def getIcon(self):
return ":/icons/Part_Box.svg"
def attach(self, vobj):
self.Object = vobj.Object
def updateData(self, obj, prop):
pass
def onChanged(self, vobj, prop):
pass
def __getstate__(self):
return None
def __setstate__(self, state):
return None
# --- Usage ---
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("Test")
obj = doc.addObject("Part::FeaturePython", "CustomBox")
MyBox(obj)
ViewProviderMyBox(obj.ViewObject)
doc.recompute()Common Property Types
| Property Type | Python Type | Description |
|---|---|---|
App::PropertyBool | bool | Boolean |
App::PropertyInteger | int | Integer |
App::PropertyFloat | float | Float |
App::PropertyString | str | String |
App::PropertyLength | float (units) | Length with units |
App::PropertyAngle | float (deg) | Angle in degrees |
App::PropertyVector | FreeCAD.Vector | 3D vector |
App::PropertyPlacement | FreeCAD.Placement | Position + rotation |
App::PropertyLink | object ref | Link to another object |
App::PropertyLinkList | list of refs | Links to multiple objects |
App::PropertyEnumeration | list/str | Dropdown selection |
App::PropertyFile | str | File path |
App::PropertyColor | tuple | RGB color (0.0-1.0) |
App::PropertyPythonObject | any | Serializable Python object |
Creating GUI Tools
Gui Commands
import FreeCAD
import FreeCADGui
class MyCommand:
"""A custom toolbar/menu command."""
def GetResources(self):
return {
"Pixmap": ":/icons/Part_Box.svg",
"MenuText": "My Custom Command",
"ToolTip": "Creates a custom box",
"Accel": "Ctrl+Shift+B"
}
def IsActive(self):
return FreeCAD.ActiveDocument is not None
def Activated(self):
# Command logic here
FreeCAD.Console.PrintMessage("Command activated\n")
FreeCADGui.addCommand("My_CustomCommand", MyCommand())PySide Dialogs
from PySide2 import QtWidgets, QtCore, QtGui
class MyDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent or FreeCADGui.getMainWindow())
self.setWindowTitle("My Tool")
self.setMinimumWidth(300)
layout = QtWidgets.QVBoxLayout(self)
# Input fields
self.label = QtWidgets.QLabel("Length:")
self.spinbox = QtWidgets.QDoubleSpinBox()
self.spinbox.setRange(0.1, 1000.0)
self.spinbox.setValue(10.0)
self.spinbox.setSuffix(" mm")
form = QtWidgets.QFormLayout()
form.addRow(self.label, self.spinbox)
layout.addLayout(form)
# Buttons
btn_layout = QtWidgets.QHBoxLayout()
self.btn_ok = QtWidgets.QPushButton("OK")
self.btn_cancel = QtWidgets.QPushButton("Cancel")
btn_layout.addWidget(self.btn_ok)
btn_layout.addWidget(self.btn_cancel)
layout.addLayout(btn_layout)
self.btn_ok.clicked.connect(self.accept)
self.btn_cancel.clicked.connect(self.reject)
# Usage
dialog = MyDialog()
if dialog.exec_() == QtWidgets.QDialog.Accepted:
length = dialog.spinbox.value()
FreeCAD.Console.PrintMessage(f"Length: {length}\n")Task Panel (Recommended for FreeCAD integration)
class MyTaskPanel:
"""Task panel shown in the left sidebar."""
def __init__(self):
self.form = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(self.form)
self.spinbox = QtWidgets.QDoubleSpinBox()
self.spinbox.setValue(10.0)
layout.addWidget(QtWidgets.QLabel("Length:"))
layout.addWidget(self.spinbox)
def accept(self):
# Called when user clicks OK
length = self.spinbox.value()
FreeCAD.Console.PrintMessage(f"Accepted: {length}\n")
FreeCADGui.Control.closeDialog()
return True
def reject(self):
FreeCADGui.Control.closeDialog()
return True
def getStandardButtons(self):
return int(QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
# Show the panel
panel = MyTaskPanel()
FreeCADGui.Control.showDialog(panel)Coin3D Scenegraph (Pivy)
from pivy import coin
import FreeCADGui
# Access the scenegraph root
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
# Add a custom separator with a sphere
sep = coin.SoSeparator()
mat = coin.SoMaterial()
mat.diffuseColor.setValue(1.0, 0.0, 0.0) # Red
trans = coin.SoTranslation()
trans.translation.setValue(10, 10, 10)
sphere = coin.SoSphere()
sphere.radius.setValue(2.0)
sep.addChild(mat)
sep.addChild(trans)
sep.addChild(sphere)
sg.addChild(sep)
# Remove later
sg.removeChild(sep)Custom Workbench Creation
import FreeCADGui
class MyWorkbench(FreeCADGui.Workbench):
MenuText = "My Workbench"
ToolTip = "A custom workbench"
Icon = ":/icons/freecad.svg"
def Initialize(self):
"""Called at workbench activation."""
import MyCommands # Import your command module
self.appendToolbar("My Tools", ["My_CustomCommand"])
self.appendMenu("My Menu", ["My_CustomCommand"])
def Activated(self):
pass
def Deactivated(self):
pass
def GetClassName(self):
return "Gui::PythonWorkbench"
FreeCADGui.addWorkbench(MyWorkbench)Macro Best Practices
# Standard macro header
# -*- coding: utf-8 -*-
# FreeCAD Macro: MyMacro
# Description: Brief description of what the macro does
# Author: YourName
# Version: 1.0
# Date: 2026-04-07
import FreeCAD
import Part
from FreeCAD import Base
# Guard for GUI availability
if FreeCAD.GuiUp:
import FreeCADGui
from PySide2 import QtWidgets, QtCore
def main():
doc = FreeCAD.ActiveDocument
if doc is None:
FreeCAD.Console.PrintError("No active document\n")
return
if FreeCAD.GuiUp:
sel = FreeCADGui.Selection.getSelection()
if not sel:
FreeCAD.Console.PrintWarning("No objects selected\n")
# ... macro logic ...
doc.recompute()
FreeCAD.Console.PrintMessage("Macro completed\n")
if __name__ == "__main__":
main()Selection Handling
# Get selected objects
sel = FreeCADGui.Selection.getSelection() # List of objects
sel_ex = FreeCADGui.Selection.getSelectionEx() # Extended (sub-elements)
for selobj in sel_ex:
obj = selobj.Object
for sub in selobj.SubElementNames:
print(f"{obj.Name}.{sub}")
shape = obj.getSubObject(sub) # Get sub-shape
# Select programmatically
FreeCADGui.Selection.addSelection(doc.MyBox)
FreeCADGui.Selection.addSelection(doc.MyBox, "Face1")
FreeCADGui.Selection.clearSelection()Console Output
FreeCAD.Console.PrintMessage("Info message\n")
FreeCAD.Console.PrintWarning("Warning message\n")
FreeCAD.Console.PrintError("Error message\n")
FreeCAD.Console.PrintLog("Debug/log message\n")Common Patterns
Parametric Pad from Sketch
doc = FreeCAD.ActiveDocument
# Create sketch
sketch = doc.addObject("Sketcher::SketchObject", "Sketch")
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(0,0,0), FreeCAD.Vector(10,0,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(10,0,0), FreeCAD.Vector(10,10,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(10,10,0), FreeCAD.Vector(0,10,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(0,10,0), FreeCAD.Vector(0,0,0)))
# Close with coincident constraints
for i in range(3):
sketch.addConstraint(Sketcher.Constraint("Coincident", i, 2, i+1, 1))
sketch.addConstraint(Sketcher.Constraint("Coincident", 3, 2, 0, 1))
# Pad (PartDesign)
pad = doc.addObject("PartDesign::Pad", "Pad")
pad.Profile = sketch
pad.Length = 5.0
sketch.Visibility = False
doc.recompute()Export Shapes
# STEP export
Part.export([doc.MyBox], "/path/to/output.step")
# STL export (mesh)
import Mesh
Mesh.export([doc.MyBox], "/path/to/output.stl")
# IGES export
Part.export([doc.MyBox], "/path/to/output.iges")
# Multiple formats via importlib
import importlib
importlib.import_module("importOBJ").export([doc.MyBox], "/path/to/output.obj")Units and Quantities
# FreeCAD uses mm internally
q = FreeCAD.Units.Quantity("10 mm")
q_inch = FreeCAD.Units.Quantity("1 in")
print(q_inch.getValueAs("mm")) # 25.4
# Parse user input with units
q = FreeCAD.Units.parseQuantity("2.5 in")
value_mm = float(q) # Value in mm (internal unit)Compensation Rules (Quasi-Coder Integration)
When interpreting shorthand or quasi-code for FreeCAD scripts:
Terminology mapping: "box" →
Part.makeBox(), "cylinder" →Part.makeCylinder(), "sphere" →Part.makeSphere(), "merge/combine/join" →.fuse(), "subtract/cut/remove" →.cut(), "intersect" →.common(), "round edges/fillet" →.makeFillet(), "bevel/chamfer" →.makeChamfer()Implicit document: If no document handling is mentioned, wrap in standard
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument()Units assumption: Default to millimeters unless stated otherwise
Recompute: Always call
doc.recompute()after modificationsGUI guard: Wrap GUI-dependent code in
if FreeCAD.GuiUp:when the script may run headlessPart.show(): Use
Part.show(shape, "Name")for quick display, ordoc.addObject("Part::Feature", "Name")for named persistent objects
References
Primary Links
Bundled Reference Documents
See the references/ directory for topic-organized guides:
scripting-fundamentals.md — Core scripting, document model, console
geometry-and-shapes.md — Part, Mesh, Sketcher, topology
parametric-objects.md — FeaturePython, properties, scripted objects
gui-and-interface.md — PySide, dialogs, task panels, Coin3D
workbenches-and-advanced.md — Workbenches, macros, FEM, Path, recipes
Files
- ATTRIBUTION.mdAdded by AutoGineer859 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md20.9 KiB
- references/geometry-and-shapes.md8.6 KiB
- references/gui-and-interface.md11.0 KiB
- references/parametric-objects.md10.6 KiB
- references/scripting-fundamentals.md6.0 KiB
- references/workbenches-and-advanced.md9.8 KiB
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Python
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
jhauga
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/freecad-scripts · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- jhauga
AutoGineer is not affiliated with or endorsed by jhauga or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download freecad-scripts.zip