algo
This commit is contained in:
@@ -340,7 +340,44 @@ class Trench(ArchComponent.Component):
|
|||||||
p2.z = 0
|
p2.z = 0
|
||||||
return p2.sub(p1)
|
return p2.sub(p1)
|
||||||
|
|
||||||
def getsegments(wire):
|
def getsegments(wire): #deepseek
|
||||||
|
"""Divide un wire en segmentos rectos basados en cambios de dirección (sin splitWiresByCurvature)"""
|
||||||
|
import Part
|
||||||
|
from math import degrees
|
||||||
|
|
||||||
|
segments = []
|
||||||
|
current_segment = []
|
||||||
|
angle_threshold = 1.0 # Grados para considerar cambio de dirección
|
||||||
|
|
||||||
|
def get_angle(v1, v2):
|
||||||
|
return degrees(v1.getAngle(v2))
|
||||||
|
|
||||||
|
edges = wire.Edges
|
||||||
|
for i in range(len(edges)):
|
||||||
|
if i == 0:
|
||||||
|
current_segment.append(edges[i])
|
||||||
|
continue
|
||||||
|
|
||||||
|
prev_edge = edges[i - 1]
|
||||||
|
curr_edge = edges[i]
|
||||||
|
|
||||||
|
# Vectores de dirección
|
||||||
|
v1 = prev_edge.tangentAt(prev_edge.FirstParameter)
|
||||||
|
v2 = curr_edge.tangentAt(curr_edge.FirstParameter)
|
||||||
|
|
||||||
|
angle = get_angle(v1, v2)
|
||||||
|
if angle > angle_threshold:
|
||||||
|
segments.append(Part.Wire(current_segment))
|
||||||
|
current_segment = [curr_edge]
|
||||||
|
else:
|
||||||
|
current_segment.append(curr_edge)
|
||||||
|
|
||||||
|
if current_segment:
|
||||||
|
segments.append(Part.Wire(current_segment))
|
||||||
|
|
||||||
|
return segments
|
||||||
|
|
||||||
|
def getsegments_old(wire):
|
||||||
import math
|
import math
|
||||||
segments = []
|
segments = []
|
||||||
|
|
||||||
@@ -381,13 +418,6 @@ class Trench(ArchComponent.Component):
|
|||||||
pts_plane.append(tmp)
|
pts_plane.append(tmp)
|
||||||
path_plane = Part.makePolygon(pts_plane)
|
path_plane = Part.makePolygon(pts_plane)
|
||||||
|
|
||||||
'''o1 = path_plane.makeOffset2D(d, 2, False, True, True)
|
|
||||||
o2 = path_plane.makeOffset2D(-d, 2, False, True, True)
|
|
||||||
points = calculateOffset(o1)
|
|
||||||
points.insert(0, points.pop(1))
|
|
||||||
points.reverse()
|
|
||||||
points2 = calculateOffset(o2)'''
|
|
||||||
|
|
||||||
points = self.calculateOffset(path_plane, d)
|
points = self.calculateOffset(path_plane, d)
|
||||||
points2 = self.calculateOffset(path_plane, -d)
|
points2 = self.calculateOffset(path_plane, -d)
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import FreeCAD
|
import FreeCAD
|
||||||
|
import FreeCADGui
|
||||||
import Part
|
import Part
|
||||||
import Draft
|
import Draft
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
@@ -15,9 +16,7 @@ scale = 1000.0
|
|||||||
class OSMImporter:
|
class OSMImporter:
|
||||||
|
|
||||||
def __init__(self, origin):
|
def __init__(self, origin):
|
||||||
self.Origin = origin
|
self.Origin = origin if origin else FreeCAD.Vector(0, 0, 0)
|
||||||
if origin is None:
|
|
||||||
self.Origin = FreeCAD.Vector(0, 0, 0)
|
|
||||||
self.overpass_url = "https://overpass-api.de/api/interpreter"
|
self.overpass_url = "https://overpass-api.de/api/interpreter"
|
||||||
self.nodes = {}
|
self.nodes = {}
|
||||||
self.ways_data = defaultdict(dict)
|
self.ways_data = defaultdict(dict)
|
||||||
@@ -40,10 +39,11 @@ class OSMImporter:
|
|||||||
'vegetation': (0.4, 0.8, 0.4),
|
'vegetation': (0.4, 0.8, 0.4),
|
||||||
'water': (0.4, 0.6, 1.0)
|
'water': (0.4, 0.6, 1.0)
|
||||||
}
|
}
|
||||||
|
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||||
|
|
||||||
def transformFromLatLon(self, lat, lon):
|
def transform_from_latlon(self, lat, lon):
|
||||||
x, y, _, _ = utm.from_latlon(lat, lon)
|
x, y, _, _ = utm.from_latlon(lat, lon)
|
||||||
return (x, y, 0) * 1000
|
return FreeCAD.Vector(x, y, .0) * scale - self.Origin
|
||||||
|
|
||||||
def get_osm_data(self, bbox):
|
def get_osm_data(self, bbox):
|
||||||
query = f"""
|
query = f"""
|
||||||
@@ -61,43 +61,36 @@ class OSMImporter:
|
|||||||
(._;>;);
|
(._;>;);
|
||||||
out body;
|
out body;
|
||||||
"""
|
"""
|
||||||
# Configurar contexto SSL seguro
|
req = urllib.request.Request(
|
||||||
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
|
||||||
|
|
||||||
# Modificar tu código de descarga
|
|
||||||
response = urllib.request.urlopen(
|
|
||||||
self.overpass_url,
|
self.overpass_url,
|
||||||
data=query.encode('utf-8'),
|
data=query.encode('utf-8'),
|
||||||
context=ssl_context,
|
headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'},
|
||||||
timeout=30
|
method='POST'
|
||||||
)
|
)
|
||||||
return response.read()
|
return urllib.request.urlopen(req, context=self.ssl_context, timeout=30).read()
|
||||||
|
|
||||||
def create_layer(self, name):
|
def create_layer(self, name):
|
||||||
if not FreeCAD.ActiveDocument.getObject(name):
|
if not FreeCAD.ActiveDocument.getObject(name):
|
||||||
layer = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
|
return FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
|
||||||
return layer
|
|
||||||
return FreeCAD.ActiveDocument.getObject(name)
|
return FreeCAD.ActiveDocument.getObject(name)
|
||||||
|
|
||||||
def process_osm_data(self, osm_data):
|
def process_osm_data(self, osm_data):
|
||||||
root = ET.fromstring(osm_data)
|
root = ET.fromstring(osm_data)
|
||||||
|
|
||||||
# Primera pasada: almacenar todos los nodos
|
# Almacenar nodos transformados
|
||||||
for node in root.findall('node'):
|
for node in root.findall('node'):
|
||||||
'''self.nodes[node.attrib['id']] = (
|
self.nodes[node.attrib['id']] = self.transform_from_latlon(
|
||||||
float(node.attrib['lon']),
|
|
||||||
float(node.attrib['lat']),
|
|
||||||
0)'''
|
|
||||||
self.nodes[node.attrib['id']] = self.transformFromLatLon(
|
|
||||||
float(node.attrib['lat']),
|
float(node.attrib['lat']),
|
||||||
float(node.attrib['lon'])
|
float(node.attrib['lon'])
|
||||||
)
|
)
|
||||||
|
|
||||||
# Segunda pasada: procesar ways y relaciones
|
# Procesar ways
|
||||||
for way in root.findall('way'):
|
for way in root.findall('way'):
|
||||||
tags = {tag.attrib['k']: tag.attrib['v'] for tag in way.findall('tag')}
|
way_id = way.attrib['id']
|
||||||
nodes = [nd.attrib['ref'] for nd in way.findall('nd')]
|
self.ways_data[way_id] = {
|
||||||
self.ways_data[way.attrib['id']] = {'tags': tags, 'nodes': nodes}
|
'tags': {tag.attrib['k']: tag.attrib['v'] for tag in way.findall('tag')},
|
||||||
|
'nodes': [nd.attrib['ref'] for nd in way.findall('nd')]
|
||||||
|
}
|
||||||
|
|
||||||
self.create_transportation()
|
self.create_transportation()
|
||||||
self.create_buildings()
|
self.create_buildings()
|
||||||
@@ -133,7 +126,7 @@ class OSMImporter:
|
|||||||
self.create_railway(nodes, transport_layer)
|
self.create_railway(nodes, transport_layer)
|
||||||
|
|
||||||
def create_road(self, nodes, width, road_type, layer):
|
def create_road(self, nodes, width, road_type, layer):
|
||||||
points = [FreeCAD.Vector(n[0], n[1], .0) * scale - self.Origin for n in nodes]
|
points = [n for n in nodes]
|
||||||
polyline = Draft.make_wire(points, closed=False, face=False)
|
polyline = Draft.make_wire(points, closed=False, face=False)
|
||||||
polyline.Label = f"Road_{road_type}"
|
polyline.Label = f"Road_{road_type}"
|
||||||
polyline.ViewObject.LineWidth = 2.0
|
polyline.ViewObject.LineWidth = 2.0
|
||||||
@@ -143,7 +136,7 @@ class OSMImporter:
|
|||||||
layer.addObject(polyline)
|
layer.addObject(polyline)
|
||||||
|
|
||||||
def create_railway(self, nodes, layer):
|
def create_railway(self, nodes, layer):
|
||||||
points = [FreeCAD.Vector(n[0], n[1], .0) * scale - self.Origin for n in nodes]
|
points = [n for n in nodes]
|
||||||
rail_line = Draft.make_wire(points, closed=False, face=False)
|
rail_line = Draft.make_wire(points, closed=False, face=False)
|
||||||
rail_line.Label = "Railway"
|
rail_line.Label = "Railway"
|
||||||
rail_line.ViewObject.LineWidth = 1.5
|
rail_line.ViewObject.LineWidth = 1.5
|
||||||
@@ -152,7 +145,6 @@ 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():
|
||||||
if 'building' not in data['tags']:
|
if 'building' not in data['tags']:
|
||||||
continue
|
continue
|
||||||
@@ -167,14 +159,14 @@ class OSMImporter:
|
|||||||
height = self.get_building_height(tags)
|
height = self.get_building_height(tags)
|
||||||
|
|
||||||
# Crear polígono base
|
# Crear polígono base
|
||||||
polygon_points = [FreeCAD.Vector(n[0], n[1], .0) * scale - self.Origin for n in nodes]
|
polygon_points = [n for n in nodes]
|
||||||
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])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
polygon = Part.makePolygon(polygon_points)
|
polygon = Part.makePolygon(polygon_points)
|
||||||
face = Part.Face(polygon)
|
face = Part.Face(polygon)
|
||||||
extruded = face.extrude(FreeCAD.Vector(0, 0, height) * scale - self.Origin )
|
extruded = face.extrude(FreeCAD.Vector(0, 0, height))# * scale - self.Origin )
|
||||||
|
|
||||||
building = building_layer.addObject("Part::Feature", f"Building_{way_id}")
|
building = building_layer.addObject("Part::Feature", f"Building_{way_id}")
|
||||||
building.Shape = extruded
|
building.Shape = extruded
|
||||||
@@ -190,7 +182,8 @@ class OSMImporter:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error en edificio {way_id}: {str(e)}")
|
print(f"Error en edificio {way_id}: {str(e)}")
|
||||||
|
|
||||||
def get_building_height(self, tags):
|
@staticmethod
|
||||||
|
def get_building_height(tags):
|
||||||
# Lógica de cálculo de altura
|
# Lógica de cálculo de altura
|
||||||
if 'height' in tags:
|
if 'height' in tags:
|
||||||
try:
|
try:
|
||||||
@@ -205,44 +198,355 @@ class OSMImporter:
|
|||||||
return 5.0 # Altura por defecto
|
return 5.0 # Altura por defecto
|
||||||
|
|
||||||
def create_power_infrastructure(self):
|
def create_power_infrastructure(self):
|
||||||
power_layer = self.create_layer("Power")
|
power_layer = self.create_layer("Power_Infrastructure")
|
||||||
|
|
||||||
for way_id, data in self.ways_data.items():
|
for way_id, data in self.ways_data.items():
|
||||||
tags = data['tags']
|
tags = data['tags']
|
||||||
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:
|
||||||
if tags['power'] == 'line':
|
feature_type = tags['power']
|
||||||
self.create_power_line(nodes, power_layer)
|
|
||||||
elif tags['power'] == 'substation':
|
|
||||||
self.create_substation(nodes, power_layer)
|
|
||||||
|
|
||||||
def create_power_line(self, nodes, layer):
|
if feature_type == 'line':
|
||||||
|
self.create_power_line(
|
||||||
|
nodes=nodes,
|
||||||
|
tags=tags,
|
||||||
|
layer=power_layer
|
||||||
|
)
|
||||||
|
|
||||||
|
elif feature_type == 'substation':
|
||||||
|
self.create_substation(
|
||||||
|
way_id=way_id,
|
||||||
|
tags=tags,
|
||||||
|
nodes=nodes,
|
||||||
|
layer=power_layer
|
||||||
|
)
|
||||||
|
|
||||||
|
elif feature_type == 'tower':
|
||||||
|
self.create_power_tower(
|
||||||
|
position=nodes[0] if nodes else None,
|
||||||
|
tags=tags,
|
||||||
|
layer=power_layer
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_power_line(self, nodes, tags, layer):
|
||||||
|
"""Crea líneas de transmisión eléctrica con propiedades técnicas"""
|
||||||
|
try:
|
||||||
|
# Configuración basada en tags
|
||||||
|
line_type = tags.get('line', 'overhead')
|
||||||
|
voltage = self.parse_voltage(tags.get('voltage', '0'))
|
||||||
|
cables = int(tags.get('cables', '3'))
|
||||||
|
material = tags.get('material', 'aluminum')
|
||||||
|
|
||||||
|
# Crear geometría
|
||||||
|
points = [FreeCAD.Vector(*n) for n in nodes]
|
||||||
|
if len(points) < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
wire = Draft.make_wire(points, closed=False, face=False)
|
||||||
|
wire.Label = f"Power_Line_{voltage}V"
|
||||||
|
|
||||||
|
# Propiedades visuales
|
||||||
|
wire.ViewObject.LineWidth = 1 + (voltage / 100000)
|
||||||
|
color = self.feature_colors['power']['line']
|
||||||
|
wire.ViewObject.ShapeColor = color
|
||||||
|
|
||||||
|
# Propiedades técnicas
|
||||||
|
wire.addProperty("App::PropertyFloat", "Voltage", "PowerLine", "Voltage in volts").Voltage = voltage
|
||||||
|
wire.addProperty("App::PropertyInteger", "Cables", "PowerLine", "Number of conductors").Cables = cables
|
||||||
|
wire.addProperty("App::PropertyString", "Material", "PowerLine", "Conductor material").Material = material
|
||||||
|
wire.addProperty("App::PropertyString", "Type", "PowerLine", "Line type").Type = line_type
|
||||||
|
|
||||||
|
layer.addObject(wire)
|
||||||
|
|
||||||
|
# Añadir torres si es overhead
|
||||||
|
if line_type == 'overhead':
|
||||||
|
distance_between_towers = 150 # metros por defecto
|
||||||
|
if 'distance_between_towers' in tags:
|
||||||
|
try:
|
||||||
|
distance_between_towers = float(tags['distance_between_towers'])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.create_power_towers_along_line(
|
||||||
|
points=points,
|
||||||
|
voltage=voltage,
|
||||||
|
distance=distance_between_towers,
|
||||||
|
layer=layer
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintError(f"Error creating power line: {str(e)}\n")
|
||||||
|
|
||||||
|
def create_power_towers_along_line(self, points, voltage, distance, layer):
|
||||||
|
"""Crea torres de transmisión a lo largo de la línea"""
|
||||||
|
total_length = 0
|
||||||
|
previous_point = None
|
||||||
|
|
||||||
|
for point in points:
|
||||||
|
if previous_point is not None:
|
||||||
|
segment_length = (point - previous_point).Length
|
||||||
|
num_towers = int(segment_length / distance)
|
||||||
|
|
||||||
|
if num_towers > 0:
|
||||||
|
step = segment_length / num_towers
|
||||||
|
direction = (point - previous_point).normalize()
|
||||||
|
|
||||||
|
for i in range(num_towers):
|
||||||
|
tower_pos = previous_point + (direction * (i * step))
|
||||||
|
self.create_power_tower(
|
||||||
|
position=tower_pos,
|
||||||
|
tags={'voltage': str(voltage)},
|
||||||
|
layer=layer
|
||||||
|
)
|
||||||
|
|
||||||
|
previous_point = point
|
||||||
|
|
||||||
|
def create_power_tower(self, position, tags, layer):
|
||||||
|
"""Crea una torre de transmisión individual"""
|
||||||
|
try:
|
||||||
|
voltage = self.parse_voltage(tags.get('voltage', '0'))
|
||||||
|
|
||||||
|
# Dimensiones basadas en voltaje
|
||||||
|
base_size = 2.0 + (voltage / 100000)
|
||||||
|
height = 25.0 + (voltage / 10000)
|
||||||
|
|
||||||
|
# Geometría de la torre
|
||||||
|
base = Part.makeBox(base_size, base_size, 3.0,
|
||||||
|
FreeCAD.Vector(position.x - base_size / 2, position.y - base_size / 2, 0))
|
||||||
|
mast = Part.makeCylinder(0.5, height, FreeCAD.Vector(position.x, position.y, 3.0))
|
||||||
|
|
||||||
|
# Unir componentes
|
||||||
|
tower = base.fuse(mast)
|
||||||
|
tower_obj = layer.addObject("Part::Feature", "Power_Tower")
|
||||||
|
tower_obj.Shape = tower
|
||||||
|
tower_obj.ViewObject.ShapeColor = self.feature_colors['power']['tower']
|
||||||
|
|
||||||
|
# Añadir propiedades
|
||||||
|
tower_obj.addProperty("App::PropertyFloat", "Voltage", "Technical", "Design voltage").Voltage = voltage
|
||||||
|
tower_obj.addProperty("App::PropertyFloat", "Height", "Technical", "Tower height").Height = height
|
||||||
|
|
||||||
|
# Añadir crossarms (crucetas)
|
||||||
|
for i in range(3):
|
||||||
|
crossarm_height = 3.0 + (i * 5.0)
|
||||||
|
crossarm = Part.makeBox(
|
||||||
|
4.0, 0.2, 0.2,
|
||||||
|
FreeCAD.Vector(position.x - 2.0, position.y - 0.1, crossarm_height)
|
||||||
|
)
|
||||||
|
tower_obj.Shape = tower_obj.Shape.fuse(crossarm)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintError(f"Error creating power tower: {str(e)}\n")
|
||||||
|
|
||||||
|
def create_power_line_simple(self, nodes, layer):
|
||||||
# Torres de alta tensión
|
# Torres de alta tensión
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
cylinder = Part.makeCylinder(1.0, 20.0, FreeCAD.Vector(node[0], node[1], 0) * scale - self.Origin )
|
cylinder = Part.makeCylinder(1.0, 20.0, FreeCAD.Vector(node[0], node[1], 0))# * scale - self.Origin )
|
||||||
pole = FreeCAD.ActiveDocument.addObject("Part::Feature", "PowerPole")
|
pole = FreeCAD.ActiveDocument.addObject("Part::Feature", "PowerPole")
|
||||||
layer.addObject(pole)
|
layer.addObject(pole)
|
||||||
pole.Shape = cylinder
|
pole.Shape = cylinder
|
||||||
pole.ViewObject.ShapeColor = self.feature_colors['power']['tower']
|
pole.ViewObject.ShapeColor = self.feature_colors['power']['tower']
|
||||||
|
|
||||||
# Líneas eléctricas
|
# Líneas eléctricas
|
||||||
points = [FreeCAD.Vector(n[0], n[1], .0) * scale - self.Origin for n in nodes]
|
points = [n for n in nodes]
|
||||||
cable = Draft.make_wire(points, closed=False, face=False)
|
cable = Draft.make_wire(points, closed=False, face=False)
|
||||||
cable.ViewObject.LineWidth = 3.0
|
cable.ViewObject.LineWidth = 3.0
|
||||||
cable.ViewObject.ShapeColor = self.feature_colors['power']['line']
|
cable.ViewObject.ShapeColor = self.feature_colors['power']['line']
|
||||||
layer.addObject(cable)
|
layer.addObject(cable)
|
||||||
|
|
||||||
def create_substation(self, nodes, layer):
|
def create_substation(self, way_id, tags, nodes, layer):
|
||||||
# Crear área de subestación
|
"""Crea subestaciones con todos los componentes detallados"""
|
||||||
polygon_points = [FreeCAD.Vector(n[0], n[1], 0) * scale - self.Origin for n in nodes]
|
try:
|
||||||
if len(polygon_points) > 2:
|
# 1. Parámetros base
|
||||||
polygon = Part.makePolygon(polygon_points)
|
voltage = self.parse_voltage(tags.get('voltage', '0'))
|
||||||
face = Part.Face(polygon)
|
substation_type = tags.get('substation', 'distribution')
|
||||||
substation = FreeCAD.ActiveDocument.addObject("Part::Feature", "Substation")
|
name = tags.get('name', f"Substation_{way_id}")
|
||||||
layer.addObject(substation)
|
|
||||||
substation.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.5) * scale - self.Origin )
|
if len(nodes) < 3:
|
||||||
substation.ViewObject.ShapeColor = self.feature_colors['power']['substation']
|
FreeCAD.Console.PrintWarning(f"Subestación {way_id} ignorada: polígono inválido\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. Geometría base
|
||||||
|
polygon_points = [n for n in nodes if isinstance(n, FreeCAD.Vector)]
|
||||||
|
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.Shape = base_extrude
|
||||||
|
base_obj.ViewObject.ShapeColor = (0.2, 0.2, 0.2)
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintError(f"Error base {way_id}: {str(e)}\n")
|
||||||
|
|
||||||
|
# 4. Cercado perimetral
|
||||||
|
if tags.get('fence', 'no') == 'yes':
|
||||||
|
try:
|
||||||
|
fence_offset = -0.8 # metros hacia adentro
|
||||||
|
fence_points = self.offset_polygon(polygon_points, fence_offset)
|
||||||
|
if len(fence_points) > 2:
|
||||||
|
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.Shape = fence_extrude
|
||||||
|
fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4)
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(f"Error cerca {way_id}: {str(e)}\n")
|
||||||
|
|
||||||
|
# 5. Edificio principal
|
||||||
|
if tags.get('building', 'no') == 'yes':
|
||||||
|
try:
|
||||||
|
building_offset = -2.0 # metros hacia adentro
|
||||||
|
building_height = 4.5 + (voltage / 100000)
|
||||||
|
building_points = self.offset_polygon(polygon_points, building_offset)
|
||||||
|
if len(building_points) > 2:
|
||||||
|
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.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:
|
||||||
|
num_transformers = int(tags.get('transformers', 1))
|
||||||
|
for i in range(num_transformers):
|
||||||
|
transformer_pos = self.calculate_equipment_position(
|
||||||
|
polygon_points,
|
||||||
|
index=i,
|
||||||
|
total=num_transformers,
|
||||||
|
offset=3.0
|
||||||
|
)
|
||||||
|
transformer = self.create_transformer(
|
||||||
|
position=transformer_pos,
|
||||||
|
voltage=voltage,
|
||||||
|
tech_type=tags.get('substation:type', 'outdoor')
|
||||||
|
)
|
||||||
|
layer.addObject(transformer)
|
||||||
|
except Exception as e:
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
tower_pos = self.calculate_tower_position(polygon_points)
|
||||||
|
tower = self.create_circuit_breaker_tower(
|
||||||
|
position=tower_pos,
|
||||||
|
voltage=voltage
|
||||||
|
)
|
||||||
|
layer.addObject(tower)
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(f"Error torre {way_id}: {str(e)}\n")
|
||||||
|
|
||||||
|
# 8. Propiedades técnicas
|
||||||
|
substation_data = layer.addObject("App::FeaturePython", f"{name}_Data")
|
||||||
|
props = {
|
||||||
|
"Voltage": voltage,
|
||||||
|
"Type": substation_type,
|
||||||
|
"Components": ['Base'] + (['Fence'] if 'fence' in tags else []) +
|
||||||
|
(['Building'] if 'building' in tags else []) +
|
||||||
|
[f'Transformer_{n + 1}' for n in range(num_transformers)]
|
||||||
|
}
|
||||||
|
for prop, value in props.items():
|
||||||
|
if isinstance(value, list):
|
||||||
|
substation_data.addProperty("App::PropertyStringList", prop, "Technical").Components = value
|
||||||
|
else:
|
||||||
|
substation_data.addProperty(
|
||||||
|
"App::PropertyFloat" if isinstance(value, float) else "App::PropertyString",
|
||||||
|
prop, "Technical").setValue(value)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintError(f"Error crítico en subestación {way_id}: {str(e)}\n")
|
||||||
|
|
||||||
|
def add_substation_fence(self, parent_obj, polygon_points):
|
||||||
|
"""Añade cerca perimetral"""
|
||||||
|
try:
|
||||||
|
offset_points = self.offset_polygon(polygon_points, -0.5)
|
||||||
|
fence = Part.makePolygon(offset_points).extrude(FreeCAD.Vector(0, 0, 2.5))
|
||||||
|
fence_obj = parent_obj.Document.addObject("Part::Feature", "Fence")
|
||||||
|
fence_obj.Shape = fence
|
||||||
|
fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4)
|
||||||
|
fence_obj.Placement.Base = parent_obj.Placement.Base
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(f"Error en cerca: {str(e)}\n")
|
||||||
|
|
||||||
|
def parse_voltage(self, voltage_str):
|
||||||
|
# Convertir valores comunes de voltaje a numéricos
|
||||||
|
voltage_map = {
|
||||||
|
'low': 400,
|
||||||
|
'medium': 20000,
|
||||||
|
'high': 110000,
|
||||||
|
'extra high': 380000,
|
||||||
|
'ultra high': 765000
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
return float(''.join(filter(str.isdigit, voltage_str.split()[0]))) * 1000
|
||||||
|
except:
|
||||||
|
return 20000 # Valor por defecto
|
||||||
|
|
||||||
|
def offset_polygon(self, points, offset):
|
||||||
|
"""Versión corregida sin error de sintaxis"""
|
||||||
|
if not points:
|
||||||
|
return []
|
||||||
|
|
||||||
|
sum_x = sum(p.x for p in points)
|
||||||
|
sum_y = sum(p.y for p in points)
|
||||||
|
centroid = FreeCAD.Vector(sum_x / len(points), sum_y / len(points), 0)
|
||||||
|
|
||||||
|
return [p + (centroid - p).normalize() * offset for p in points]
|
||||||
|
|
||||||
|
def create_transformer(self, position, voltage, technology):
|
||||||
|
# Crear transformador básico según características
|
||||||
|
height = 2.0 + (voltage / 100000)
|
||||||
|
radius = 0.5 + (voltage / 500000)
|
||||||
|
|
||||||
|
transformer = Part.makeCylinder(radius, height, FreeCAD.Vector(position))
|
||||||
|
transformer_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Transformer")
|
||||||
|
transformer_obj.Shape = transformer
|
||||||
|
transformer_obj.ViewObject.ShapeColor = (0.1, 0.1, 0.5) if 'oil' in technology.lower() else (0.5, 0.5, 0.5)
|
||||||
|
return transformer_obj
|
||||||
|
|
||||||
|
def calculate_equipment_position(self, polygon_points, index, total, offset=0.0):
|
||||||
|
"""Calcula posición equidistante alrededor del perímetro con offset"""
|
||||||
|
perimeter = sum((p2 - p1).Length for p1, p2 in zip(polygon_points, polygon_points[1:]))
|
||||||
|
target_dist = (perimeter / total) * index
|
||||||
|
|
||||||
|
accumulated = 0.0
|
||||||
|
for i in range(len(polygon_points) - 1):
|
||||||
|
p1 = polygon_points[i]
|
||||||
|
p2 = polygon_points[i + 1]
|
||||||
|
segment_length = (p2 - p1).Length
|
||||||
|
if accumulated + segment_length >= target_dist:
|
||||||
|
direction = (p2 - p1).normalize()
|
||||||
|
return p1 + direction * (target_dist - accumulated) + direction.cross(FreeCAD.Vector(0, 0, 1)) * offset
|
||||||
|
accumulated += segment_length
|
||||||
|
return polygon_points[0]
|
||||||
|
|
||||||
|
def create_circuit_breaker_tower(self, position, voltage):
|
||||||
|
"""Crea torre de seccionamiento especializada"""
|
||||||
|
tower = Part.makeCompound([
|
||||||
|
Part.makeCylinder(0.8, 12, position), # Poste principal
|
||||||
|
Part.makeBox(3, 0.3, 0.3, position + FreeCAD.Vector(-1.5, -0.15, 12)), # Cruceta superior
|
||||||
|
Part.makeBox(2.5, 0.3, 0.3, position + FreeCAD.Vector(-1.25, -0.15, 9)) # Cruceta media
|
||||||
|
])
|
||||||
|
tower_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "CircuitBreakerTower")
|
||||||
|
tower_obj.Shape = tower
|
||||||
|
tower_obj.ViewObject.ShapeColor = (0.1, 0.1, 0.1)
|
||||||
|
tower_obj.addProperty("App::PropertyFloat", "Voltage", "Technical").Voltage = voltage
|
||||||
|
return tower_obj
|
||||||
|
|
||||||
|
def calculate_tower_position(self, polygon_points):
|
||||||
|
# Colocar torre en el punto medio del lado más largo
|
||||||
|
# (Implementación compleja que requeriría cálculo geométrico)
|
||||||
|
return FreeCAD.Vector(polygon_points[0].x, polygon_points[0].y, 0)
|
||||||
|
|
||||||
|
|
||||||
def create_vegetation(self):
|
def create_vegetation(self):
|
||||||
vegetation_layer = self.create_layer("Vegetation")
|
vegetation_layer = self.create_layer("Vegetation")
|
||||||
@@ -251,7 +555,7 @@ class OSMImporter:
|
|||||||
for node_id, coords in self.nodes.items():
|
for node_id, coords in self.nodes.items():
|
||||||
# Verificar si es un árbol
|
# Verificar si es un árbol
|
||||||
# (Necesitarías procesar los tags de los nodos, implementación simplificada)
|
# (Necesitarías procesar los tags de los nodos, implementación simplificada)
|
||||||
cylinder = Part.makeCylinder(0.5, 5.0, FreeCAD.Vector(coords[0], coords[1], 0) * scale - self.Origin )
|
cylinder = Part.makeCylinder(0.5, 5.0, FreeCAD.Vector(coords[0], coords[1], 0))# * scale - self.Origin )
|
||||||
tree = FreeCAD.ActiveDocument.addObject("Part::Feature", "Tree")
|
tree = FreeCAD.ActiveDocument.addObject("Part::Feature", "Tree")
|
||||||
vegetation_layer.addObject(tree)
|
vegetation_layer.addObject(tree)
|
||||||
tree.Shape = cylinder
|
tree.Shape = cylinder
|
||||||
@@ -262,7 +566,7 @@ class OSMImporter:
|
|||||||
if 'natural' in data['tags'] or 'landuse' in data['tags']:
|
if 'natural' in data['tags'] or 'landuse' in data['tags']:
|
||||||
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 len(nodes) > 2:
|
if len(nodes) > 2:
|
||||||
polygon_points = [FreeCAD.Vector(n[0], n[1], 0) * scale - self.Origin for n in nodes]
|
polygon_points = [n for n in nodes]
|
||||||
polygon = Part.makePolygon(polygon_points)
|
polygon = Part.makePolygon(polygon_points)
|
||||||
face = Part.Face(polygon)
|
face = Part.Face(polygon)
|
||||||
area = vegetation_layer.addObject("Part::Feature", "GreenArea")
|
area = vegetation_layer.addObject("Part::Feature", "GreenArea")
|
||||||
@@ -276,9 +580,10 @@ class OSMImporter:
|
|||||||
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]
|
||||||
if len(nodes) > 2:
|
if len(nodes) > 2:
|
||||||
polygon_points = [FreeCAD.Vector(n[0], n[1], 0) * scale - self.Origin for n in nodes]
|
polygon_points = [n for n in nodes]
|
||||||
polygon = Part.makePolygon(polygon_points)
|
polygon = Part.makePolygon(polygon_points)
|
||||||
face = Part.Face(polygon)
|
face = Part.Face(polygon)
|
||||||
water = water_layer.addObject("Part::Feature", "WaterBody")
|
water = water_layer.addObject("Part::Feature", "WaterBody")
|
||||||
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']
|
||||||
|
|
||||||
|
|||||||
111
Importer/module_inserter.py
Normal file
111
Importer/module_inserter.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from PySide import QtGui, QtCore
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorDialog(QtGui.QDialog):
|
||||||
|
def __init__(self, csv_path, title, parent=None):
|
||||||
|
super(SelectorDialog, self).__init__(parent)
|
||||||
|
self.setWindowTitle(title)
|
||||||
|
self.csv_path = csv_path
|
||||||
|
self.data = []
|
||||||
|
self.brand_filter = ""
|
||||||
|
self.model_filter = ""
|
||||||
|
|
||||||
|
# Cargar datos del CSV
|
||||||
|
self.load_csv_data()
|
||||||
|
|
||||||
|
# Crear widgets
|
||||||
|
self.create_widgets()
|
||||||
|
self.create_layout()
|
||||||
|
self.create_connections()
|
||||||
|
|
||||||
|
def load_csv_data(self):
|
||||||
|
"""Carga los datos desde el archivo CSV"""
|
||||||
|
if os.path.exists(self.csv_path):
|
||||||
|
with open(self.csv_path, 'r') as f:
|
||||||
|
reader = csv.DictReader(f, delimiter=';')
|
||||||
|
self.data = [row for row in reader]
|
||||||
|
|
||||||
|
def get_unique_brands(self):
|
||||||
|
"""Obtiene marcas únicas"""
|
||||||
|
return list(set(row['Marca'] for row in self.data))
|
||||||
|
|
||||||
|
def get_models_by_brand(self, brand):
|
||||||
|
"""Filtra modelos por marca"""
|
||||||
|
return [row['Modelo'] for row in self.data if row['Marca'] == brand]
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
self.lbl_brand = QtGui.QLabel("Marca:")
|
||||||
|
self.cb_brand = QtGui.QComboBox()
|
||||||
|
self.cb_brand.addItems(self.get_unique_brands())
|
||||||
|
|
||||||
|
self.lbl_model = QtGui.QLabel("Modelo:")
|
||||||
|
self.cb_model = QtGui.QComboBox()
|
||||||
|
self.update_model_combo()
|
||||||
|
|
||||||
|
self.btn_accept = QtGui.QPushButton("Aceptar")
|
||||||
|
self.btn_cancel = QtGui.QPushButton("Cancelar")
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
layout = QtGui.QVBoxLayout()
|
||||||
|
form_layout = QtGui.QFormLayout()
|
||||||
|
form_layout.addRow(self.lbl_brand, self.cb_brand)
|
||||||
|
form_layout.addRow(self.lbl_model, self.cb_model)
|
||||||
|
|
||||||
|
button_layout = QtGui.QHBoxLayout()
|
||||||
|
button_layout.addWidget(self.btn_accept)
|
||||||
|
button_layout.addWidget(self.btn_cancel)
|
||||||
|
|
||||||
|
layout.addLayout(form_layout)
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def create_connections(self):
|
||||||
|
self.cb_brand.currentIndexChanged.connect(self.update_model_combo)
|
||||||
|
self.btn_accept.clicked.connect(self.accept)
|
||||||
|
self.btn_cancel.clicked.connect(self.reject)
|
||||||
|
|
||||||
|
def update_model_combo(self):
|
||||||
|
brand = self.cb_brand.currentText()
|
||||||
|
models = self.get_models_by_brand(brand)
|
||||||
|
self.cb_model.clear()
|
||||||
|
self.cb_model.addItems(models)
|
||||||
|
|
||||||
|
def get_selected_item(self):
|
||||||
|
brand = self.cb_brand.currentText()
|
||||||
|
model = self.cb_model.currentText()
|
||||||
|
for row in self.data:
|
||||||
|
if row['Marca'] == brand and row['Modelo'] == model:
|
||||||
|
return row
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def select_modulo():
|
||||||
|
csv_path = "/ruta/a/tu/databases/modulos.csv" # Ajusta esta ruta
|
||||||
|
dialog = SelectorDialog(csv_path, "Seleccionar Módulo")
|
||||||
|
if dialog.exec_():
|
||||||
|
selected = dialog.get_selected_item()
|
||||||
|
print("Módulo seleccionado:", selected) # Aquí puedes agregar la lógica de importación
|
||||||
|
|
||||||
|
|
||||||
|
def select_inversor():
|
||||||
|
csv_path = "/ruta/a/tu/databases/inversores.csv" # Ajusta esta ruta
|
||||||
|
dialog = SelectorDialog(csv_path, "Seleccionar Inversor")
|
||||||
|
if dialog.exec_():
|
||||||
|
selected = dialog.get_selected_item()
|
||||||
|
print("Inversor seleccionado:", selected) # Aquí puedes agregar la lógica de importación
|
||||||
|
|
||||||
|
|
||||||
|
# Crear una barra de herramientas para acceder fácilmente
|
||||||
|
toolbar = QtGui.QToolBar()
|
||||||
|
select_modulo_action = QtGui.QAction("Seleccionar Módulo", toolbar)
|
||||||
|
select_modulo_action.triggered.connect(select_modulo)
|
||||||
|
toolbar.addAction(select_modulo_action)
|
||||||
|
|
||||||
|
select_inversor_action = QtGui.QAction("Seleccionar Inversor", toolbar)
|
||||||
|
select_inversor_action.triggered.connect(select_inversor)
|
||||||
|
toolbar.addAction(select_inversor_action)
|
||||||
|
|
||||||
|
# Agregar la barra de herramientas a FreeCAD
|
||||||
|
Gui.addToolBar(toolbar)
|
||||||
@@ -53,12 +53,12 @@ class PVPlantWorkbench(Workbench):
|
|||||||
from Export import ExporterCommands
|
from Export import ExporterCommands
|
||||||
self.inportExportlist = ExporterCommands.Exportlist
|
self.inportExportlist = ExporterCommands.Exportlist
|
||||||
|
|
||||||
|
self.objectlist = PVPlantTools.objectlist
|
||||||
self.objectlist = [
|
''' [
|
||||||
"PVPlantTree",
|
"PVPlantTree",
|
||||||
"PVPlantBuilding",
|
"PVPlantBuilding",
|
||||||
"PVPlantFenceGroup",
|
"PVPlantFenceGroup",
|
||||||
]
|
]'''
|
||||||
self.electricalList = ["PVPlantStringBox",
|
self.electricalList = ["PVPlantStringBox",
|
||||||
"PVPlantCable",
|
"PVPlantCable",
|
||||||
"PVPlanElectricalLine",
|
"PVPlanElectricalLine",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Part
|
|||||||
import ArchComponent
|
import ArchComponent
|
||||||
from pivy import coin
|
from pivy import coin
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import DraftGeomUtils
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
import FreeCADGui, os
|
import FreeCADGui, os
|
||||||
@@ -516,6 +517,7 @@ class EarthWorksTaskPanel:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
FreeCAD.ActiveDocument.openTransaction("Calcular movimiento de tierras")
|
FreeCAD.ActiveDocument.openTransaction("Calcular movimiento de tierras")
|
||||||
|
|
||||||
def calculateEarthWorks(line, extreme=False):
|
def calculateEarthWorks(line, extreme=False):
|
||||||
pts = []
|
pts = []
|
||||||
pts1 = []
|
pts1 = []
|
||||||
@@ -576,33 +578,6 @@ class EarthWorksTaskPanel:
|
|||||||
elif ver == 1:
|
elif ver == 1:
|
||||||
from PVPlantPlacement import getCols
|
from PVPlantPlacement import getCols
|
||||||
columns = getCols(frames)
|
columns = getCols(frames)
|
||||||
|
|
||||||
'''colelements = set()
|
|
||||||
rowelements = set()
|
|
||||||
for groups in columns:
|
|
||||||
for group in groups:
|
|
||||||
for frame in group:
|
|
||||||
colelements.add(frame.Placement.Base.x)
|
|
||||||
rowelements.add(frame.Placement.Base.y)
|
|
||||||
colelements = sorted(colelements)
|
|
||||||
rowelements = sorted(rowelements, reverse=True)
|
|
||||||
print("Cols: ", len(colelements), " - ", colelements)
|
|
||||||
print("Rows: ", len(rowelements), " - ", rowelements)
|
|
||||||
|
|
||||||
a = []
|
|
||||||
colnum = len(colelements)
|
|
||||||
for r in range(len(rowelements)):
|
|
||||||
a.append([None] * colnum)
|
|
||||||
mat = np.array(a, dtype=object)
|
|
||||||
for groups in columns:
|
|
||||||
for group in groups:
|
|
||||||
for frame in group:
|
|
||||||
colidx = colelements.index(frame.Placement.Base.x)
|
|
||||||
rowidx = rowelements.index(frame.Placement.Base.y)
|
|
||||||
mat[rowidx][colidx] = frame
|
|
||||||
print(mat)
|
|
||||||
return'''
|
|
||||||
|
|
||||||
for groups in columns:
|
for groups in columns:
|
||||||
for group in groups:
|
for group in groups:
|
||||||
first = group[0]
|
first = group[0]
|
||||||
@@ -709,44 +684,41 @@ class EarthWorksTaskPanel:
|
|||||||
import Mesh
|
import Mesh
|
||||||
pro = utils.getProjected(sh)
|
pro = utils.getProjected(sh)
|
||||||
pro = utils.simplifyWire(pro)
|
pro = utils.simplifyWire(pro)
|
||||||
#pro = pro.makeOffset2D(20000, 2, False, False, True)
|
|
||||||
Part.show(sh, "loft")
|
|
||||||
Part.show(pro, "pro")
|
|
||||||
pts = [ver.Point for ver in pro.Vertexes]
|
pts = [ver.Point for ver in pro.Vertexes]
|
||||||
'''if pts[0] != pts[-1]:
|
|
||||||
pts.append(pts[0])'''
|
|
||||||
land.trim(pts, 1)
|
land.trim(pts, 1)
|
||||||
|
|
||||||
tmp = []
|
tmp = []
|
||||||
for face in sh.Faces:
|
for face in sh.Faces:
|
||||||
wire = face.Wires[0].copy()
|
wire = face.Wires[0].copy()
|
||||||
pl = wire.Placement.Base
|
pl = wire.Placement.Base
|
||||||
wire.Placement.Base = wire.Placement.Base - pl
|
wire.Placement.Base = wire.Placement.Base - pl
|
||||||
wire = wire.scale(2)
|
|
||||||
wire.Placement.Base = wire.Placement.Base + pl
|
|
||||||
#wire = wire.makeOffset2D(10000, 0, False, False, True)
|
|
||||||
wire.Placement.Base.z = wire.Placement.Base.z - 10000
|
|
||||||
face1 = Part.makeLoft([face.Wires[0], wire], True, True, False)
|
|
||||||
|
|
||||||
|
if DraftGeomUtils.isPlanar(wire):
|
||||||
|
# Caso simple
|
||||||
|
wire = wire.makeOffset2D(10000, 0, False, False, True)
|
||||||
|
wire.Placement.Base.z = wire.Placement.Base.z - 10000
|
||||||
|
wire = wire.makeFillet(1, wire.Edges)
|
||||||
|
tmp.append(Part.makeLoft([face.Wires[0], wire], True, True, False))
|
||||||
|
else:
|
||||||
|
# Caso complejo:
|
||||||
|
vertices = face.Vertexes
|
||||||
|
# Dividir rectángulo en 2 triángulos
|
||||||
|
triangles = [
|
||||||
|
[vertices[0], vertices[1], vertices[2]],
|
||||||
|
[vertices[2], vertices[3], vertices[0]]
|
||||||
|
]
|
||||||
|
|
||||||
Part.show(face1, "tool")
|
for tri in triangles:
|
||||||
#tmp.append(face.extrude(FreeCAD.Vector(0, 0, -10000)))
|
# Crear wire triangular
|
||||||
#Part.show(tmp[-1], "face-extrude")
|
wire = Part.makePolygon([v.Point for v in tri] + [tri[0].Point])
|
||||||
sh = sh.extrude(FreeCAD.Vector(0, 0, -10000))
|
# Hacer offset (ahora es coplanar por ser triángulo)
|
||||||
sh = Part.Solid(sh)
|
wire = wire.makeOffset2D(10000, 0, False, False, True)
|
||||||
Part.show(sh)
|
wire.Placement.Base.z = wire.Placement.Base.z - 10000
|
||||||
import MeshPart as mp
|
wire = wire.makeFillet(1, wire.Edges)
|
||||||
msh = mp.meshFromShape(Shape=sh) # , MaxLength=1)
|
tmp.append(Part.makeLoft([face.Wires[0], wire], True, True, False))
|
||||||
# msh = msh.smooth("Laplace", 3)
|
|
||||||
Mesh.show(msh, "tool")
|
final_tool = Part.makeCompound(tmp)
|
||||||
Mesh.show(land, "trim")
|
Part.show(final_tool, "tool")
|
||||||
'''inner = msh.inner(land)
|
|
||||||
Mesh.show(inner)
|
|
||||||
outer = msh.inner(land)
|
|
||||||
Mesh.show(outer)'''
|
|
||||||
'''intersec = land.section(msh, MinDist=0.01)
|
|
||||||
import Draft
|
|
||||||
for sec in intersec:
|
|
||||||
Draft.makeWire(sec)'''
|
|
||||||
|
|
||||||
FreeCAD.ActiveDocument.commitTransaction()
|
FreeCAD.ActiveDocument.commitTransaction()
|
||||||
self.closeForm()
|
self.closeForm()
|
||||||
@@ -964,4 +936,5 @@ def accept():
|
|||||||
|
|
||||||
FreeCAD.ActiveDocument.commitTransaction()
|
FreeCAD.ActiveDocument.commitTransaction()
|
||||||
self.closeForm()
|
self.closeForm()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -100,9 +100,11 @@ def get_elevation_from_oe(coordinates): # v1 deepseek
|
|||||||
|
|
||||||
return points
|
return points
|
||||||
|
|
||||||
|
|
||||||
def getElevationFromOE(coordinates):
|
def getElevationFromOE(coordinates):
|
||||||
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM."""
|
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM."""
|
||||||
|
|
||||||
|
import certifi
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
if len(coordinates) == 0:
|
if len(coordinates) == 0:
|
||||||
return None
|
return None
|
||||||
@@ -110,15 +112,15 @@ def getElevationFromOE(coordinates):
|
|||||||
from requests import get
|
from requests import get
|
||||||
import utm
|
import utm
|
||||||
|
|
||||||
str=""
|
locations_str=""
|
||||||
total = len(coordinates) - 1
|
total = len(coordinates) - 1
|
||||||
for i, point in enumerate(coordinates):
|
for i, point in enumerate(coordinates):
|
||||||
str += '{:.6f},{:.6f}'.format(point[0], point[1])
|
locations_str += '{:.6f},{:.6f}'.format(point[0], point[1])
|
||||||
if i != total:
|
if i != total:
|
||||||
str += '|'
|
locations_str += '|'
|
||||||
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + str
|
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + locations_str
|
||||||
try:
|
try:
|
||||||
r = get(query, timeout=20, verify=False)
|
r = get(query, timeout=20, verify=certifi.where()) # <-- Corrección aquí
|
||||||
except RequestException as e:
|
except RequestException as e:
|
||||||
points = []
|
points = []
|
||||||
for i, point in enumerate(coordinates):
|
for i, point in enumerate(coordinates):
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ class _PadTaskPanel:
|
|||||||
self.new = False
|
self.new = False
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
|
||||||
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantTrench.ui"))
|
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "Civil/PVPlantTrench.ui"))
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
FreeCAD.ActiveDocument.openTransaction("Create Pad")
|
FreeCAD.ActiveDocument.openTransaction("Create Pad")
|
||||||
|
|||||||
@@ -50,12 +50,9 @@ version = "0.1.0"
|
|||||||
|
|
||||||
|
|
||||||
def selectionFilter(sel, objtype):
|
def selectionFilter(sel, objtype):
|
||||||
print("type: ", objtype)
|
|
||||||
fil = []
|
fil = []
|
||||||
for obj in sel:
|
for obj in sel:
|
||||||
if hasattr(obj, "Proxy"):
|
if hasattr(obj, "Proxy"):
|
||||||
print("objeto:", obj.Proxy.__class__)
|
|
||||||
print(obj.Proxy.__class__ is objtype)
|
|
||||||
if obj.Proxy.__class__ is objtype:
|
if obj.Proxy.__class__ is objtype:
|
||||||
fil.append(obj)
|
fil.append(obj)
|
||||||
return fil
|
return fil
|
||||||
@@ -143,7 +140,7 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
|
|
||||||
def calculateWorkingArea(self):
|
def calculateWorkingArea(self):
|
||||||
self.Area = self.getProjected(self.PVArea.Shape)
|
self.Area = self.getProjected(self.PVArea.Shape)
|
||||||
tmp = FreeCAD.ActiveDocument.findObjects(Name="ProhibitedArea")
|
tmp = FreeCAD.ActiveDocument.findObjects(Name="ExclusionArea")
|
||||||
if len(tmp):
|
if len(tmp):
|
||||||
ProhibitedAreas = list()
|
ProhibitedAreas = list()
|
||||||
for obj in tmp:
|
for obj in tmp:
|
||||||
@@ -189,7 +186,6 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
def adjustToTerrain(self, coordinates):
|
def adjustToTerrain(self, coordinates):
|
||||||
mode = 1
|
mode = 1
|
||||||
terrain = self.Terrain.Mesh
|
terrain = self.Terrain.Mesh
|
||||||
type = 0
|
|
||||||
|
|
||||||
def placeRegion(df): # TODO: new
|
def placeRegion(df): # TODO: new
|
||||||
import MeshPart as mp
|
import MeshPart as mp
|
||||||
@@ -210,11 +206,10 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
pbot = FreeCAD.Vector(base)
|
pbot = FreeCAD.Vector(base)
|
||||||
pbot.y -= yl
|
pbot.y -= yl
|
||||||
line = Part.LineSegment(ptop, pbot).toShape()
|
line = Part.LineSegment(ptop, pbot).toShape()
|
||||||
if type == 0: # Mesh:
|
profilepoints = mp.projectShapeOnMesh(line, terrain, FreeCAD.Vector(0, 0, 1))[0]
|
||||||
profilepoints = mp.projectShapeOnMesh(line, terrain, FreeCAD.Vector(0, 0, 1))[0]
|
'''else: # Shape: sumamente lento por lo que quedaría eliminado si no se encuetra otro modo.
|
||||||
else: # Shape:
|
|
||||||
tmp = terrain.makeParallelProjection(line, FreeCAD.Vector(0, 0, 1))
|
tmp = terrain.makeParallelProjection(line, FreeCAD.Vector(0, 0, 1))
|
||||||
profilepoints = [ver.Point for ver in tmp.Vertexes]
|
profilepoints = [ver.Point for ver in tmp.Vertexes]'''
|
||||||
|
|
||||||
xx = list()
|
xx = list()
|
||||||
yy = list()
|
yy = list()
|
||||||
@@ -285,7 +280,7 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
placeRegion(df)
|
placeRegion(df)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def placeonregion_old(frames): # old
|
"""def placeonregion_old(frames): # old
|
||||||
for colnum, col in enumerate(frames):
|
for colnum, col in enumerate(frames):
|
||||||
groups = list()
|
groups = list()
|
||||||
groups.append([col[0]])
|
groups.append([col[0]])
|
||||||
@@ -381,7 +376,7 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
rot = FreeCAD.Rotation(FreeCAD.Vector(-1, 0, 0), vec)
|
rot = FreeCAD.Rotation(FreeCAD.Vector(-1, 0, 0), vec)
|
||||||
pl.Rotation = FreeCAD.Rotation(rot.toEuler()[0], rot.toEuler()[1], 0)
|
pl.Rotation = FreeCAD.Rotation(rot.toEuler()[0], rot.toEuler()[1], 0)
|
||||||
placements.append(pl)
|
placements.append(pl)
|
||||||
return placements
|
return placements"""
|
||||||
|
|
||||||
def isInside(self, frame, point):
|
def isInside(self, frame, point):
|
||||||
if self.Area.isInside(point, 10, True):
|
if self.Area.isInside(point, 10, True):
|
||||||
@@ -456,6 +451,8 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
if countcols == self.form.editColCount.value():
|
if countcols == self.form.editColCount.value():
|
||||||
offsetcols += valcols
|
offsetcols += valcols
|
||||||
countcols = 0
|
countcols = 0
|
||||||
|
print("/n/n")
|
||||||
|
print(cols)
|
||||||
return self.adjustToTerrain(cols)
|
return self.adjustToTerrain(cols)
|
||||||
|
|
||||||
def calculateNonAlignedArray(self):
|
def calculateNonAlignedArray(self):
|
||||||
@@ -566,19 +563,20 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
self.offsetY = FreeCAD.Units.Quantity(self.form.editOffsetVertical.text()).Value
|
self.offsetY = FreeCAD.Units.Quantity(self.form.editOffsetVertical.text()).Value
|
||||||
|
|
||||||
FreeCAD.ActiveDocument.openTransaction("Create Placement")
|
FreeCAD.ActiveDocument.openTransaction("Create Placement")
|
||||||
|
# 1. Calculate working area:
|
||||||
self.calculateWorkingArea()
|
self.calculateWorkingArea()
|
||||||
|
# 2. Calculate aligned array:
|
||||||
if self.form.cbAlignFrames.isChecked():
|
if self.form.cbAlignFrames.isChecked():
|
||||||
dataframe = self.calculateAlignedArray()
|
dataframe = self.calculateAlignedArray()
|
||||||
else:
|
else:
|
||||||
dataframe = self.calculateNonAlignedArray()
|
dataframe = self.calculateNonAlignedArray()
|
||||||
|
# 3. Adjust to terrain:
|
||||||
|
self.createFrameFromPoints(dataframe)
|
||||||
|
FreeCAD.ActiveDocument.commitTransaction()
|
||||||
|
|
||||||
# last step: ------------------------------
|
|
||||||
FreeCAD.ActiveDocument.RecomputesFrozen = False
|
FreeCAD.ActiveDocument.RecomputesFrozen = False
|
||||||
params.SetBool("AutoSaveEnabled", auto_save_enabled)
|
params.SetBool("AutoSaveEnabled", auto_save_enabled)
|
||||||
|
|
||||||
self.createFrameFromPoints(dataframe)
|
|
||||||
|
|
||||||
total_time = datetime.now() - starttime
|
total_time = datetime.now() - starttime
|
||||||
print(" -- Tiempo tardado:", total_time)
|
print(" -- Tiempo tardado:", total_time)
|
||||||
FreeCADGui.Control.closeDialog()
|
FreeCADGui.Control.closeDialog()
|
||||||
|
|||||||
@@ -120,12 +120,12 @@ class Terrain(ArchComponent.Component):
|
|||||||
"Surface",
|
"Surface",
|
||||||
"Use a Point Group to generate the surface")
|
"Use a Point Group to generate the surface")
|
||||||
|
|
||||||
if not ("Mesh" in pl):
|
if not ("mesh" in pl):
|
||||||
obj.addProperty("Mesh::PropertyMeshKernel",
|
obj.addProperty("Mesh::PropertyMeshKernel",
|
||||||
"Mesh",
|
"mesh",
|
||||||
"Surface",
|
"Surface",
|
||||||
"Mesh")
|
"Mesh")
|
||||||
obj.setEditorMode("Mesh", 1)
|
obj.setEditorMode("mesh", 1)
|
||||||
|
|
||||||
if not ("InitialMesh" in pl):
|
if not ("InitialMesh" in pl):
|
||||||
obj.addProperty("Mesh::PropertyMeshKernel",
|
obj.addProperty("Mesh::PropertyMeshKernel",
|
||||||
@@ -156,7 +156,7 @@ class Terrain(ArchComponent.Component):
|
|||||||
'''Do something when a property has changed'''
|
'''Do something when a property has changed'''
|
||||||
|
|
||||||
if prop == "InitialMesh":
|
if prop == "InitialMesh":
|
||||||
obj.Mesh = obj.InitialMesh.copy()
|
obj.mesh = obj.InitialMesh.copy()
|
||||||
|
|
||||||
if prop == "DEM" or prop == "CuttingBoundary":
|
if prop == "DEM" or prop == "CuttingBoundary":
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -197,11 +197,10 @@ class Terrain(ArchComponent.Component):
|
|||||||
del templist
|
del templist
|
||||||
|
|
||||||
# create xy coordinates
|
# create xy coordinates
|
||||||
import PVPlantSite
|
offset = self.site.Origin
|
||||||
offset = PVPlantSite.get().Origin
|
x = (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) * 1000 - offset.x
|
||||||
x = 1000 * (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) - offset.x
|
y = (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) * 1000 - offset.y
|
||||||
y = 1000 * (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) - offset.y
|
datavals = datavals * 1000 # Ajuste de altura
|
||||||
datavals = 1000 * datavals # - offset.z
|
|
||||||
|
|
||||||
# remove points out of area
|
# remove points out of area
|
||||||
# 1. coarse:
|
# 1. coarse:
|
||||||
@@ -210,7 +209,6 @@ class Terrain(ArchComponent.Component):
|
|||||||
inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0
|
inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0
|
||||||
tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x),
|
tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x),
|
||||||
x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0]
|
x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0]
|
||||||
print(tmp)
|
|
||||||
x_max = np.ndarray.max(tmp)
|
x_max = np.ndarray.max(tmp)
|
||||||
x_min = np.ndarray.min(tmp)
|
x_min = np.ndarray.min(tmp)
|
||||||
|
|
||||||
@@ -249,10 +247,10 @@ class Terrain(ArchComponent.Component):
|
|||||||
pts.append([x[i], y[j], datavals[j][i]])
|
pts.append([x[i], y[j], datavals[j][i]])
|
||||||
if len(pts) > 3:
|
if len(pts) > 3:
|
||||||
try:
|
try:
|
||||||
mesh.addMesh(Triangulation.Triangulate(pts))
|
triangulated = Triangulation.Triangulate(pts)
|
||||||
#Mesh.show(mesh)
|
mesh.addMesh(triangulated)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
print("error al procesar: {0} puntos".format(len(pts)))
|
print(f"Error al procesar {len(pts)} puntos: {str(e)}")
|
||||||
|
|
||||||
mesh.removeDuplicatedPoints()
|
mesh.removeDuplicatedPoints()
|
||||||
mesh.removeFoldsOnSurface()
|
mesh.removeFoldsOnSurface()
|
||||||
@@ -284,12 +282,9 @@ class Terrain(ArchComponent.Component):
|
|||||||
|
|
||||||
import MeshTools.Triangulation as Triangulation
|
import MeshTools.Triangulation as Triangulation
|
||||||
mesh = Triangulation.Triangulate(Data)
|
mesh = Triangulation.Triangulate(Data)
|
||||||
'''shape = PVPlantCreateTerrainMesh.MeshToShape(mesh)
|
|
||||||
shape.Placement.move(nbase)'''
|
|
||||||
|
|
||||||
obj.Shape = shape
|
|
||||||
if obj.DEM:
|
if obj.DEM:
|
||||||
obj.DEM = None
|
obj.DEM = None
|
||||||
|
obj.mesh = mesh
|
||||||
|
|
||||||
def execute(self, obj):
|
def execute(self, obj):
|
||||||
''''''
|
''''''
|
||||||
@@ -307,7 +302,6 @@ class ViewProviderTerrain:
|
|||||||
"A View Provider for the Pipe object"
|
"A View Provider for the Pipe object"
|
||||||
|
|
||||||
def __init__(self, vobj):
|
def __init__(self, vobj):
|
||||||
self.Object = vobj.Object
|
|
||||||
self.boundary_color = None
|
self.boundary_color = None
|
||||||
self.edge_style = None
|
self.edge_style = None
|
||||||
self.edge_color = None
|
self.edge_color = None
|
||||||
@@ -321,16 +315,16 @@ class ViewProviderTerrain:
|
|||||||
# Triangulation properties.
|
# Triangulation properties.
|
||||||
pl = vobj.PropertiesList
|
pl = vobj.PropertiesList
|
||||||
if not ("Transparency" in pl):
|
if not ("Transparency" in pl):
|
||||||
vobj.addProperty("App::PropertyIntegerConstraint",
|
'''vobj.addProperty("App::PropertyIntegerConstraint",
|
||||||
"Transparency",
|
"Transparency",
|
||||||
"Surface Style",
|
"Surface Style",
|
||||||
"Set triangle face transparency").Transparency = (50, 0, 100, 1)
|
"Set triangle face transparency").Transparency = (50, 0, 100, 1)'''
|
||||||
|
|
||||||
if not ("ShapeColor" in pl):
|
if not ("ShapeColor" in pl):
|
||||||
vobj.addProperty("App::PropertyColor",
|
vobj.addProperty("App::PropertyColor",
|
||||||
"ShapeColor",
|
"ShapeColor",
|
||||||
"Surface Style",
|
"Surface Style",
|
||||||
"Set triangle face color").ShapeColor = (r, g, b, vobj.Transparency / 100)
|
"Set triangle face color").ShapeColor = (0.0, 0.667, 0.49, vobj.Transparency / 100)
|
||||||
|
|
||||||
if not ("ShapeMaterial" in pl):
|
if not ("ShapeMaterial" in pl):
|
||||||
vobj.addProperty("App::PropertyMaterial",
|
vobj.addProperty("App::PropertyMaterial",
|
||||||
@@ -413,18 +407,21 @@ class ViewProviderTerrain:
|
|||||||
"Set major contour line width").MinorWidth = (2.0, 1.0, 20.0, 1.0)
|
"Set major contour line width").MinorWidth = (2.0, 1.0, 20.0, 1.0)
|
||||||
|
|
||||||
vobj.Proxy = self
|
vobj.Proxy = self
|
||||||
|
self.Object = vobj.Object
|
||||||
|
# Inicializar colores correctamente
|
||||||
vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor
|
vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor
|
||||||
|
|
||||||
def onDocumentRestored(self, vobj):
|
def onDocumentRestored(self, vobj):
|
||||||
self.setProperties(vobj)
|
self.setProperties(vobj)
|
||||||
|
|
||||||
def onChanged(self, vobj, prop):
|
def onChanged(self, vobj, prop):
|
||||||
''' Update Object visuals when a view property changed. '''
|
""" Update Object visuals when a view property changed. """
|
||||||
|
|
||||||
if prop == "ShapeColor" or prop == "Transparency":
|
if prop == "ShapeColor" or prop == "Transparency":
|
||||||
if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"):
|
if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"):
|
||||||
color = vobj.getPropertyByName("ShapeColor")
|
color = vobj.getPropertyByName("ShapeColor")
|
||||||
transparency = vobj.getPropertyByName("Transparency")
|
transparency = vobj.getPropertyByName("Transparency")
|
||||||
color = (color[0], color[1], color[2], transparency / 100)
|
color = (color[0], color[1], color[2], 50 / 100)
|
||||||
vobj.ShapeMaterial.DiffuseColor = color
|
vobj.ShapeMaterial.DiffuseColor = color
|
||||||
|
|
||||||
if prop == "ShapeMaterial":
|
if prop == "ShapeMaterial":
|
||||||
@@ -555,7 +552,7 @@ class ViewProviderTerrain:
|
|||||||
highlight.addChild(mat_binding)
|
highlight.addChild(mat_binding)
|
||||||
highlight.addChild(self.geo_coords)
|
highlight.addChild(self.geo_coords)
|
||||||
highlight.addChild(self.triangles)
|
highlight.addChild(self.triangles)
|
||||||
highlight.addChild(boundaries)
|
#highlight.addChild(boundaries)
|
||||||
|
|
||||||
# Face root.
|
# Face root.
|
||||||
face = coin.SoSeparator()
|
face = coin.SoSeparator()
|
||||||
@@ -573,14 +570,14 @@ class ViewProviderTerrain:
|
|||||||
surface_root.addChild(face)
|
surface_root.addChild(face)
|
||||||
surface_root.addChild(offset)
|
surface_root.addChild(offset)
|
||||||
surface_root.addChild(edge)
|
surface_root.addChild(edge)
|
||||||
surface_root.addChild(major_contours)
|
#surface_root.addChild(major_contours)
|
||||||
surface_root.addChild(minor_contours)
|
#surface_root.addChild(minor_contours)
|
||||||
vobj.addDisplayMode(surface_root, "Surface")
|
vobj.addDisplayMode(surface_root, "Surface")
|
||||||
|
|
||||||
# Boundary root.
|
# Boundary root.
|
||||||
boundary_root = coin.SoSeparator()
|
#boundary_root = coin.SoSeparator()
|
||||||
boundary_root.addChild(boundaries)
|
#boundary_root.addChild(boundaries)
|
||||||
vobj.addDisplayMode(boundary_root, "Boundary")
|
#vobj.addDisplayMode(boundary_root, "Boundary")
|
||||||
|
|
||||||
# Elevation/Shaded root.
|
# Elevation/Shaded root.
|
||||||
'''shaded_root = coin.SoSeparator()
|
'''shaded_root = coin.SoSeparator()
|
||||||
@@ -599,8 +596,8 @@ class ViewProviderTerrain:
|
|||||||
# Wireframe root.
|
# Wireframe root.
|
||||||
wireframe_root = coin.SoSeparator()
|
wireframe_root = coin.SoSeparator()
|
||||||
wireframe_root.addChild(edge)
|
wireframe_root.addChild(edge)
|
||||||
wireframe_root.addChild(major_contours)
|
#wireframe_root.addChild(major_contours)
|
||||||
wireframe_root.addChild(minor_contours)
|
#wireframe_root.addChild(minor_contours)
|
||||||
vobj.addDisplayMode(wireframe_root, "Wireframe")
|
vobj.addDisplayMode(wireframe_root, "Wireframe")
|
||||||
|
|
||||||
# Take features from properties.
|
# Take features from properties.
|
||||||
@@ -629,19 +626,19 @@ class ViewProviderTerrain:
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
if prop == "Mesh":
|
if prop == "Mesh":
|
||||||
print("update terrain mesh")
|
if obj.mesh:
|
||||||
mesh = obj.Mesh
|
print("Mostrar mesh")
|
||||||
copy_mesh = mesh.copy()
|
|
||||||
# copy_mesh.Placement.move(origin.Origin)
|
|
||||||
|
|
||||||
triangles = []
|
mesh = obj.mesh
|
||||||
for i in copy_mesh.Topology[1]:
|
vertices = [tuple(v) for v in mesh.Topology[0]]
|
||||||
triangles.extend(list(i))
|
faces = []
|
||||||
triangles.append(-1)
|
for face in mesh.Topology[1]:
|
||||||
|
faces.extend(face)
|
||||||
|
faces.append(-1)
|
||||||
|
|
||||||
self.geo_coords.point.values = copy_mesh.Topology[0]
|
# Asignar a los nodos de visualización
|
||||||
self.triangles.coordIndex.values = triangles
|
self.geo_coords.point.values = vertices # <-- ¡Clave!
|
||||||
del copy_mesh
|
self.triangles.coordIndex.values = faces # <-- ¡Clave!
|
||||||
|
|
||||||
def getDisplayModes(self, vobj):
|
def getDisplayModes(self, vobj):
|
||||||
''' Return a list of display modes. '''
|
''' Return a list of display modes. '''
|
||||||
@@ -656,7 +653,9 @@ class ViewProviderTerrain:
|
|||||||
return "Surface"
|
return "Surface"
|
||||||
|
|
||||||
def claimChildren(self):
|
def claimChildren(self):
|
||||||
return [self.Object.CuttingBoundary, ]
|
if hasattr(self, "Object") and self.Object:
|
||||||
|
return [self.Object.CuttingBoundary, ]
|
||||||
|
return []
|
||||||
|
|
||||||
def getIcon(self):
|
def getIcon(self):
|
||||||
return str(os.path.join(DirIcons, "terrain.svg"))
|
return str(os.path.join(DirIcons, "terrain.svg"))
|
||||||
|
|||||||
@@ -25,10 +25,8 @@ __title__ = "RebarCommands"
|
|||||||
__author__ = "Amritpal Singh"
|
__author__ = "Amritpal Singh"
|
||||||
__url__ = "https://www.freecadweb.org"
|
__url__ = "https://www.freecadweb.org"
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import FreeCADGui, FreeCAD
|
import FreeCADGui, FreeCAD
|
||||||
from PySide import QtGui, QtCore
|
from PySide import QtCore
|
||||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||||
from PVPlantResources import DirIcons as DirIcons
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
import os
|
import os
|
||||||
@@ -165,7 +163,6 @@ class CommandDivideArea:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Activated():
|
def Activated():
|
||||||
from Project.Area import PVPlantArea
|
|
||||||
sel = FreeCADGui.Selection.getSelection()[0]
|
sel = FreeCADGui.Selection.getSelection()[0]
|
||||||
|
|
||||||
|
|
||||||
@@ -210,7 +207,6 @@ class CommandFrameArea:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Activated():
|
def Activated():
|
||||||
from Project.Area import PVPlantArea
|
|
||||||
sel = FreeCADGui.Selection.getSelection()
|
sel = FreeCADGui.Selection.getSelection()
|
||||||
makeFramedArea(None, sel)
|
makeFramedArea(None, sel)
|
||||||
|
|
||||||
@@ -443,7 +439,7 @@ class CommandTrench: # V1:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def Activated():
|
def Activated():
|
||||||
"""Execute when the command is called."""
|
"""Execute when the command is called."""
|
||||||
import PVPlantTrench
|
from Civil import PVPlantTrench
|
||||||
sel = FreeCADGui.Selection.getSelection()
|
sel = FreeCADGui.Selection.getSelection()
|
||||||
done = False
|
done = False
|
||||||
|
|
||||||
@@ -489,7 +485,7 @@ class CommandSemiAutomaticTrench: # V1:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def Activated():
|
def Activated():
|
||||||
"""Execute when the command is called."""
|
"""Execute when the command is called."""
|
||||||
import PVPlantTrench
|
from Civil import PVPlantTrench
|
||||||
semi = PVPlantTrench.semiAutomaticTrench()
|
semi = PVPlantTrench.semiAutomaticTrench()
|
||||||
|
|
||||||
|
|
||||||
@@ -510,8 +506,8 @@ class CommandCalculateEarthworks:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Activated():
|
def Activated():
|
||||||
import PVPlantEarthworks
|
import PVPlantEarthWorks
|
||||||
TaskPanel = PVPlantEarthworks.EarthWorksTaskPanel()
|
TaskPanel = PVPlantEarthWorks.EarthWorksTaskPanel()
|
||||||
FreeCADGui.Control.showDialog(TaskPanel)
|
FreeCADGui.Control.showDialog(TaskPanel)
|
||||||
|
|
||||||
|
|
||||||
@@ -646,6 +642,15 @@ if FreeCAD.GuiUp:
|
|||||||
FreeCADGui.addCommand('PVPlantAdjustToTerrain', PVPlantPlacement.CommandAdjustToTerrain())
|
FreeCADGui.addCommand('PVPlantAdjustToTerrain', PVPlantPlacement.CommandAdjustToTerrain())
|
||||||
FreeCADGui.addCommand('PVPlantConvertTo', PVPlantPlacement.CommandConvert())
|
FreeCADGui.addCommand('PVPlantConvertTo', PVPlantPlacement.CommandConvert())
|
||||||
|
|
||||||
|
import hydro.hydrological as hydro
|
||||||
|
FreeCADGui.addCommand('HydrologicalAnalysis', hydro.CommandHydrologicalAnalysis())
|
||||||
|
|
||||||
|
import Vegetation.PVPlantTreeGenerator as TreeGenerator
|
||||||
|
FreeCADGui.addCommand('PVPlantTree', TreeGenerator.CommandTree())
|
||||||
|
|
||||||
|
import Project.GenerateExternalDocument as GED
|
||||||
|
FreeCADGui.addCommand('newExternalDocument', GED.CommandGenerateExternalDocument())
|
||||||
|
|
||||||
projectlist = [ # "Reload",
|
projectlist = [ # "Reload",
|
||||||
"PVPlantSite",
|
"PVPlantSite",
|
||||||
"ProjectSetup",
|
"ProjectSetup",
|
||||||
@@ -668,7 +673,11 @@ projectlist = [ # "Reload",
|
|||||||
# "PVPlantFoundation"
|
# "PVPlantFoundation"
|
||||||
# "GraphTerrainProfile",
|
# "GraphTerrainProfile",
|
||||||
# "Trace",
|
# "Trace",
|
||||||
|
"Separator",
|
||||||
|
'HydrologicalAnalysis',
|
||||||
|
'newExternalDocument',
|
||||||
]
|
]
|
||||||
|
|
||||||
pv_list = [
|
pv_list = [
|
||||||
# "RackType",
|
# "RackType",
|
||||||
# "PVPlantRackCheck",
|
# "PVPlantRackCheck",
|
||||||
@@ -678,3 +687,5 @@ pv_list = [
|
|||||||
"PVPlantConvertTo",
|
"PVPlantConvertTo",
|
||||||
# "PVArea"
|
# "PVArea"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
objectlist = ['PVPlantTree',]
|
||||||
|
|||||||
92
Project/GenerateExternalDocument.py
Normal file
92
Project/GenerateExternalDocument.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import FreeCAD
|
||||||
|
import FreeCADGui
|
||||||
|
from PySide2 import QtWidgets
|
||||||
|
import os
|
||||||
|
|
||||||
|
if FreeCAD.GuiUp:
|
||||||
|
import FreeCADGui
|
||||||
|
from PySide import QtCore, QtGui, QtWidgets
|
||||||
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
|
import os
|
||||||
|
else:
|
||||||
|
# \cond
|
||||||
|
def translate(ctxt, txt):
|
||||||
|
return txt
|
||||||
|
|
||||||
|
|
||||||
|
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||||
|
return txt
|
||||||
|
# \endcond
|
||||||
|
|
||||||
|
__title__ = "PVPlant Export to DXF"
|
||||||
|
__author__ = "Javier Braña"
|
||||||
|
__url__ = "http://www.sogos-solar.com"
|
||||||
|
|
||||||
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
|
||||||
|
|
||||||
|
def copy_object_with_reference():
|
||||||
|
try:
|
||||||
|
# Verificar selección
|
||||||
|
selected = FreeCADGui.Selection.getSelection()
|
||||||
|
if len(selected) != 1:
|
||||||
|
QtWidgets.QMessageBox.critical(None, "Error", "Selecciona exactamente un objeto")
|
||||||
|
return
|
||||||
|
|
||||||
|
original_doc = FreeCAD.ActiveDocument
|
||||||
|
original_obj = selected[0]
|
||||||
|
original_center = original_obj.Shape.BoundBox.Center
|
||||||
|
|
||||||
|
# Crear nuevo documento
|
||||||
|
new_doc = FreeCAD.newDocument(f"{original_doc.Name} - {original_obj.Label}")
|
||||||
|
|
||||||
|
# Copiar objeto al nuevo documento
|
||||||
|
new_obj = new_doc.copyObject(original_obj, True)
|
||||||
|
new_obj.Label = f"Linked_{original_obj.Label}"
|
||||||
|
new_obj.Placement.Base = original_obj.Placement.Base - original_center
|
||||||
|
|
||||||
|
# Guardar el documenton nuevp
|
||||||
|
path = os.path.dirname(FreeCAD.ActiveDocument.FileName)
|
||||||
|
new_doc.saveAs(os.path.join(path, new_doc.Name))
|
||||||
|
|
||||||
|
# Mantener posición original en el nuevo documento
|
||||||
|
# new_obj.Placement = original_obj.Placement
|
||||||
|
|
||||||
|
# Crear referencia (App::Link) en el documento original
|
||||||
|
link = original_doc.addObject("App::Link", f"Link_{new_obj.Label}")
|
||||||
|
link.LinkedObject = new_obj
|
||||||
|
|
||||||
|
# Mantener posición original del objeto
|
||||||
|
link.Placement = original_obj.Placement
|
||||||
|
|
||||||
|
# Actualizar vistas
|
||||||
|
original_doc.recompute()
|
||||||
|
new_doc.recompute()
|
||||||
|
|
||||||
|
# Regresar al documento original
|
||||||
|
FreeCAD.setActiveDocument(original_doc.Name)
|
||||||
|
|
||||||
|
#QtWidgets.QMessageBox.information(None, "Éxito", "Operación completada correctamente")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
QtWidgets.QMessageBox.critical(None, "Error", f"Error: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
# Ejecutar la función
|
||||||
|
class CommandGenerateExternalDocument:
|
||||||
|
def GetResources(self):
|
||||||
|
return {'Pixmap': str(os.path.join(DirIcons, "dxf.svg")),
|
||||||
|
'Accel': "P, E",
|
||||||
|
'MenuText': "Export to DXF",
|
||||||
|
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Export choosed layers to dxf")}
|
||||||
|
|
||||||
|
def Activated(self):
|
||||||
|
''' '''
|
||||||
|
copy_object_with_reference()
|
||||||
|
|
||||||
|
def IsActive(self):
|
||||||
|
if FreeCAD.ActiveDocument:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
@@ -21,6 +21,10 @@
|
|||||||
# ***********************************************************************
|
# ***********************************************************************
|
||||||
|
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
|
import FreeCADGui
|
||||||
|
from PySide import QtGui, QtCore
|
||||||
|
import datetime
|
||||||
|
import getpass
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
import FreeCADGui, os
|
import FreeCADGui, os
|
||||||
@@ -43,51 +47,99 @@ except AttributeError:
|
|||||||
import PVPlantResources
|
import PVPlantResources
|
||||||
from PVPlantResources import DirIcons as DirIcons
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
|
||||||
|
class SafeDict(dict):
|
||||||
|
"""Diccionario seguro para manejar placeholders no definidos"""
|
||||||
|
|
||||||
def rename(objects, mask, mode=0):
|
def __missing__(self, key):
|
||||||
'''
|
return f'{{{key}}}'
|
||||||
mode = 0/1/2/3
|
|
||||||
0: izquierda a derecha - arriba a abajo
|
|
||||||
1: arriba a abajo - izquierda a derecha
|
|
||||||
'''
|
|
||||||
|
|
||||||
# sort:
|
|
||||||
tmp = sorted(objects, key=lambda x: (x.Placement.Base.x,
|
|
||||||
x.Placement.Base.y))
|
|
||||||
|
|
||||||
for idx, obj in tmp:
|
|
||||||
obj.Name = name
|
|
||||||
|
|
||||||
class renamerTaskPanel:
|
|
||||||
def __init__(self, obj=None):
|
|
||||||
self.obj = obj
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------------------------------------
|
|
||||||
# Module widget form
|
|
||||||
# -------------------------------------------------------------------------------------------------------------
|
|
||||||
self.formRack = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantFrame.ui")
|
|
||||||
self.formRack.widgetTracker.setVisible(False)
|
|
||||||
self.formRack.comboFrameType.currentIndexChanged.connect(self.selectionchange)
|
|
||||||
|
|
||||||
self.formPiling = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantRackFixedPiling.ui")
|
|
||||||
self.formPiling.editBreadthwaysNumOfPost.valueChanged.connect(self.editBreadthwaysNumOfPostChange)
|
|
||||||
self.formPiling.editAlongNumOfPost.valueChanged.connect(self.editAlongNumOfPostChange)
|
|
||||||
|
|
||||||
self.form = [self.formRack, self.formPiling]
|
|
||||||
|
|
||||||
def accept(self):
|
|
||||||
self.closeForm()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def reject(self):
|
|
||||||
self.closeForm()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def closeForm(self):
|
|
||||||
FreeCADGui.Control.closeDialog()
|
|
||||||
|
|
||||||
|
|
||||||
class _CommandRenamer:
|
class RenameDialog(QtGui.QDialog):
|
||||||
|
def __init__(self):
|
||||||
|
super(RenameDialog, self).__init__()
|
||||||
|
self.setupUI()
|
||||||
|
|
||||||
|
def setupUI(self):
|
||||||
|
self.setWindowTitle("Renombrar objetos con plantilla")
|
||||||
|
self.setMinimumWidth(400)
|
||||||
|
layout = QtGui.QVBoxLayout(self)
|
||||||
|
|
||||||
|
# Campo para la plantilla
|
||||||
|
layout.addWidget(QtGui.QLabel("Plantilla de nombre:"))
|
||||||
|
self.template_input = QtGui.QLineEdit()
|
||||||
|
self.template_input.setPlaceholderText("Ej: {label}_mod_{index:03d}_{date:%Y%m%d}")
|
||||||
|
layout.addWidget(self.template_input)
|
||||||
|
|
||||||
|
# Info de placeholders
|
||||||
|
info = QtGui.QLabel(
|
||||||
|
"Placeholders disponibles:\n"
|
||||||
|
"• {index} - Número en orden\n"
|
||||||
|
"• {label} - Nombre actual del objeto\n"
|
||||||
|
"• {name} - Nombre interno\n"
|
||||||
|
"• {date} - Fecha actual\n"
|
||||||
|
"• {time} - Hora actual\n"
|
||||||
|
"• {user} - Usuario del sistema\n"
|
||||||
|
"• {datetime} - Fecha y hora completa\n"
|
||||||
|
"Formatos: {date:%Y/%m/%d}, {index:03d}, etc."
|
||||||
|
)
|
||||||
|
layout.addWidget(info)
|
||||||
|
|
||||||
|
# Botones
|
||||||
|
btn_box = QtGui.QDialogButtonBox()
|
||||||
|
btn_box.addButton(QtGui.QDialogButtonBox.Apply)
|
||||||
|
btn_box.addButton(QtGui.QDialogButtonBox.Close)
|
||||||
|
btn_box.clicked.connect(self.on_button_click)
|
||||||
|
layout.addWidget(btn_box)
|
||||||
|
|
||||||
|
def on_button_click(self, button):
|
||||||
|
if button == btn_box.button(QtGui.QDialogButtonBox.Apply):
|
||||||
|
self.rename_objects()
|
||||||
|
else:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def rename_objects(self):
|
||||||
|
template = self.template_input.text()
|
||||||
|
if not template:
|
||||||
|
QtGui.QMessageBox.warning(self, "Error", "¡La plantilla no puede estar vacía!")
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_objects = FreeCADGui.Selection.getSelection()
|
||||||
|
if not selected_objects:
|
||||||
|
QtGui.QMessageBox.warning(self, "Error", "¡No hay objetos seleccionados!")
|
||||||
|
return
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
user_name = getpass.getuser()
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for idx, obj in enumerate(selected_objects, 1):
|
||||||
|
try:
|
||||||
|
placeholders = SafeDict({
|
||||||
|
'index': idx,
|
||||||
|
'label': obj.Label,
|
||||||
|
'name': obj.Name,
|
||||||
|
'date': now.date(),
|
||||||
|
'time': now.time(),
|
||||||
|
'datetime': now,
|
||||||
|
'user': user_name
|
||||||
|
})
|
||||||
|
|
||||||
|
new_name = template.format_map(placeholders)
|
||||||
|
obj.Label = new_name
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"{obj.Name}: {str(e)}")
|
||||||
|
|
||||||
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
error_msg = "\n".join(errors)
|
||||||
|
QtGui.QMessageBox.critical(self, "Errores", f"Error(es) encontrado(s):\n{error_msg}")
|
||||||
|
else:
|
||||||
|
QtGui.QMessageBox.information(self, "Éxito", "¡Objetos renombrados correctamente!")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CommandRenamer:
|
||||||
"the Arch Building command definition"
|
"the Arch Building command definition"
|
||||||
|
|
||||||
def GetResources(self):
|
def GetResources(self):
|
||||||
|
|||||||
389
hydro/hydrological.py
Normal file
389
hydro/hydrological.py
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
import FreeCAD
|
||||||
|
import FreeCADGui
|
||||||
|
import Mesh
|
||||||
|
import Part
|
||||||
|
import numpy as np
|
||||||
|
import random
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
from multiprocessing import Pool, cpu_count
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
|
||||||
|
|
||||||
|
def mesh_to_numpy(mesh_obj):
|
||||||
|
"""Convierte la malla a arrays de NumPy con validación robusta"""
|
||||||
|
mesh = mesh_obj.Mesh
|
||||||
|
|
||||||
|
# Convertir vértices a array NumPy (shape: Nx3)
|
||||||
|
vertices = np.array([(v.x, v.y, v.z) for v in mesh.Points], dtype=np.float32)
|
||||||
|
|
||||||
|
# Convertir facetas a array NumPy (shape: Mx3)
|
||||||
|
facets = np.array( [f.PointIndices for f in mesh.Facets], dtype=np.uint32)
|
||||||
|
|
||||||
|
# Verificar integridad de índices
|
||||||
|
max_index = len(mesh.Points) - 1
|
||||||
|
if facets.size > 0 and (facets > max_index).any():
|
||||||
|
raise ValueError("Índices de vértices fuera de rango")
|
||||||
|
|
||||||
|
return vertices, facets
|
||||||
|
|
||||||
|
|
||||||
|
def build_adjacency_matrix(facets):
|
||||||
|
"""Construye matriz de adyacencia con conversión segura de tipos"""
|
||||||
|
edges = {}
|
||||||
|
adjacency = [[] for _ in range(len(facets))]
|
||||||
|
|
||||||
|
for idx, facet in enumerate(facets):
|
||||||
|
if len(facet) != 3:
|
||||||
|
continue
|
||||||
|
|
||||||
|
v0, v1, v2 = facet
|
||||||
|
|
||||||
|
for edge in [(v0, v1), (v1, v2), (v2, v0)]:
|
||||||
|
sorted_edge = tuple(sorted(edge))
|
||||||
|
|
||||||
|
if sorted_edge not in edges:
|
||||||
|
edges[sorted_edge] = []
|
||||||
|
edges[sorted_edge].append(idx)
|
||||||
|
|
||||||
|
# Procesar solo aristas con 2 facetas
|
||||||
|
for edge, facet_indices in edges.items():
|
||||||
|
if len(facet_indices) == 2:
|
||||||
|
f1, f2 = facet_indices
|
||||||
|
adjacency[f1].append(f2)
|
||||||
|
adjacency[f2].append(f1)
|
||||||
|
|
||||||
|
return adjacency
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_incenters_parallel(vertices, facets):
|
||||||
|
"""Cálculo paralelizado de incentros usando NumPy"""
|
||||||
|
v0 = vertices[facets[:, 0]]
|
||||||
|
v1 = vertices[facets[:, 1]]
|
||||||
|
v2 = vertices[facets[:, 2]]
|
||||||
|
|
||||||
|
a = np.linalg.norm(v1 - v2, axis=1)
|
||||||
|
b = np.linalg.norm(v0 - v2, axis=1)
|
||||||
|
c = np.linalg.norm(v0 - v1, axis=1)
|
||||||
|
|
||||||
|
perimeters = a + b + c
|
||||||
|
return (a[:, None] * v0 + b[:, None] * v1 + c[:, None] * v2) / perimeters[:, None]
|
||||||
|
|
||||||
|
|
||||||
|
def find_basins_parallel(args):
|
||||||
|
"""Función paralelizable para procesamiento de cuencas"""
|
||||||
|
chunk, adjacency, elevations = args
|
||||||
|
basins = []
|
||||||
|
visited = np.zeros(len(elevations), dtype=bool)
|
||||||
|
|
||||||
|
for seed in chunk:
|
||||||
|
if visited[seed]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
queue = deque([seed])
|
||||||
|
basin = []
|
||||||
|
min_elev = elevations[seed]
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
if visited[current]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
visited[current] = True
|
||||||
|
basin.append(current)
|
||||||
|
|
||||||
|
neighbors = [n for n in adjacency[current] if elevations[n] >= min_elev]
|
||||||
|
queue.extend(neighbors)
|
||||||
|
|
||||||
|
if len(basin) > 0:
|
||||||
|
basins.append(basin)
|
||||||
|
|
||||||
|
return basins
|
||||||
|
|
||||||
|
|
||||||
|
def find_hydrological_basins(mesh_obj, min_area=100):
|
||||||
|
"""Identificación de cuencas optimizada"""
|
||||||
|
FreeCAD.Console.PrintMessage(f" -- vertices y facets: ")
|
||||||
|
FreeCADGui.updateGui()
|
||||||
|
vertices, facets = mesh_to_numpy(mesh_obj)
|
||||||
|
FreeCAD.Console.PrintMessage(f" -- Adjacency: ")
|
||||||
|
FreeCADGui.updateGui()
|
||||||
|
adjacency = build_adjacency_matrix(facets)
|
||||||
|
FreeCAD.Console.PrintMessage(f" -- Elevations: ")
|
||||||
|
FreeCADGui.updateGui()
|
||||||
|
elevations = calculate_incenters_parallel(vertices, facets)[:, 2]
|
||||||
|
|
||||||
|
# Dividir trabajo en chunks
|
||||||
|
chunk_size = len(facets) // (cpu_count() * 2)
|
||||||
|
chunks = [
|
||||||
|
(chunk_range, adjacency, elevations) # Empaqueta los 3 argumentos
|
||||||
|
for chunk_range in [
|
||||||
|
range(i, min(i + chunk_size, len(facets)))
|
||||||
|
for i in range(0, len(facets), chunk_size)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Procesamiento paralelo
|
||||||
|
with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
|
||||||
|
results = list(executor.map(find_basins_parallel, chunks))
|
||||||
|
|
||||||
|
# Combinar resultados
|
||||||
|
all_basins = [b for sublist in results for b in sublist]
|
||||||
|
|
||||||
|
# Filtrar por área mínima
|
||||||
|
valid_basins = []
|
||||||
|
for basin in all_basins:
|
||||||
|
area = sum(triangle_area(vertices[facets[i]]) for i in basin)
|
||||||
|
if area >= min_area:
|
||||||
|
valid_basins.append({'facets': basin, 'area': area})
|
||||||
|
|
||||||
|
return valid_basins
|
||||||
|
|
||||||
|
|
||||||
|
def triangle_area(vertices):
|
||||||
|
"""Cálculo rápido de área con producto cruz"""
|
||||||
|
return 0.5 * np.linalg.norm(
|
||||||
|
np.cross(vertices[1] - vertices[0], vertices[2] - vertices[0])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_facet(facet):
|
||||||
|
"""Valida que la faceta sea un triángulo válido"""
|
||||||
|
return hasattr(facet, 'Points') and len(facet.Points) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_incenter(facet):
|
||||||
|
"""Calcula el incentro usando la función nativa de FreeCAD"""
|
||||||
|
try:
|
||||||
|
return facet.InCircle[0] # (x, y, z)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def build_adjacency(mesh):
|
||||||
|
"""Construye matriz de adyacencia eficiente en memoria"""
|
||||||
|
edges = {}
|
||||||
|
adjacency = [[] for _ in mesh.Facets]
|
||||||
|
|
||||||
|
for idx, facet in enumerate(mesh.Facets):
|
||||||
|
if not validate_facet(facet):
|
||||||
|
continue
|
||||||
|
|
||||||
|
pts = facet.Points
|
||||||
|
for edge in [(min(pts[0], pts[1]), max(pts[0], pts[1])),
|
||||||
|
(min(pts[1], pts[2]), max(pts[1], pts[2])),
|
||||||
|
(min(pts[2], pts[0]), max(pts[2], pts[0]))]:
|
||||||
|
if edge in edges:
|
||||||
|
neighbor = edges[edge]
|
||||||
|
adjacency[idx].append(neighbor)
|
||||||
|
adjacency[neighbor].append(idx)
|
||||||
|
del edges[edge] # Liberar memoria
|
||||||
|
else:
|
||||||
|
edges[edge] = idx
|
||||||
|
return adjacency
|
||||||
|
|
||||||
|
|
||||||
|
def find_hydrological_basins_old(mesh_obj, min_area=100):
|
||||||
|
"""Identificación de cuencas con validación de datos"""
|
||||||
|
mesh = mesh_obj.Mesh
|
||||||
|
adjacency = build_adjacency(mesh)
|
||||||
|
basin_map = {}
|
||||||
|
current_basin = 0
|
||||||
|
|
||||||
|
for seed in range(len(mesh.Facets)):
|
||||||
|
if seed in basin_map or not validate_facet(mesh.Facets[seed]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
queue = deque([seed])
|
||||||
|
basin_area = 0.0
|
||||||
|
basin_facets = []
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
facet_idx = queue.popleft()
|
||||||
|
if facet_idx in basin_map:
|
||||||
|
continue
|
||||||
|
|
||||||
|
facet = mesh.Facets[facet_idx]
|
||||||
|
in_center = calculate_incenter(facet)
|
||||||
|
if not in_center:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Verificar mínimo local
|
||||||
|
is_sink = True
|
||||||
|
for neighbor in adjacency[facet_idx]:
|
||||||
|
if neighbor >= len(mesh.Facets) or not validate_facet(mesh.Facets[neighbor]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
n_center = calculate_incenter(mesh.Facets[neighbor])
|
||||||
|
if n_center and n_center[2] < in_center[2]:
|
||||||
|
is_sink = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if is_sink:
|
||||||
|
basin_map[facet_idx] = current_basin
|
||||||
|
basin_facets.append(facet_idx)
|
||||||
|
basin_area += facet.Area
|
||||||
|
|
||||||
|
# Expansión controlada
|
||||||
|
for neighbor in adjacency[facet_idx]:
|
||||||
|
if neighbor not in basin_map:
|
||||||
|
queue.append(neighbor)
|
||||||
|
|
||||||
|
if basin_area >= min_area:
|
||||||
|
yield {
|
||||||
|
'facets': basin_facets,
|
||||||
|
'area': basin_area,
|
||||||
|
'depth': calculate_basin_depth(mesh, basin_facets)
|
||||||
|
}
|
||||||
|
current_basin += 1
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_basin_depth(mesh, basin_facets):
|
||||||
|
"""Calcula la profundidad máxima de la cuenca"""
|
||||||
|
min_z = float('inf')
|
||||||
|
max_z = -float('inf')
|
||||||
|
for idx in basin_facets:
|
||||||
|
center = calculate_incenter(mesh.Facets[idx])
|
||||||
|
if center:
|
||||||
|
min_z = min(min_z, center[2])
|
||||||
|
max_z = max(max_z, center[2])
|
||||||
|
return max_z - min_z if max_z != min_z else 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def simulate_water_flow(mesh_obj, basins, rainfall=1.0):
|
||||||
|
""" Simulación de flujo con prevención de bucles infinitos """
|
||||||
|
mesh = mesh_obj.Mesh
|
||||||
|
adjacency = build_adjacency(mesh)
|
||||||
|
flow_paths = []
|
||||||
|
|
||||||
|
for basin in basins:
|
||||||
|
start_facets = basin['facets'][:2] # Muestra primeros 10 caminos
|
||||||
|
for start in start_facets:
|
||||||
|
path = []
|
||||||
|
visited = set()
|
||||||
|
current = start
|
||||||
|
|
||||||
|
while current is not None and current not in visited:
|
||||||
|
visited.add(current)
|
||||||
|
facet = mesh.Facets[current]
|
||||||
|
center = calculate_incenter(facet)
|
||||||
|
if not center:
|
||||||
|
break
|
||||||
|
|
||||||
|
path.append(FreeCAD.Vector(*center))
|
||||||
|
|
||||||
|
# Buscar vecino más bajo
|
||||||
|
next_facet = None
|
||||||
|
min_elev = float('inf')
|
||||||
|
for neighbor in adjacency[current]:
|
||||||
|
if neighbor >= len(mesh.Facets):
|
||||||
|
continue
|
||||||
|
|
||||||
|
n_center = calculate_incenter(mesh.Facets[neighbor])
|
||||||
|
if n_center and n_center[2] < min_elev:
|
||||||
|
min_elev = n_center[2]
|
||||||
|
next_facet = neighbor
|
||||||
|
|
||||||
|
current = next_facet if min_elev < center[2] else None
|
||||||
|
|
||||||
|
if len(path) > 1:
|
||||||
|
flow_paths.append(path)
|
||||||
|
|
||||||
|
return flow_paths
|
||||||
|
|
||||||
|
|
||||||
|
def colorize_mesh(mesh_obj, facet_indices, color):
|
||||||
|
"""Coloriza facetas específicas de forma compatible"""
|
||||||
|
mesh = mesh_obj.Mesh
|
||||||
|
|
||||||
|
# Crear nuevo objeto Mesh
|
||||||
|
colored_mesh = Mesh.Mesh()
|
||||||
|
colored_mesh.addMesh(mesh)
|
||||||
|
|
||||||
|
# Crear nuevo objeto en el documento
|
||||||
|
new_obj = FreeCAD.ActiveDocument.addObject("Mesh::Feature", "ColoredBasin")
|
||||||
|
new_obj.Mesh = colored_mesh
|
||||||
|
|
||||||
|
# Asignar colores a los vértices
|
||||||
|
vcolors = []
|
||||||
|
for idx in range(len(mesh.Points)):
|
||||||
|
vcolors.append((0.8, 0.8, 0.8)) # Color base
|
||||||
|
|
||||||
|
for facet_id in facet_indices:
|
||||||
|
facet = mesh.Facets[facet_id]
|
||||||
|
for vtx in facet.PointIndices:
|
||||||
|
vcolors[vtx] = color # Color de la cuenca
|
||||||
|
|
||||||
|
new_obj.ViewObject.PointColor = vcolors
|
||||||
|
new_obj.ViewObject.Lighting = "One side"
|
||||||
|
new_obj.ViewObject.Shading = "Flat Lines"
|
||||||
|
|
||||||
|
|
||||||
|
def create_polyline(points):
|
||||||
|
"""Crea un objeto Polyline en FreeCAD"""
|
||||||
|
if len(points) < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
poly = Part.makePolygon(points)
|
||||||
|
obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "FlowPath")
|
||||||
|
obj.Shape = poly
|
||||||
|
obj.ViewObject.LineWidth = 2.0
|
||||||
|
obj.ViewObject.LineColor = (0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandHydrologicalAnalysis:
|
||||||
|
|
||||||
|
def GetResources(self):
|
||||||
|
return {'Pixmap': str(os.path.join(DirIcons, "drop.jpg")),
|
||||||
|
'MenuText': "Hidrological analysis",
|
||||||
|
'Accel': "H, A",
|
||||||
|
'ToolTip': "Hidrological analysis"}
|
||||||
|
|
||||||
|
def IsActive(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def Activated(self):
|
||||||
|
# User input parameters (example values)
|
||||||
|
os.environ['OMP_NUM_THREADS'] = str(cpu_count())
|
||||||
|
os.environ['MKL_NUM_THREADS'] = str(cpu_count())
|
||||||
|
os.environ["FREECAD_NO_FORK"] = "1" # Desactiva el fork en sistemas Unix
|
||||||
|
#try:
|
||||||
|
# Parámetros de usuario
|
||||||
|
min_basin_area = 100 # m²
|
||||||
|
rainfall_intensity = 1.0
|
||||||
|
|
||||||
|
# Validar selección
|
||||||
|
mesh_obj = FreeCADGui.Selection.getSelection()[0]
|
||||||
|
if not mesh_obj.isDerivedFrom("Mesh::Feature"):
|
||||||
|
raise ValueError("Selecciona un objeto de malla")
|
||||||
|
|
||||||
|
# Procesamiento principal
|
||||||
|
FreeCAD.Console.PrintMessage(f"buscar basins: ")
|
||||||
|
FreeCADGui.updateGui()
|
||||||
|
basins = list(find_hydrological_basins(mesh_obj, min_basin_area))
|
||||||
|
FreeCAD.Console.PrintMessage(f" - Cuencas identificadas: {len(basins)}\n")
|
||||||
|
'''FreeCAD.Console.PrintMessage(f"simulate_water_flow: ")
|
||||||
|
FreeCADGui.updateGui()
|
||||||
|
flow_paths = simulate_water_flow(mesh_obj, basins, rainfall_intensity)
|
||||||
|
FreeCAD.Console.PrintMessage(f" - Trayectorias de flujo generadas: {len(flow_paths)}\n")
|
||||||
|
FreeCADGui.updateGui()'''
|
||||||
|
|
||||||
|
# Visualización
|
||||||
|
for basin in basins:
|
||||||
|
color = (random.random(), random.random(), random.random())
|
||||||
|
colorize_mesh(mesh_obj, basin['facets'], color)
|
||||||
|
|
||||||
|
'''for path in flow_paths:
|
||||||
|
create_polyline(path)'''
|
||||||
|
|
||||||
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
|
||||||
|
'''except Exception as e:
|
||||||
|
FreeCAD.Console.PrintError(f"Error: {str(e)}\n")
|
||||||
|
finally:
|
||||||
|
# Limpieza de memoria
|
||||||
|
import gc
|
||||||
|
gc.collect()'''
|
||||||
14
reload.py
14
reload.py
@@ -26,16 +26,17 @@ class _CommandReload:
|
|||||||
PVPlantGeoreferencing, PVPlantImportGrid, PVPlantTerrainAnalisys, \
|
PVPlantGeoreferencing, PVPlantImportGrid, PVPlantTerrainAnalisys, \
|
||||||
PVPlantSite, PVPlantRackChecking, PVPlantFence, PVPlantFencePost, PVPlantFenceGate, \
|
PVPlantSite, PVPlantRackChecking, PVPlantFence, PVPlantFencePost, PVPlantFenceGate, \
|
||||||
PVPlantCreateTerrainMesh, \
|
PVPlantCreateTerrainMesh, \
|
||||||
PVPlantFoundation, PVPlantTreeGenerator, PVPlantBuilding, PVPlantTrench, PVPlantEarthWorks, PVPlantPad, \
|
PVPlantFoundation, PVPlantBuilding, PVPlantEarthWorks, PVPlantPad, \
|
||||||
PVPlantRoad, PVPlantTerrain, PVPlantStringing, PVPlantManhole, \
|
PVPlantRoad, PVPlantTerrain, PVPlantStringing, PVPlantManhole, \
|
||||||
GraphProfile
|
GraphProfile
|
||||||
|
from Civil import PVPlantTrench
|
||||||
|
from Vegetation import PVPlantTreeGenerator
|
||||||
|
|
||||||
from Mechanical.Frame import PVPlantFrame
|
from Mechanical.Frame import PVPlantFrame
|
||||||
from Project.Area import PVPlantArea, PVPlantAreaUtils
|
from Project.Area import PVPlantArea, PVPlantAreaUtils
|
||||||
#from Importer import importDXF
|
#from Importer import importDXF
|
||||||
from Export import PVPlantBOQCivil, PVPlantBOQElectrical, PVPlantBOQMechanical, exportPVSyst, exportDXF
|
from Export import PVPlantBOQCivil, PVPlantBOQElectrical, PVPlantBOQMechanical, exportPVSyst, exportDXF
|
||||||
from Utils import PVPlantUtils, PVPlantTrace, m_gui_edit, profile_editor, graphics
|
from Utils import PVPlantUtils, PVPlantTrace, m_gui_edit, profile_editor, graphics
|
||||||
#from Lib import GoogleMapDownloader
|
|
||||||
|
|
||||||
from Electrical.Cable import PVPlantCable, PVPlantElectricalLine
|
from Electrical.Cable import PVPlantCable, PVPlantElectricalLine
|
||||||
from Electrical import Conduit
|
from Electrical import Conduit
|
||||||
@@ -47,6 +48,8 @@ class _CommandReload:
|
|||||||
import MeshTools.Triangulation as Triangulation
|
import MeshTools.Triangulation as Triangulation
|
||||||
from Project import ProjectSetup
|
from Project import ProjectSetup
|
||||||
import importlib
|
import importlib
|
||||||
|
import hydro.hydrological as hydro
|
||||||
|
import Importer.importOSM as iOSM
|
||||||
|
|
||||||
importlib.reload(ProjectSetup)
|
importlib.reload(ProjectSetup)
|
||||||
importlib.reload(PVPlantPlacement)
|
importlib.reload(PVPlantPlacement)
|
||||||
@@ -98,6 +101,11 @@ class _CommandReload:
|
|||||||
importlib.reload(layoutToExcel)
|
importlib.reload(layoutToExcel)
|
||||||
importlib.reload(Conduit)
|
importlib.reload(Conduit)
|
||||||
|
|
||||||
|
importlib.reload(hydro)
|
||||||
|
importlib.reload(iOSM)
|
||||||
|
|
||||||
|
import Project.GenerateExternalDocument as GED
|
||||||
|
importlib.reload(GED)
|
||||||
|
|
||||||
#importlib.reload(GoogleMapDownloader)
|
#importlib.reload(GoogleMapDownloader)
|
||||||
print("Reload modules...")
|
print("Reload modules...")
|
||||||
|
|||||||
Reference in New Issue
Block a user