Let's say I have a FreeCAD model that defines a certain geometry with dimensions height, width, and length that are defined in cells of the model's spreadsheet. I want to build an automation pipeline in a stand-alone Python script using the FreeCAD module that - reads that model - sets the spreadsheet cells to values provided by the client - recomputes the model accordingly - generates geometry by tessellating the model's parts for further processing outside of FreeCAD (irrelevant with regard to this question)
The script is working except that it fails to change the parameter values that are used to compute the model. The resulting geometry always reflects the original values that were saved with the model.
I've added an assert command (line 44). geometry[0][0][2]
is a Vector whose x
coordinate happens to equal the value of the width
parameter in spreadsheet cell A5.
If one invokes the script as
python so.py so_example.FCStd 10 5 3 6
the assert fails because the geometry gets generated based on the spreadsheet's original values (width=2
) and not the overwritten ones (width=3
).
How can I effectively overwrite those spreadsheet cell values?
Script so.py:
FREECADPATH = '/usr/lib/freecad/lib'
import sys
sys.path.append(FREECADPATH)
from collections import defaultdict
def convert_model(filename, arclen, radius, width, height):
try:
import FreeCAD
from FreeCAD import Vector
except ValueError:
print ('import error\n')
else:
FreeCAD.open(filename)
doc = App.ActiveDocument
sheet = doc.Spreadsheet
print("mode = "+str(sheet.getEditorMode("A5")))
sheet.setEditorMode("A5", 0)
print("mode' = "+str(sheet.getEditorMode("A5")))
print("arclen = "+str(sheet.arclen))
print("radius = "+str(sheet.radius))
print("angle = "+str(sheet.angle))
print("width = "+str(sheet.width))
print("height = "+str(sheet.height))
sheet.set("arclen", str(arclen))
sheet.set("radius", str(radius))
sheet.set("width", str(width))
sheet.set("height", str(height))
sheet.recompute()
# verify that the radius and also the dependent angle have changed after the recomputer
print("arclen' = "+str(sheet.arclen))
print("radius' = "+str(sheet.radius))
print("angle' = "+str(sheet.angle))
print("width' = "+str(sheet.width))
print("height' = "+str(sheet.height))
# recompute the model
doc.recompute()
geometry = generate_geometry(doc)
print("generated geometry: "+str(geometry[0][0]))
assert geometry[0][0][2] == Vector(width, 0, 0)
def generate_geometry(doc):
objects = doc.Objects
return [tessellate_shape(shaped) for shaped in objects if shaped.TypeId == 'PartDesign::Body']
def tessellate_shape(shaped):
return shaped.Shape.tessellate(0.1)
def main():
filename=sys.argv[1]
arclen=float(sys.argv[2])
radius=float(sys.argv[3])
width=float(sys.argv[4])
height=float(sys.argv[5])
convert_model(filename, arclen, radius, width, height)
if __name__=='__main__':
main()
Finally I found out how to make it work: The key to changing cell values in the spreadsheet seems to be to use
sheet.set("radius", str(radius))
sheet.recompute()
This is different from doing
sheet.radius = str(radius)
sheet.recompute()
which has no effect on the model geometry. These Spreadsheet properties seem to be read-only copies of the actual cell values. However, by invoking Spreadsheet.recompute(), these properties get updated as well.
UPDATE:
To make the actual model geometry change after updating the Spreadsheet properties, one also needs to call touch() on each model object and then doc.recompute().