parameters: ( exportList, filename, colors = None, camera = None )
docstring: Exports objects to an html file
docstring_summary: Exports objects to an html file
docstring_tokens: Exports objects to an html file
function: def export( exportList, filename, colors = None, camera = None ):
      """Exports objects to an html file"""
      
      global disableCompression, base, baseFloat
      
      data = { 'camera':{}, 'file':{}, 'objects':[] }
      
      if not FreeCADGui and not camera:
          camera = OfflineRenderingUtils.getCamera(FreeCAD.ActiveDocument.FileName)
      
      if camera:
          # REF: https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/Arch/OfflineRenderingUtils.py
          camnode = OfflineRenderingUtils.getCoinCamera(camera)
          cameraPosition = camnode.position.getValue().getValue()
          data['camera']['type'] = 'Orthographic'
          if 'PerspectiveCamera' in camera: data['camera']['type'] = 'Perspective'
          data['camera']['focalDistance'] = camnode.focalDistance.getValue()
          data['camera']['position_x'] = cameraPosition[0]
          data['camera']['position_y'] = cameraPosition[1]
          data['camera']['position_z'] = cameraPosition[2]
      else:
          v = FreeCADGui.ActiveDocument.ActiveView
          data['camera']['type'] = v.getCameraType()
          data['camera']['focalDistance'] = v.getCameraNode().focalDistance.getValue()
          data['camera']['position_x'] = v.viewPosition().Base.x
          data['camera']['position_y'] = v.viewPosition().Base.y
          data['camera']['position_z'] = v.viewPosition().Base.z 
      
      # Take the objects out of groups
      objectslist = Draft.get_group_contents(exportList, walls=True, addgroups=False)
      # objectslist = Arch.pruneIncluded(objectslist)
      
      for obj in objectslist:
          
          # Pull all obj data before we dig down the links
          label = obj.Label
          
          color = '#cccccc';
          opacity = 1.0
          if FreeCADGui:
              color = Draft.getrgb(obj.ViewObject.ShapeColor, testbw = False)
              opacity = int((100 - obj.ViewObject.Transparency)/5) / 20 # 0>>1 with step of 0.05
          elif colors:
              if label in colors:
                  color = Draft.getrgb(colors[label], testbw = False) 
          
          validObject = False
          if obj.isDerivedFrom('Mesh::Feature'):
              mesh = obj.Mesh
              validObject = True
          if obj.isDerivedFrom('Part::Feature'):
              objShape = obj.Shape
              validObject = True
          if obj.isDerivedFrom('App::Link'):
              linkPlacement = obj.LinkPlacement
              while True: # drill down to get to the actual obj
                  if obj.isDerivedFrom("App::Link"):
                      if obj.ViewObject.OverrideMaterial: color = Draft.getrgb(obj.ViewObject.ShapeMaterial.DiffuseColor, testbw = False)
                      obj = obj.LinkedObject
                      if hasattr(obj, "__len__"):
                          FreeCAD.Console.PrintMessage(label + ": Sub-Links are Unsupported.\n")
                          break
                  elif obj.isDerivedFrom('Part::Feature'):
                      objShape = obj.Shape.copy(False)
                      objShape.Placement = linkPlacement
                      validObject = True
                      break
                  elif obj.isDerivedFrom("Mesh::Feature"):
                      mesh = obj.Mesh.copy()
                      mesh.Placement = linkPlacement
                      validObject = True
                      break
          
          if not validObject: continue
          
          objdata = { 'name': label, 'color': color, 'opacity': opacity, 'verts':'', 'facets':'', 'wires':[], 'faceColors':[], 'facesToFacets':[], 'floats':[] }
          
          if obj.isDerivedFrom('Part::Feature'):
              
              deviation = 0.5
              if FreeCADGui:
                  deviation = obj.ViewObject.Deviation
                  
                  # obj.ViewObject.DiffuseColor is length=1 when all faces are the same color, length=len(faces) for when they're not
                  if len(obj.ViewObject.DiffuseColor) == len(objShape.Faces):
                      for fc in obj.ViewObject.DiffuseColor:
                          objdata['faceColors'].append( Draft.getrgb(fc, testbw = False) )
              
              # get verts and facets for ENTIRE object
              shapeData = objShape.tessellate( deviation )
              mesh = Mesh.Mesh(shapeData)
              
              if len(objShape.Faces) > 1:
                  # Map each Facet created by tessellate() to a Face so that it can be colored correctly using faceColors
                  # This is done by matching the results of a tessellate() on EACH FACE to the overall tessellate stored in shapeData
                  # if there is any error in matching these two then we display the whole object as one face and forgo the face colors
                  for f in objShape.Faces:
                      faceData = f.tessellate( deviation )
                      found = True
                      for fv in range( len(faceData[0]) ): # face verts. List of type Vector()
                          found = False
                          for sv in range( len(shapeData[0]) ): #shape verts
                              if faceData[0][fv] == shapeData[0][sv]: # do not use isEqual() here
                                  faceData[0][fv] = sv # replace with the index of shapeData[0]
                                  found = True
                                  break
                          if not found: break
                      if not found:
                          FreeCAD.Console.PrintMessage("Facet to Face Mismatch.\n")
                          objdata['facesToFacets'] = []
                          break
                      
                      # map each of the face facets to the shape facets and make a list of shape facet indices that belong to this face 
                      facetList = []
                      for ff in faceData[1]: # face facets
                          found = False
                          for sf in range( len(shapeData[1]) ): #shape facets
                              if faceData[0][ff[0]] in shapeData[1][sf] and faceData[0][ff[1]] in shapeData[1][sf] and faceData[0][ff[2]] in shapeData[1][sf]:
                                  facetList.append(sf)
                                  found = True
                                  break
                          if not found: break
                      if not found:
                          FreeCAD.Console.PrintMessage("Facet List Mismatch.\n")
                          objdata['facesToFacets'] = []
                          break
                      
                      objdata['facesToFacets'].append( baseEncode(facetList) )
              
              wires = [] # Add wires
              for f in objShape.Faces:
                  for w in f.Wires:
                      wo = Part.Wire(Part.__sortEdges__(w.Edges))
                      wire = []
                      for v in wo.discretize(QuasiDeflection = 0.005):
                          wire.append( '{:.5f}'.format(v.x) ) # use strings to avoid 0.00001 written as 1e-05
                          wire.append( '{:.5f}'.format(v.y) )
                          wire.append( '{:.5f}'.format(v.z) )
                      wires.append( wire )
              
              if not disableCompression:
                  for w in range( len(wires) ):
                      for wv in range( len(wires[w]) ):
                          found = False
                          for f in range( len(objdata['floats']) ):
                              if objdata['floats'][f] == wires[w][wv]:
                                  wires[w][wv] = f
                                  found = True
                                  break
                          if not found:
                              objdata['floats'].append( wires[w][wv] )
                              wires[w][wv] = len(objdata['floats'])-1
                      wires[w] = baseEncode(wires[w])
              objdata['wires'] = wires
          
          vIndex = {}
          verts = []
          for p in range( len(mesh.Points) ):
              vIndex[ mesh.Points[p].Index ] = p
              verts.append( '{:.5f}'.format(mesh.Points[p].Vector.x) )
              verts.append( '{:.5f}'.format(mesh.Points[p].Vector.y) )
              verts.append( '{:.5f}'.format(mesh.Points[p].Vector.z) )
          
          # create floats list to compress verts and wires being written into the JS
          if not disableCompression:
              for v in range( len(verts) ):
                  found = False
                  for f in range( len(objdata['floats']) ):
                      if objdata['floats'][f] == verts[v]:
                          verts[v] = f
                          found = True
                          break
                  if not found:
                      objdata['floats'].append( verts[v] )
                      verts[v] = len(objdata['floats'])-1
          objdata['verts'] = baseEncode(verts)
          
          facets = []
          for f in mesh.Facets:
              for i in f.PointIndices:
                  facets.append( vIndex[i] )
          objdata['facets'] = baseEncode(facets)
          
          # compress floats
          if not disableCompression:
              # use ratio of 7x base13 to 4x base90 because 13^7 ~ 90^4
              fullstr = json.dumps(objdata['floats'], separators=(',', ':'))
              fullstr = fullstr.replace('[', '').replace(']', '').replace('"', '')
              floatStr = ''
              baseFloatCt = len(baseFloat)
              baseCt = len(base)
              for fs in range( 0, len(fullstr), 7 ): # chunks of 7 chars, skip the first one
                  str7 = fullstr[fs:(fs+7)]
                  quotient = 0
                  for s in range( len(str7) ):
                      quotient += baseFloat.find(str7[s]) * pow(baseFloatCt, (6-s))
                  for v in range(4):
                      floatStr += base[ quotient % baseCt ]
                      quotient = int(quotient / baseCt)
              objdata['floats'] = floatStr
          
          data['objects'].append( objdata )
      
      html = getHTMLTemplate()
      
      html = html.replace('$pagetitle',FreeCAD.ActiveDocument.Label)
      version = FreeCAD.Version()
      html = html.replace('$version',version[0] + '.' + version[1] + '.' + version[2])
      
      # Remove data compression in JS
      data['compressed'] = not disableCompression
      data['base'] = base
      data['baseFloat'] = baseFloat
      
      html = html.replace('$data', json.dumps(data, separators=(',', ':')) ) # Shape Data
      
      if six.PY2:
          outfile = pythonopen(filename, "wb")
      else:
          outfile = pythonopen(filename, "w")
      outfile.write( html )
      outfile.close()
      FreeCAD.Console.PrintMessage( translate("Arch", "Successfully written") + ' ' + filename + "\n" )
function_tokens: def export ( exportList , filename , colors = None , camera = None ) : global disableCompression , base , baseFloat data = { 'camera' : { } , 'file' : { } , 'objects' : [ ] } if not FreeCADGui and not camera : camera = OfflineRenderingUtils . getCamera ( FreeCAD . ActiveDocument . FileName ) if camera : # REF: https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/Arch/OfflineRenderingUtils.py camnode = OfflineRenderingUtils . getCoinCamera ( camera ) cameraPosition = camnode . position . getValue ( ) . getValue ( ) data [ 'camera' ] [ 'type' ] = 'Orthographic' if 'PerspectiveCamera' in camera : data [ 'camera' ] [ 'type' ] = 'Perspective' data [ 'camera' ] [ 'focalDistance' ] = camnode . focalDistance . getValue ( ) data [ 'camera' ] [ 'position_x' ] = cameraPosition [ 0 ] data [ 'camera' ] [ 'position_y' ] = cameraPosition [ 1 ] data [ 'camera' ] [ 'position_z' ] = cameraPosition [ 2 ] else : v = FreeCADGui . ActiveDocument . ActiveView data [ 'camera' ] [ 'type' ] = v . getCameraType ( ) data [ 'camera' ] [ 'focalDistance' ] = v . getCameraNode ( ) . focalDistance . getValue ( ) data [ 'camera' ] [ 'position_x' ] = v . viewPosition ( ) . Base . x data [ 'camera' ] [ 'position_y' ] = v . viewPosition ( ) . Base . y data [ 'camera' ] [ 'position_z' ] = v . viewPosition ( ) . Base . z # Take the objects out of groups objectslist = Draft . get_group_contents ( exportList , walls = True , addgroups = False ) # objectslist = Arch.pruneIncluded(objectslist) for obj in objectslist : # Pull all obj data before we dig down the links label = obj . Label color = '#cccccc' opacity = 1.0 if FreeCADGui : color = Draft . getrgb ( obj . ViewObject . ShapeColor , testbw = False ) opacity = int ( ( 100 - obj . ViewObject . Transparency ) / 5 ) / 20 # 0>>1 with step of 0.05 elif colors : if label in colors : color = Draft . getrgb ( colors [ label ] , testbw = False ) validObject = False if obj . isDerivedFrom ( 'Mesh::Feature' ) : mesh = obj . Mesh validObject = True if obj . isDerivedFrom ( 'Part::Feature' ) : objShape = obj . Shape validObject = True if obj . isDerivedFrom ( 'App::Link' ) : linkPlacement = obj . LinkPlacement while True : # drill down to get to the actual obj if obj . isDerivedFrom ( "App::Link" ) : if obj . ViewObject . OverrideMaterial : color = Draft . getrgb ( obj . ViewObject . ShapeMaterial . DiffuseColor , testbw = False ) obj = obj . LinkedObject if hasattr ( obj , "__len__" ) : FreeCAD . Console . PrintMessage ( label + ": Sub-Links are Unsupported.\n" ) break elif obj . isDerivedFrom ( 'Part::Feature' ) : objShape = obj . Shape . copy ( False ) objShape . Placement = linkPlacement validObject = True break elif obj . isDerivedFrom ( "Mesh::Feature" ) : mesh = obj . Mesh . copy ( ) mesh . Placement = linkPlacement validObject = True break if not validObject : continue objdata = { 'name' : label , 'color' : color , 'opacity' : opacity , 'verts' : '' , 'facets' : '' , 'wires' : [ ] , 'faceColors' : [ ] , 'facesToFacets' : [ ] , 'floats' : [ ] } if obj . isDerivedFrom ( 'Part::Feature' ) : deviation = 0.5 if FreeCADGui : deviation = obj . ViewObject . Deviation # obj.ViewObject.DiffuseColor is length=1 when all faces are the same color, length=len(faces) for when they're not if len ( obj . ViewObject . DiffuseColor ) == len ( objShape . Faces ) : for fc in obj . ViewObject . DiffuseColor : objdata [ 'faceColors' ] . append ( Draft . getrgb ( fc , testbw = False ) ) # get verts and facets for ENTIRE object shapeData = objShape . tessellate ( deviation ) mesh = Mesh . Mesh ( shapeData ) if len ( objShape . Faces ) > 1 : # Map each Facet created by tessellate() to a Face so that it can be colored correctly using faceColors # This is done by matching the results of a tessellate() on EACH FACE to the overall tessellate stored in shapeData # if there is any error in matching these two then we display the whole object as one face and forgo the face colors for f in objShape . Faces : faceData = f . tessellate ( deviation ) found = True for fv in range ( len ( faceData [ 0 ] ) ) : # face verts. List of type Vector() found = False for sv in range ( len ( shapeData [ 0 ] ) ) : #shape verts if faceData [ 0 ] [ fv ] == shapeData [ 0 ] [ sv ] : # do not use isEqual() here faceData [ 0 ] [ fv ] = sv # replace with the index of shapeData[0] found = True break if not found : break if not found : FreeCAD . Console . PrintMessage ( "Facet to Face Mismatch.\n" ) objdata [ 'facesToFacets' ] = [ ] break # map each of the face facets to the shape facets and make a list of shape facet indices that belong to this face  facetList = [ ] for ff in faceData [ 1 ] : # face facets found = False for sf in range ( len ( shapeData [ 1 ] ) ) : #shape facets if faceData [ 0 ] [ ff [ 0 ] ] in shapeData [ 1 ] [ sf ] and faceData [ 0 ] [ ff [ 1 ] ] in shapeData [ 1 ] [ sf ] and faceData [ 0 ] [ ff [ 2 ] ] in shapeData [ 1 ] [ sf ] : facetList . append ( sf ) found = True break if not found : break if not found : FreeCAD . Console . PrintMessage ( "Facet List Mismatch.\n" ) objdata [ 'facesToFacets' ] = [ ] break objdata [ 'facesToFacets' ] . append ( baseEncode ( facetList ) ) wires = [ ] # Add wires for f in objShape . Faces : for w in f . Wires : wo = Part . Wire ( Part . __sortEdges__ ( w . Edges ) ) wire = [ ] for v in wo . discretize ( QuasiDeflection = 0.005 ) : wire . append ( '{:.5f}' . format ( v . x ) ) # use strings to avoid 0.00001 written as 1e-05 wire . append ( '{:.5f}' . format ( v . y ) ) wire . append ( '{:.5f}' . format ( v . z ) ) wires . append ( wire ) if not disableCompression : for w in range ( len ( wires ) ) : for wv in range ( len ( wires [ w ] ) ) : found = False for f in range ( len ( objdata [ 'floats' ] ) ) : if objdata [ 'floats' ] [ f ] == wires [ w ] [ wv ] : wires [ w ] [ wv ] = f found = True break if not found : objdata [ 'floats' ] . append ( wires [ w ] [ wv ] ) wires [ w ] [ wv ] = len ( objdata [ 'floats' ] ) - 1 wires [ w ] = baseEncode ( wires [ w ] ) objdata [ 'wires' ] = wires vIndex = { } verts = [ ] for p in range ( len ( mesh . Points ) ) : vIndex [ mesh . Points [ p ] . Index ] = p verts . append ( '{:.5f}' . format ( mesh . Points [ p ] . Vector . x ) ) verts . append ( '{:.5f}' . format ( mesh . Points [ p ] . Vector . y ) ) verts . append ( '{:.5f}' . format ( mesh . Points [ p ] . Vector . z ) ) # create floats list to compress verts and wires being written into the JS if not disableCompression : for v in range ( len ( verts ) ) : found = False for f in range ( len ( objdata [ 'floats' ] ) ) : if objdata [ 'floats' ] [ f ] == verts [ v ] : verts [ v ] = f found = True break if not found : objdata [ 'floats' ] . append ( verts [ v ] ) verts [ v ] = len ( objdata [ 'floats' ] ) - 1 objdata [ 'verts' ] = baseEncode ( verts ) facets = [ ] for f in mesh . Facets : for i in f . PointIndices : facets . append ( vIndex [ i ] ) objdata [ 'facets' ] = baseEncode ( facets ) # compress floats if not disableCompression : # use ratio of 7x base13 to 4x base90 because 13^7 ~ 90^4 fullstr = json . dumps ( objdata [ 'floats' ] , separators = ( ',' , ':' ) ) fullstr = fullstr . replace ( '[' , '' ) . replace ( ']' , '' ) . replace ( '"' , '' ) floatStr = '' baseFloatCt = len ( baseFloat ) baseCt = len ( base ) for fs in range ( 0 , len ( fullstr ) , 7 ) : # chunks of 7 chars, skip the first one str7 = fullstr [ fs : ( fs + 7 ) ] quotient = 0 for s in range ( len ( str7 ) ) : quotient += baseFloat . find ( str7 [ s ] ) * pow ( baseFloatCt , ( 6 - s ) ) for v in range ( 4 ) : floatStr += base [ quotient % baseCt ] quotient = int ( quotient / baseCt ) objdata [ 'floats' ] = floatStr data [ 'objects' ] . append ( objdata ) html = getHTMLTemplate ( ) html = html . replace ( '$pagetitle' , FreeCAD . ActiveDocument . Label ) version = FreeCAD . Version ( ) html = html . replace ( '$version' , version [ 0 ] + '.' + version [ 1 ] + '.' + version [ 2 ] ) # Remove data compression in JS data [ 'compressed' ] = not disableCompression data [ 'base' ] = base data [ 'baseFloat' ] = baseFloat html = html . replace ( '$data' , json . dumps ( data , separators = ( ',' , ':' ) ) ) # Shape Data if six . PY2 : outfile = pythonopen ( filename , "wb" ) else : outfile = pythonopen ( filename , "w" ) outfile . write ( html ) outfile . close ( ) FreeCAD . Console . PrintMessage ( translate ( "Arch" , "Successfully written" ) + ' ' + filename + "\n" )