#*************************************************************************** #* Copyright (c) 2011 Yorik van Havre * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program 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 Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** import FreeCAD, Mesh, os, numpy, MeshPart, Arch, Draft from draftutils import params if FreeCAD.GuiUp: from draftutils.translate import translate else: # \cond def translate(context,text): return text # \endcond ## @package importDAE # \ingroup ARCH # \brief DAE (Collada) file format importer and exporter # # This module provides tools to import and export Collada (.dae) files. __title__ = "FreeCAD Collada importer" __author__ = "Yorik van Havre" __url__ = "https://www.freecad.org" DEBUG = True try: # Python 2 forward compatibility range = xrange except NameError: pass def checkCollada(): "checks if collada if available" global collada COLLADA = None try: import collada except ImportError: FreeCAD.Console.PrintError(translate("Arch","pycollada not found, collada support is disabled.")+"\n") return False else: return True def triangulate(shape): "triangulates the given face" mesher = params.get_param_arch("ColladaMesher") tessellation = params.get_param_arch("ColladaTessellation") grading = params.get_param_arch("ColladaGrading") segsperedge = params.get_param_arch("ColladaSegsPerEdge") segsperradius = params.get_param_arch("ColladaSegsPerRadius") secondorder = params.get_param_arch("ColladaSecondOrder") optimize = params.get_param_arch("ColladaOptimize") allowquads = params.get_param_arch("ColladaAllowQuads") if mesher == 0: return shape.tessellate(tessellation) elif mesher == 1: return MeshPart.meshFromShape(Shape=shape,MaxLength=tessellation).Topology else: return MeshPart.meshFromShape(Shape=shape,GrowthRate=grading,SegPerEdge=segsperedge, SegPerRadius=segsperradius,SecondOrder=secondorder,Optimize=optimize, AllowQuad=allowquads).Topology def open(filename): "called when freecad wants to open a file" if not checkCollada(): return docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname) doc.Label = docname FreeCAD.ActiveDocument = doc read(filename) return doc def insert(filename,docname): "called when freecad wants to import a file" if not checkCollada(): return try: doc = FreeCAD.getDocument(docname) except NameError: doc = FreeCAD.newDocument(docname) FreeCAD.ActiveDocument = doc read(filename) return doc def read(filename): "reads a DAE file" global col col = collada.Collada(filename, ignore=[collada.common.DaeUnsupportedError]) # Read the unitmeter info from dae file and compute unit to convert to mm unitmeter = col.assetInfo.unitmeter or 1 unit = unitmeter / 0.001 #for geom in col.geometries: #for geom in col.scene.objects('geometry'): for node in col.scene.nodes: if list(node.objects("geometry")): color = None # retrieving material if "}" in node.xmlnode.tag: bt = node.xmlnode.tag.split("}")[0]+"}" gnode = node.xmlnode.find(bt+"instance_geometry") if gnode is not None: bnode = gnode.find(bt+"bind_material") if bnode is not None: tnode = bnode.find(bt+"technique_common") if tnode is not None: mnode = tnode.find(bt+"instance_material") if mnode is not None: if "target" in mnode: mname = mnode.get("target").strip("#") for m in col.materials: if m.id == mname: e = m.effect if isinstance(e.diffuse,tuple): color = e.diffuse for geom in node.objects("geometry"): for prim in geom.primitives(): #print(prim, dir(prim)) meshdata = [] if hasattr(prim,"triangles"): tset = prim.triangles() elif hasattr(prim,"triangleset"): tset = prim.triangleset() else: tset = [] for tri in tset: face = [] for v in tri.vertices: v = [x * unit for x in v] face.append([v[0],v[1],v[2]]) meshdata.append(face) #print(meshdata) newmesh = Mesh.Mesh(meshdata) #print(newmesh) obj = FreeCAD.ActiveDocument.addObject("Mesh::Feature","Mesh") obj.Mesh = newmesh if color and FreeCAD.GuiUp: obj.ViewObject.ShapeColor = color def export(exportList,filename,tessellation=1,colors=None): """export(exportList,filename,tessellation=1,colors=None) -- exports FreeCAD contents to a DAE file. colors is an optional dictionary of objName:shapeColorTuple or objName:diffuseColorList elements to be used in non-GUI mode if you want to be able to export colors. Tessellation is used when breaking curved surfaces into triangles.""" if not checkCollada(): return scale = params.get_param_arch("ColladaScalingFactor") scale = scale * 0.001 # from millimeters (FreeCAD) to meters (Collada) defaultcolor = Draft.get_rgba_tuple(params.get_param_view("DefaultShapeColor"))[:3] colmesh = collada.Collada() colmesh.assetInfo.upaxis = collada.asset.UP_AXIS.Z_UP # authoring info cont = collada.asset.Contributor() try: author = FreeCAD.ActiveDocument.CreatedBy except UnicodeEncodeError: author = FreeCAD.ActiveDocument.CreatedBy.encode("utf8") author = author.replace("<","") author = author.replace(">","") cont.author = author ver = FreeCAD.Version() appli = "FreeCAD v" + ver[0] + "." + ver[1] + " build" + ver[2] + "\n" cont.authoring_tool = appli #print(author, appli) colmesh.assetInfo.contributors.append(cont) colmesh.assetInfo.unitname = "meter" colmesh.assetInfo.unitmeter = 1.0 defaultmat = None objind = 0 scenenodes = [] objectslist = Draft.get_group_contents(exportList, walls=True, addgroups=True) objectslist = Arch.pruneIncluded(objectslist) for obj in objectslist: findex = numpy.array([]) m = None if obj.isDerivedFrom("Part::Feature"): print("exporting object ",obj.Name, obj.Shape) new_shape = obj.Shape.copy() new_shape.Placement = obj.getGlobalPlacement() m = Mesh.Mesh(triangulate(new_shape)) elif obj.isDerivedFrom("Mesh::Feature"): print("exporting object ",obj.Name, obj.Mesh) m = obj.Mesh elif obj.isDerivedFrom("App::Part"): for child in obj.OutList: objectslist.append(child) continue else: continue if m: Topology = m.Topology Facets = m.Facets # vertex indices vindex = numpy.empty(len(Topology[0]) * 3) for i in range(len(Topology[0])): v = Topology[0][i] vindex[list(range(i*3, i*3+3))] = (v.x*scale,v.y*scale,v.z*scale) # normals nindex = numpy.empty(len(Facets) * 3) for i in range(len(Facets)): n = Facets[i].Normal nindex[list(range(i*3, i*3+3))] = (n.x,n.y,n.z) # face indices findex = numpy.empty(len(Topology[1]) * 6, numpy.int64) for i in range(len(Topology[1])): f = Topology[1][i] findex[list(range(i*6, i*6+6))] = (f[0],i,f[1],i,f[2],i) print(len(vindex), " vert indices, ", len(nindex), " norm indices, ", len(findex), " face indices.") vert_src = collada.source.FloatSource("cubeverts-array"+str(objind), vindex, ('X', 'Y', 'Z')) normal_src = collada.source.FloatSource("cubenormals-array"+str(objind), nindex, ('X', 'Y', 'Z')) geom = collada.geometry.Geometry(colmesh, "geometry"+str(objind), obj.Name, [vert_src, normal_src]) input_list = collada.source.InputList() input_list.addInput(0, 'VERTEX', "#cubeverts-array"+str(objind)) input_list.addInput(1, 'NORMAL', "#cubenormals-array"+str(objind)) matnode = None matref = "materialref" if hasattr(obj,"Material"): if obj.Material: if hasattr(obj.Material,"Material"): if "DiffuseColor" in obj.Material.Material: kd = tuple([float(k) for k in obj.Material.Material["DiffuseColor"].strip("()").split(",")]) effect = collada.material.Effect("effect_"+obj.Material.Name, [], "phong", diffuse=kd, specular=(1,1,1)) mat = collada.material.Material("mat_"+obj.Material.Name, obj.Material.Name, effect) colmesh.effects.append(effect) colmesh.materials.append(mat) matref = "ref_"+obj.Material.Name matnode = collada.scene.MaterialNode(matref, mat, inputs=[]) if not matnode: if colors: if obj.Name in colors: color = colors[obj.Name] if color: if isinstance(color[0],tuple): # this is a diffusecolor. For now, use the first color - #TODO: Support per-face colors color = color[0] #print("found color for obj",obj.Name,":",color) kd = color[:3] effect = collada.material.Effect("effect_"+obj.Name, [], "phong", diffuse=kd, specular=(1,1,1)) mat = collada.material.Material("mat_"+obj.Name, obj.Name, effect) colmesh.effects.append(effect) colmesh.materials.append(mat) matref = "ref_"+obj.Name matnode = collada.scene.MaterialNode(matref, mat, inputs=[]) elif FreeCAD.GuiUp: if hasattr(obj.ViewObject,"ShapeColor"): kd = obj.ViewObject.ShapeColor[:3] effect = collada.material.Effect("effect_"+obj.Name, [], "phong", diffuse=kd, specular=(1,1,1)) mat = collada.material.Material("mat_"+obj.Name, obj.Name, effect) colmesh.effects.append(effect) colmesh.materials.append(mat) matref = "ref_"+obj.Name matnode = collada.scene.MaterialNode(matref, mat, inputs=[]) if not matnode: if not defaultmat: effect = collada.material.Effect("effect_default", [], "phong", diffuse=defaultcolor, specular=(1,1,1)) defaultmat = collada.material.Material("mat_default", "default_material", effect) colmesh.effects.append(effect) colmesh.materials.append(defaultmat) matnode = collada.scene.MaterialNode(matref, defaultmat, inputs=[]) triset = geom.createTriangleSet(findex, input_list, matref) geom.primitives.append(triset) colmesh.geometries.append(geom) geomnode = collada.scene.GeometryNode(geom, [matnode]) node = collada.scene.Node("node"+str(objind), children=[geomnode]) scenenodes.append(node) objind += 1 myscene = collada.scene.Scene("myscene", scenenodes) colmesh.scenes.append(myscene) colmesh.scene = myscene colmesh.write(filename) FreeCAD.Console.PrintMessage(translate("Arch","file %s successfully created.") % filename)