Installation
Install with CLI
Recommended
gh skills-hub install freecad-scripts Don't have the extension? Run gh extension install samueltauil/skills-hub first.
Download and extract to your repository:
.github/skills/freecad-scripts/ Extract the ZIP to .github/skills/ in your repo. The folder name must match freecad-scripts for Copilot to auto-discover it.
Skill Files (6)
SKILL.md 20.8 KB
---
name: freecad-scripts
description: '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.'
---
# 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:
```python
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 utilities
```
### The FreeCAD Document Model
```python
# 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
```python
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:
```python
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
```python
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
```python
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:
```python
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
```python
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
```python
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)
```python
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)
```python
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
```python
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
```python
# 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
```python
# 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
```python
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
```python
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
```python
# 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
```python
# 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:
1. **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()`
2. **Implicit document**: If no document handling is mentioned, wrap in standard `doc = FreeCAD.ActiveDocument or FreeCAD.newDocument()`
3. **Units assumption**: Default to millimeters unless stated otherwise
4. **Recompute**: Always call `doc.recompute()` after modifications
5. **GUI guard**: Wrap GUI-dependent code in `if FreeCAD.GuiUp:` when the script may run headless
6. **Part.show()**: Use `Part.show(shape, "Name")` for quick display, or `doc.addObject("Part::Feature", "Name")` for named persistent objects
## References
### Primary Links
- [Writing Python code](https://wiki.freecad.org/Manual:A_gentle_introduction#Writing_Python_code)
- [Manipulating FreeCAD objects](https://wiki.freecad.org/Manual:A_gentle_introduction#Manipulating_FreeCAD_objects)
- [Vectors and Placements](https://wiki.freecad.org/Manual:A_gentle_introduction#Vectors_and_Placements)
- [Creating and manipulating geometry](https://wiki.freecad.org/Manual:Creating_and_manipulating_geometry)
- [Creating parametric objects](https://wiki.freecad.org/Manual:Creating_parametric_objects)
- [Creating interface tools](https://wiki.freecad.org/Manual:Creating_interface_tools)
- [Python](https://en.wikipedia.org/wiki/Python_%28programming_language%29)
- [Introduction to Python](https://wiki.freecad.org/Introduction_to_Python)
- [Python scripting tutorial](https://wiki.freecad.org/Python_scripting_tutorial)
- [FreeCAD scripting basics](https://wiki.freecad.org/FreeCAD_Scripting_Basics)
- [Gui Command](https://wiki.freecad.org/Gui_Command)
### Bundled Reference Documents
See the [references/](references/) directory for topic-organized guides:
1. [scripting-fundamentals.md](references/scripting-fundamentals.md) ā Core scripting, document model, console
2. [geometry-and-shapes.md](references/geometry-and-shapes.md) ā Part, Mesh, Sketcher, topology
3. [parametric-objects.md](references/parametric-objects.md) ā FeaturePython, properties, scripted objects
4. [gui-and-interface.md](references/gui-and-interface.md) ā PySide, dialogs, task panels, Coin3D
5. [workbenches-and-advanced.md](references/workbenches-and-advanced.md) ā Workbenches, macros, FEM, Path, recipes
references/
geometry-and-shapes.md 8.6 KB
# FreeCAD Geometry and Shapes
Reference guide for creating and manipulating geometry in FreeCAD using the Part, Mesh, and Sketcher modules.
## Official Wiki References
- [Creating and manipulating geometry](https://wiki.freecad.org/Manual:Creating_and_manipulating_geometry)
- [Part scripting](https://wiki.freecad.org/Part_scripting)
- [Topological data scripting](https://wiki.freecad.org/Topological_data_scripting)
- [Mesh scripting](https://wiki.freecad.org/Mesh_Scripting)
- [Mesh to Part conversion](https://wiki.freecad.org/Mesh_to_Part)
- [Sketcher scripting](https://wiki.freecad.org/Sketcher_scripting)
- [Drawing API example](https://wiki.freecad.org/Drawing_API_example)
- [Part: Create a ball bearing I](https://wiki.freecad.org/Scripted_Parts:_Ball_Bearing_-_Part_1)
- [Part: Create a ball bearing II](https://wiki.freecad.org/Scripted_Parts:_Ball_Bearing_-_Part_2)
- [Line drawing function](https://wiki.freecad.org/Line_drawing_function)
## Part Module ā Shape Hierarchy
OpenCASCADE topology levels (bottom to top):
```
Vertex ā Edge ā Wire ā Face ā Shell ā Solid ā CompSolid ā Compound
```
Each level contains the levels below it.
## Primitive Shapes
```python
import Part
import FreeCAD as App
# Boxes
box = Part.makeBox(length, width, height)
box = Part.makeBox(10, 20, 30, App.Vector(0,0,0), App.Vector(0,0,1))
# Cylinders
cyl = Part.makeCylinder(radius, height)
cyl = Part.makeCylinder(5, 20, App.Vector(0,0,0), App.Vector(0,0,1), 360)
# Cones
cone = Part.makeCone(r1, r2, height)
# Spheres
sph = Part.makeSphere(radius)
sph = Part.makeSphere(10, App.Vector(0,0,0), App.Vector(0,0,1), -90, 90, 360)
# Torus
tor = Part.makeTorus(majorR, minorR)
# Planes (infinite ā bounded face)
plane = Part.makePlane(length, width)
plane = Part.makePlane(10, 10, App.Vector(0,0,0), App.Vector(0,0,1))
# Helix
helix = Part.makeHelix(pitch, height, radius)
# Wedge
wedge = Part.makeWedge(xmin, ymin, zmin, z2min, x2min,
xmax, ymax, zmax, z2max, x2max)
```
## Curves and Edges
```python
# Line segment
line = Part.makeLine((0,0,0), (10,0,0))
line = Part.LineSegment(App.Vector(0,0,0), App.Vector(10,0,0)).toShape()
# Circle (full)
circle = Part.makeCircle(radius)
circle = Part.makeCircle(5, App.Vector(0,0,0), App.Vector(0,0,1))
# Arc (partial circle)
arc = Part.makeCircle(5, App.Vector(0,0,0), App.Vector(0,0,1), 0, 180)
# Arc through 3 points
arc3 = Part.Arc(App.Vector(0,0,0), App.Vector(5,5,0), App.Vector(10,0,0)).toShape()
# Ellipse
ellipse = Part.Ellipse(App.Vector(0,0,0), 10, 5).toShape()
# BSpline curve
points = [App.Vector(0,0,0), App.Vector(2,3,0), App.Vector(5,1,0), App.Vector(8,4,0)]
bspline = Part.BSplineCurve()
bspline.interpolate(points)
edge = bspline.toShape()
# BSpline with control points (approximate)
bspline2 = Part.BSplineCurve()
bspline2.buildFromPoles(points)
edge2 = bspline2.toShape()
# Bezier curve
bezier = Part.BezierCurve()
bezier.setPoles([App.Vector(0,0,0), App.Vector(3,5,0),
App.Vector(7,5,0), App.Vector(10,0,0)])
edge3 = bezier.toShape()
```
## Wires, Faces, and Solids
```python
# Wire from edges
wire = Part.Wire([edge1, edge2, edge3]) # edges must connect end-to-end
# Wire by sorting edges
wire = Part.Wire(Part.__sortEdges__([edges_list]))
# Face from wire (must be closed and planar, or a surface)
face = Part.Face(wire)
# Face from multiple wires (first = outer, rest = holes)
face = Part.Face([outer_wire, hole_wire1, hole_wire2])
# Shell from faces
shell = Part.Shell([face1, face2, face3])
# Solid from shell (must be closed)
solid = Part.Solid(shell)
# Compound (group shapes without merging)
compound = Part.Compound([shape1, shape2, shape3])
```
## Shape Operations
```python
# Boolean operations
union = shape1.fuse(shape2)
diff = shape1.cut(shape2)
inter = shape1.common(shape2)
# Multi-fuse / multi-cut
multi_fuse = shape1.multiFuse([shape2, shape3, shape4])
# Clean seam edges after boolean
clean = union.removeSplitter()
# Fillet (round edges)
filleted = solid.makeFillet(radius, solid.Edges)
filleted = solid.makeFillet(radius, [solid.Edges[0], solid.Edges[3]])
# Chamfer
chamfered = solid.makeChamfer(distance, solid.Edges)
chamfered = solid.makeChamfer(dist1, dist2, [solid.Edges[0]]) # asymmetric
# Offset (shell/thicken)
offset = solid.makeOffsetShape(offset_distance, tolerance)
thick = solid.makeThickness([face_to_remove], thickness, tolerance)
# Section (intersection curve of solid with plane)
section = solid.section(Part.makePlane(100, 100, App.Vector(0,0,5)))
```
## Extrude, Revolve, Loft, Sweep
```python
# Extrude face or wire
extruded = face.extrude(App.Vector(0, 0, 10)) # direction vector
# Revolve
revolved = face.revolve(
App.Vector(0, 0, 0), # center
App.Vector(0, 1, 0), # axis
360 # angle (degrees)
)
# Loft between wires/profiles
loft = Part.makeLoft([wire1, wire2, wire3], True) # solid=True
# Sweep (pipe)
sweep = Part.Wire([path_edge]).makePipe(profile_wire)
# Sweep with Frenet frame
sweep = Part.Wire([path_edge]).makePipeShell(
[profile_wire],
True, # make solid
False # use Frenet frame
)
```
## Topological Exploration
```python
shape = obj.Shape
# Sub-element access
shape.Vertexes # [Vertex, ...]
shape.Edges # [Edge, ...]
shape.Wires # [Wire, ...]
shape.Faces # [Face, ...]
shape.Shells # [Shell, ...]
shape.Solids # [Solid, ...]
# Vertex properties
v = shape.Vertexes[0]
v.Point # FreeCAD.Vector ā the 3D coordinate
# Edge properties
e = shape.Edges[0]
e.Length
e.Curve # underlying geometric curve (Line, Circle, BSpline, ...)
e.Vertexes # start and end vertices
e.firstVertex() # first Vertex
e.lastVertex() # last Vertex
e.tangentAt(0.5) # tangent at parameter
e.valueAt(0.5) # point at parameter
e.parameterAt(vertex) # parameter at vertex
# Face properties
f = shape.Faces[0]
f.Area
f.Surface # underlying geometric surface (Plane, Cylinder, ...)
f.CenterOfMass
f.normalAt(0.5, 0.5) # normal at (u, v) parameter
f.Wires # bounding wires
f.OuterWire # or Wires[0]
# Bounding box
bb = shape.BoundBox
bb.XMin, bb.XMax, bb.YMin, bb.YMax, bb.ZMin, bb.ZMax
bb.Center, bb.DiagonalLength
bb.XLength, bb.YLength, bb.ZLength
# Shape properties
shape.Volume
shape.Area
shape.CenterOfMass
shape.ShapeType # "Solid", "Compound", "Face", etc.
shape.isValid()
shape.isClosed()
```
## Sketcher Constraints Reference
| Constraint | Syntax | Description |
|---|---|---|
| Coincident | `("Coincident", geo1, pt1, geo2, pt2)` | Points coincide |
| Horizontal | `("Horizontal", geo)` | Line is horizontal |
| Vertical | `("Vertical", geo)` | Line is vertical |
| Parallel | `("Parallel", geo1, geo2)` | Lines are parallel |
| Perpendicular | `("Perpendicular", geo1, geo2)` | Lines are perpendicular |
| Tangent | `("Tangent", geo1, geo2)` | Curves are tangent |
| Equal | `("Equal", geo1, geo2)` | Equal length/radius |
| Symmetric | `("Symmetric", geo1, pt1, geo2, pt2, geoLine)` | Symmetric about line |
| Distance | `("Distance", geo1, pt1, geo2, pt2, value)` | Distance between points |
| DistanceX | `("DistanceX", geo, pt1, pt2, value)` | Horizontal distance |
| DistanceY | `("DistanceY", geo, pt1, pt2, value)` | Vertical distance |
| Radius | `("Radius", geo, value)` | Circle/arc radius |
| Angle | `("Angle", geo1, geo2, value)` | Angle between lines |
| Fixed | `("Fixed", geo)` | Lock geometry |
Point indices: `1` = start, `2` = end, `3` = center (circles/arcs).
External geometry index: `-1` = X axis, `-2` = Y axis.
## Mesh Operations
```python
import Mesh
# Create from file
mesh = Mesh.Mesh("/path/to/model.stl")
# Create from topology (vertices + facets)
verts = [[0,0,0], [10,0,0], [10,10,0], [0,10,0], [5,5,10]]
facets = [[0,1,4], [1,2,4], [2,3,4], [3,0,4], [0,1,2], [0,2,3]]
mesh = Mesh.Mesh([verts[f[0]] + verts[f[1]] + verts[f[2]] for f in facets])
# Mesh properties
mesh.CountPoints
mesh.CountFacets
mesh.Volume
mesh.Area
mesh.isSolid()
# Mesh operations
mesh.unite(mesh2) # Boolean union
mesh.intersect(mesh2) # Boolean intersection
mesh.difference(mesh2) # Boolean difference
mesh.offset(1.0) # Offset surface
mesh.smooth() # Laplacian smoothing
# Export
mesh.write("/path/to/output.stl")
mesh.write("/path/to/output.obj")
# Convert Part ā Mesh
import MeshPart
mesh = MeshPart.meshFromShape(
Shape=part_shape,
LinearDeflection=0.1,
AngularDeflection=0.523599, # ~30 degrees
Relative=False
)
# Convert Mesh ā Part
import Part
tolerance = 0.05
shape = Part.Shape()
shape.makeShapeFromMesh(mesh.Topology, tolerance)
solid = Part.makeSolid(shape)
```
gui-and-interface.md 11.0 KB
# 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
- [Creating interface tools](https://wiki.freecad.org/Manual:Creating_interface_tools)
- [Gui Command](https://wiki.freecad.org/Gui_Command)
- [Define a command](https://wiki.freecad.org/Command)
- [PySide](https://wiki.freecad.org/PySide)
- [PySide beginner examples](https://wiki.freecad.org/PySide_Beginner_Examples)
- [PySide intermediate examples](https://wiki.freecad.org/PySide_Intermediate_Examples)
- [PySide advanced examples](https://wiki.freecad.org/PySide_Advanced_Examples)
- [PySide usage snippets](https://wiki.freecad.org/PySide_usage_snippets)
- [Interface creation](https://wiki.freecad.org/Interface_creation)
- [Dialog creation](https://wiki.freecad.org/Dialog_creation)
- [Dialog creation with various widgets](https://wiki.freecad.org/Dialog_creation_with_various_widgets)
- [Dialog creation reading and writing files](https://wiki.freecad.org/Dialog_creation_reading_and_writing_files)
- [Dialog creation setting colors](https://wiki.freecad.org/Dialog_creation_setting_colors)
- [Dialog creation image and animated GIF](https://wiki.freecad.org/Dialog_creation_image_and_animated_GIF)
- [Qt Example](https://wiki.freecad.org/Qt_Example)
- [3D view](https://wiki.freecad.org/3D_view)
- [The Coin scenegraph](https://wiki.freecad.org/Scenegraph)
- [Pivy](https://wiki.freecad.org/Pivy)
## Gui Command
The standard way to add toolbar buttons and menu items in FreeCAD:
```python
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:
```python
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)
```python
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
```python
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
```python
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
```python
# 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
```python
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
```python
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.
```python
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
```python
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()
```
parametric-objects.md 10.6 KB
# FreeCAD Parametric Objects
Reference guide for creating FeaturePython objects, scripted objects, properties, view providers, and serialization.
## Official Wiki References
- [Creating parametric objects](https://wiki.freecad.org/Manual:Creating_parametric_objects)
- [Create a FeaturePython object part I](https://wiki.freecad.org/Create_a_FeaturePython_object_part_I)
- [Create a FeaturePython object part II](https://wiki.freecad.org/Create_a_FeaturePython_object_part_II)
- [Scripted objects](https://wiki.freecad.org/Scripted_objects)
- [Scripted objects saving attributes](https://wiki.freecad.org/Scripted_objects_saving_attributes)
- [Scripted objects migration](https://wiki.freecad.org/Scripted_objects_migration)
- [Scripted objects with attachment](https://wiki.freecad.org/Scripted_objects_with_attachment)
- [Viewprovider](https://wiki.freecad.org/Viewprovider)
- [Custom icon in tree view](https://wiki.freecad.org/Custom_icon_in_tree_view)
- [Properties](https://wiki.freecad.org/Property)
- [PropertyLink: InList and OutList](https://wiki.freecad.org/PropertyLink:_InList_and_OutList)
- [FeaturePython methods](https://wiki.freecad.org/FeaturePython_methods)
## FeaturePython Object ā Complete Template
```python
import FreeCAD
import Part
class MyParametricObject:
"""Proxy class for a custom parametric object."""
def __init__(self, obj):
"""Initialize and add properties."""
obj.Proxy = self
self.Type = "MyParametricObject"
# Add custom properties
obj.addProperty("App::PropertyLength", "Length", "Dimensions",
"The length of the object").Length = 10.0
obj.addProperty("App::PropertyLength", "Width", "Dimensions",
"The width of the object").Width = 10.0
obj.addProperty("App::PropertyLength", "Height", "Dimensions",
"The height of the object").Height = 5.0
obj.addProperty("App::PropertyBool", "Chamfered", "Options",
"Apply chamfer to edges").Chamfered = False
obj.addProperty("App::PropertyLength", "ChamferSize", "Options",
"Size of chamfer").ChamferSize = 1.0
def execute(self, obj):
"""Called when the document is recomputed. Build the shape here."""
shape = Part.makeBox(obj.Length, obj.Width, obj.Height)
if obj.Chamfered and obj.ChamferSize > 0:
shape = shape.makeChamfer(obj.ChamferSize, shape.Edges)
obj.Shape = shape
def onChanged(self, obj, prop):
"""Called when any property changes."""
if prop == "Chamfered":
# Show/hide ChamferSize based on Chamfered toggle
if obj.Chamfered:
obj.setPropertyStatus("ChamferSize", "-Hidden")
else:
obj.setPropertyStatus("ChamferSize", "Hidden")
def onDocumentRestored(self, obj):
"""Called when the document is loaded. Re-initialize if needed."""
self.Type = "MyParametricObject"
def __getstate__(self):
"""Serialize the proxy (for saving .FCStd)."""
return {"Type": self.Type}
def __setstate__(self, state):
"""Deserialize the proxy (for loading .FCStd)."""
if state:
self.Type = state.get("Type", "MyParametricObject")
```
## ViewProvider ā Complete Template
```python
import FreeCADGui
from pivy import coin
class ViewProviderMyObject:
"""Controls how the object appears in the 3D view and tree."""
def __init__(self, vobj):
vobj.Proxy = self
# Add view properties if needed
# vobj.addProperty("App::PropertyColor", "Color", "Display", "Object color")
def attach(self, vobj):
"""Called when the view provider is attached to the view object."""
self.Object = vobj.Object
self.standard = coin.SoGroup()
vobj.addDisplayMode(self.standard, "Standard")
def getDisplayModes(self, vobj):
"""Return available display modes."""
return ["Standard"]
def getDefaultDisplayMode(self):
"""Return the default display mode."""
return "Standard"
def setDisplayMode(self, mode):
return mode
def getIcon(self):
"""Return the icon path for the tree view."""
return ":/icons/Part_Box.svg"
# Or return an XPM string, or path to a .svg/.png file
def updateData(self, obj, prop):
"""Called when the model object's data changes."""
pass
def onChanged(self, vobj, prop):
"""Called when a view property changes."""
pass
def doubleClicked(self, vobj):
"""Called on double-click in the tree."""
# Open a task panel, for example
return True
def setupContextMenu(self, vobj, menu):
"""Add items to the right-click context menu."""
action = menu.addAction("My Action")
action.triggered.connect(lambda: self._myAction(vobj))
def _myAction(self, vobj):
FreeCAD.Console.PrintMessage("Context menu action triggered\n")
def claimChildren(self):
"""Return list of child objects to show in tree hierarchy."""
# return [self.Object.BaseFeature] if hasattr(self.Object, "BaseFeature") else []
return []
def __getstate__(self):
return None
def __setstate__(self, state):
return None
```
## Creating the Object
```python
def makeMyObject(name="MyObject"):
"""Factory function to create the parametric object."""
doc = FreeCAD.ActiveDocument
if doc is None:
doc = FreeCAD.newDocument()
obj = doc.addObject("Part::FeaturePython", name)
MyParametricObject(obj)
if FreeCAD.GuiUp:
ViewProviderMyObject(obj.ViewObject)
doc.recompute()
return obj
# Usage
obj = makeMyObject("ChamferedBlock")
obj.Length = 20.0
obj.Chamfered = True
FreeCAD.ActiveDocument.recompute()
```
## Complete Property Type Reference
### Numeric Properties
| Type | Python | Notes |
|---|---|---|
| `App::PropertyInteger` | `int` | Standard integer |
| `App::PropertyFloat` | `float` | Standard float |
| `App::PropertyLength` | `float` | Length with units (mm) |
| `App::PropertyDistance` | `float` | Distance (can be negative) |
| `App::PropertyAngle` | `float` | Angle in degrees |
| `App::PropertyArea` | `float` | Area with units |
| `App::PropertyVolume` | `float` | Volume with units |
| `App::PropertySpeed` | `float` | Speed with units |
| `App::PropertyAcceleration` | `float` | Acceleration |
| `App::PropertyForce` | `float` | Force |
| `App::PropertyPressure` | `float` | Pressure |
| `App::PropertyPercent` | `int` | 0-100 integer |
| `App::PropertyQuantity` | `Quantity` | Generic unit-aware value |
| `App::PropertyIntegerConstraint` | `(val,min,max,step)` | Bounded integer |
| `App::PropertyFloatConstraint` | `(val,min,max,step)` | Bounded float |
### String/Path Properties
| Type | Python | Notes |
|---|---|---|
| `App::PropertyString` | `str` | Text string |
| `App::PropertyFont` | `str` | Font name |
| `App::PropertyFile` | `str` | File path |
| `App::PropertyFileIncluded` | `str` | Embedded file |
| `App::PropertyPath` | `str` | Directory path |
### Boolean and Enumeration
| Type | Python | Notes |
|---|---|---|
| `App::PropertyBool` | `bool` | True/False |
| `App::PropertyEnumeration` | `list`/`str` | Dropdown; set list then value |
```python
# Enumeration usage
obj.addProperty("App::PropertyEnumeration", "Style", "Options", "Style choice")
obj.Style = ["Solid", "Wireframe", "Points"] # set choices FIRST
obj.Style = "Solid" # then set value
```
### Geometric Properties
| Type | Python | Notes |
|---|---|---|
| `App::PropertyVector` | `FreeCAD.Vector` | 3D vector |
| `App::PropertyVectorList` | `[Vector,...]` | List of vectors |
| `App::PropertyPlacement` | `Placement` | Position + rotation |
| `App::PropertyMatrix` | `Matrix` | 4x4 matrix |
| `App::PropertyVectorDistance` | `Vector` | Vector with units |
| `App::PropertyPosition` | `Vector` | Position with units |
| `App::PropertyDirection` | `Vector` | Direction vector |
### Link Properties
| Type | Python | Notes |
|---|---|---|
| `App::PropertyLink` | obj ref | Link to one object |
| `App::PropertyLinkList` | `[obj,...]` | Link to multiple objects |
| `App::PropertyLinkSub` | `(obj, [subs])` | Link with sub-elements |
| `App::PropertyLinkSubList` | `[(obj,[subs]),...]` | Multiple link+subs |
| `App::PropertyLinkChild` | obj ref | Claimed child link |
| `App::PropertyLinkListChild` | `[obj,...]` | Multiple claimed children |
### Shape and Material
| Type | Python | Notes |
|---|---|---|
| `Part::PropertyPartShape` | `Part.Shape` | Full shape |
| `App::PropertyColor` | `(r,g,b)` | Color (0.0-1.0) |
| `App::PropertyColorList` | `[(r,g,b),...]` | Color per element |
| `App::PropertyMaterial` | `Material` | Material definition |
### Container Properties
| Type | Python | Notes |
|---|---|---|
| `App::PropertyPythonObject` | any | Serializable Python object |
| `App::PropertyIntegerList` | `[int,...]` | List of integers |
| `App::PropertyFloatList` | `[float,...]` | List of floats |
| `App::PropertyStringList` | `[str,...]` | List of strings |
| `App::PropertyBoolList` | `[bool,...]` | List of booleans |
| `App::PropertyMap` | `{str:str}` | String dictionary |
## Object Dependency Tracking
```python
# InList: objects that reference this object
obj.InList # [objects referencing obj]
obj.InListRecursive # all ancestors
# OutList: objects this object references
obj.OutList # [objects obj references]
obj.OutListRecursive # all descendants
```
## Migration Between Versions
```python
class MyParametricObject:
# ... existing code ...
def onDocumentRestored(self, obj):
"""Handle version migration when document loads."""
# Add properties that didn't exist in older versions
if not hasattr(obj, "NewProp"):
obj.addProperty("App::PropertyFloat", "NewProp", "Group", "Tip")
obj.NewProp = default_value
# Rename properties (copy value, remove old)
if hasattr(obj, "OldPropName"):
if not hasattr(obj, "NewPropName"):
obj.addProperty("App::PropertyFloat", "NewPropName", "Group", "Tip")
obj.NewPropName = obj.OldPropName
obj.removeProperty("OldPropName")
```
## Attachment Support
```python
import Part
class MyAttachableObject:
def __init__(self, obj):
obj.Proxy = self
obj.addExtension("Part::AttachExtensionPython")
def execute(self, obj):
# The attachment sets the Placement automatically
if not obj.MapPathParameter:
obj.positionBySupport()
# Build your shape at the origin; Placement handles positioning
obj.Shape = Part.makeBox(10, 10, 10)
```
scripting-fundamentals.md 5.9 KB
# FreeCAD Scripting Fundamentals
Reference guide for FreeCAD Python scripting basics: the document model, the console, objects, selection, and the Python environment.
## Official Wiki References
- [A gentle introduction](https://wiki.freecad.org/Manual:A_gentle_introduction)
- [Introduction to Python](https://wiki.freecad.org/Introduction_to_Python)
- [Python scripting tutorial](https://wiki.freecad.org/Python_scripting_tutorial)
- [FreeCAD Scripting Basics](https://wiki.freecad.org/FreeCAD_Scripting_Basics)
- [Scripting and macros](https://wiki.freecad.org/Scripting_and_macros)
- [Working with macros](https://wiki.freecad.org/Macros)
- [Code snippets](https://wiki.freecad.org/Code_snippets)
- [Debugging](https://wiki.freecad.org/Debugging)
- [Profiling](https://wiki.freecad.org/Profiling)
- [Python development environment](https://wiki.freecad.org/Python_Development_Environment)
- [Extra python modules](https://wiki.freecad.org/Extra_python_modules)
- [FreeCAD vector math library](https://wiki.freecad.org/FreeCAD_vector_math_library)
- [Embedding FreeCAD](https://wiki.freecad.org/Embedding_FreeCAD)
- [Embedding FreeCADGui](https://wiki.freecad.org/Embedding_FreeCADGui)
- [Macro at startup](https://wiki.freecad.org/Macro_at_Startup)
- [How to install macros](https://wiki.freecad.org/How_to_install_macros)
- [IPython notebook integration](https://wiki.freecad.org/IPython_notebook_integration)
- [Quantity](https://wiki.freecad.org/Quantity)
## The FreeCAD Module Hierarchy
```
FreeCAD (App) ā Core application, documents, objects, properties
āāā FreeCAD.Vector ā 3D vector
āāā FreeCAD.Rotation ā Quaternion rotation
āāā FreeCAD.Placement ā Position + rotation
āāā FreeCAD.Matrix ā 4x4 transformation matrix
āāā FreeCAD.Units ā Unit conversion and quantities
āāā FreeCAD.Console ā Message output
āāā FreeCAD.Base ā Base types
FreeCADGui (Gui) ā GUI module (only when GUI is active)
āāā Selection ā Selection management
āāā Control ā Task panel management
āāā ActiveDocument ā GUI document wrapper
āāā getMainWindow() ā Qt main window
```
## Document Operations
```python
import FreeCAD
# Document lifecycle
doc = FreeCAD.newDocument("DocName")
doc = FreeCAD.openDocument("/path/to/file.FCStd")
doc = FreeCAD.ActiveDocument
FreeCAD.setActiveDocument("DocName")
doc.save()
doc.saveAs("/path/to/newfile.FCStd")
FreeCAD.closeDocument("DocName")
# Object management
obj = doc.addObject("Part::Feature", "ObjectName")
obj = doc.addObject("Part::FeaturePython", "CustomObj")
obj = doc.addObject("App::DocumentObjectGroup", "Group")
doc.removeObject("ObjectName")
# Object access
obj = doc.getObject("ObjectName")
obj = doc.ObjectName # attribute syntax
all_objs = doc.Objects # all objects in document
names = doc.findObjects("Part::Feature") # by type
# Recompute
doc.recompute() # recompute all
doc.recompute([obj1, obj2]) # recompute specific objects
obj.touch() # mark as needing recompute
```
## Selection API
```python
import FreeCADGui
# Get selection
sel = FreeCADGui.Selection.getSelection() # [obj, ...]
sel = FreeCADGui.Selection.getSelection("DocName") # from specific doc
sel_ex = FreeCADGui.Selection.getSelectionEx() # extended info
# Extended selection details
for s in sel_ex:
print(s.Object.Name) # parent object
print(s.SubElementNames) # ("Face1", "Edge3", ...)
print(s.SubObjects) # actual sub-shapes
for pt in s.PickedPoints:
print(pt) # 3D pick point
# Set selection
FreeCADGui.Selection.addSelection(obj)
FreeCADGui.Selection.addSelection(obj, "Face1")
FreeCADGui.Selection.removeSelection(obj)
FreeCADGui.Selection.clearSelection()
# Selection observer
class MySelectionObserver:
def addSelection(self, doc, obj, sub, pos):
print(f"Selected: {obj}.{sub} at {pos}")
def removeSelection(self, doc, obj, sub):
print(f"Deselected: {obj}.{sub}")
def setSelection(self, doc):
print(f"Selection set changed in {doc}")
def clearSelection(self, doc):
print(f"Selection cleared in {doc}")
obs = MySelectionObserver()
FreeCADGui.Selection.addObserver(obs)
# Later: FreeCADGui.Selection.removeObserver(obs)
```
## Console and Logging
```python
FreeCAD.Console.PrintMessage("Normal message\n") # blue/default
FreeCAD.Console.PrintWarning("Warning\n") # orange
FreeCAD.Console.PrintError("Error\n") # red
FreeCAD.Console.PrintLog("Debug info\n") # log only
# Console message observer
class MyLogger:
def __init__(self):
FreeCAD.Console.PrintMessage("Logger started\n")
def receive(self, msg):
# process msg
pass
```
## Units and Quantities
```python
from FreeCAD import Units
# Create quantities
q = Units.Quantity("10 mm")
q = Units.Quantity("1 in")
q = Units.Quantity(25.4, Units.Unit("mm"))
q = Units.parseQuantity("3.14 rad")
# Convert
value_mm = float(q) # internal unit (mm for length)
value_in = q.getValueAs("in") # convert to other unit
value_m = q.getValueAs("m")
# Available unit schemes: mm/kg/s (FreeCAD default), SI, Imperial, etc.
# Common units: mm, m, in, ft, deg, rad, kg, g, lb, s, min, hr
```
## Property System
```python
# Add properties to any DocumentObject
obj.addProperty("App::PropertyFloat", "MyProp", "GroupName", "Tooltip")
obj.MyProp = 42.0
# Check property existence
if hasattr(obj, "MyProp"):
print(obj.MyProp)
# Property metadata
obj.getPropertyByName("MyProp")
obj.getTypeOfProperty("MyProp") # returns list: ["App::PropertyFloat"]
obj.getDocumentationOfProperty("MyProp")
obj.getGroupOfProperty("MyProp")
# Set property as read-only, hidden, etc.
obj.setPropertyStatus("MyProp", "ReadOnly")
obj.setPropertyStatus("MyProp", "Hidden")
obj.setPropertyStatus("MyProp", "-ReadOnly") # remove status
# Statuses: ReadOnly, Hidden, Transient, Output, NoRecompute
```
workbenches-and-advanced.md 9.8 KB
# FreeCAD Workbenches and Advanced Topics
Reference guide for workbench creation, macros, FEM scripting, Path/CAM scripting, and advanced recipes.
## Official Wiki References
- [Workbench creation](https://wiki.freecad.org/Workbench_creation)
- [Script tutorial](https://wiki.freecad.org/Scripts)
- [Macros recipes](https://wiki.freecad.org/Macros_recipes)
- [FEM scripting](https://wiki.freecad.org/FEM_Tutorial_Python)
- [Path scripting](https://wiki.freecad.org/Path_scripting)
- [Raytracing scripting](https://wiki.freecad.org/Raytracing_API_example)
- [Svg namespace](https://wiki.freecad.org/Svg_Namespace)
- [Python](https://wiki.freecad.org/Python)
- [PythonOCC](https://wiki.freecad.org/PythonOCC)
## Custom Workbench ā Full Template
### Directory Structure
```
MyWorkbench/
āāā __init__.py # Empty or minimal
āāā Init.py # Runs at FreeCAD startup (no GUI)
āāā InitGui.py # Runs at GUI startup (defines workbench)
āāā MyCommands.py # Command implementations
āāā Resources/
ā āāā icons/
ā ā āāā MyWorkbench.svg
ā ā āāā MyCommand.svg
ā āāā translations/ # Optional i18n
āāā README.md
```
### Init.py
```python
# Runs at FreeCAD startup (before GUI)
# Register importers/exporters, add module paths, etc.
import FreeCAD
FreeCAD.addImportType("My Format (*.myf)", "MyImporter")
FreeCAD.addExportType("My Format (*.myf)", "MyExporter")
```
### InitGui.py
```python
import FreeCADGui
class MyWorkbench(FreeCADGui.Workbench):
"""Custom FreeCAD workbench."""
MenuText = "My Workbench"
ToolTip = "A custom workbench for specialized tasks"
def __init__(self):
import os
self.__class__.Icon = os.path.join(
os.path.dirname(__file__), "Resources", "icons", "MyWorkbench.svg"
)
def Initialize(self):
"""Called when workbench is first activated."""
import MyCommands # deferred import
# Define toolbars
self.appendToolbar("My Tools", [
"My_CreateBox",
"Separator", # toolbar separator
"My_EditObject"
])
# Define menus
self.appendMenu("My Workbench", [
"My_CreateBox",
"My_EditObject"
])
# Submenus
self.appendMenu(["My Workbench", "Advanced"], [
"My_AdvancedCommand"
])
import FreeCAD
FreeCAD.Console.PrintMessage("My Workbench initialized\n")
def Activated(self):
"""Called when workbench is switched to."""
pass
def Deactivated(self):
"""Called when leaving the workbench."""
pass
def ContextMenu(self, recipient):
"""Called for right-click context menus."""
self.appendContextMenu("My Tools", ["My_CreateBox"])
def GetClassName(self):
return "Gui::PythonWorkbench"
FreeCADGui.addWorkbench(MyWorkbench)
```
### MyCommands.py
```python
import FreeCAD
import FreeCADGui
import os
ICON_PATH = os.path.join(os.path.dirname(__file__), "Resources", "icons")
class CmdCreateBox:
def GetResources(self):
return {
"Pixmap": os.path.join(ICON_PATH, "MyCommand.svg"),
"MenuText": "Create Box",
"ToolTip": "Create a parametric box"
}
def IsActive(self):
return FreeCAD.ActiveDocument is not None
def Activated(self):
import Part
doc = FreeCAD.ActiveDocument
box = Part.makeBox(10, 10, 10)
Part.show(box, "MyBox")
doc.recompute()
class CmdEditObject:
def GetResources(self):
return {
"Pixmap": ":/icons/edit-undo.svg",
"MenuText": "Edit Object",
"ToolTip": "Edit selected object"
}
def IsActive(self):
return len(FreeCADGui.Selection.getSelection()) > 0
def Activated(self):
sel = FreeCADGui.Selection.getSelection()[0]
FreeCAD.Console.PrintMessage(f"Editing {sel.Name}\n")
# Register commands
FreeCADGui.addCommand("My_CreateBox", CmdCreateBox())
FreeCADGui.addCommand("My_EditObject", CmdEditObject())
```
### Installing a Workbench
Place the workbench folder in one of:
```python
# User macro folder
FreeCAD.getUserMacroDir(True)
# User mod folder (preferred)
os.path.join(FreeCAD.getUserAppDataDir(), "Mod")
# System mod folder
os.path.join(FreeCAD.getResourceDir(), "Mod")
```
## FEM Scripting
```python
import FreeCAD
import ObjectsFem
import Fem
import femmesh.femmesh2mesh
doc = FreeCAD.ActiveDocument
# Get the solid object to analyse (must already exist in the document)
obj = doc.getObject("Body") or doc.Objects[0]
# Create analysis
analysis = ObjectsFem.makeAnalysis(doc, "Analysis")
# Create a solver
solver = ObjectsFem.makeSolverCalculixCcxTools(doc, "Solver")
analysis.addObject(solver)
# Material
material = ObjectsFem.makeMaterialSolid(doc, "Steel")
mat = material.Material
mat["Name"] = "Steel"
mat["YoungsModulus"] = "210000 MPa"
mat["PoissonRatio"] = "0.3"
mat["Density"] = "7900 kg/m^3"
material.Material = mat
analysis.addObject(material)
# Fixed constraint
fixed = ObjectsFem.makeConstraintFixed(doc, "Fixed")
fixed.References = [(obj, "Face1")]
analysis.addObject(fixed)
# Force constraint
force = ObjectsFem.makeConstraintForce(doc, "Force")
force.References = [(obj, "Face6")]
force.Force = 1000.0 # Newtons
force.Direction = (obj, ["Edge1"])
force.Reversed = False
analysis.addObject(force)
# Mesh
mesh = ObjectsFem.makeMeshGmsh(doc, "FEMMesh")
mesh.Part = obj
mesh.CharacteristicLengthMax = 5.0
analysis.addObject(mesh)
doc.recompute()
# Run solver
from femtools import ccxtools
fea = ccxtools.FemToolsCcx(analysis, solver)
fea.update_objects()
fea.setup_working_dir()
fea.setup_ccx()
fea.write_inp_file()
fea.ccx_run()
fea.load_results()
```
## Path/CAM Scripting
```python
import Path
import FreeCAD
# Create a path
commands = []
commands.append(Path.Command("G0", {"X": 0, "Y": 0, "Z": 5})) # Rapid move
commands.append(Path.Command("G1", {"X": 10, "Y": 0, "Z": 0, "F": 100})) # Feed
commands.append(Path.Command("G1", {"X": 10, "Y": 10, "Z": 0}))
commands.append(Path.Command("G1", {"X": 0, "Y": 10, "Z": 0}))
commands.append(Path.Command("G1", {"X": 0, "Y": 0, "Z": 0}))
commands.append(Path.Command("G0", {"Z": 5})) # Retract
path = Path.Path(commands)
# Add to document
doc = FreeCAD.ActiveDocument
path_obj = doc.addObject("Path::Feature", "MyPath")
path_obj.Path = path
# G-code output
gcode = path.toGCode()
print(gcode)
```
## Common Recipes
### Mirror a Shape
```python
import Part
import FreeCAD
shape = obj.Shape
mirrored = shape.mirror(FreeCAD.Vector(0,0,0), FreeCAD.Vector(1,0,0)) # mirror about YZ
Part.show(mirrored, "Mirrored")
```
### Array of Shapes
```python
import Part
import FreeCAD
def linear_array(shape, direction, count, spacing):
"""Create a linear array compound."""
shapes = []
for i in range(count):
offset = FreeCAD.Vector(direction)
offset.multiply(i * spacing)
moved = shape.copy()
moved.translate(offset)
shapes.append(moved)
return Part.Compound(shapes)
result = linear_array(obj.Shape, FreeCAD.Vector(1,0,0), 5, 15.0)
Part.show(result, "Array")
```
### Circular/Polar Array
```python
import Part
import FreeCAD
import math
def polar_array(shape, axis, center, count):
"""Create a polar array compound."""
shapes = []
angle = 360.0 / count
for i in range(count):
rot = FreeCAD.Rotation(axis, angle * i)
placement = FreeCAD.Placement(FreeCAD.Vector(0,0,0), rot, center)
moved = shape.copy()
moved.Placement = placement
shapes.append(moved)
return Part.Compound(shapes)
result = polar_array(obj.Shape, FreeCAD.Vector(0,0,1), FreeCAD.Vector(0,0,0), 8)
Part.show(result, "PolarArray")
```
### Measure Distance Between Shapes
```python
dist = shape1.distToShape(shape2)
# Returns: (min_distance, [(point_on_shape1, point_on_shape2), ...], ...)
min_dist = dist[0]
closest_points = dist[1] # List of (Vector, Vector) pairs
```
### Create a Tube/Pipe
```python
import Part
outer_cyl = Part.makeCylinder(outer_radius, height)
inner_cyl = Part.makeCylinder(inner_radius, height)
tube = outer_cyl.cut(inner_cyl)
Part.show(tube, "Tube")
```
### Assign Color to Faces
```python
# Set per-face colors
obj.ViewObject.DiffuseColor = [
(1.0, 0.0, 0.0, 0.0), # Face1 = red
(0.0, 1.0, 0.0, 0.0), # Face2 = green
(0.0, 0.0, 1.0, 0.0), # Face3 = blue
# ... one tuple per face, (R, G, B, transparency)
]
# Or set single color for whole object
obj.ViewObject.ShapeColor = (0.8, 0.2, 0.2)
```
### Batch Export All Objects
```python
import FreeCAD
import Part
import os
doc = FreeCAD.ActiveDocument
export_dir = "/path/to/export"
if doc is None:
FreeCAD.Console.PrintMessage("No active document to export.\n")
else:
os.makedirs(export_dir, exist_ok=True)
for obj in doc.Objects:
if hasattr(obj, "Shape") and obj.Shape.Solids:
filepath = os.path.join(export_dir, f"{obj.Name}.step")
Part.export([obj], filepath)
FreeCAD.Console.PrintMessage(f"Exported {filepath}\n")
```
### Timer / Progress Bar
```python
from PySide2 import QtWidgets, QtCore
# Simple progress dialog
progress = QtWidgets.QProgressDialog("Processing...", "Cancel", 0, total_steps)
progress.setWindowModality(QtCore.Qt.WindowModal)
for i in range(total_steps):
if progress.wasCanceled():
break
# ... do work ...
progress.setValue(i)
progress.setValue(total_steps)
```
### Run a Macro Programmatically
```python
import FreeCADGui
import runpy
# Execute a macro file
FreeCADGui.runCommand("Std_Macro") # Opens macro dialog
# Only execute trusted macros. Prefer an explicit path and a clearer runner.
runpy.run_path("/path/to/macro.py", run_name="__main__")
# Or use the FreeCAD macro runner with the same trusted, explicit path
FreeCADGui.doCommand('import runpy; runpy.run_path("/path/to/macro.py", run_name="__main__")')
```
License (MIT)
View full license text
MIT License Copyright GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.