Automate with Python
Strata’s engine is a plain Python module, strata.project, so everything the panel does can be scripted: in FreeCAD’s Python console, in macros, or headless with freecadcmd.
Run the examples
With Strata installed, FreeCAD finds the strata package by itself. Save an example to a file and run it with FreeCAD’s console executable:
freecadcmd first_project.py
On Windows, freecadcmd.exe is next to freecad.exe, for example in %LOCALAPPDATA%\Programs\FreeCAD 1.1\bin.
Each example works in a new temporary folder unless you pass an empty folder as argument.
In a script, call project.record() after saving. In the FreeCAD window Strata records every save by itself.
|
Example 1: version control for one part
first_project.py: record edits, create a version, restore it
# SPDX-License-Identifier: LGPL-2.1-or-later
"""Minimal working example: version control for one part, from a script.
Run with FreeCAD's console (Strata installed):
freecadcmd first_project.py [empty-folder]
"""
import os
import sys
import tempfile
import FreeCAD as App
from strata.project import Project
folder = next((a for a in sys.argv[1:] if not a.endswith(".py")), None) or tempfile.mkdtemp()
path = os.path.join(folder, "Plate.FCStd")
# A simple part, saved into the project folder
doc = App.newDocument("Plate")
plate = doc.addObject("Part::Box", "Plate")
plate.Length, plate.Width, plate.Height = 100, 60, 8
doc.recompute()
doc.saveAs(path)
# 1. Turn on version control for the folder (records "Start")
project = Project.create(folder, "Ada Lovelace", "ada@example.com")
# 2. Edit and save. In the FreeCAD window Strata records every save by itself;
# from a script, call record() after saving.
plate.Height = 10
doc.recompute()
doc.save()
project.record()
# 3. Name the milestone
v1 = project.create_version("V1", "First prototype")
# 4. Keep working ...
plate.Height = 4
doc.recompute()
doc.save()
project.record()
print("Height now:", plate.Height)
# 5. ... and go back in time. Restore adds a new history entry; nothing is lost.
App.closeDocument(doc.Name)
project.restore(v1.sha)
doc = App.openDocument(path)
print("Height after restoring V1:", doc.getObject("Plate").Height)
print("History (newest first):")
for entry in project.history():
marks = " [%s]" % ", ".join(entry.versions) if entry.versions else ""
print(" %-13s %s%s" % (entry.author, entry.title, marks))
App.closeDocument(doc.Name)
project.repo.close()
Output of
freecadcmd first_project.pyHeight now: 4.0 mm
Height after restoring V1: 10.0 mm
History (newest first):
Ada Lovelace Restored to V1
Ada Lovelace Edited Plate: Height 10 mm → 4 mm
Ada Lovelace Edited Plate: Height 8 mm → 10 mm [V1]
Ada Lovelace Start
Example 2: an alternative in a workspace
workspaces.py: an alternative in a workspace, compared and merged
# SPDX-License-Identifier: LGPL-2.1-or-later
"""Minimal working example: try a design alternative in a workspace, compare it, merge it.
Run with FreeCAD's console (Strata installed):
freecadcmd workspaces.py [empty-folder]
"""
import os
import sys
import tempfile
import FreeCAD as App
from strata.project import Project
folder = next((a for a in sys.argv[1:] if not a.endswith(".py")), None) or tempfile.mkdtemp()
path = os.path.join(folder, "Bracket.FCStd")
doc = App.newDocument("Bracket")
body = doc.addObject("Part::Box", "Body")
body.Length, body.Width, body.Height = 60, 40, 12
doc.recompute()
doc.saveAs(path)
App.closeDocument(doc.Name)
project = Project.create(folder, "Ada Lovelace", "ada@example.com")
v1 = project.create_version("V1", "Baseline")
# A workspace is a separate line of work. Creating one switches to it.
project.create_workspace("Lightweight", description="thinner plate")
doc = App.openDocument(path)
doc.getObject("Body").Height = 6
doc.recompute()
doc.save()
project.record()
App.closeDocument(doc.Name)
print("Workspaces:", ", ".join(w.display for w in project.workspaces()))
# Main is untouched while you work elsewhere
project.switch_workspace("main")
doc = App.openDocument(path)
print("Height in Main:", doc.getObject("Body").Height)
App.closeDocument(doc.Name)
# What would change? A semantic comparison, feature by feature
for document, diff in project.diff(v1.sha, project.repo.rev_parse("refs/heads/Lightweight")).items():
for change in diff.modified:
for p in change.props:
print("Compare %s: %s.%s %s -> %s" % (document, change.label, p.name, p.old, p.new))
# Bring the alternative into Main
plan = project.merge_plan("Lightweight")
print("Taken automatically:", plan.take_theirs, "| conflicts:", plan.conflicts)
project.merge(plan)
doc = App.openDocument(path)
print("Height in Main after merge:", doc.getObject("Body").Height)
print("Latest entry:", project.history()[0].title)
App.closeDocument(doc.Name)
project.repo.close()
Output of
freecadcmd workspaces.pyWorkspaces: Main, Lightweight
Height in Main: 12.0 mm
Compare Bracket.FCStd: Body.Height 12 mm -> 6 mm
Taken automatically: ['Bracket.FCStd'] | conflicts: []
Height in Main after merge: 6.0 mm
Latest entry: Merged Lightweight into Main
Example 3: releases
releases.py: release, approve a new revision, obsolete the old one
# SPDX-License-Identifier: LGPL-2.1-or-later
"""Minimal working example: release a version, revise it, obsolete the old revision.
Run with FreeCAD's console (Strata installed):
freecadcmd releases.py [empty-folder]
"""
import os
import sys
import tempfile
import FreeCAD as App
from strata.project import Project
folder = next((a for a in sys.argv[1:] if not a.endswith(".py")), None) or tempfile.mkdtemp()
path = os.path.join(folder, "Shaft.FCStd")
doc = App.newDocument("Shaft")
shaft = doc.addObject("Part::Cylinder", "Shaft")
shaft.Radius, shaft.Height = 6, 80
doc.recompute()
doc.saveAs(path)
project = Project.create(folder, "Ada Lovelace", "ada@example.com")
v1 = project.create_version("V1", "Prototype")
# Release V1 as part number SFT-200; the revision letter is chosen automatically
rev_a = project.release(v1, "Shaft.FCStd", "SFT-200", description="Prototype")
print("Released %s rev %s (%s)" % (rev_a.part_number, rev_a.revision, rev_a.state))
# A design change, a new version, released through an approval step
shaft.Height = 95
doc.recompute()
doc.save()
project.record()
v2 = project.create_version("V2", "Longer shaft")
rev_b = project.release(v2, "Shaft.FCStd", "SFT-200", description="Longer shaft", approval=True)
print("Submitted %s rev %s (%s)" % (rev_b.part_number, rev_b.revision, rev_b.state))
project.set_release_state(rev_b, "Released") # approve
project.set_release_state(rev_a, "Obsolete") # retire the old revision
print("Release register:")
for r in project.releases():
print(" %-8s rev %-2s %-9s from %s (%s)" % (r.part_number, r.revision, r.state, r.version, r.description))
print("Next revision would be:", project.next_revision("SFT-200"))
App.closeDocument(doc.Name)
project.repo.close()
Output of
freecadcmd releases.pyReleased SFT-200 rev A (Released)
Submitted SFT-200 rev B (Pending)
Release register:
SFT-200 rev A Obsolete from V1 (Prototype)
SFT-200 rev B Released from V2 (Longer shaft)
Next revision would be: C
More
The Python API reference documents every class and method. To build Strata into your own add-on, see Embedding Strata in an add-on.