Please try another browser if you have difficulties.
FreeCAD - Export VRML
I had been using Blender 2.79 to create VRML files for making objects to be imported into RoomArranger but while version 2.80 was being developed I saw that the export of VRML files might be dropped. So I looked around for an alternative and found FreeCAD.
At first I was pleased to see that it had VRML export built in, but then discovered that the files it produced were very convoluted, being full of nested DEF statements. Then I found a macro that replaced this which seemed to be better until I found that it produced one shape in the VRML file for each face on the object. This made colouring the object for RoomArranger really difficult so I set about trying to amend it.
The original macro was called PartToVRML so I named my version PartToVRMLsbc (for "shape by colour"). It can be downloaded here:
It is installed and run the same way as any other macro in FreeCAD. I put it into the toolbar of my Part and Part Design workbenches using the instructions here:
https://www.freecadweb.org/wiki/Customize_Toolbars.
Features of my version are:
- Merges all faces with one colour into a single shape element in the VRML file
- Sets colours using the Appearance.. > Material and Set colors.. on faces of a Part
- Uses the fix (tessellate by object not face) found by forum user Fdancad https://forum.freecadweb.org/viewtopic.php?t=32889
- Displays the object bounds, size and number of faces in the file
- Allows translation into Z-forward Y-up coordinates (as used by RoomArranger)
I haven't looked into giving it a proper GUI interface as that seems to require installing multiple Python and Qt libraries. So to configure it for yourself you should edit the macro and set:
Line | Item | Description |
---|---|---|
57 | dflt_path | The directory where the files will be written |
58 | dflt_deviation | A value that ranges from 0.01 to 1.0 |
221 | outputFiles | The list of files you want written |
The path can be your user directory or any other location you specify.
The deviation value determines how many faces are created when the object is converted
into a mesh of triangles. Note that this is similar to a logarithmic curve: there is more difference going
from 0.01 to 0.02 or to 0.03 than there is in going from 0.2 to 0.7 for example. A reasonable number
for most cases might be 0.03, but RoomArranger prefers quite a low polygon count so I find a value
of 0.2 or more works fine.
For each output file specify the scale factor, the number of decimal places for each vertex, whether you
want it rotated to Z-forward/Y-up and the filename.
When you run the macro it will identify the colours used and merge together all faces of each colour. Creating a list of faces by colour isn't very efficient so this might take some time for a large, complex object.
At the time I wrote this script the latest version of FreeCAD (18.3) didn't allow for colouring objects by vertex. If that gets added in the future it might be difficult to amend this script: faces by colour gets tricky when one triangular face might have three colours! FreeCAD also doesn't currently allow for UV textures.
Python script
The download link above will save the following macro script:
# -*- coding: utf-8 -*-
# PartToVRMLsbc.FCMacro
# Originally based on PartToVRML.FCMacro version 1.9.2
# Exports a VRML model of selected object(s), making one Shape for each colour ("PartToVRML shape by colour")
# Create the object (e.g. using Part) then use "Set Colors.." to group together all faces that should be in
# each VRML Shape element. You can use Material colours too, and rotate to Z-forward/Y-up if required.
# See the Report view for any messages when running the macro
__title__ = "PartToVRMLsbc"
__author__ = "Graham ONeill, easyw-fc, hyOzd"
__url__ = "http://www.freecadweb.org/"
__version__ = "1.1.0"
__date__ = "19/08/2019"
__Comment__ = "This macro creates VRML model of selected object(s) using the colors (for Kicad, Blender and RoomArranger compatibility)"
__Web__ = "http://www.freecadweb.org/"
__Wiki__ = "http://www.freecadweb.org/wiki/index.php?title=Macro_PartToVRML"
__Icon__ = "/usr/lib/freecad/Mod/plugins/icons/Macro_PartToVRML.png"
__IconW__ = "C:/Users/User Name/AppData/Roaming/FreeCAD/Macro_PartToVRML.png"
__Help__ = "start the macro and follow the instructions"
__Status__ = "stable"
__Requires__ = "Freecad"
# FreeCAD VRML python exporter is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PartToVRMLsbc.FCMacro. If not, see
# http://www.gnu.org/licenses/
import FreeCAD,FreeCADGui,Part,Mesh
import PySide
from PySide import QtGui, QtCore
from collections import namedtuple
import sys, os, math
from os.path import expanduser
ColorSet = namedtuple('ColorSet', ['diffuse', 'emissive', 'specular', 'ambient', 'intensity', 'shininess', 'transparency'])
Mesh = namedtuple('Mesh', ['points', 'faces', 'colorset'])
ColorIndex = namedtuple('ColorIndex', ['colorset', 'meshlist'])
FileParam = namedtuple('FileParam', ['scale', 'decplc', 'zforward', 'filename'])
Bounds = namedtuple('Bounds', ['mn', 'mx'])
# Set default values
dflt_path = expanduser("~")
#dflt_path = 'D:\Data\FreeCAD' #alternative: set to required path on your machine
dflt_deviation=0.03 #the smaller the best quality, 1 coarse; 0.03 good compromise
def say(msg):
FreeCAD.Console.PrintMessage(msg+"\n")
def faceToMesh(face, facecolors):
mesh_data = face.tessellate(dflt_deviation)
points = mesh_data[0]
newMesh = Mesh(points = points, faces = mesh_data[1], colorset = facecolors)
return newMesh
def valToStr(val, dec):
fmt = '%.'+str(dec)+'f'
ret = (fmt % val).rstrip('0').rstrip('.')
if ret == '-0': ret = '0'
return ret
def intensity(amb, dif):
aint = 0
tot = 0
cnt = 0
for i in range(3):
if amb[i] != 0 and dif[i] != 0:
tot += (amb[i]/dif[i])
cnt += 1
if cnt != 0: aint = tot/cnt
return aint
def minmax(p, bnd):
for i in range(3):
if bnd.mn[i] is None or p[i] < bnd.mn[i]: bnd.mn[i] = p[i]
if bnd.mx[i] is None or p[i] > bnd.mx[i]: bnd.mx[i] = p[i]
return bnd
def writeVRML(colList, mshList, filename, scale, dp, zforward):
with open(filename, 'w') as fil:
# write the standard VRML header
fil.write("#VRML V2.0 utf8\n")
fil.write("#modelled using FreeCAD https://www.freecadweb.org\n")
fcCnt = 0
objBounds = Bounds(mn = [None, None, None], mx = [None, None, None])
for col in colList:
fil.write("\nShape {\n")
# ----- Appearance -----
fil.write("\tappearance Appearance {\n\t\tmaterial Material {\n")
fil.write("\t\t\tdiffuseColor %f %f %f\n" % col.colorset.diffuse)
if col.colorset.emissive != (0.0, 0.0, 0.0):
fil.write("\t\t\temissiveColor %f %f %f\n" % col.colorset.emissive)
if col.colorset.specular != (0.0, 0.0, 0.0):
fil.write("\t\t\tspecularColor %f %f %f\n" % col.colorset.specular)
if col.colorset.intensity != 0.0:
fil.write("\t\t\tambientIntensity %f\n" % col.colorset.intensity)
fil.write("\t\t\tshininess %f\n" % col.colorset.shininess)
if col.colorset.transparency != 0.0:
fil.write("\t\t\ttransparency %f\n" % col.colorset.transparency)
fil.write("\t\t}\n\t}\n")
# ----- Geometry -----
ptStr = ''; ptDel = ''
coStr = ''; coDel = ''
ptTot = 0
for m in col.meshlist:
msh = mshList[m]
ptCnt = 0
for p in msh.points:
if scale != 1.0: ps = p * scale
else: ps = p
if zforward:
ptStr += ptDel + valToStr(-1*ps.x,dp)+' '+valToStr(ps.z,dp)+' '+valToStr(ps.y,dp)
else:
ptStr += ptDel + valToStr(ps.x,dp)+' '+valToStr(ps.y,dp)+' '+valToStr(ps.z,dp)
ptDel = ','
objBounds = minmax(ps, objBounds)
ptCnt += 1
for f in msh.faces:
coStr += coDel + ("%d" % (f[0]+ptTot))+','+("%d" % (f[1]+ptTot))+','+("%d" % (f[2]+ptTot))+',-1'
coDel = ','
fcCnt += 1
ptTot += ptCnt
# write coordinate points for each vertex
fil.write("\tgeometry IndexedFaceSet {\n\t\tcoord Coordinate { point [")
fil.write(ptStr)
fil.write("] }\n")
# write coordinate indexes for each face
fil.write("\t\tcoordIndex [")
fil.write(coStr)
fil.write("]\n\t}\n")
fil.write("}\n") # closes Shape
fil.write("\n#Tessellation value: %.2f" % dflt_deviation)
fil.write(" (%d faces)\n\n" % fcCnt)
fil.write("#Object boundaries: From (")
fil.write(valToStr(objBounds.mn[0],dp)+", "+valToStr(objBounds.mn[1],dp)+", "+valToStr(objBounds.mn[2],dp)+") to (")
fil.write(valToStr(objBounds.mx[0],dp)+", "+valToStr(objBounds.mx[1],dp)+", "+valToStr(objBounds.mx[2],dp)+")\n")
fil.write("#Object dimensions: ")
fil.write(valToStr(abs(objBounds.mx[0]-objBounds.mn[0]),dp)+" x ")
fil.write(valToStr(abs(objBounds.mx[1]-objBounds.mn[1]),dp)+" x ")
fil.write(valToStr(abs(objBounds.mx[2]-objBounds.mn[2]),dp)+"\n")
say(filename+' written')
def export(selObjs, fileList):
# ----- Create meshes with colours -----
meshes=[]
for obj in selObjs:
gobj = Gui.ActiveDocument.getObject(obj.Name)
gmat = gobj.ShapeMaterial
ambInt = intensity(gmat.AmbientColor, gobj.ShapeColor)
# Note: colours have 4 numbers but we only want 3 (RGB) so remove last (Alpha value) using [:-1]
shapeSet = ColorSet(diffuse=gobj.ShapeColor[:-1], emissive=gmat.EmissiveColor[:-1],
specular=gmat.SpecularColor[:-1], ambient=gmat.AmbientColor[:-1],
intensity=ambInt, shininess=gmat.Shininess, transparency=gobj.Transparency/100.0)
faceColors = gobj.DiffuseColor
# Obj.DiffuseColor should have a colour for each face, but if not use ShapeColor on all faces
for i in range(len(obj.Shape.Faces)):
if len(faceColors) != len(obj.Shape.Faces):
meshes.append(faceToMesh(obj.Shape.Faces[i], shapeSet))
else:
faceSet = shapeSet._replace(diffuse=faceColors[i][:-1])
meshes.append(faceToMesh(obj.Shape.Faces[i], faceSet))
# ----- Build index of meshes by colour -----
colIndex=[]
for m in range(len(meshes)):
fnd=False
for c in range(len(colIndex)):
if colIndex[c].colorset==meshes[m].colorset:
# "colIndex[c].meshlist.append(m)" works, but probably shouldn't so instead replace colIndex[c]:
newlist = colIndex[c].meshlist
newlist.append(m)
colIndex[c] = ColorIndex(colorset = meshes[m].colorset, meshlist = newlist)
fnd=True
break
if not fnd:
colIndex.append(ColorIndex(colorset = meshes[m].colorset, meshlist = [m]))
# ----- Use colour index and meshes to output files -----
for f in fileList:
writeVRML(colIndex, meshes, f.filename, f.scale, f.decplc, f.zforward)
return
# Clear previous messages
mw=Gui.getMainWindow()
r=mw.findChild(QtGui.QTextEdit, "Report view")
r.clear()
doc = FreeCAD.ActiveDocument
if doc is not None:
sel = FreeCADGui.Selection.getSelection()
if not sel:
FreeCAD.Console.PrintWarning("Select something first!\n\n")
msg="Export VRML from FreeCAD is a python macro that will export simplified VRML of "
msg+="a (multi)selected Part or fused Part to VRML optimized to Kicad and compatible with Blender. "
msg+="The size of VRML is much smaller compared to the one exported from FC Gui "
msg+="and the loading/rendering time is also smaller\n"
msg+="Change mesh deviation to increase quality of VRML"
say(msg)
else:
selectObjs = []
for obj in sel:
obj.Shape.tessellate(dflt_deviation,True) # Must tessellate at object level
selectObjs.append(obj)
filePathName=doc.FileName
if filePathName=="":
say('Path not found, saving to '+dflt_path)
filePathName = dflt_path
else:
filePathName = os.path.dirname(os.path.abspath(filePathName))
filePathName = filePathName + os.sep + doc.Label + '-' + selectObjs[0].Label
outputFiles = [ FileParam(scale=1.0000, decplc=4, zforward=False, filename=filePathName+'.wrl'),
FileParam(scale=0.3937, decplc=5, zforward=False, filename=filePathName+'_inches.wrl'),
FileParam(scale=1.0000, decplc=4, zforward=True, filename=filePathName+'_ZY.wrl') ]
export(selectObjs, outputFiles)