First we load required libraries for the translation from FreeCAD Coin3D scene graph to a pythreejs WebGL rendering scene. And even before that make sure you have a working FreeCAD and python3 setup. Then install pythreejs:
pip3 install pythreejs
jupyter nbextension install --py --symlink --sys-prefix pythreejs
jupyter nbextension enable --py --sys-prefix pythreejs
The following function will do the job: freecadviewer.render_document
is everything that you need to know in order to render your FreeCAD document.
For this to work we need to iterate over the entire scene graph and extract the edges and surfaces. By default we grab the face and edge representations (by chosing the right switch node children. The switch node allows switching between different FreeCAD views such as showing the mesh, surface, only edges etc.)
To view the scene graph structure in a convenient way open FreeCAD > Tools > view scene graph
in the GUI. That's how I verified the locations of SoIndexedFaceSet
and the corresponding SoCoordinate3
object. The coordinates switch node children refer to are always the coordinates in the root of the object. So one level below the document root node.
Now we can verify that these functions do what they are supposed to do. First we add the JUPYTER_REPO_PATH (path to the Github repository this file is part of) and the FreeCAD shared library path. Then we just import FreeCAD and set it up for headless usage. No firing up the desktop app!
import sys, os
JUPYTER_REPO_PATH = "/opt/jupyter_freecad/"
sys.path.append("/opt/freecad/freecad_build/lib")
sys.path.append(JUPYTER_REPO_PATH + "IPythonFreeCADViewer")
import FreeCAD, FreeCADGui
from freecadviewer import render_document, get_document_renderer
FreeCADGui.setupWithoutGUI()
Creating a document with objects and a scene graph to be iterated over later on.
doc = FreeCAD.newDocument("test_document")
doc.addObject("Part::Box","Box")
doc.addObject("Part::Cylinder","Cylinder")
doc.addObject("Part::Sphere","Sphere")
doc.addObject("Part::Torus","Torus")
doc.recompute()
doc.FileName = "test_document.FCStd"
doc.save()
Now if everything works as expected this is all we need to render the 3D view right in the notebook:
render_document(doc)
There are a few options to modify what is being rendered, you can enable or disable the rendering of faces, edges, normals and face meshes. We can also check what the default config is:
from freecadviewer import RendererConfig
renderer_config = RendererConfig()
renderer_config.show_config()
Now we can modify these settings to our wishes:
renderer_config.selection_mode = None
renderer_config.show_edges = False
renderer_config.view_width = renderer_config.view_height = 400
render_document(doc, renderer_config)
renderer_config.show_mesh = True
renderer_config.show_normals = True
render_document(doc, renderer_config)
renderer_config.show_edges = True
renderer_config.show_normals = False
renderer_config.show_faces = False
renderer_config.selection_mode = "mousemove"
render_document(doc, renderer_config)
from ipywidgets import interact
import ipywidgets as widgets
doc_dyn = FreeCAD.newDocument()
doc_dyn.addObject("Part::Box","Box")
renderer_config_ = RendererConfig()
renderer_config_.view_width = renderer_config_.view_height = 400
def create_scene(box, cylinder, sphere, torus):
if box:
if not doc_dyn.getObject("Box"):
doc_dyn.addObject("Part::Box","Box")
elif doc_dyn.getObject("Box"):
doc_dyn.removeObject("Box")
if cylinder:
if not doc_dyn.getObject("Cylinder"):
doc_dyn.addObject("Part::Cylinder","Cylinder")
elif doc_dyn.getObject("Cylinder"):
doc_dyn.removeObject("Cylinder")
if sphere:
if not doc_dyn.getObject("Sphere"):
doc_dyn.addObject("Part::Sphere","Sphere")
elif doc_dyn.getObject("Sphere"):
doc_dyn.removeObject("Sphere")
if torus:
if not doc_dyn.getObject("Torus"):
doc_dyn.addObject("Part::Torus","Torus")
elif doc_dyn.getObject("Torus"):
doc_dyn.removeObject("Torus")
doc_dyn.recompute()
return render_document(doc_dyn, renderer_config_)
interact(create_scene, box=True, cylinder=False, sphere=False, torus=False);
You can also recreate the same thing visuall by just linking the buttons and the view without going through the Python kernel by using jslink
. That way the visual change is faster and you could embed this interactive view on a static page like here. You can do this for the widget above as follows:
from ipywidgets import jslink, Checkbox
renderer_config_.show_edges = False
# workaround since it becomes hard to identify the objects in
# the scene group since all lines and meshes are in the
# same group. Fixing this is a TODO
doc_stat = FreeCAD.newDocument()
doc_stat.addObject("Part::Box","Box")
doc_stat.addObject("Part::Cylinder","Cylinder")
doc_stat.addObject("Part::Sphere","Sphere")
doc_stat.addObject("Part::Torus","Torus")
doc_stat.recompute()
renderer, html = get_document_renderer(doc_stat, renderer_config_)
checkboxes = [Checkbox(description="Box"), Checkbox(description="Cylinder"), Checkbox(description="Sphere"), Checkbox(description="Torus"), ]
for i, obj in enumerate(renderer.scene.children[3].children):
jslink((checkboxes[i], "value"), (obj, "visible"))
checkboxes[0].value = True
checkboxes.extend([renderer, html])
display(*checkboxes)
doc_prev = FreeCAD.openDocument(JUPYTER_REPO_PATH + "test_document.FCStd")
render_document(doc_prev, renderer_config_)
Created with FreeCAD 0.18 on MacOS
doc_desktop = FreeCAD.openDocument(JUPYTER_REPO_PATH + "test_freecad_0.18_macos.FCStd")
render_document(doc_desktop, renderer_config_)