Mejoras PVPlantTerrain: fix XYZ import, DEM rendimiento, ViewProvider boundary+contour, error handling

This commit is contained in:
2026-05-02 22:47:58 +02:00
parent 3bcdc95978
commit 0b13a8c5f1
+116 -52
View File
@@ -129,8 +129,14 @@ class Terrain(ArchComponent.Component):
# obj.IfcType = "Fence"
# obj.MoveWithHost = False
try:
self.site = PVPlantSite.get()
except Exception:
self.site = None
if self.site:
self.site.Terrain = obj
else:
FreeCAD.Console.PrintWarning('Terrain: No se encontró Site, algunas funciones DEM requerirán Site.\n')
obj.ViewObject.ShapeColor = (0.0000, 0.6667, 0.4980)
obj.ViewObject.LineColor = (0.0000, 0.6000, 0.4392)
@@ -237,7 +243,7 @@ class Terrain(ArchComponent.Component):
del templist
# create xy coordinates
offset = self.site.Origin
offset = self.site.Origin if self.site else FreeCAD.Vector(0, 0, 0)
x = (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) * 1000 - offset.x
y = (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) * 1000 - offset.y
datavals = datavals * 1000 # Ajuste de altura
@@ -269,35 +275,90 @@ class Terrain(ArchComponent.Component):
stepx = math.ceil(nx / stepsize)
stepy = math.ceil(ny / stepsize)
mesh = Mesh.Mesh()
for indx in range(stepx):
inix = indx * stepsize - 1
finx = min([stepsize * (indx + 1), len(x)-1])
for indy in range(stepy):
iniy = indy * stepsize - 1
finy = min([stepsize * (indy + 1), len(y) - 1])
pts = []
for i in range(inix, finx):
for j in range(iniy, finy):
if datavals[j][i] != nodata_value:
# Malla completa primero como numpy y filtramos todo de una
from datetime import datetime
t_start = datetime.now()
# Crear grid completo de coordenadas
XX, YY = np.meshgrid(x, y)
ZZ = datavals.copy()
# Enmascarar nodata
mask_valida = ZZ != nodata_value
# Enmascarar cutting boundary si existe
if obj.CuttingBoundary:
if obj.CuttingBoundary.Shape.isInside(FreeCAD.Vector(x[i], y[j], 0), 0, True):
pts.append([x[i], y[j], datavals[j][i]])
else:
pts.append([x[i], y[j], datavals[j][i]])
if len(pts) > 3:
from FreeCAD import Base
shape = obj.CuttingBoundary.Shape
mask_boundary = np.zeros_like(ZZ, dtype=bool)
# Sampling: revisar solo puntos estratégicos para boundary grande
stride = max(1, min(nx, ny) // 200)
for i in range(0, ny, stride):
for j in range(0, nx, stride):
if mask_valida[i, j]:
if shape.isInside(FreeCAD.Vector(x[j], y[i], 0), 0, True):
mask_boundary[i, j] = True
mask_valida = mask_valida & mask_boundary
# Extraer puntos válidos como lista plana
pts_validos = np.column_stack([
XX[mask_valida].ravel(),
YY[mask_valida].ravel(),
ZZ[mask_valida].ravel()
])
del XX, YY, ZZ, mask_valida
# Triangulación completa de una vez (no por parches)
mesh = Mesh.Mesh()
if len(pts_validos) > 3:
# Si hay muchos puntos, triangulamos por parches para evitar OOM
patch_size = 50000
n_patches = max(1, math.ceil(len(pts_validos) / patch_size))
for p in range(n_patches):
patch = pts_validos[p * patch_size:(p + 1) * patch_size].tolist()
if len(patch) > 3:
try:
triangulated = Triangulation.Triangulate(pts)
triangulated = Triangulation.Triangulate(patch)
mesh.addMesh(triangulated)
except TypeError:
print(f"Error al procesar {len(pts)} puntos: {str(e)}")
except TypeError as e:
print(f"Patch {p}: error al procesar {len(patch)} puntos: {str(e)}")
except Exception as e:
print(f"Patch {p}: error inesperado: {str(e)}")
print(f'Terraín DEM: {len(pts_validos)} pts válidos, {n_patches} parches, {datetime.now()-t_start}')
del pts_validos
mesh.removeDuplicatedPoints()
mesh.removeFoldsOnSurface()
obj.InitialMesh = mesh.copy()
Mesh.show(mesh)
# Limpiar objetos mesh huérfanos previos si existen
for o in FreeCAD.ActiveDocument.Objects:
if o.TypeId == 'Mesh::Feature' and o.Label.startswith('Terrain_mesh_'):
FreeCAD.ActiveDocument.removeObject(o.Name)
mesh_obj = Mesh.show(mesh)
mesh_obj.Label = 'Terrain_mesh_' + obj.Label
elif suffix in ['.xyz']:
data = open_xyz_mmap(obj.DEM)
pts_array = open_xyz_mmap(obj.DEM)
if pts_array is not None and len(pts_array) > 3:
import MeshTools.Triangulation as Triangulation
import Mesh
if obj.CuttingBoundary:
mask = []
for pt in pts_array:
mask.append(obj.CuttingBoundary.Shape.isInside(
FreeCAD.Vector(pt[0], pt[1], 0), 0, True))
pts_array = pts_array[mask]
if len(pts_array) > 3:
from datetime import datetime
t0 = datetime.now()
pts_list = pts_array.tolist()
mesh = Triangulation.Triangulate(pts_list)
mesh.removeDuplicatedPoints()
mesh.removeFoldsOnSurface()
obj.InitialMesh = mesh.copy()
Mesh.show(mesh)
print(f'XYZ import: {len(pts_array)} puntos en {datetime.now()-t0}')
@@ -547,47 +608,47 @@ class ViewProviderTerrain:
offset.factor = -2.0
# Boundary features.
'''self.boundary_color = coin.SoBaseColor()
self.boundary_color = coin.SoBaseColor()
self.boundary_coords = coin.SoGeoCoordinate()
self.boundary_lines = coin.SoLineSet()
self.boundary_style = coin.SoDrawStyle()
self.boundary_style.style = coin.SoDrawStyle.LINES'''
self.boundary_style.style = coin.SoDrawStyle.LINES
# Boundary root.
'''boundaries = coin.SoType.fromName('SoFCSelection').createInstance()
boundaries = coin.SoType.fromName('SoFCSelection').createInstance()
boundaries.style = 'EMISSIVE_DIFFUSE'
boundaries.addChild(self.boundary_color)
boundaries.addChild(self.boundary_style)
boundaries.addChild(self.boundary_coords)
boundaries.addChild(self.boundary_lines)'''
boundaries.addChild(self.boundary_lines)
# Major Contour features.
'''self.major_color = coin.SoBaseColor()
self.major_color = coin.SoBaseColor()
self.major_coords = coin.SoGeoCoordinate()
self.major_lines = coin.SoLineSet()
self.major_style = coin.SoDrawStyle()
self.major_style.style = coin.SoDrawStyle.LINES'''
self.major_style.style = coin.SoDrawStyle.LINES
# Major Contour root.
'''major_contours = coin.SoSeparator()
major_contours = coin.SoSeparator()
major_contours.addChild(self.major_color)
major_contours.addChild(self.major_style)
major_contours.addChild(self.major_coords)
major_contours.addChild(self.major_lines)'''
major_contours.addChild(self.major_lines)
# Minor Contour features.
'''self.minor_color = coin.SoBaseColor()
self.minor_color = coin.SoBaseColor()
self.minor_coords = coin.SoGeoCoordinate()
self.minor_lines = coin.SoLineSet()
self.minor_style = coin.SoDrawStyle()
self.minor_style.style = coin.SoDrawStyle.LINES'''
self.minor_style.style = coin.SoDrawStyle.LINES
# Minor Contour root.
'''minor_contours = coin.SoSeparator()
minor_contours = coin.SoSeparator()
minor_contours.addChild(self.minor_color)
minor_contours.addChild(self.minor_style)
minor_contours.addChild(self.minor_coords)
minor_contours.addChild(self.minor_lines)'''
minor_contours.addChild(self.minor_lines)
# Highlight for selection.
highlight = coin.SoType.fromName('SoFCSelection').createInstance()
@@ -596,7 +657,7 @@ class ViewProviderTerrain:
highlight.addChild(mat_binding)
highlight.addChild(self.geo_coords)
highlight.addChild(self.triangles)
#highlight.addChild(boundaries)
highlight.addChild(boundaries)
# Face root.
face = coin.SoSeparator()
@@ -609,19 +670,19 @@ class ViewProviderTerrain:
edge.addChild(self.edge_style)
edge.addChild(highlight)
# Surface root.
# Surface root - con contour lines visibles.
surface_root = coin.SoSeparator()
surface_root.addChild(face)
surface_root.addChild(offset)
surface_root.addChild(edge)
#surface_root.addChild(major_contours)
#surface_root.addChild(minor_contours)
surface_root.addChild(major_contours)
surface_root.addChild(minor_contours)
vobj.addDisplayMode(surface_root, "Surface")
# Boundary root.
#boundary_root = coin.SoSeparator()
#boundary_root.addChild(boundaries)
#vobj.addDisplayMode(boundary_root, "Boundary")
boundary_root = coin.SoSeparator()
boundary_root.addChild(boundaries)
vobj.addDisplayMode(boundary_root, "Boundary")
# Elevation/Shaded root.
'''shaded_root = coin.SoSeparator()
@@ -648,26 +709,28 @@ class ViewProviderTerrain:
self.onChanged(vobj, "ShapeColor")
self.onChanged(vobj, "LineColor")
self.onChanged(vobj, "LineWidth")
#self.onChanged(vobj, "BoundaryColor")
#self.onChanged(vobj, "BoundaryWidth")
#self.onChanged(vobj, "BoundaryPattern")
#self.onChanged(vobj, "PatternScale")
#self.onChanged(vobj, "MajorColor")
#self.onChanged(vobj, "MajorWidth")
#self.onChanged(vobj, "MinorColor")
#self.onChanged(vobj, "MinorWidth")
self.onChanged(vobj, "BoundaryColor")
self.onChanged(vobj, "BoundaryWidth")
self.onChanged(vobj, "BoundaryPattern")
self.onChanged(vobj, "PatternScale")
self.onChanged(vobj, "MajorColor")
self.onChanged(vobj, "MajorWidth")
self.onChanged(vobj, "MinorColor")
self.onChanged(vobj, "MinorWidth")
def updateData(self, obj, prop):
''' Update Object visuals when a data property changed. '''
# Set geosystem.
geo_system = ["UTM", FreeCAD.ActiveDocument.Site.UtmZone, "FLAT"]
try:
utm_zone = FreeCAD.ActiveDocument.Site.UtmZone
except:
utm_zone = "30"
geo_system = ["UTM", utm_zone, "FLAT"]
self.geo_coords.geoSystem.setValues(geo_system)
'''
self.boundary_coords.geoSystem.setValues(geo_system)
self.major_coords.geoSystem.setValues(geo_system)
self.minor_coords.geoSystem.setValues(geo_system)
'''
if prop == "Mesh":
if obj.mesh:
@@ -736,3 +799,4 @@ class ViewProviderTerrain:
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Terrain', _CommandTerrain())'''