updates
This commit is contained in:
@@ -971,6 +971,7 @@ class _PVPlantExportDXF(QtGui.QWidget):
|
|||||||
if FreeCAD.ActiveDocument.Transport:
|
if FreeCAD.ActiveDocument.Transport:
|
||||||
for road in FreeCAD.ActiveDocument.Transport.Group:
|
for road in FreeCAD.ActiveDocument.Transport.Group:
|
||||||
base = exporter.createPolyline(road, "CIVIL External Roads")
|
base = exporter.createPolyline(road, "CIVIL External Roads")
|
||||||
|
if hasattr(road, 'Width'):
|
||||||
base.dxf.const_width = road.Width
|
base.dxf.const_width = road.Width
|
||||||
|
|
||||||
axis = exporter.createPolyline(road, "CIVIL External Roads Axis")
|
axis = exporter.createPolyline(road, "CIVIL External Roads Axis")
|
||||||
|
|||||||
@@ -43,13 +43,18 @@ class OSMImporter:
|
|||||||
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
|
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||||
|
|
||||||
def transform_from_latlon(self, coordinates):
|
def transform_from_latlon(self, coordinates):
|
||||||
|
"""Transforma coordenadas lat/lon a coordenadas FreeCAD"""
|
||||||
|
if not coordinates:
|
||||||
|
return []
|
||||||
|
|
||||||
points = ImportElevation.getElevationFromOE(coordinates)
|
points = ImportElevation.getElevationFromOE(coordinates)
|
||||||
pts = [FreeCAD.Vector(p.x, p.y, p.z).sub(self.Origin) for p in points]
|
pts = [FreeCAD.Vector(p.x, p.y, p.z).sub(self.Origin) for p in points]
|
||||||
return pts
|
return pts
|
||||||
|
|
||||||
def get_osm_data(self, bbox):
|
def get_osm_data(self, bbox):
|
||||||
query = f"""
|
""" Obtiene datos de OpenStreetMap """
|
||||||
[out:xml][bbox:{bbox}];
|
# Modificar la consulta en get_osm_data para incluir más tipos de agua:
|
||||||
|
query = f"""[out:xml][bbox:{bbox}];
|
||||||
(
|
(
|
||||||
way["building"];
|
way["building"];
|
||||||
way["highway"];
|
way["highway"];
|
||||||
@@ -57,39 +62,55 @@ class OSMImporter:
|
|||||||
way["power"="line"];
|
way["power"="line"];
|
||||||
way["power"="substation"];
|
way["power"="substation"];
|
||||||
way["natural"="water"];
|
way["natural"="water"];
|
||||||
way["landuse"="forest"];
|
way["waterway"];
|
||||||
|
way["waterway"="river"];
|
||||||
|
way["waterway"="stream"];
|
||||||
|
way["waterway"="canal"];
|
||||||
|
way["landuse"="basin"];
|
||||||
|
way["landuse"="reservoir"];
|
||||||
node["natural"="tree"];
|
node["natural"="tree"];
|
||||||
|
way["landuse"="forest"];
|
||||||
|
way["landuse"="farmland"];
|
||||||
);
|
);
|
||||||
(._;>;);
|
(._;>;);
|
||||||
out body;
|
out body;
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
self.overpass_url,
|
self.overpass_url,
|
||||||
data=query.encode('utf-8'),
|
data=query.encode('utf-8'),
|
||||||
headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'},
|
#headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'},
|
||||||
method='POST'
|
method='POST'
|
||||||
)
|
)
|
||||||
return urllib.request.urlopen(req, context=self.ssl_context, timeout=160).read()
|
|
||||||
|
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):
|
def create_layer(self, name):
|
||||||
|
"""Crea o obtiene una capa en el documento"""
|
||||||
if not FreeCAD.ActiveDocument.getObject(name):
|
if not FreeCAD.ActiveDocument.getObject(name):
|
||||||
return FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
|
return FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
|
||||||
return FreeCAD.ActiveDocument.getObject(name)
|
return FreeCAD.ActiveDocument.getObject(name)
|
||||||
|
|
||||||
def process_osm_data(self, osm_data):
|
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)
|
root = ET.fromstring(osm_data)
|
||||||
|
|
||||||
|
# Primero, recolectar todos los nodos
|
||||||
|
print(f"Procesando {len(root.findall('node'))} nodos...")
|
||||||
|
|
||||||
# Almacenar nodos transformados
|
# Almacenar nodos transformados
|
||||||
coordinates = [[float(node.attrib['lat']), float(node.attrib['lon'])] for node in root.findall('node')]
|
coordinates = [[float(node.attrib['lat']), float(node.attrib['lon'])] for node in root.findall('node')]
|
||||||
coordinates = self.transform_from_latlon(coordinates)
|
coordinates = self.transform_from_latlon(coordinates)
|
||||||
for i, node in enumerate(root.findall('node')):
|
for i, node in enumerate(root.findall('node')):
|
||||||
self. nodes[node.attrib['id']] = coordinates[i]
|
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'])
|
|
||||||
)'''
|
|
||||||
|
|
||||||
# Procesar ways
|
# Procesar ways
|
||||||
for way in root.findall('way'):
|
for way in root.findall('way'):
|
||||||
@@ -166,7 +187,7 @@ class OSMImporter:
|
|||||||
def create_buildings(self):
|
def create_buildings(self):
|
||||||
building_layer = self.create_layer("Buildings")
|
building_layer = self.create_layer("Buildings")
|
||||||
for way_id, data in self.ways_data.items():
|
for way_id, data in self.ways_data.items():
|
||||||
print(data)
|
#print(data)
|
||||||
if 'building' not in data['tags']:
|
if 'building' not in data['tags']:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -226,11 +247,11 @@ class OSMImporter:
|
|||||||
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
|
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
|
||||||
|
|
||||||
if 'power' in tags:
|
if 'power' in tags:
|
||||||
print("\n\n")
|
#print("\n\n")
|
||||||
print(tags)
|
#print(tags)
|
||||||
feature_type = tags['power']
|
feature_type = tags['power']
|
||||||
if feature_type == 'line':
|
if feature_type == 'line':
|
||||||
print("3.1. Create Power Lines")
|
#print("3.1. Create Power Lines")
|
||||||
FreeCADGui.updateGui()
|
FreeCADGui.updateGui()
|
||||||
self.create_power_line(
|
self.create_power_line(
|
||||||
nodes=nodes,
|
nodes=nodes,
|
||||||
@@ -239,7 +260,7 @@ class OSMImporter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif feature_type == 'substation':
|
elif feature_type == 'substation':
|
||||||
print("3.1. Create substations")
|
#print("3.1. Create substations")
|
||||||
FreeCADGui.updateGui()
|
FreeCADGui.updateGui()
|
||||||
self.create_substation(
|
self.create_substation(
|
||||||
way_id=way_id,
|
way_id=way_id,
|
||||||
@@ -249,7 +270,7 @@ class OSMImporter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif feature_type == 'tower':
|
elif feature_type == 'tower':
|
||||||
print("3.1. Create power towers")
|
#print("3.1. Create power towers")
|
||||||
FreeCADGui.updateGui()
|
FreeCADGui.updateGui()
|
||||||
self.create_power_tower(
|
self.create_power_tower(
|
||||||
position=nodes[0] if nodes else None,
|
position=nodes[0] if nodes else None,
|
||||||
@@ -562,13 +583,15 @@ class OSMImporter:
|
|||||||
if polygon_points[0] != polygon_points[-1]:
|
if polygon_points[0] != polygon_points[-1]:
|
||||||
polygon_points.append(polygon_points[0])
|
polygon_points.append(polygon_points[0])
|
||||||
|
|
||||||
|
|
||||||
# 3. Base del terreno
|
# 3. Base del terreno
|
||||||
base_height = 0.3
|
base_height = 0.3
|
||||||
try:
|
try:
|
||||||
base_shape = Part.makePolygon(polygon_points)
|
base_shape = Part.makePolygon(polygon_points)
|
||||||
base_face = Part.Face(base_shape)
|
base_face = Part.Face(base_shape)
|
||||||
base_extrude = base_face.extrude(FreeCAD.Vector(0, 0, base_height))
|
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.Shape = base_extrude
|
||||||
base_obj.ViewObject.ShapeColor = (0.2, 0.2, 0.2)
|
base_obj.ViewObject.ShapeColor = (0.2, 0.2, 0.2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -583,7 +606,8 @@ class OSMImporter:
|
|||||||
fence_shape = Part.makePolygon(fence_points)
|
fence_shape = Part.makePolygon(fence_points)
|
||||||
fence_face = Part.Face(fence_shape)
|
fence_face = Part.Face(fence_shape)
|
||||||
fence_extrude = fence_face.extrude(FreeCAD.Vector(0, 0, 2.8))
|
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.Shape = fence_extrude
|
||||||
fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4)
|
fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -599,14 +623,15 @@ class OSMImporter:
|
|||||||
building_shape = Part.makePolygon(building_points)
|
building_shape = Part.makePolygon(building_points)
|
||||||
building_face = Part.Face(building_shape)
|
building_face = Part.Face(building_shape)
|
||||||
building_extrude = building_face.extrude(FreeCAD.Vector(0, 0, building_height))
|
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.Shape = building_extrude
|
||||||
building_obj.ViewObject.ShapeColor = (0.7, 0.7, 0.7)
|
building_obj.ViewObject.ShapeColor = (0.7, 0.7, 0.7)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(f"Error edificio {way_id}: {str(e)}\n")
|
FreeCAD.Console.PrintWarning(f"Error edificio {way_id}: {str(e)}\n")
|
||||||
|
|
||||||
# 6. Transformadores
|
# 6. Transformadores
|
||||||
try:
|
'''try:
|
||||||
num_transformers = int(tags.get('transformers', 1))
|
num_transformers = int(tags.get('transformers', 1))
|
||||||
for i in range(num_transformers):
|
for i in range(num_transformers):
|
||||||
transformer_pos = self.calculate_equipment_position(
|
transformer_pos = self.calculate_equipment_position(
|
||||||
@@ -618,11 +643,11 @@ class OSMImporter:
|
|||||||
transformer = self.create_transformer(
|
transformer = self.create_transformer(
|
||||||
position=transformer_pos,
|
position=transformer_pos,
|
||||||
voltage=voltage,
|
voltage=voltage,
|
||||||
tech_type=tags.get('substation:type', 'outdoor')
|
technology=tags.get('substation:type', 'outdoor')
|
||||||
)
|
)
|
||||||
layer.addObject(transformer)
|
layer.addObject(transformer)
|
||||||
except Exception as e:
|
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
|
# 7. Torre de seccionamiento para alta tensión
|
||||||
if substation_type == 'transmission' and voltage >= 110000:
|
if substation_type == 'transmission' and voltage >= 110000:
|
||||||
@@ -637,7 +662,8 @@ class OSMImporter:
|
|||||||
FreeCAD.Console.PrintWarning(f"Error torre {way_id}: {str(e)}\n")
|
FreeCAD.Console.PrintWarning(f"Error torre {way_id}: {str(e)}\n")
|
||||||
|
|
||||||
# 8. Propiedades técnicas
|
# 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 = {
|
props = {
|
||||||
"Voltage": voltage,
|
"Voltage": voltage,
|
||||||
"Type": substation_type,
|
"Type": substation_type,
|
||||||
@@ -651,7 +677,8 @@ class OSMImporter:
|
|||||||
else:
|
else:
|
||||||
substation_data.addProperty(
|
substation_data.addProperty(
|
||||||
"App::PropertyFloat" if isinstance(value, float) else "App::PropertyString",
|
"App::PropertyFloat" if isinstance(value, float) else "App::PropertyString",
|
||||||
prop, "Technical").setValue(value)
|
prop, "Technical")
|
||||||
|
setattr(substation_data, prop, value)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintError(f"Error crítico en subestación {way_id}: {str(e)}\n")
|
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):
|
if face.isInside(rand_point, 0.1, True):
|
||||||
return rand_point
|
return rand_point
|
||||||
|
|
||||||
def create_water_bodies(self):
|
def create_water_bodies_old(self):
|
||||||
water_layer = self.create_layer("Water")
|
water_layer = self.create_layer("Water")
|
||||||
|
print(self.ways_data)
|
||||||
for way_id, data in self.ways_data.items():
|
for way_id, data in self.ways_data.items():
|
||||||
if 'natural' in data['tags'] and data['tags']['natural'] == 'water':
|
if 'natural' in data['tags'] and data['tags']['natural'] == 'water':
|
||||||
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
|
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.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))# * scale - self.Origin )
|
||||||
water.ViewObject.ShapeColor = self.feature_colors['water']
|
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
|
||||||
|
|||||||
15
InitGui.py
15
InitGui.py
@@ -39,9 +39,11 @@ class PVPlantWorkbench(Workbench):
|
|||||||
ToolTip = "Workbench for PV design"
|
ToolTip = "Workbench for PV design"
|
||||||
Icon = str(os.path.join(DirIcons, "icon.svg"))
|
Icon = str(os.path.join(DirIcons, "icon.svg"))
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
''' init '''
|
||||||
|
|
||||||
def Initialize(self):
|
def Initialize(self):
|
||||||
|
|
||||||
#sys.path.append(r"C:\Users\javie\AppData\Roaming\FreeCAD\Mod")
|
|
||||||
sys.path.append(os.path.join(FreeCAD.getUserAppDataDir(), 'Mod'))
|
sys.path.append(os.path.join(FreeCAD.getUserAppDataDir(), 'Mod'))
|
||||||
import PVPlantTools, reload
|
import PVPlantTools, reload
|
||||||
|
|
||||||
@@ -144,7 +146,10 @@ class PVPlantWorkbench(Workbench):
|
|||||||
from widgets import CountSelection
|
from widgets import CountSelection
|
||||||
|
|
||||||
def Activated(self):
|
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 SelectionObserver
|
||||||
import FreeCADGui
|
import FreeCADGui
|
||||||
|
|
||||||
@@ -153,7 +158,9 @@ class PVPlantWorkbench(Workbench):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def Deactivated(self):
|
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)
|
#FreeCADGui.Selection.removeObserver(self.observer)
|
||||||
return
|
return
|
||||||
@@ -201,4 +208,4 @@ class PVPlantWorkbench(Workbench):
|
|||||||
return "Gui::PythonWorkbench"
|
return "Gui::PythonWorkbench"
|
||||||
|
|
||||||
|
|
||||||
Gui.addWorkbench(PVPlantWorkbench())
|
FreeCADGui.addWorkbench(PVPlantWorkbench())
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ class MapWindow(QtGui.QWidget):
|
|||||||
for item in items['features']:
|
for item in items['features']:
|
||||||
if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle:
|
if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle:
|
||||||
coord = item['geometry']['coordinates']
|
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)
|
c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2]).sub(offset)
|
||||||
if item['properties'].get('radius'):
|
if item['properties'].get('radius'):
|
||||||
r = round(item['properties']['radius'] * 1000, 0)
|
r = round(item['properties']['radius'] * 1000, 0)
|
||||||
|
|||||||
@@ -119,21 +119,9 @@ def getElevationFromOE(coordinates):
|
|||||||
if i != total:
|
if i != total:
|
||||||
locations_str += '|'
|
locations_str += '|'
|
||||||
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + locations_str
|
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + locations_str
|
||||||
|
points = []
|
||||||
try:
|
try:
|
||||||
r = get(query, timeout=20, verify=certifi.where()) # <-- Corrección aquí
|
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()
|
results = r.json()
|
||||||
for point in results["results"]:
|
for point in results["results"]:
|
||||||
c = utm.from_latlon(point["latitude"], point["longitude"])
|
c = utm.from_latlon(point["latitude"], point["longitude"])
|
||||||
@@ -141,6 +129,14 @@ def getElevationFromOE(coordinates):
|
|||||||
round(c[1], 0),
|
round(c[1], 0),
|
||||||
round(point["elevation"], 0)) * 1000
|
round(point["elevation"], 0)) * 1000
|
||||||
points.append(v)
|
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
|
return points
|
||||||
|
|
||||||
def getSinglePointElevationFromBing(lat, lng):
|
def getSinglePointElevationFromBing(lat, lng):
|
||||||
|
|||||||
@@ -652,6 +652,9 @@ if FreeCAD.GuiUp:
|
|||||||
from Civil.Fence import PVPlantFence
|
from Civil.Fence import PVPlantFence
|
||||||
FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup())
|
FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup())
|
||||||
|
|
||||||
|
import docgenerator
|
||||||
|
FreeCADGui.addCommand('GenerateDocuments', docgenerator.generateDocuments())
|
||||||
|
|
||||||
projectlist = [ # "Reload",
|
projectlist = [ # "Reload",
|
||||||
"PVPlantSite",
|
"PVPlantSite",
|
||||||
"ProjectSetup",
|
"ProjectSetup",
|
||||||
@@ -687,4 +690,6 @@ pv_mechanical = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
objectlist = ['PVPlantTree',
|
objectlist = ['PVPlantTree',
|
||||||
'PVPlantFenceGroup',]
|
'PVPlantFenceGroup',
|
||||||
|
'GenerateDocuments',
|
||||||
|
]
|
||||||
@@ -4,12 +4,15 @@
|
|||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
|
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css">
|
<!-- 1. Core Leaflet library -->
|
||||||
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"></script>
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://rawgit.com/Leaflet/Leaflet.draw/v1.0.4/dist/leaflet.draw.css">
|
<!-- 2. Leaflet.draw Plugin (MUST be loaded AFTER Leaflet) -->
|
||||||
<script src="https://rawgit.com/Leaflet/Leaflet.draw/v1.0.4/dist/leaflet.draw-src.js"></script>
|
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" />
|
||||||
|
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
|
||||||
|
|
||||||
|
<!-- 3. Other plugins -->
|
||||||
<script src="https://unpkg.com/togeojson@0.16.0"></script>
|
<script src="https://unpkg.com/togeojson@0.16.0"></script>
|
||||||
<script src="https://unpkg.com/leaflet-filelayer@1.2.0"></script>
|
<script src="https://unpkg.com/leaflet-filelayer@1.2.0"></script>
|
||||||
|
|
||||||
|
|||||||
357
docgenerator.py
Normal file
357
docgenerator.py
Normal file
@@ -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("<h2>Procesador de Documentos Word</h2>")
|
||||||
|
title.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
layout.addWidget(title)
|
||||||
|
|
||||||
|
# Información
|
||||||
|
info_text = QLabel(
|
||||||
|
"Este script buscará recursivamente todos los archivos .docx en una carpeta,\n"
|
||||||
|
"insertará una carátula y aplicará formato estándar a todos los documentos."
|
||||||
|
)
|
||||||
|
info_text.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
layout.addWidget(info_text)
|
||||||
|
|
||||||
|
# Botón seleccionar carátula
|
||||||
|
self.btn_caratula = QPushButton("1. Seleccionar Carátula")
|
||||||
|
self.btn_caratula.clicked.connect(self.seleccionar_caratula)
|
||||||
|
layout.addWidget(self.btn_caratula)
|
||||||
|
|
||||||
|
self.label_caratula = QLabel("No se ha seleccionado carátula")
|
||||||
|
self.label_caratula.setWordWrap(True)
|
||||||
|
layout.addWidget(self.label_caratula)
|
||||||
|
|
||||||
|
# Botón seleccionar carpeta
|
||||||
|
self.btn_carpeta = QPushButton("2. Seleccionar Carpeta de Documentos")
|
||||||
|
self.btn_carpeta.clicked.connect(self.seleccionar_carpeta)
|
||||||
|
layout.addWidget(self.btn_carpeta)
|
||||||
|
|
||||||
|
self.label_carpeta = QLabel("No se ha seleccionado carpeta")
|
||||||
|
self.label_carpeta.setWordWrap(True)
|
||||||
|
layout.addWidget(self.label_carpeta)
|
||||||
|
|
||||||
|
# Botón procesar
|
||||||
|
self.btn_procesar = QPushButton("3. Procesar Documentos")
|
||||||
|
self.btn_procesar.clicked.connect(self.procesar_documentos)
|
||||||
|
self.btn_procesar.setEnabled(False)
|
||||||
|
layout.addWidget(self.btn_procesar)
|
||||||
|
|
||||||
|
# Área de log
|
||||||
|
self.log_area = QTextEdit()
|
||||||
|
self.log_area.setReadOnly(True)
|
||||||
|
layout.addWidget(self.log_area)
|
||||||
|
|
||||||
|
# Botón cerrar
|
||||||
|
self.btn_cerrar = QPushButton("Cerrar")
|
||||||
|
self.btn_cerrar.clicked.connect(self.close)
|
||||||
|
layout.addWidget(self.btn_cerrar)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def log(self, mensaje):
|
||||||
|
"""Agrega un mensaje al área de log"""
|
||||||
|
self.log_area.append(mensaje)
|
||||||
|
QApplication.processEvents() # Para actualizar la UI
|
||||||
|
|
||||||
|
def seleccionar_caratula(self):
|
||||||
|
"""Abre un diálogo para seleccionar el archivo de carátula"""
|
||||||
|
archivo, _ = QFileDialog.getOpenFileName(
|
||||||
|
self,
|
||||||
|
"Seleccionar archivo de carátula",
|
||||||
|
"",
|
||||||
|
"Word documents (*.docx);;All files (*.*)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if archivo and os.path.exists(archivo):
|
||||||
|
self.caratula_path = archivo
|
||||||
|
self.label_caratula.setText(f"Carátula: {os.path.basename(archivo)}")
|
||||||
|
self.verificar_estado()
|
||||||
|
self.log(f"✓ Carátula seleccionada: {archivo}")
|
||||||
|
|
||||||
|
def seleccionar_carpeta(self):
|
||||||
|
"""Abre un diálogo para seleccionar la carpeta de documentos"""
|
||||||
|
carpeta = QFileDialog.getExistingDirectory(
|
||||||
|
self,
|
||||||
|
"Seleccionar carpeta con documentos"
|
||||||
|
)
|
||||||
|
|
||||||
|
if carpeta:
|
||||||
|
self.carpeta_path = carpeta
|
||||||
|
self.label_carpeta.setText(f"Carpeta: {carpeta}")
|
||||||
|
self.verificar_estado()
|
||||||
|
self.log(f"✓ Carpeta seleccionada: {carpeta}")
|
||||||
|
|
||||||
|
def verificar_estado(self):
|
||||||
|
"""Habilita el botón procesar si ambos paths están seleccionados"""
|
||||||
|
if self.caratula_path and self.carpeta_path:
|
||||||
|
self.btn_procesar.setEnabled(True)
|
||||||
|
|
||||||
|
def buscar_docx_recursivamente(self, carpeta):
|
||||||
|
"""Busca recursivamente todos los archivos .docx en una carpeta"""
|
||||||
|
archivos_docx = []
|
||||||
|
patron = os.path.join(carpeta, "**", "*.docx")
|
||||||
|
|
||||||
|
for archivo in glob.glob(patron, recursive=True):
|
||||||
|
archivos_docx.append(archivo)
|
||||||
|
|
||||||
|
return archivos_docx
|
||||||
|
|
||||||
|
def aplicar_formato_estandar(self, doc):
|
||||||
|
"""Aplica formato estándar al documento"""
|
||||||
|
try:
|
||||||
|
# Configurar estilos por defecto
|
||||||
|
style = doc.styles['Normal']
|
||||||
|
font = style.font
|
||||||
|
font.name = 'Arial'
|
||||||
|
font.size = Pt(11)
|
||||||
|
font.color.rgb = RGBColor(0, 0, 0) # Negro
|
||||||
|
|
||||||
|
# Configurar encabezados
|
||||||
|
try:
|
||||||
|
heading_style = doc.styles['Heading 1']
|
||||||
|
heading_font = heading_style.font
|
||||||
|
heading_font.name = 'Arial'
|
||||||
|
heading_font.size = Pt(14)
|
||||||
|
heading_font.bold = True
|
||||||
|
heading_font.color.rgb = RGBColor(0, 51, 102) # Azul oscuro
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f" ⚠ Advertencia en formato: {str(e)}")
|
||||||
|
|
||||||
|
def aplicar_formato_avanzado(self, doc):
|
||||||
|
"""Aplica formato más avanzado y personalizado"""
|
||||||
|
try:
|
||||||
|
# Configurar márgenes
|
||||||
|
sections = doc.sections
|
||||||
|
for section in sections:
|
||||||
|
section.top_margin = Inches(1)
|
||||||
|
section.bottom_margin = Inches(1)
|
||||||
|
section.left_margin = Inches(1)
|
||||||
|
section.right_margin = Inches(1)
|
||||||
|
|
||||||
|
# Configurar estilos de párrafo
|
||||||
|
for paragraph in doc.paragraphs:
|
||||||
|
paragraph.paragraph_format.space_after = Pt(6)
|
||||||
|
paragraph.paragraph_format.space_before = Pt(0)
|
||||||
|
paragraph.paragraph_format.line_spacing = 1.15
|
||||||
|
|
||||||
|
# Alinear párrafos justificados
|
||||||
|
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||||
|
|
||||||
|
# Aplicar fuente específica a cada run
|
||||||
|
for run in paragraph.runs:
|
||||||
|
run.font.name = 'Arial'
|
||||||
|
run._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
|
||||||
|
run.font.size = Pt(11)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f" ⚠ Advertencia en formato avanzado: {str(e)}")
|
||||||
|
|
||||||
|
def insertar_caratula_y_formatear(self, archivo_docx, archivo_caratula):
|
||||||
|
"""Inserta la carátula y aplica formato al documento"""
|
||||||
|
try:
|
||||||
|
# Abrir el documento de carátula
|
||||||
|
doc_caratula = Document(archivo_caratula)
|
||||||
|
|
||||||
|
# Abrir el documento destino
|
||||||
|
doc_destino = Document(archivo_docx)
|
||||||
|
|
||||||
|
# Crear un nuevo documento que contendrá la carátula + contenido original
|
||||||
|
nuevo_doc = Document()
|
||||||
|
|
||||||
|
# Copiar todo el contenido de la carátula
|
||||||
|
for elemento in doc_caratula.element.body:
|
||||||
|
print(elemento)
|
||||||
|
nuevo_doc.element.body.append(elemento)
|
||||||
|
|
||||||
|
# Agregar un salto de página después de la carátula
|
||||||
|
nuevo_doc.add_page_break()
|
||||||
|
|
||||||
|
# Copiar todo el contenido del documento original
|
||||||
|
for elemento in doc_destino.element.body:
|
||||||
|
nuevo_doc.element.body.append(elemento)
|
||||||
|
|
||||||
|
# Aplicar formatos
|
||||||
|
self.aplicar_formato_estandar(nuevo_doc)
|
||||||
|
self.aplicar_formato_avanzado(nuevo_doc)
|
||||||
|
|
||||||
|
# Guardar el documento (sobrescribir el original)
|
||||||
|
nombre_base = os.path.splitext(os.path.basename(archivo_docx))[0]
|
||||||
|
extension = os.path.splitext(archivo_docx)[1]
|
||||||
|
name = f"{nombre_base}{extension}"
|
||||||
|
nuevo_docx = os.path.join(self.output_carpeta, name)
|
||||||
|
nuevo_doc.save(nuevo_docx)
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
def procesar_documentos(self):
|
||||||
|
"""Función principal que orquesta todo el proceso"""
|
||||||
|
if not DOCX_AVAILABLE:
|
||||||
|
QMessageBox.critical(self, "Error",
|
||||||
|
"La biblioteca python-docx no está disponible.\n\n"
|
||||||
|
"Instala con: pip install python-docx")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Verificar paths
|
||||||
|
if not os.path.exists(self.caratula_path):
|
||||||
|
QMessageBox.warning(self, "Error", "El archivo de carátula no existe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(self.carpeta_path):
|
||||||
|
QMessageBox.warning(self, "Error", "La carpeta de documentos no existe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("\n=== INICIANDO PROCESAMIENTO ===")
|
||||||
|
self.log(f"Carátula: {self.caratula_path}")
|
||||||
|
self.log(f"Carpeta: {self.carpeta_path}")
|
||||||
|
|
||||||
|
directorio_padre = os.path.dirname(self.carpeta_path)
|
||||||
|
self.output_carpeta = os.path.join(directorio_padre, "03.Outputs")
|
||||||
|
os.makedirs(self.output_carpeta, exist_ok=True)
|
||||||
|
|
||||||
|
# Buscar archivos .docx
|
||||||
|
self.log("Buscando archivos .docx...")
|
||||||
|
archivos_docx = self.buscar_docx_recursivamente(self.carpeta_path)
|
||||||
|
|
||||||
|
if not archivos_docx:
|
||||||
|
self.log("No se encontraron archivos .docx en la carpeta seleccionada.")
|
||||||
|
QMessageBox.information(self, "Información",
|
||||||
|
"No se encontraron archivos .docx en la carpeta seleccionada.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log(f"Se encontraron {len(archivos_docx)} archivos .docx")
|
||||||
|
|
||||||
|
# Crear diálogo de progreso
|
||||||
|
progress = QProgressDialog("Procesando documentos...", "Cancelar", 0, len(archivos_docx), self)
|
||||||
|
progress.setWindowTitle("Procesando")
|
||||||
|
progress.setWindowModality(QtCore.Qt.WindowModal)
|
||||||
|
progress.show()
|
||||||
|
|
||||||
|
# Procesar cada archivo
|
||||||
|
exitosos = 0
|
||||||
|
fallidos = 0
|
||||||
|
errores_detallados = []
|
||||||
|
|
||||||
|
for i, archivo_docx in enumerate(archivos_docx):
|
||||||
|
if progress.wasCanceled():
|
||||||
|
self.log("Proceso cancelado por el usuario.")
|
||||||
|
break
|
||||||
|
|
||||||
|
progress.setValue(i)
|
||||||
|
progress.setLabelText(f"Procesando {i + 1}/{len(archivos_docx)}: {os.path.basename(archivo_docx)}")
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
|
self.log(f"Procesando: {os.path.basename(archivo_docx)}")
|
||||||
|
|
||||||
|
success, error_msg = self.insertar_caratula_y_formatear(archivo_docx, self.caratula_path)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.log(f" ✓ Completado")
|
||||||
|
exitosos += 1
|
||||||
|
else:
|
||||||
|
self.log(f" ✗ Error: {error_msg}")
|
||||||
|
fallidos += 1
|
||||||
|
errores_detallados.append(f"{os.path.basename(archivo_docx)}: {error_msg}")
|
||||||
|
|
||||||
|
progress.setValue(len(archivos_docx))
|
||||||
|
|
||||||
|
# Mostrar resumen
|
||||||
|
self.log("\n=== RESUMEN ===")
|
||||||
|
self.log(f"Documentos procesados exitosamente: {exitosos}")
|
||||||
|
self.log(f"Documentos con errores: {fallidos}")
|
||||||
|
self.log(f"Total procesados: {exitosos + fallidos}")
|
||||||
|
|
||||||
|
# Mostrar mensaje final
|
||||||
|
mensaje = (f"Procesamiento completado:\n"
|
||||||
|
f"✓ Exitosos: {exitosos}\n"
|
||||||
|
f"✗ Fallidos: {fallidos}\n"
|
||||||
|
f"Total: {len(archivos_docx)}")
|
||||||
|
|
||||||
|
if fallidos > 0:
|
||||||
|
mensaje += f"\n\nErrores encontrados:\n" + "\n".join(
|
||||||
|
errores_detallados[:5]) # Mostrar solo primeros 5 errores
|
||||||
|
if len(errores_detallados) > 5:
|
||||||
|
mensaje += f"\n... y {len(errores_detallados) - 5} más"
|
||||||
|
|
||||||
|
QMessageBox.information(self, "Proceso Completado", mensaje)
|
||||||
|
|
||||||
|
|
||||||
|
# Función para ejecutar desde FreeCAD
|
||||||
|
def run_document_processor():
|
||||||
|
"""Función principal para ejecutar el procesador desde FreeCAD"""
|
||||||
|
# Verificar si python-docx está disponible
|
||||||
|
if not DOCX_AVAILABLE:
|
||||||
|
msg = QMessageBox()
|
||||||
|
msg.setIcon(QMessageBox.Critical)
|
||||||
|
msg.setText("Biblioteca python-docx no encontrada")
|
||||||
|
msg.setInformativeText(
|
||||||
|
"Para usar este script necesitas instalar python-docx:\n\n"
|
||||||
|
"1. Abre la consola de FreeCAD\n"
|
||||||
|
"2. Ejecuta: import subprocess, sys\n"
|
||||||
|
"3. Ejecuta: subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'python-docx'])\n\n"
|
||||||
|
"O instala desde una terminal externa con: pip install python-docx"
|
||||||
|
)
|
||||||
|
msg.setWindowTitle("Dependencia faltante")
|
||||||
|
msg.exec_()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Crear y mostrar la interfaz
|
||||||
|
dialog = DocumentProcessor(FreeCADGui.getMainWindow())
|
||||||
|
dialog.exec_()
|
||||||
|
|
||||||
|
class generateDocuments:
|
||||||
|
def GetResources(self):
|
||||||
|
return {'Pixmap': str(os.path.join(DirIcons, "house.svg")),
|
||||||
|
'MenuText': "DocumentGenerator",
|
||||||
|
'Accel': "D, G",
|
||||||
|
'ToolTip': "Creates a Building object from setup dialog."}
|
||||||
|
|
||||||
|
def IsActive(self):
|
||||||
|
return not FreeCAD.ActiveDocument is None
|
||||||
|
|
||||||
|
|
||||||
|
def Activated(self):
|
||||||
|
run_document_processor()
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
||||||
<name>PVPlant</name>
|
<name>PVPlant</name>
|
||||||
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
||||||
<version>2025.11.20</version>
|
<version>2026.02.12</version>
|
||||||
<date>2025.11.20</date>
|
<date>2026.02.15</date>
|
||||||
<maintainer email="javier.branagutierrez@gmail.com">Javier Braña</maintainer>
|
<maintainer email="javier.branagutierrez@gmail.com">Javier Braña</maintainer>
|
||||||
<license file="LICENSE">LGPL-2.1-or-later</license>
|
<license file="LICENSE">LGPL-2.1-or-later</license>
|
||||||
<url type="repository" branch="developed">https://homehud.duckdns.org/javier/PVPlant</url>
|
<url type="repository" branch="main">https://homehud.duckdns.org/javier/PVPlant/src/branch/main/</url>
|
||||||
<url type="bugtracker">https://homehud.duckdns.org/javier/PVPlant/issues</url>
|
<url type="bugtracker">https://homehud.duckdns.org/javier/PVPlant/issues</url>
|
||||||
<url type="readme">https://homehud.duckdns.org/javier/PVPlant/src/branch/developed/README.md</url>
|
<url type="readme">https://homehud.duckdns.org/javier/PVPlant/src/branch/developed/README.md</url>
|
||||||
<icon>PVPlant/Resources/Icons/PVPlantWorkbench.svg</icon>
|
<icon>PVPlant/Resources/Icons/PVPlantWorkbench.svg</icon>
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ class _CommandReload:
|
|||||||
import hydro.hydrological as hydro
|
import hydro.hydrological as hydro
|
||||||
import Importer.importOSM as iOSM
|
import Importer.importOSM as iOSM
|
||||||
|
|
||||||
|
import docgenerator
|
||||||
|
|
||||||
|
importlib.reload(docgenerator)
|
||||||
|
|
||||||
importlib.reload(ProjectSetup)
|
importlib.reload(ProjectSetup)
|
||||||
importlib.reload(PVPlantPlacement)
|
importlib.reload(PVPlantPlacement)
|
||||||
importlib.reload(PVPlantImportGrid)
|
importlib.reload(PVPlantImportGrid)
|
||||||
|
|||||||
Reference in New Issue
Block a user