Search code examples
python-3.xscriptingfreecad

Macro ignores parameter change


Given a FreeCAD model that consists of

  • Spreadsheet "parameters" with a cell aliased as "radius" and value 50
  • Icosahedron (from the Pyramids-and-Polyhedrons macro) with Radius=parameters.radius
  • some facebinders which are unimportant for the purpose of this question,

the python script below opens this model, changes the radius cell in the Spreadsheet to 15, call recompute() on the spreadsheet, invokes touch() on the icosahedron, calls recompute() on the document, and finally tessellates the icosahedron. The z coordinate of the vertex at index 11 in the tessellated mesh happens to be equal to the icosahedron's radius. I was expecting it to change to 15, according to the parameter change. But it remains at its original value 50. What am I doing wrong?

To my understanding the macro's execute method should get invoked during the recomputation of the document.

When I trace Document.recompute() with pudb, I only see it executing Facebinder.execute() but not Icosahedron.execute(). The path it takes from Document.recompute() to Facebinder.execute() method is not visible in pudb; it immediately stops in Facebinder.execute().

FREECADPATH = '/usr/local/lib' # path to your FreeCAD.so or FreeCAD.dll POLYHEDRONS_PATH = '/home/user/.FreeCAD/Mod/Pyramids-and-Polyhedrons'
import sys
sys.path.append(FREECADPATH)
sys.path.append(POLYHEDRONS_PATH)
import FreeCAD

filename = 'icosahedron.FCStd'
newRadius = 15

doc = FreeCAD.open(filename)
sheet = doc.Spreadsheet
sheet.set('radius', str(newRadius))
sheet.recompute()
print('radius = ' + str(sheet.get('radius')))

icosahedron = doc.getObjectsByLabel('Icosahedron')[0]
print('icosahedron.Radius = '+str(icosahedron.Radius))
icosahedron.touch()
doc.recompute()
print('icosahedron.Radius = '+str(icosahedron.Radius))

(vertices, faces) = icosahedron.Shape.tessellate(1)
z11 = vertices[11][2]
print('z11 = ' + str(z11))
FreeCAD.closeDocument(doc.Name)

Solution

  • I figured it out. The reason was Pyramids-and-Polyhedrons not being imported after all. The first problem was that Pyramids-and-Polyhedrons imports FreeCADGui (in order to install its workbench), which depends on certain native libs that need to be added to LD_LIBRARY_PATH before running the script:

    export LD_LIBRARY_PATH=$(echo $(find /usr/local/lib/python3.7/site-packages -maxdepth 2 -mindepth 2 -type f -name *.so* | sed -r 's|/[^/]+$||' | sort -u) | sed -E 's/ /:/g')
    

    Now FreeCADGui could be imported, but it was lacking the required addCommand method that Pyramids-and-Polyhedrons invokes. I found out that FreeCADGui will only be fully initialized after FreeCADGui.showMainWindow() has been called. This requires a display, however, and I want to run the script as part of a headless toolchain in a Docker container. Therefore I added Xvfb to the image. I'm launching it in the background before running the updated script with DISPLAY pointing to Xvfb:

    FREECADPATH = '/usr/local/lib' # path to your FreeCAD.so or FreeCAD.dll file
    POLYHEDRONS_PATH = '/home/user/.FreeCAD/Mod/Pyramids-and-Polyhedrons'
    import sys
    sys.path.append(FREECADPATH)
    sys.path.append(POLYHEDRONS_PATH)
    import FreeCAD
    import FreeCADGui
    FreeCADGui.showMainWindow()
    
    import polyhedrons
    
    filename = 'icosahedron.FCStd'
    newRadius = 15
    
    doc = FreeCAD.open(filename)
    sheet = doc.Spreadsheet
    sheet.set('radius', str(newRadius))
    sheet.recompute()
    print('radius = ' + str(sheet.get('radius')))
    
    icosahedron = doc.getObjectsByLabel('Icosahedron')[0]
    print('icosahedron.Radius = '+str(icosahedron.Radius))
    breakpoint()
    icosahedron.touch()
    doc.recompute()
    print('icosahedron.Radius = '+str(icosahedron.Radius))
    
    (vertices, faces) = icosahedron.Shape.tessellate(1)
    z11 = vertices[11][2]
    print('z11 = ' + str(z11))
    FreeCAD.closeDocument(doc.Name)
    

    Now the output is

    ...
    z11 = 15.0
    

    as expected.