From 3a188cc47d42cef62b121e6dcce4e3f0ba24a0f7 Mon Sep 17 00:00:00 2001 From: Javi Date: Sun, 17 Aug 2025 13:33:17 +0400 Subject: [PATCH] new code --- Export/exportPVSyst.py | 350 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 323 insertions(+), 27 deletions(-) diff --git a/Export/exportPVSyst.py b/Export/exportPVSyst.py index 9368f24..2828185 100644 --- a/Export/exportPVSyst.py +++ b/Export/exportPVSyst.py @@ -29,6 +29,15 @@ 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 @@ -63,6 +72,11 @@ def check_collada(): 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): @@ -249,7 +263,306 @@ def export(exportList, filename, tessellation=1, colors=None): FreeCAD.Console.PrintMessage(translate("Arch", "file %s successfully created.") % filename) -def exportToPVC(path, exportTerrain = False): +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 @@ -291,17 +604,18 @@ def exportToPVC(path, exportTerrain = False): # xml: 1. Asset: asset = SubElement(root, 'asset') + asset_contributor = SubElement(asset, 'contributor') - asset_contributor_autor = SubElement(asset_contributor, 'autor') - #asset_contributor_autor.text = author + 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_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_tittle.text = FreeCAD.ActiveDocument.Name asset_unit = SubElement(asset, 'unit') asset_unit.set('meter', '0.001') asset_unit.set('name', 'millimeter') @@ -359,7 +673,6 @@ def exportToPVC(path, exportTerrain = False): # 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' @@ -505,36 +818,20 @@ def exportToPVC(path, exportTerrain = False): end_time.text = '1.000000' # xml: 6. scene: - scene = SubElement(root, 'scene') + '''scene = SubElement(root, 'scene') instance = SubElement(scene, 'instance_visual_scene') - instance.set('url', '#') - - full_list_of_objects = FreeCAD.ActiveDocument.Objects + instance.set('url', '#')''' # CASO 1 - FRAMES: frameType = site.Frames - frame_setup = {"type": [], - "footprint": []} - for obj in frameType: - frame_setup["type"] = obj - frame_setup["footprint"] = "" - objind = 0 # TODO: revisar for typ in frameType: isTracker = "tracker" in typ.Proxy.Type.lower() - isTracker = False - - objectlist = FreeCAD.ActiveDocument.findObjects(Name="Tracker") - tmp = [] - for obj in objectlist: - if obj.Name.startswith("TrackerSetup"): - continue - else: - tmp.append(obj) - objectlist = tmp.copy() + #isTracker = False + objectlist = utils.findObjects("Tracker") for obj in objectlist: if obj.Setup == typ: findex = numpy.array([]) @@ -580,7 +877,6 @@ def exportToPVC(path, exportTerrain = False): 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])):