# /********************************************************************** # * * # * Copyright (c) 2021 Javier Braña * # * * # * 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 Arch import Draft import FreeCAD import Mesh import MeshPart import Part import numpy import os from xml.etree.ElementTree import Element, SubElement import xml.etree.ElementTree as ElementTree import datetime from xml.dom import minidom from numpy.matrixlib.defmatrix import matrix from Utils import PVPlantUtils as utils if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP from DraftTools import translate else: def translate(ctxt, txt): return txt try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: import collada COLLADA_AVAILABLE = True except ImportError: COLLADA_AVAILABLE = False __title__ = "FreeCAD PVSyst importer" __author__ = "Javier Braña" #__url__ = "http://www.freecadweb.org" scale = 0.001 # from millimeters (FreeCAD) to meters (Collada) def check_collada(): """Verifica la disponibilidad de pycollada""" if not COLLADA_AVAILABLE: FreeCAD.Console.PrintError(translate("PVPlant", "pycollada no encontrado, soporte Collada desactivado.") + "\n") return COLLADA_AVAILABLE # Asegurar que el texto es Unicode válido def safe_text(text): if isinstance(text, bytes): return text.decode('utf-8', errors='replace') return text # from ARCH: def triangulate(shape): "triangulates the given face" p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") mesher = p.GetInt("ColladaMesher", 0) tessellation = p.GetFloat("ColladaTessellation", 1.0) grading = p.GetFloat("ColladaGrading", 0.3) segsperedge = p.GetInt("ColladaSegsPerEdge", 1) segsperradius = p.GetInt("ColladaSegsPerRadius", 2) secondorder = p.GetBool("ColladaSecondOrder", False) optimize = p.GetBool("ColladaOptimize", True) allowquads = p.GetBool("ColladaAllowQuads", False) 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 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 p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") scale = p.GetFloat("ColladaScalingFactor", 1.0) scale = scale * 0.001 # from millimeters (FreeCAD) to meters (Collada) p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View") c = p.GetUnsigned("DefaultShapeColor", 4294967295) defaultcolor = (float((c >> 24) & 0xFF) / 255.0, float((c >> 16) & 0xFF) / 255.0, float((c >> 8) & 0xFF) / 255.0) 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 = "PVPlant for FreeCAD" + ver[0] + "." + ver[1] + " build" + ver[2] + "\n" cont.authoring_tool = appli colmesh.assetInfo.contributors.append(cont) colmesh.assetInfo.unitname = "meter" colmesh.assetInfo.unitmeter = 1.0 defaultmat = None objind = 0 scenenodes = [] # TODO: cambiar lo de objeclist. Buscar los elementos que se necesitan exportar objectlist = Draft.get_group_contents(exportList, walls=True, addgroups=True) objectlist = Arch.pruneIncluded(objectlist) for obj in objectlist: 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: objectlist.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("PVScene", scenenodes) colmesh.scenes.append(myscene) colmesh.scene = myscene colmesh.write(filename) FreeCAD.Console.PrintMessage(translate("Arch", "file %s successfully created.") % filename) def exportToPVC(path, exportTerrain=False): filename = f"{path}.pvc" # 1. Validación inicial de objetos esenciales site = None for obj in FreeCAD.ActiveDocument.Objects: if obj.Name.startswith("Site") and hasattr(obj, 'Terrain'): site = obj break if not site: FreeCAD.Console.PrintError("No se encontró objeto 'Site' válido\n") return False # 2. Configuración de metadatos y autor generated_on = str(datetime.datetime.now()) try: author = FreeCAD.ActiveDocument.CreatedBy except (AttributeError, UnicodeEncodeError): author = "Unknown" author = author.replace("<", "").replace(">", "") ver = FreeCAD.Version() appli = f"PVPlant for FreeCAD {ver[0]}.{ver[1]} build{ver[2]}" # 3. Creación estructura XML base root = Element('COLLADA') root.set('xmlns', 'http://www.collada.org/2005/11/COLLADASchema') root.set('version', '1.4.1') # 4. Sección asset = SubElement(root, 'asset') contrib = SubElement(asset, 'contributor') SubElement(contrib, 'author').text = safe_text(author) SubElement(contrib, 'authoring_tool').text = safe_text(appli) SubElement(asset, 'created').text = generated_on SubElement(asset, 'modified').text = generated_on SubElement(asset, 'title').text = safe_text(FreeCAD.ActiveDocument.Name) unit = SubElement(asset, 'unit') unit.set('name', 'meter') unit.set('meter', '1') # 5. Materiales y efectos materials = ["Frames", "Tree_trunk", "Tree_crown", "Topography_mesh"] # Library materials lib_materials = SubElement(root, 'library_materials') for i, name in enumerate(materials): mat = SubElement(lib_materials, 'material') mat.set('id', f'Material{i}') mat.set('name', name) SubElement(mat, 'instance_effect').set('url', f'#Material{i}-fx') # Library effects lib_effects = SubElement(root, 'library_effects') for i, _ in enumerate(materials): effect = SubElement(lib_effects, 'effect') effect.set('id', f'Material{i}-fx') effect.set('name', f'Material{i}') profile = SubElement(effect, 'profile_COMMON') technique = SubElement(profile, 'technique') technique.set('sid', 'standard') lambert = SubElement(technique, 'lambert') # Componentes del material color = SubElement(SubElement(lambert, 'emission'), 'color') color.set('sid', 'emission') color.text = '0.000000 0.000000 0.000000 1.000000' color = SubElement(SubElement(lambert, 'ambient'), 'color') color.set('sid', 'ambient') color.text = '0.200000 0.200000 0.200000 1.000000' color = SubElement(SubElement(lambert, 'diffuse'), 'color') color.set('sid', 'diffuse') color.text = '0.250000 0.500000 0.000000 1.000000' transparent = SubElement(lambert, 'transparent') transparent.set('opaque', 'RGB_ZERO') color = SubElement(transparent, 'color') color.set('sid', 'transparent') color.text = '0.000000 0.000000 0.000000 1.000000' value = SubElement(SubElement(lambert, 'transparency'), 'float') value.set('sid', 'transparency') value.text = '0.000000' # 6. Geometrías lib_geometries = SubElement(root, 'library_geometries') # 7. Escena visual lib_visual = SubElement(root, 'library_visual_scenes') visual_scene = SubElement(lib_visual, 'visual_scene') visual_scene.set('id', 'Scene') # cambiar a visual_scene_0 visual_scene.set('name', 'Scene') # cambiar a Default visual scene scene_node = SubElement(visual_scene, 'node') scene_node.set('id', 'node_0_id') scene_node.set('name', 'node_0_name') scene_node.set('sid', 'node_0_sid') scene_matrix = SubElement(scene_node, 'matrix') scene_matrix.set('sid', 'matrix_0') scene_matrix.text = '1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000' root_node = SubElement(scene_node, 'node') root_node.set('id', 'node_1_id') root_node.set('name', 'node_1_name') root_node.set('sid', 'node_1_sid') # 8. Función para procesar geometrías def create_geometry(name, vindex, findex, material_id, objind=0, frame_data=None, isTracker = False, axis_line=None): """Crea elementos COLLADA para una geometría""" # Source (vertices) source_mesh = SubElement(geom, 'mesh') source = SubElement(source_mesh, 'source') source.set('id', f'{name}-mesh_source') float_array = SubElement(source, 'float_array') float_array.set('id', f'{name}-float_array') float_array.set('count', str(len(vindex))) float_array.text = ' '.join(f'{v:.6f}' for v in vindex) technique = SubElement(source, 'technique_common') accessor = SubElement(technique, 'accessor') accessor.set('count', str(len(vindex))) accessor.set('source', f'#{name}-float_array') accessor.set('stride', '3') for ax in ['X', 'Y', 'Z']: param = SubElement(accessor, 'param') param.set('name', ax) param.set('type', 'float') # Vertices vertices = SubElement(source_mesh, 'vertices') vertices.set('id', f'{name}-vertices_source') vertices = SubElement(vertices, 'input') vertices.set('semantic', 'POSITION') vertices.set('source', f'#{name}-mesh_source') # Triangles triangles = SubElement(source_mesh, 'triangles') triangles.set('count', '0') triangles.set('material', f'Material{material_id}') triangles_input = SubElement(triangles, 'input') triangles_input.set('offset', '0') triangles_input.set('semantic', 'VERTEX') triangles_input.set('source', f'#{name}-vertices_source') p = SubElement(triangles, 'p') p.text = ' '.join(map(str, findex)) # Parámetros especiales para estructuras frame_params = SubElement(source_mesh, 'tracker_parameters') if frame_data: for key, val in frame_data.items(): elem = SubElement(frame_params, key) elem.text = str(val) if isTracker: axis_parameter = SubElement(frame_params, 'axis_vertices') if axis_line: for idx, vert in enumerate(axis_line): array = SubElement(axis_parameter, 'float_array') array.set('id', f'{name}-axis_float_array{idx}') array.set('count', '3') array.text = ' '.join(f'{v:.6f}' for v in vert) # 9. Procesar estructuras (frames/trackers) center = FreeCAD.Vector() if site.Terrain: center = site.Terrain.Mesh.BoundBox.Center objind = 0 for frame_type in site.Frames: is_tracker = "tracker" in frame_type.Proxy.Type.lower() modules = frame_type.Shape.SubShapes[0].SubShapes[0] pts = [] for i in range(4): pts.append(modules.BoundBox.getPoint(i)) new_shape = Part.Face(Part.makePolygon(pts)) mesh = Mesh.Mesh(triangulate(new_shape)) axis = Part.makeLine(FreeCAD.Vector(modules.BoundBox.XMin, 0, modules.BoundBox.ZMax), FreeCAD.Vector(modules.BoundBox.XMax, 0, modules.BoundBox.ZMax)) for obj in FreeCAD.ActiveDocument.Objects: if hasattr(obj, "Setup") and obj.Setup == frame_type: # Procesar geometría mesh.Placement = obj.getGlobalPlacement() axis.Placement = obj.getGlobalPlacement() # Transformar vértices vindex = [] for point in mesh.Points: adjusted = (point.Vector - center) * scale vindex.extend([ -adjusted.x, adjusted.z, adjusted.y ]) # Índices de caras findex = [] for facet in mesh.Facets: findex.extend(facet.PointIndices) # AXIS # TODO: revisar si es así: vaxis = [] for vert in axis.Vertexes: adjusted = (vert.Point - center) * scale vaxis.append([ -adjusted.x, adjusted.z, adjusted.y ]) # Crear geometría COLLADA geom = SubElement(lib_geometries, 'geometry') geom.set('id', f'Frame_{objind}') # Parámetros específicos de estructura frame_data = { 'module_width': obj.Setup.ModuleWidth.Value, 'module_height': obj.Setup.ModuleHeight.Value, 'module_x_spacing': obj.Setup.ModuleColGap.Value, 'module_y_spacing': obj.Setup.ModuleRowGap.Value, 'module_name': 'Generic' } if is_tracker: frame_data.update({ 'tracker_type': 'single_axis_trackers', 'min_phi': obj.Setup.MinPhi.Value, 'max_phi': obj.Setup.MaxPhi.Value, 'min_theta': 0, 'max_theta': 0 }) create_geometry( name=f'Frame_{objind}', vindex=vindex, findex=findex, material_id=0, objind=objind, frame_data=frame_data, isTracker = is_tracker, axis_line=vaxis ) # Instancia en escena instance = SubElement(root_node, 'instance_geometry') instance.set('url', f'#Frame_{objind}') bind_material = SubElement(instance, 'bind_material') technique_common = SubElement(bind_material, 'technique_common') instance_material = SubElement(technique_common, 'instance_material') instance_material.set('symbol', 'Material0') instance_material.set('target', '#Material0') objind += 1 # 10. Procesar terreno si está habilitado if exportTerrain and site.Terrain: mesh = site.Terrain.Mesh vindex = [] for point in mesh.Points: point = point.Vector vindex.extend([ -point.x * SCALE, point.z * SCALE, point.y * SCALE ]) findex = [] for facet in mesh.Facets: findex.extend(facet.PointIndices) geom = SubElement(lib_geometries, 'geometry') geom.set('id', 'Terrain') create_geometry('Terrain', vindex, findex, material_id=3) instance = SubElement(root_node, 'instance_geometry') instance.set('url', '#Terrain') # 11. Escena principal scene = SubElement(root, 'scene') SubElement(scene, 'instance_visual_scene').set('url', '#Scene') # 12. Exportar a archivo xml_str = minidom.parseString( ElementTree.tostring(root, encoding='utf-8') ).toprettyxml(indent=" ") with open(filename, 'w', encoding='utf-8') as f: f.write(xml_str) FreeCAD.Console.PrintMessage(f"Archivo PVC generado: {filename}\n") return True def exportToPVC_old(path, exportTerrain = False): filename = f"{path}.pvc" from xml.etree.ElementTree import Element, SubElement import datetime generated_on = str(datetime.datetime.now()) site = None tmp = FreeCAD.ActiveDocument.getObjectsByLabel('Site') for obj in tmp: if obj.Name.startswith("Site"): site = obj break terrain = None center = None if not(site.Terrain is None): terrain = site.Terrain.Mesh center = terrain.BoundBox.Center else: center = FreeCAD.Vector() try: author = FreeCAD.ActiveDocument.CreatedBy except UnicodeEncodeError: author = FreeCAD.ActiveDocument.CreatedBy.encode("utf8") author = author.replace("<", "") author = author.replace(">", "") ver = FreeCAD.Version() appli = "PVPlant for FreeCAD" + ver[0] + "." + ver[1] + " build" + ver[2] + "\n" # xml: Configure one attribute with set() root = Element('COLLADA') root.set('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema') root.set('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance') root.set('version', '1.4.1') root.set('xmlns', 'http://www.collada.org/2005/11/COLLADASchema') #root.append(Comment('Generated by ElementTree_csv_to_xml.py for PyMOTW')) # xml: 1. Asset: asset = SubElement(root, 'asset') asset_contributor = SubElement(asset, 'contributor') asset_contributor_autor = SubElement(asset_contributor, 'author') asset_contributor_autor.text = author asset_contributor_authoring_tool = SubElement(asset_contributor, 'authoring_tool') asset_contributor_authoring_tool.text = appli asset_contributor_comments = SubElement(asset_contributor, 'comments') asset_keywords = SubElement(asset, 'keywords') asset_revision = SubElement(asset, 'revision') asset_subject = SubElement(asset, 'subject') asset_tittle = SubElement(asset, 'title') asset_tittle.text = FreeCAD.ActiveDocument.Name asset_unit = SubElement(asset, 'unit') asset_unit.set('meter', '0.001') asset_unit.set('name', 'millimeter') # xml: 2. library_materials: library_materials = SubElement(root, 'library_materials') buf = ['Frames', 'Tree_trunk', 'Tree_crown', 'Topography_mesh'] for i in range(0, len(buf)): material = SubElement(library_materials, 'material') material.set('id', 'Material{0}'.format(i)) material.set('name', buf[i]) material_effect = SubElement(material, 'instance_effect') material_effect.set('url', '#Material{0}-fx'.format(i)) # xml: 3. library_effects: library_effects = SubElement(root, 'library_effects') buf = ['0.250000 0.500000 0.000000 1.000000', '0.500000 0.375000 0.250000 1.000000', '0.250000 1.000000 0.000000 1.000000', '0.250000 1.000000 0.000000 1.000000'] for i in range(0, len(buf)): effect = SubElement(library_effects, 'effect') effect.set('id', 'Material{0}-fx'.format(i)) effect.set('name', 'Material{0}'.format(i)) profile_COMMON = SubElement(effect, 'profile_COMMON') library_effects_effect_technique = SubElement(profile_COMMON, 'technique') library_effects_effect_technique.set('sid', 'standard') library_effects_effect_technique_lambert = SubElement(library_effects_effect_technique, 'lambert') library_effects_effect_technique_lambert_emission = SubElement(library_effects_effect_technique_lambert, 'emission') library_effects_effect_technique_lambert_emission_color = SubElement( library_effects_effect_technique_lambert_emission, 'color') library_effects_effect_technique_lambert_emission_color.set('sid', 'emission') library_effects_effect_technique_lambert_emission_color.text = '0.000000 0.000000 0.000000 1.000000' ambient = SubElement(library_effects_effect_technique_lambert, 'ambient') ambient_color = SubElement(ambient, 'color') ambient_color.set('sid', 'ambient') ambient_color.text = '0.200000 0.200000 0.200000 1.000000' diffuse = SubElement(library_effects_effect_technique_lambert, 'diffuse') diffuse_color = SubElement(diffuse, 'color') diffuse_color.set('sid', 'diffuse') diffuse_color.text = buf[i] transparent = SubElement(library_effects_effect_technique_lambert, 'transparent') transparent.set('opaque', 'RGB_ZERO') transparent_color = SubElement(transparent, 'color') transparent_color.set('sid', 'transparent') transparent_color.text = '0.000000 0.000000 0.000000 1.000000' transparency = SubElement(library_effects_effect_technique_lambert, 'transparency') transparency_value = SubElement(transparency, 'float') transparency_value.set('sid', 'transparency') transparency_value.text = '0' # xml: 4. library_geometries: library_geometries = SubElement(root, 'library_geometries') def add_geometry(objtype, vindex, findex, objind = 0, centers = None): isFrame = False if objtype == 0: geometryName = 'Frame' referenceSTR = 'frame' isFrame = True elif objtype == 1: geometryName = 'ShadowMesh' ## --> ??? referenceSTR = 'ShadowMesh' ## --> ??? elif objtype == 2: geometryName = 'TerrainMesh' referenceSTR = 'TerrainMesh' geometry = SubElement(library_geometries, 'geometry') geometry.set('id', geometryName + '{0}'.format(objind)) mesh = SubElement(geometry, 'mesh') source = SubElement(mesh, 'source') source.set('id', referenceSTR + '{0}MeshSource'.format(objind)) float_array = SubElement(source, 'float_array') float_array.set('id', referenceSTR + '{0}FloatArray'.format(objind)) float_array.set('count', '{0}'.format(len(vindex))) float_array.text = "" # vindex for ver in vindex: if len(float_array.text) > 0: float_array.text += ' ' float_array.text += '{0:.6f}'.format(ver) technique_common = SubElement(source, 'technique_common') accessor = SubElement(technique_common, 'accessor') accessor.set('count', '{0}'.format(len(vindex))) accessor.set('source', '#' + referenceSTR + '{0}FloatArray'.format(objind)) accessor.set('stride', '3') param = SubElement(accessor, 'param') param.set('name', 'X') param.set('type', 'float') param = SubElement(accessor, 'param') param.set('name', 'Y') param.set('type', 'float') param = SubElement(accessor, 'param') param.set('name', 'Z') param.set('type', 'float') vertices = SubElement(mesh, 'vertices') vertices.set('id', referenceSTR + '{0}VerticesSource'.format(objind)) input = SubElement(vertices, "input") input.set('semantic', 'POSITION') input.set('source', '#' + referenceSTR + '{0}MeshSource'.format(objind)) triangles = SubElement(mesh, 'triangles') triangles.set('count', '0') triangles.set('material', 'Material0') input = SubElement(triangles, "input") input.set('offset', '0') input.set('semantic', 'VERTEX') input.set('source', '#' + referenceSTR + '{0}VerticesSource'.format(objind)) p = SubElement(triangles, "p") p.text = '' for f in findex: if len(p.text) > 0: p.text += ' ' p.text += '{0}'.format(f) if isFrame: frame = SubElement(mesh, 'tracker_parameters' if isTracker else 'frame_parameters') module_width = SubElement(frame, "module_width") module_width.text = '{0}'.format(int(obj.Setup.ModuleWidth.Value)) module_height = SubElement(frame, "module_height") module_height.text = '{0}'.format(int(obj.Setup.ModuleHeight.Value)) module_x_spacing = SubElement(frame, "module_x_spacing") module_x_spacing.text = '{0}'.format(int(obj.Setup.ModuleColGap.Value)) module_y_spacing = SubElement(frame, "module_y_spacing") module_y_spacing.text = '{0}'.format(int(obj.Setup.ModuleRowGap.Value)) module_manufacturer = SubElement(frame, "module_manufacturer") module_manufacturer.text = 'generic' module_name = SubElement(frame, "module_name") module_name.text = 'generic' if isTracker: tracker_type = SubElement(frame, 'tracker_type') tracker_type.text = 'single_axis_trackers' axis = SubElement(frame, 'axis_vertices') for ind in range(0, len(centers)): array = SubElement(axis, 'float_array') array.set('id', 'tracker{0}AxisFloatArray1'.format(ind)) array.set('count', '3') array.text = '{0:.6f} {1:.6f} {2:.6f}'.format(centers[ind].x, centers[ind].y, centers[ind].z) min_phi = SubElement(frame, 'min_phi') min_phi.text = '{0}'.format(int(obj.Setup.MinPhi.Value)) max_phi = SubElement(frame, 'max_phi') max_phi.text = '{0}'.format(int(obj.Setup.MaxPhi.Value)) min_theta = SubElement(frame, 'min_theta') min_theta.text = '{0}'.format(0) max_theta = SubElement(frame, 'max_theta') max_theta.text = '{0}'.format(0) # xml: 5. library_visual_scenes: instance_geometry = SubElement(node, 'instance_geometry') instance_geometry.set('url', geometryName + '{0}'.format(objind)) bind_material = SubElement(instance_geometry, 'bind_material') technique_common = SubElement(bind_material, 'technique_common') instance_material = SubElement(technique_common, 'instance_material') instance_material.set('symbol', 'Material0') instance_material.set('target', '#Material0') instance_material = SubElement(technique_common, 'instance_material') instance_material.set('symbol', 'Material1') instance_material.set('target', '#Material1') instance_material = SubElement(technique_common, 'instance_material') instance_material.set('symbol', 'Material2') instance_material.set('target', '#Material2') # xml: 5. library_visual_scenes: library_visual_scenes = SubElement(root, 'library_visual_scenes') visual_scene = SubElement(library_visual_scenes, 'visual_scene') visual_scene.set('id', '') visual_scene.set('name', '') node = SubElement(visual_scene, 'node') node.set('id', 'Fbx_Root') node.set('name', 'Fbx_Root') node.set('sid', 'Fbx_Root') matrix = SubElement(node, 'matrix') matrix.set('sid', 'matrix') matrix.text = '1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000' node = SubElement(node, 'node') node.set('id', 'PVcase52') node.set('name', 'PVcase52') node.set('sid', 'PVcase52') matrix = SubElement(node, 'matrix') matrix.set('sid', 'matrix') matrix.text = '1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000' extra = SubElement(visual_scene, 'extra') technique = SubElement(extra, 'technique') technique.set('profile', 'MAX3D') frame_rate = SubElement(technique, 'frame_rate') frame_rate.text = '30.000000' technique = SubElement(extra, 'technique') technique.set('profile', 'FCOLLADA') start_time = SubElement(technique, 'start_time') start_time.text = '0.000000' end_time = SubElement(technique, 'end_time') end_time.text = '1.000000' # xml: 6. scene: '''scene = SubElement(root, 'scene') instance = SubElement(scene, 'instance_visual_scene') instance.set('url', '#')''' # CASO 1 - FRAMES: frameType = site.Frames objind = 0 # TODO: revisar for typ in frameType: isTracker = "tracker" in typ.Proxy.Type.lower() #isTracker = False objectlist = utils.findObjects("Tracker") for obj in objectlist: if obj.Setup == typ: findex = numpy.array([]) modules = obj.Setup.Shape.SubShapes[0].SubShapes[0] pts = [] for i in range(4): pts.append(modules.BoundBox.getPoint(i)) # temp ----- if obj.Tilt.Value != 0: zz = int(pts[0].z) - (obj.Setup.MainBeamHeight.Value / 2 + obj.Setup.BeamHeight.Value) for p in pts: p.z -= zz new_shape = Part.Face(Part.makePolygon(pts)) new_shape.Placement.Rotation.setEulerAngles("XYZ", obj.Tilt.Value, 0, 0) pts = [] for ver in new_shape.Vertexes: p = ver.Point p.z += zz pts.append(p) # end temp ----- new_shape = Part.Face(Part.makePolygon(pts)) new_shape.Placement = obj.Placement.copy() m = Mesh.Mesh(triangulate(new_shape)) centers = [] if isTracker: minLengths = [] for ed in new_shape.Edges: minLengths.append(ed.Length) minLength = min(minLengths) for ed in new_shape.Edges: if ed.Length == minLength: centers.append(ed.CenterOfMass) if m: Topology = m.Topology # 1. 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 - center.x) * scale, (v.z - center.z) * scale, (v.y - center.y) * scale) # 2. face indices findex = numpy.empty(len(Topology[1]) * 3, numpy.int64) for i in range(len(Topology[1])): f = Topology[1][i] findex[list(range(i * 3, i * 3 + 3))] = (f[0], f[1], f[2]) add_geometry(0, vindex, findex, objind, centers if isTracker else None) objind += 1 # CASE 2: Shadow objects #objectlist = FreeCAD.ActiveDocument.findObjects(Label = "Tree") # TODO: Cambiar label por name objectlist=[] for obj in FreeCAD.ActiveDocument.Objects: if obj.Name.startswith("Tracker"): continue else: objectlist.append(obj) objind = 0 for obj in objectlist: findex = numpy.array([]) m = None if obj.isDerivedFrom("Part::Feature"): new_shape = obj.Shape.copy() new_shape.Placement = obj.getGlobalPlacement() m = Mesh.Mesh(triangulate(new_shape)) elif obj.isDerivedFrom("Mesh::Feature"): m = obj.Mesh elif obj.isDerivedFrom("App::Part"): for child in obj.OutList: objectlist.append(child) continue else: continue if m: Topology = m.Topology # 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 - center.x) * scale, (v.z - center.z) * scale, (v.y - center.y) * scale) # face indices findex = numpy.empty(len(Topology[1]) * 3, numpy.int64) for i in range(len(Topology[1])): f = Topology[1][i] findex[list(range(i * 3, i * 3 + 3))] = (f[0], f[1], f[2]) add_geometry(1, vindex, findex, objind) objind += 1 # CASE 3: Terrain # TODO: ver si se puede partir en varias mesh para que trabaje más rápido if exportTerrain: m = terrain 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.z * scale, v.y * scale) # face indices findex = numpy.empty(len(Topology[1]) * 3, numpy.int64) for i in range(len(Topology[1])): f = Topology[1][i] findex[list(range(i * 3, i * 3 + 3))] = (f[0], f[1], f[2]) add_geometry(2, vindex, findex) # xml: 5. library_visual_scenes: ¿¿¿¿¿??????? ''' instance_geometry = SubElement(node, 'instance_geometry') instance_geometry.set('url', 'TerrainMesh{0}'.format(0)) bind_material = SubElement(instance_geometry, 'bind_material') technique_common = SubElement(bind_material, 'technique_common') instance_material = SubElement(technique_common, 'instance_material') instance_material.set('symbol', 'Material0') instance_material.set('target', '#Material0') instance_material = SubElement(technique_common, 'instance_material') instance_material.set('symbol', 'Material1') instance_material.set('target', '#Material1') instance_material = SubElement(technique_common, 'instance_material') instance_material.set('symbol', 'Material2') instance_material.set('target', '#Material2') ''' extra = SubElement(node, 'extra') technique = SubElement(extra, 'technique') technique.set('profile', 'FCOLLADA') visibility = SubElement(technique, 'visibility') visibility.text = '1.000000' # save the file: st = prettify(root) #print(st) f = open(filename, "w") f.write(st) f.close() FreeCAD.Console.PrintMessage("Se ha generado correctamente el archivo PVC: ", filename) return True def prettify(elem): """ Return a pretty-printed XML string for the Element. """ from xml.etree import ElementTree from xml.dom import minidom rough_string = ElementTree.tostring(elem, 'utf-8') reparsed = minidom.parseString(rough_string) return reparsed.toprettyxml(indent=" ") def exportToH2P(path): # sólo válido para mesas filename = path + ".h2p" f2 = '{:.2f}' f3 = '{:.3f}' st = 'START\n' # TODO: hacer un bucle para cada tipo-tamaño de estructura. # posible solucción: un primer bucle para identificar los tipos-tamaños de estructura #FreeCAD.ActiveDocument.findObjects #FreeCAD.ActiveDocument.Objects objects = FreeCAD.ActiveDocument.findObjects(Name="Tracker") grouptype = [] #for obj in objects: grouptype.append(objects[0]) for typ in grouptype: st += 'TABLE\n' \ '10\n' st += f3.format(typ.Width.Value) + ',' + f3.format(typ.Length.Value) + ',' + \ f3.format(0) + ',' + f3.format(0) + ',' + f3.format(0) + ',' + f3.format(0) + "\n" #'#{ f3 %pvsyst.ilb.to_mm },#{f3 %pvsyst.irb.to_mm},#{f3 %pvsyst.itb.to_mm},' \ #'#{f3 %pvsyst.ibb.to_mm}\n' st += '20\n' st += str(int(typ.ModulesCols.Value)) + ',' + str(int(typ.ModulesRows.Value)) + ',' + \ str(typ.ModuleColGap.Value) + ',' + str(typ.ModuleRowGap.Value) + ',' + '30\n' st += '30\n' st += '1,' + f3.format(typ.ModuleWidth.Value) + ',' + f3.format(typ.ModuleHeight.Value) + ',' + \ f3.format(typ.ModuleThick.Value) + ',' + f2.format(450) + '\n' #f2.format(typ.ModulePower.Value) + '\n' # cornerdown = find_component_sizes(group.cdef)[1] # pvorigin = Geom::Point3d.new(cornerdown.x, cornerdown.y, 0) # group.instances.each{ | ins | str += pvsyst_insert(ins, pvorigin)} for obj in objects: if obj.CloneOf == typ: st += H2PInsert(obj) ## TODO: Bucle para buscar objetos que den sombra y el terreno. Todos llaman a H2PMesh mesh = FreeCAD.ActiveDocument.getObjectsByLabel('Surface')[0].Mesh st += H2PMesh(mesh, False) st += "END\n" # save the file: f = open(filename, "w") f.write(st) f.close() FreeCAD.Console.PrintMessage("Se ha generado el archivo PVC: ", filename) return True def H2PInsert(obj): f3 = '{:.3f}' f2 = '{:.2f}' scale = 0.001 ## ver como se puede hacer para que sea general. Pasar de mm a m st = 'INSERT\n' \ '10\n' st += f3.format(obj.Placement.Base.x * scale) + ',' + f3.format(obj.Placement.Base.y * scale) + ',' + \ f3.format((obj.Placement.Base.z + obj.PoleLength.Value - obj.RammingDeep.Value + 1000) * scale) + '\n' st += '50\n' st += f2.format(-obj.Placement.Rotation.toEuler()[0]) + '\n' st += '55\n' st += f2.format(obj.Placement.Rotation.toEuler()[1]) + '\n' st += '56\n' st += f2.format(obj.Placement.Rotation.toEuler()[2]) + '\n' return st def H2PMesh(mesh, typ): scale = 0.001 ## ver como se puede hacer para que sea general. Pasar de mm a m f3 = '{:.3f}' st = '' if typ: st = 'ShadowObject\nFence\n' else: st = 'DGM\n' for face in mesh.Facets: p1 = face.Points[0] p2 = face.Points[1] p3 = face.Points[2] st += f3.format(p1[0] * scale) + "," + f3.format(p1[1] * scale) + "," + f3.format(p1[2] * scale) + ";" st += f3.format(p2[0] * scale) + "," + f3.format(p2[1] * scale) + "," + f3.format(p2[2] * scale) + ";" st += f3.format(p3[0] * scale) + "," + f3.format(p3[1] * scale) + "," + f3.format(p3[2] * scale) + "\n" return st class PVSystTaskPanel: def __init__(self): self.form = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__) + "/exportPVSyst.ui") self.form = self.form def show(self): # self.form.setWindowModality(Qt.WindowModal) self.form.show() def accept(self): import datetime x = datetime.datetime.now() date = x.strftime("%Y%m%d%H%M%S") overwrite = True path = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "PVSyst") if not os.path.exists(path): os.makedirs(path) name = FreeCAD.ActiveDocument.Label #if not overwrite: name = date + "-" + name filename = os.path.join(path, name) if self.form.cbPVC.isChecked(): exportToPVC(filename, self.form.cbTerrain.isChecked()) if self.form.cbH2P.isChecked(): exportToH2P(filename) FreeCADGui.Control.closeDialog() return True def reject(self): FreeCADGui.Control.closeDialog() return True '''class _CommandExportToPVSyst: "Export to PVSyst" def GetResources(self): return {'Pixmap': str(os.path.join(DirIcons, "PVsyst.png")), 'Accel': "E, P", 'MenuText': QT_TRANSLATE_NOOP("Outputs", "Export to PVSyst"), 'ToolTip': QT_TRANSLATE_NOOP("Outputs", "Exportar a PVSyst")} def Activated(self): taskd = _PVSystTaskPanel() # taskd.show() FreeCADGui.Control.showDialog(taskd) def IsActive(self): if FreeCAD.ActiveDocument: return True else: return False if FreeCAD.GuiUp: FreeCADGui.addCommand('ExportToPVSyst', _CommandExportToPVSyst())'''