diff --git a/Export/exportDXF.py b/Export/exportDXF.py index c2d5ce6..cc3ded7 100644 --- a/Export/exportDXF.py +++ b/Export/exportDXF.py @@ -971,7 +971,8 @@ class _PVPlantExportDXF(QtGui.QWidget): if FreeCAD.ActiveDocument.Transport: for road in FreeCAD.ActiveDocument.Transport.Group: base = exporter.createPolyline(road, "CIVIL External Roads") - base.dxf.const_width = road.Width + if hasattr(road, 'Width'): + base.dxf.const_width = road.Width axis = exporter.createPolyline(road, "CIVIL External Roads Axis") axis.dxf.const_width = .2 diff --git a/Importer/importOSM.py b/Importer/importOSM.py index 05421b6..bfaf682 100644 --- a/Importer/importOSM.py +++ b/Importer/importOSM.py @@ -43,53 +43,74 @@ class OSMImporter: self.ssl_context = ssl.create_default_context(cafile=certifi.where()) def transform_from_latlon(self, coordinates): + """Transforma coordenadas lat/lon a coordenadas FreeCAD""" + if not coordinates: + return [] + points = ImportElevation.getElevationFromOE(coordinates) pts = [FreeCAD.Vector(p.x, p.y, p.z).sub(self.Origin) for p in points] return pts def get_osm_data(self, bbox): - query = f""" - [out:xml][bbox:{bbox}]; - ( - way["building"]; - way["highway"]; - way["railway"]; - way["power"="line"]; - way["power"="substation"]; - way["natural"="water"]; - way["landuse"="forest"]; - node["natural"="tree"]; - ); - (._;>;); - out body; - """ - req = urllib.request.Request( - self.overpass_url, - data=query.encode('utf-8'), - headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'}, - method='POST' - ) - return urllib.request.urlopen(req, context=self.ssl_context, timeout=160).read() + """ Obtiene datos de OpenStreetMap """ + # Modificar la consulta en get_osm_data para incluir más tipos de agua: + query = f"""[out:xml][bbox:{bbox}]; + ( + way["building"]; + way["highway"]; + way["railway"]; + way["power"="line"]; + way["power"="substation"]; + way["natural"="water"]; + way["waterway"]; + way["waterway"="river"]; + way["waterway"="stream"]; + way["waterway"="canal"]; + way["landuse"="basin"]; + way["landuse"="reservoir"]; + node["natural"="tree"]; + way["landuse"="forest"]; + way["landuse"="farmland"]; + ); + (._;>;); + out body; + """ + try: + req = urllib.request.Request( + self.overpass_url, + data=query.encode('utf-8'), + #headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'}, + method='POST' + ) + + response = urllib.request.urlopen(req, context=self.ssl_context, timeout=160) + return response.read() + except Exception as e: + print(f"Error obteniendo datos OSM: {str(e)}") + return None def create_layer(self, name): + """Crea o obtiene una capa en el documento""" if not FreeCAD.ActiveDocument.getObject(name): return FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name) return FreeCAD.ActiveDocument.getObject(name) def process_osm_data(self, osm_data): + """Procesa los datos XML de OSM""" + if not osm_data: + print("No hay datos OSM para procesar") + return + root = ET.fromstring(osm_data) + # Primero, recolectar todos los nodos + print(f"Procesando {len(root.findall('node'))} nodos...") + # Almacenar nodos transformados coordinates = [[float(node.attrib['lat']), float(node.attrib['lon'])] for node in root.findall('node')] coordinates = self.transform_from_latlon(coordinates) for i, node in enumerate(root.findall('node')): - self. nodes[node.attrib['id']] = coordinates[i] - '''return - for node in root.findall('node'): - self.nodes[node.attrib['id']] = self.transform_from_latlon( - float(node.attrib['lat']), - float(node.attrib['lon']) - )''' + self.nodes[node.attrib['id']] = coordinates[i] # Procesar ways for way in root.findall('way'): @@ -166,7 +187,7 @@ class OSMImporter: def create_buildings(self): building_layer = self.create_layer("Buildings") for way_id, data in self.ways_data.items(): - print(data) + #print(data) if 'building' not in data['tags']: continue @@ -226,11 +247,11 @@ class OSMImporter: nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes] if 'power' in tags: - print("\n\n") - print(tags) + #print("\n\n") + #print(tags) feature_type = tags['power'] if feature_type == 'line': - print("3.1. Create Power Lines") + #print("3.1. Create Power Lines") FreeCADGui.updateGui() self.create_power_line( nodes=nodes, @@ -239,7 +260,7 @@ class OSMImporter: ) elif feature_type == 'substation': - print("3.1. Create substations") + #print("3.1. Create substations") FreeCADGui.updateGui() self.create_substation( way_id=way_id, @@ -249,7 +270,7 @@ class OSMImporter: ) elif feature_type == 'tower': - print("3.1. Create power towers") + #print("3.1. Create power towers") FreeCADGui.updateGui() self.create_power_tower( position=nodes[0] if nodes else None, @@ -562,13 +583,15 @@ class OSMImporter: if polygon_points[0] != polygon_points[-1]: polygon_points.append(polygon_points[0]) + # 3. Base del terreno base_height = 0.3 try: base_shape = Part.makePolygon(polygon_points) base_face = Part.Face(base_shape) base_extrude = base_face.extrude(FreeCAD.Vector(0, 0, base_height)) - base_obj = layer.addObject("Part::Feature", f"{name}_Base") + base_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"{name}_Base") + layer.addObject(base_obj) base_obj.Shape = base_extrude base_obj.ViewObject.ShapeColor = (0.2, 0.2, 0.2) except Exception as e: @@ -583,7 +606,8 @@ class OSMImporter: fence_shape = Part.makePolygon(fence_points) fence_face = Part.Face(fence_shape) fence_extrude = fence_face.extrude(FreeCAD.Vector(0, 0, 2.8)) - fence_obj = layer.addObject("Part::Feature", f"{name}_Fence") + fence_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"{name}_Fence") + layer.addObject(fence_obj) fence_obj.Shape = fence_extrude fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4) except Exception as e: @@ -599,14 +623,15 @@ class OSMImporter: building_shape = Part.makePolygon(building_points) building_face = Part.Face(building_shape) building_extrude = building_face.extrude(FreeCAD.Vector(0, 0, building_height)) - building_obj = layer.addObject("Part::Feature", f"{name}_Building") + building_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"{name}_Building") + layer.addObject(building_obj) building_obj.Shape = building_extrude building_obj.ViewObject.ShapeColor = (0.7, 0.7, 0.7) except Exception as e: FreeCAD.Console.PrintWarning(f"Error edificio {way_id}: {str(e)}\n") # 6. Transformadores - try: + '''try: num_transformers = int(tags.get('transformers', 1)) for i in range(num_transformers): transformer_pos = self.calculate_equipment_position( @@ -618,11 +643,11 @@ class OSMImporter: transformer = self.create_transformer( position=transformer_pos, voltage=voltage, - tech_type=tags.get('substation:type', 'outdoor') + technology=tags.get('substation:type', 'outdoor') ) layer.addObject(transformer) except Exception as e: - FreeCAD.Console.PrintWarning(f"Error transformadores {way_id}: {str(e)}\n") + FreeCAD.Console.PrintWarning(f"Error transformadores {way_id}: {str(e)}\n")''' # 7. Torre de seccionamiento para alta tensión if substation_type == 'transmission' and voltage >= 110000: @@ -637,7 +662,8 @@ class OSMImporter: FreeCAD.Console.PrintWarning(f"Error torre {way_id}: {str(e)}\n") # 8. Propiedades técnicas - substation_data = layer.addObject("App::FeaturePython", f"{name}_Data") + substation_data = FreeCAD.ActiveDocument.addObject("App::FeaturePython", f"{name}_Data") + layer.addObject(substation_data) props = { "Voltage": voltage, "Type": substation_type, @@ -651,7 +677,8 @@ class OSMImporter: else: substation_data.addProperty( "App::PropertyFloat" if isinstance(value, float) else "App::PropertyString", - prop, "Technical").setValue(value) + prop, "Technical") + setattr(substation_data, prop, value) except Exception as e: FreeCAD.Console.PrintError(f"Error crítico en subestación {way_id}: {str(e)}\n") @@ -900,9 +927,9 @@ class OSMImporter: if face.isInside(rand_point, 0.1, True): return rand_point - def create_water_bodies(self): + def create_water_bodies_old(self): water_layer = self.create_layer("Water") - + print(self.ways_data) for way_id, data in self.ways_data.items(): if 'natural' in data['tags'] and data['tags']['natural'] == 'water': nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes] @@ -915,3 +942,80 @@ class OSMImporter: water.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))# * scale - self.Origin ) water.ViewObject.ShapeColor = self.feature_colors['water'] + def create_water_bodies(self): + + water_layer = self.create_layer("Water") + + for way_id, data in self.ways_data.items(): + tags = data['tags'] + nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes] + + if len(nodes) < 2: + continue + + # ===== 1) RÍOS / CANALES (líneas) ===== + name = self.get_osm_name(tags, tags["waterway"]) + if 'waterway' in tags: + if len(nodes) < 2: + continue + + try: + width = self.parse_width(tags, default=2.0) + points = [FreeCAD.Vector(n.x, n.y, n.z) for n in nodes] + wire = Draft.make_wire(points, closed=False, face=False) + wire.Label = f"{name} ({tags['waterway']})" + + wire.ViewObject.LineWidth = max(1, int(width * 0.5)) + wire.ViewObject.ShapeColor = self.feature_colors['water'] + + water_layer.addObject(wire) + + except Exception as e: + print(f"Error creando waterway {way_id}: {e}") + + continue # importante + + # ===== 2) LAGOS / EMBALSES (polígonos) ===== + is_area_water = ( + tags.get('natural') == 'water' or + tags.get('landuse') in ['reservoir', 'basin'] or + tags.get('water') is not None + ) + + if not is_area_water or len(nodes) < 3: + continue + + try: + polygon_points = [FreeCAD.Vector(n.x, n.y, n.z) for n in nodes] + + if polygon_points[0] != polygon_points[-1]: + polygon_points.append(polygon_points[0]) + + polygon = Part.makePolygon(polygon_points) + face = Part.Face(polygon) + + water = FreeCAD.ActiveDocument.addObject("Part::Feature", f"Water_{way_id}") + water.Shape = face + water.ViewObject.ShapeColor = self.feature_colors['water'] + water.Label = f"{name} ({tags['waterway']})" + + water_layer.addObject(water) + + except Exception as e: + print(f"Error creando área de agua {way_id}: {e}") + + def get_osm_name(self, tags, fallback=""): + for key in ["name", "name:es", "name:en", "alt_name", "ref"]: + if key in tags and tags[key].strip(): + return tags[key] + return fallback + + def parse_width(self, tags, default=2.0): + for key in ["width", "est_width"]: + if key in tags: + try: + w = tags[key].replace("m", "").strip() + return float(w) + except: + pass + return default diff --git a/InitGui.py b/InitGui.py index f5afc97..44a4e1a 100644 --- a/InitGui.py +++ b/InitGui.py @@ -39,9 +39,11 @@ class PVPlantWorkbench(Workbench): ToolTip = "Workbench for PV design" Icon = str(os.path.join(DirIcons, "icon.svg")) + def __init__(self): + ''' init ''' + def Initialize(self): - #sys.path.append(r"C:\Users\javie\AppData\Roaming\FreeCAD\Mod") sys.path.append(os.path.join(FreeCAD.getUserAppDataDir(), 'Mod')) import PVPlantTools, reload @@ -144,7 +146,10 @@ class PVPlantWorkbench(Workbench): from widgets import CountSelection def Activated(self): - "This function is executed when the workbench is activated" + """This function is executed when the workbench is activated""" + + FreeCAD.Console.PrintLog("Road workbench activated.\n") + import SelectionObserver import FreeCADGui @@ -153,7 +158,9 @@ class PVPlantWorkbench(Workbench): return def Deactivated(self): - "This function is executed when the workbench is deactivated" + """This function is executed when the workbench is deactivated""" + + FreeCAD.Console.PrintLog("Road workbench deactivated.\n") #FreeCADGui.Selection.removeObserver(self.observer) return @@ -201,4 +208,4 @@ class PVPlantWorkbench(Workbench): return "Gui::PythonWorkbench" -Gui.addWorkbench(PVPlantWorkbench()) +FreeCADGui.addWorkbench(PVPlantWorkbench()) diff --git a/PVPlantGeoreferencing.py b/PVPlantGeoreferencing.py index 064670a..7885931 100644 --- a/PVPlantGeoreferencing.py +++ b/PVPlantGeoreferencing.py @@ -265,7 +265,7 @@ class MapWindow(QtGui.QWidget): for item in items['features']: if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle: coord = item['geometry']['coordinates'] - point = ImportElevation.getElevationFromOE([[coord[0], coord[1]],]) + point = ImportElevation.getElevationFromOE([[coord[1], coord[0]],]) c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2]).sub(offset) if item['properties'].get('radius'): r = round(item['properties']['radius'] * 1000, 0) diff --git a/PVPlantImportGrid.py b/PVPlantImportGrid.py index a4ecca5..fe6970b 100644 --- a/PVPlantImportGrid.py +++ b/PVPlantImportGrid.py @@ -119,21 +119,9 @@ def getElevationFromOE(coordinates): if i != total: locations_str += '|' query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + locations_str + points = [] try: r = get(query, timeout=20, verify=certifi.where()) # <-- Corrección aquí - except RequestException as e: - print(f"Error en la solicitud: {str(e)}") - points = [] - for i, point in enumerate(coordinates): - c = utm.from_latlon(point[0], point[1]) - points.append(FreeCAD.Vector(round(c[0], 0), - round(c[1], 0), - 0) * 1000) - return points - - # Only get the json response in case of 200 or 201 - points = [] - if r.status_code == 200 or r.status_code == 201: results = r.json() for point in results["results"]: c = utm.from_latlon(point["latitude"], point["longitude"]) @@ -141,6 +129,14 @@ def getElevationFromOE(coordinates): round(c[1], 0), round(point["elevation"], 0)) * 1000 points.append(v) + except RequestException as e: + # print(f"Error en la solicitud: {str(e)}") + for i, point in enumerate(coordinates): + c = utm.from_latlon(point[0], point[1]) + points.append(FreeCAD.Vector(round(c[0], 0), + round(c[1], 0), + 0) * 1000) + return points def getSinglePointElevationFromBing(lat, lng): diff --git a/PVPlantTools.py b/PVPlantTools.py index 8240e0d..57dd2c4 100644 --- a/PVPlantTools.py +++ b/PVPlantTools.py @@ -652,6 +652,9 @@ if FreeCAD.GuiUp: from Civil.Fence import PVPlantFence FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup()) +import docgenerator +FreeCADGui.addCommand('GenerateDocuments', docgenerator.generateDocuments()) + projectlist = [ # "Reload", "PVPlantSite", "ProjectSetup", @@ -687,4 +690,6 @@ pv_mechanical = [ ] objectlist = ['PVPlantTree', - 'PVPlantFenceGroup',] \ No newline at end of file + 'PVPlantFenceGroup', + 'GenerateDocuments', + ] \ No newline at end of file diff --git a/Resources/webs/main.html b/Resources/webs/main.html index 2f5c683..d7cac7e 100644 --- a/Resources/webs/main.html +++ b/Resources/webs/main.html @@ -4,14 +4,17 @@ - - - - - + + + - - + + + + + + + diff --git a/docgenerator.py b/docgenerator.py new file mode 100644 index 0000000..0bc7f66 --- /dev/null +++ b/docgenerator.py @@ -0,0 +1,357 @@ +# Script para FreeCAD - Procesador de Documentos Word con Carátula +import os +import glob +from PySide2 import QtWidgets, QtCore +from PySide2.QtWidgets import (QFileDialog, QMessageBox, QProgressDialog, + QApplication, QVBoxLayout, QWidget, QPushButton, + QLabel, QTextEdit) +import FreeCAD +import FreeCADGui + +import PVPlantResources +from PVPlantResources import DirIcons as DirIcons + +try: + from docx import Document + from docx.shared import Pt, RGBColor, Inches + from docx.enum.text import WD_ALIGN_PARAGRAPH + from docx.oxml.ns import qn + + DOCX_AVAILABLE = True +except ImportError: + DOCX_AVAILABLE = False + FreeCAD.Console.PrintError("Error: python-docx no está instalado. Instala con: pip install python-docx\n") + + +class DocumentProcessor(QtWidgets.QDialog): + def __init__(self, parent=None): + super(DocumentProcessor, self).__init__(parent) + self.caratula_path = "" + self.carpeta_path = "" + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle("Procesador de Documentos Word") + self.setMinimumWidth(600) + self.setMinimumHeight(500) + + layout = QVBoxLayout() + + # Título + title = QLabel("