❗ Don't use this repo if you care about py2-support and support of freecad < 0.18. If so please have a look at the traditional workbench structure.
This is a template for a FreeCAD workbench / module. As python-packaging and packaging for FreeCAD is not an easy task, this repository should give an overview of the things learned so far. If all you want is to create an extension for FreeCAD (module, additional gui-stuff, workbench), simple copy this repo and start replacing things.
To try the latest release of this template:
# Install this template
pip install freecad.workbench_starterkit
# Uninstall (to remove this template)
pip uninstall freecad.workbench_starterkit
Note: There are currently two FreeCAD workbench styles. The origin workbench type is called "legacy-workbench". In addition the "namespace-workbench" is now an optional (and preffered) way to extend the FreeCAD. (See Glossary terms used in this discussion). For more discussion about the motivation behind the "namespace-workbench" see Motivation for namespace-workbenches.
init_gui.py
: mandatory for modules adding new functionality to the GUI.__init__.py
: entry function for non-gui FreeCAD and python. Called when you import your package:from freecad import my_package
Both of these initialization files are called when the FreeCAD-gui is launched. Launching freecadcmd
or importing FreeCAD (import freead
) will call the __init__.py
file. (The python import will not yet work on every system, but we are working towards a standardization).
This is the minimal structure of a namespace-package to add a workbench to FreeCAD.
freecad/
└── workbench_starterkit/
├── __init__.py
└── init_gui.py
Note: init_gui.py
are called at startup of FreeCAD.
❗ Do not put very time-intensive code in these files to reduce the start-up time.
Several names are needed:
-
repository-name
The name of the repository AKAfreecad.repository_name
eg.:freecad.workbench_starterkit
-
distribution-name
This name is set in the setup.py
eg.:name='freecad.workbench_starterkit'
-
package-name
The name of the package which can be imported from python.
Notes: it's possible that there are several packages in one repository with only onesetup.py
. You simply specify all packages and modules in thepackages
section of thesetup.py
.
This name must not contain any python operator symbols like "-".
If the repository contains only one pthon-package it makes sense to choose the same names for the repository-name, python-package and the pypi-package.
eg.:freecad.workbench_starterkit
The "freecad" namespace is not allowed to be used directly.
This means it is not allowed to set any variables in the __init__.py
of freecad. (But as with python3 this __init__.py
should not exist anyway, this isn't a problem.) Further it's not allowed to add variables to the freecad-namespace directly. This can introduce name-clashes.
Examples:
❌ freecad.myVariable = 10
👍 freecad.app.myVariable = 10
If you want to work on your extension you have the following options:
- Start FreeCAD from the root-directory you are working in (eg. freecad.workbench_starterkit)
- Simply link the extension to a location where python can find it.
pip install -e .
adds the root-directory to easy_install.path.
Currently FreeCAD has several ways to install packages:
With pip
and pypi
a third option is introduced. In addition, utilizing pip
also provides powerful possibilities to install third party dependencies.
The setup.py
file located in the main directory is a minimal example to get an extension installed. There we are using setuptools
. If you need advanced options to install your package, please have a look at the setuptools docs.
It's common practice to include a version-string in the python-package. The version should then be imported to the root-__init__.py
to use it like this:
import freecad.workbench_starterkit
freecad.workbench_starterkit.__version__
In the setup.py we do not have access to the library itself, so the __version__
must be imported without the assumption that the package is installed. This can be done by running the file directly with exec
.
❗TODO: Is there any better way to do this?
In addition to the setup.py
there is often the need for a MANIFEST.in
With this file it's possible to install data like icons, documentation files, ... (everything not directly connected to python).
To tell setuptools
to use the MANIFEST.in
add this line to the setup function in the setup.py:
setup(..., include_package_data=True)
you can specify required packages by setting the install_requires
in the setup-function of the setup.py
setup(..., install_requires=['required_package'], ...)
To install your extension locally with pip, do the following from a cmd (windows) or terminal (unix):
cd <path_to_your_package>
pip install .
Please have a look at this pypi twine tutorial.
Be careful with version-numbering. It seems pypi doesn't allow to upload a package with a version smaller then the biggest version of the package uploaded. This seems to be true also for deleted packages and deleted versions.
Once uploaded, the package can be installed with:
pip install <package-name>
- pyrate - Optical raytracing based on Python
- OpenGlider - Python library to build paragliders
- FCGear - a gear module for FreeCAD
- freecad_pipintegration - support pip installable freecad-packages
- module : a Python source file, which can expose classes, functions and global variables
- package : a directory containing Python modules.
- distribution : the artifacts which are created by running the setup.py. Can contain multiple packages.
- workbench : a graphical space inside the FreeCAD-Gui which adds functionality related to a specific task
- namespace-package : a package which adds functionality to a specific namespace. For FreeCAD we are talking about packages which are importable with
from freecad import my_package
. (Sometimes also called new-style-module) - namespace-workbench: a namespace-package containing the freecad-initialization files.
- extension-module: a library (
.so
or.dll
) written in C/C++ which adds the possibility to import this library with python.
- freecad-module: It's anything available through FreeCAD's python interpreter and placed in FreeCADs directory structure.
This can be a module, package, workbench, namespace-package, extension-module. - new_style_module: This refers to packages which are added to FreeCAD as namespace-packages
- old_style_module: A package which is plugged into FreeCAD by adding it's base-directory to
sys.path
and usesInit.py
andInitGui.py
to get initialized by FreeCAD.
There are several reasons why you might consider using the namespace-workbenche proposed in this template, and upgrade existing workbenches using the "old style".
-
The ability to execute your module using a regular
python
interpreter and have it "just work" (See related forum discussion). -
Name-spaced packages (namespace-workbenches) avoid namespace collisions and the need to have a common prefix on all classes and files to ensure uniqueness (See related forum discussion).
-
Integrating with PyPI / pip. The ability to
pip install freecad.myworkbench
(See related forum discussion). -
InitGui.py and Init.py (legacy-workbenches) do not behave like expected because these files are called with exec and are not properly imported. This leads to problems like:
__file__
not useable to get the path to the python file- predefined variables
Due to the fact we are now using the pktutil-module
to find extensions of FreeCAD, it's possible to use standard-python-paths to place the extension. This is any location which is included in the sys.path
.
To get a list of all the locations simple run this code in the FreeCAD-console:
import sys
sys.path