Mejoras PVPlantTerrain: fix XYZ import, DEM rendimiento, ViewProvider boundary+contour, error handling
This commit is contained in:
+118
-54
@@ -129,8 +129,14 @@ class Terrain(ArchComponent.Component):
|
||||
# obj.IfcType = "Fence"
|
||||
# obj.MoveWithHost = False
|
||||
|
||||
self.site = PVPlantSite.get()
|
||||
self.site.Terrain = obj
|
||||
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)
|
||||
|
||||
# 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:
|
||||
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()
|
||||
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:
|
||||
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:
|
||||
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())'''
|
||||
|
||||
|
||||
Reference in New Issue
Block a user