export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by easyw-fc »

Hi
I've done this macro (thanks to hyOzd and Nico) to export VRML from FreeCAD to obtain a simplified VRML of a (multi)selected Part or fused Part, optimized for Kicad and compatible with Blender.
https://github.com/easyw/kicad-3d-model ... rs.FCMacro

The size of VRML is much smaller compared to the one exported from FC Gui and the loading/rendering time is smaller too.
Just change mesh deviation to increase quality of VRML.
To Do:
add Material Properties to VRML

If you may consider it useful, I could add it to Macro recipes

Maurice
User avatar
microelly2
Veteran
Posts: 4688
Joined: Tue Nov 12, 2013 4:06 pm
Contact:

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by microelly2 »

easyw-fc wrote:Hi

To Do:
add Material Properties to VRML

If you may consider it useful, I could add it to Macro recipes

Maurice
Yes, please
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by easyw-fc »

microelly2 wrote:Yes, please
Do I need to have a special credit/permission to write on FreeCAD Macro recepies?
User avatar
microelly2
Veteran
Posts: 4688
Joined: Tue Nov 12, 2013 4:06 pm
Contact:

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by microelly2 »

I think you need edit rights to the forum, send a mail to yorik.
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by easyw-fc »

microelly2 wrote:
easyw-fc wrote: If you may consider it useful, I could add it to Macro recipes
Maurice
Yes, please
http://www.freecadweb.org/wiki/index.ph ... PartToVRML
Done :)
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by easyw-fc »

Hi to all,

here two screenshots of the realistic rendering in Blender thanks to the new VRML exporter and kicad StepUp :D

Image
Image
Graham
Posts: 2
Joined: Sun Jul 21, 2019 11:41 pm

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by Graham »

Hello all,

I was looking at creating objects in VRML for RoomArranger. The PartToVRML macro is really useful and much better than the built-in exporter but for me has a problem. RoomArranger exposes one SFNode on the shape to set the colour of (part of) an object but the macro creates one shape for each face. I'd be there forever identifying the correct faces and manually editing the VRML file to add the SFNode to each one.

So I created a new version of the macro which:

  • 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
  • displays the object bounds, size and number of faces in the file
  • optionally allows translation into Z-forward Y-up coordinates (as used by RoomArranger)
I call my version PartToVRMLsbc for "PartToVRML shape by colour".

Obviously this doesn't just apply to RoomArranger, so if this is useful could somebody add it to the list of Macros on the web site?

Thanks,
Graham

Code: Select all

# -*- 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.0.0"
__date__    = "25/07/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(clr):
    # Need ambientIntensity but only have ambientColor, where ambientColor = ambientIntensity * diffuseColor 
    # Maybe use "perceived brightness" instead:
    pb = math.sqrt(0.299*clr[0]*clr[0] + 0.587*clr[1]*clr[1] + 0.114*clr[2]*clr[2])
    return pb

def minmax(p, bnd):
    for i in range(3):
        if bnd.mn[i]==None or p[i]<bnd.mn[i]: bnd.mn[i] = p[i]
        if bnd.mx[i]==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)
        # Note: colours have 4 numbers but we only want 3 (RGB) so remove last 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!=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)

PS. FreeCAD and VRML1 use AmbientColor but VRML2 uses AmbientIntensity. If anybody knows a good way to calculate AmbientIntensity given the AmbientColor and DiffuseColor (i.e. how to divide one colour by another) please let me know!
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by easyw-fc »

The ambientIntensity: This field specifies the amount of ambient light reflected by the object. There is more information about ambient light in section 4.1. Ambient color is calculated as ambientIntensity × diffuseColor.
Geometry-Color-Texture_in_VRML_ColtekinA_SSF99.pdf
http://vrmlstuff.free.fr/materials/index.html
Graham wrote: Wed Jul 24, 2019 11:35 pm So I created a new version of the macro which:

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 tessellate fix (tessellate by object not face) found by Fdancad https://forum.freecadweb.org/viewtopic.php?t=32889

displays the object bounds, size and number of faces in the file

optionally allows translation into Z-forward Y-up coordinates (as used by RoomArranger)

I call my version PartToVRMLsbc for "PartToVRML shape by colour".
Thanks for posting it here...
this could be useful to improve the vrml importing in Blender, limiting the part to assign materials to a bunch of faces.
I see if I can adapt it to my StepUp script that is used to create WRL models for KiCAD.
Maurice
Graham
Posts: 2
Joined: Sun Jul 21, 2019 11:41 pm

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by Graham »

Thanks for the information about Ambient colours. I have now updated my script for this, and the new version can be found on my web page at:

https://goneill.co.nz/freecad.php

Did you have any success in using my script for your StepUp project? I tried importing an exported VRML file into Blender and it seemed to be OK.
User avatar
easyw-fc
Veteran
Posts: 3633
Joined: Thu Jul 09, 2015 9:34 am

Re: export VRML from FreeCAD with python for smaller size, kicad and Blender compatible

Post by easyw-fc »

Graham wrote: Thu Aug 22, 2019 12:16 am Did you have any success in using my script for your StepUp project? I tried importing an exported VRML file into Blender and it seemed to be OK.
Not at the moment... I still need to merges all faces with one color into a single shape element in the VRML file.
Thx for posting your updates.
Post Reply