13 Commits

Author SHA1 Message Date
Javier Braña 7d1127c6b5 Trench: fix except genérico -> AttributeError 2026-05-03 02:57:50 +02:00
Javier Braña 7a54e424cb Georeferencing: fallback modo manual cuando QtWebEngine no está disponible (FreeCAD flatpak) 2026-05-03 01:15:16 +02:00
Javier Braña 065f840941 Georeferencing: import QWebEngineView multi-versión (PySide6 QtWebEngineCore/Quick fallback) 2026-05-03 00:56:01 +02:00
Javier Braña 74aedf6122 PVPlant: utm → pyproj (adaptador con sys.modules patch en ImportGrid, eliminado de requirements) 2026-05-03 00:32:36 +02:00
Javier Braña 7c81beb1ba PVPlant: PySide2 -> PySide genérico (FreeCAD resuelve el binding), eliminado de requirements 2026-05-03 00:22:53 +02:00
Javier Braña 02b639d4ed requirements: añadido pandas, separado rtree 2026-05-03 00:09:55 +02:00
Javier Braña fc4142cfec PVPlantTerrain: fix visualización en pantalla — updateData escuchaba Mesh en vez de mesh, añadido publishProperty forzado, más display modes 2026-05-02 23:49:48 +02:00
javier a515f31726 hydro/hydrological: fix except genérico -> (IndexError, AttributeError) 2026-05-02 23:34:53 +02:00
javier e0a0dc2f0d EarthWorks: fix except genérico -> Part.OCCError 2026-05-02 23:22:41 +02:00
javier 02d6c4f412 ImportGrid: fix str(e) sin except, excepts genéricos a específicos 2026-05-02 23:20:59 +02:00
javier e129aba2fe Site: fix computeAreas return prematuro, excepts genéricos a ImportError/Exception 2026-05-02 23:16:27 +02:00
javier 9d65323052 TerrainAnalisys: fix hardcode i=2, var obj undefined, remove threading innecesario 2026-05-02 22:50:10 +02:00
javier 0b13a8c5f1 Mejoras PVPlantTerrain: fix XYZ import, DEM rendimiento, ViewProvider boundary+contour, error handling 2026-05-02 22:47:58 +02:00
12 changed files with 341 additions and 190 deletions
+2
View File
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
+1 -1
View File
@@ -114,7 +114,7 @@ def makeTrench(base=None):
try:
folder = FreeCAD.ActiveDocument.Trenches
except:
except AttributeError:
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Trenches')
folder.Label = "Trenches"
folder.addObject(obj)
+1 -1
View File
@@ -760,7 +760,7 @@ class EarthWorksTaskPanel:
if len(section) > 0:
try:
boundary.add(Part.makePolygon(section))
except:
except Part.OCCError:
pass
Part.show(boundary)'''
#mesh.smooth("Laplace", 3)
+89 -28
View File
@@ -58,8 +58,9 @@ class MapWindow(QtGui.QWidget):
self.setupUi()
def setupUi(self):
from PySide2.QtWebEngineWidgets import QWebEngineView
from PySide2.QtWebChannel import QWebChannel
# Intentar cargar QtWebEngine (no siempre disponible, ej: FreeCAD flatpak)
QWebEngineView, QWebChannel = self._load_webengine()
self._webengine_available = QWebEngineView is not None
self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self)
@@ -86,36 +87,54 @@ class MapWindow(QtGui.QWidget):
self.layout.addWidget(RightWidget)
# Left Widgets:
# -- Search Bar:
self.valueSearch = QtGui.QLineEdit(self)
self.valueSearch.setPlaceholderText("Search")
self.valueSearch.returnPressed.connect(self.onSearch)
if self._webengine_available:
# -- Search Bar:
self.valueSearch = QtGui.QLineEdit(self)
self.valueSearch.setPlaceholderText("Search")
self.valueSearch.returnPressed.connect(self.onSearch)
searchbutton = QtGui.QPushButton('Search')
searchbutton.setFixedWidth(80)
searchbutton.clicked.connect(self.onSearch)
searchbutton = QtGui.QPushButton('Search')
searchbutton.setFixedWidth(80)
searchbutton.clicked.connect(self.onSearch)
SearchBarLayout = QtGui.QHBoxLayout(self)
SearchBarLayout.addWidget(self.valueSearch)
SearchBarLayout.addWidget(searchbutton)
LeftLayout.addLayout(SearchBarLayout)
SearchBarLayout = QtGui.QHBoxLayout(self)
SearchBarLayout.addWidget(self.valueSearch)
SearchBarLayout.addWidget(searchbutton)
LeftLayout.addLayout(SearchBarLayout)
# -- Webbroser:
self.view = QWebEngineView()
self.channel = QWebChannel(self.view.page())
self.view.page().setWebChannel(self.channel)
self.channel.registerObject("MyApp", self)
file = os.path.join(DirResources, "webs", "main.html")
self.view.page().loadFinished.connect(self.onLoadFinished)
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
LeftLayout.addWidget(self.view)
# self.layout.addWidget(self.view, 1, 0, 1, 3)
# -- Web browser:
self.view = QWebEngineView()
self.channel = QWebChannel(self.view.page())
self.view.page().setWebChannel(self.channel)
self.channel.registerObject("MyApp", self)
file = os.path.join(DirResources, "webs", "main.html")
self.view.page().loadFinished.connect(self.onLoadFinished)
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
LeftLayout.addWidget(self.view)
else:
# -- Modo manual: entrada de coordenadas sin mapa web
self.valueSearch = QtGui.QLineEdit(self)
self.valueSearch.setPlaceholderText("Latitud, Longitud (ej: 40.4168, -3.7038)")
self.valueSearch.returnPressed.connect(self.onManualCoords)
searchbutton = QtGui.QPushButton('Ir')
searchbutton.setFixedWidth(80)
searchbutton.clicked.connect(self.onManualCoords)
SearchBarLayout = QtGui.QHBoxLayout(self)
SearchBarLayout.addWidget(self.valueSearch)
SearchBarLayout.addWidget(searchbutton)
LeftLayout.addLayout(SearchBarLayout)
info = QtGui.QLabel("Mapa web no disponible. Introduce coordenadas manualmente.")
info.setStyleSheet("color: #888; font-style: italic; padding: 20px;")
info.setAlignment(QtCore.Qt.AlignCenter)
LeftLayout.addWidget(info)
# -- Latitud y longitud:
self.labelCoordinates = QtGui.QLabel()
self.labelCoordinates.setFixedHeight(21)
LeftLayout.addWidget(self.labelCoordinates)
# self.layout.addWidget(self.labelCoordinates, 2, 0, 1, 3)
# Right Widgets:
labelKMZ = QtGui.QLabel()
@@ -139,9 +158,6 @@ class MapWindow(QtGui.QWidget):
radio3 = QtGui.QRadioButton("Datos GPS")
radio1.setChecked(True)
# buttonDialog = QtGui.QPushButton('...')
# buttonDialog.setEnabled(False)
vbox = QtGui.QVBoxLayout(self)
vbox.addWidget(radio1)
vbox.addWidget(radio2)
@@ -149,7 +165,6 @@ class MapWindow(QtGui.QWidget):
self.groupbox.setLayout(vbox)
RightLayout.addWidget(self.groupbox)
# ------------------------
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
RightLayout.addWidget(self.checkboxImportGis)
@@ -174,6 +189,52 @@ class MapWindow(QtGui.QWidget):
with open(file, 'r') as f:
frame.runJavaScript(f.read())
def _load_webengine(self):
"""Intenta cargar QWebEngineView desde cualquier versión de PySide.
Retorna (QWebEngineView_class, QWebChannel_class) o (None, None)."""
for modpath in [
'PySide6.QtWebEngineWidgets',
'PySide6.QtWebEngineCore',
'PySide6.QtWebEngineQuick',
'PySide2.QtWebEngineWidgets',
'PySide.QtWebEngineWidgets',
]:
try:
parts = modpath.split('.')
mod = __import__(parts[0], fromlist=parts[1:])
for p in parts[1:]:
mod = getattr(mod, p)
View = getattr(mod, 'QWebEngineView', None)
Channel = getattr(mod, 'QWebChannel', None)
if View is not None:
return View, Channel
except (ImportError, AttributeError):
continue
# Fallback: intentar por separado QtWebChannel (sí existe en flatpak)
try:
from PySide6.QtWebChannel import QWebChannel as Channel
except ImportError:
Channel = None
FreeCAD.Console.PrintWarning(
"PVPlantGeoreferencing: QtWebEngine no disponible. "
"Usando modo manual de coordenadas.\n")
return None, Channel
def onManualCoords(self):
"""Procesa entrada manual de latitud,longitud"""
text = self.valueSearch.text().strip()
if not text:
return
try:
parts = text.replace(',', ' ').split()
lat = float(parts[0])
lon = float(parts[1])
self.georeference_coordinates = {'lat': lat, 'lon': lon}
self.labelCoordinates.setText(f"{lat:.6f}, {lon:.6f}")
FreeCAD.Console.PrintMessage(f"Coordenadas: {lat:.6f}, {lon:.6f}\n")
except (ValueError, IndexError):
FreeCAD.Console.PrintError("Formato inválido. Usa: latitud, longitud\n")
def onSearch(self):
if self.valueSearch.text() == "":
return
+49 -10
View File
@@ -39,6 +39,51 @@ import os
from PVPlantResources import DirIcons as DirIcons
import PVPlantSite
# ---------------------------------------------------------------------------
# Adaptador UTM: emula la API de la librería 'utm' usando pyproj
# La librería 'utm' dejó de usarse en favor de pyproj (más completa y mantenida).
# from_latlon(lat, lon) -> (easting, northing, zone_number, zone_letter)
# to_latlon(easting, northing, zone_number, zone_letter) -> (lat, lon)
# ---------------------------------------------------------------------------
_utm_cache = {}
def _get_transformer(lat, lon):
"""Obtiene o crea un transformador UTM para las coordenadas dadas."""
from pyproj import Transformer
zone = int((lon + 180) / 6) + 1
hem = 'S' if lat < 0 else 'N'
key = (zone, hem)
if key not in _utm_cache:
crs_utm = f'+proj=utm +zone={zone} +{hem.lower()} +ellps=WGS84 +datum=WGS84 +units=m +no_defs'
_utm_cache[key] = Transformer.from_crs('EPSG:4326', crs_utm, always_xy=True)
return _utm_cache[key], zone, hem
def from_latlon(lat, lon):
"""Convierte (lat, lon) a UTM. Retorna (easting, northing, zone_number, zone_letter)."""
transformer, zone, hem = _get_transformer(lat, lon)
easting, northing = transformer.transform(lon, lat)
return (easting, northing, zone, hem)
def to_latlon(easting, northing, zone_number, zone_letter):
"""Convierte UTM a (lat, lon)."""
from pyproj import Transformer
hem = zone_letter.upper()
key = (zone_number, hem)
if key not in _utm_cache:
crs_utm = f'+proj=utm +zone={zone_number} +{hem.lower()} +ellps=WGS84 +datum=WGS84 +units=m +no_defs'
_utm_cache[key] = Transformer.from_crs(crs_utm, 'EPSG:4326', always_xy=True)
lon, lat = _utm_cache[key].transform(easting, northing)
return (lat, lon)
# Parche: reemplazar el módulo 'utm' por nuestro adaptador
import sys
class _UTMWrapper:
"""Wrapper para que 'import utm' devuelva nuestras funciones."""
from_latlon = staticmethod(from_latlon)
to_latlon = staticmethod(to_latlon)
sys.modules['utm'] = _UTMWrapper
# ---------------------------------------------------------------------------
def get_elevation_from_oe(coordinates): # v1 deepseek
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM.
@@ -52,7 +97,6 @@ def get_elevation_from_oe(coordinates): # v1 deepseek
return []
import requests
import utm
from requests.exceptions import RequestException
# Construcción más eficiente de parámetros
@@ -68,7 +112,7 @@ def get_elevation_from_oe(coordinates): # v1 deepseek
response.raise_for_status() # Lanza excepción para códigos 4xx/5xx
except RequestException as e:
print(f"Error en la solicitud: {str(e)}")
print(f"Error en la solicitud: {e}")
return []
try:
@@ -95,7 +139,7 @@ def get_elevation_from_oe(coordinates): # v1 deepseek
round(result["elevation"])) * 1000)
except Exception as e:
print(f"Error procesando coordenadas: {str(e)}")
print(f"Error procesando coordenadas: {e}")
continue
return points
@@ -110,7 +154,6 @@ def getElevationFromOE(coordinates):
return None
from requests import get
import utm
locations_str=""
total = len(coordinates) - 1
@@ -141,7 +184,6 @@ def getElevationFromOE(coordinates):
def getSinglePointElevationFromBing(lat, lng):
#http://dev.virtualearth.net/REST/v1/Elevation/List?points={lat1,long1,lat2,long2,latN,longnN}&heights={heights}&key={BingMapsAPIKey}
import utm
source = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
source += str(lat) + "," + str(lng)
@@ -166,7 +208,6 @@ def getSinglePointElevationFromBing(lat, lng):
def getGridElevationFromBing(polygon, lat, lng, resolution = 1000):
#http://dev.virtualearth.net/REST/v1/Elevation/Polyline?points=35.89431,-110.72522,35.89393,-110.72578,35.89374,-110.72606,35.89337,-110.72662
# &heights=ellipsoid&samples=10&key={BingMapsAPIKey}
import utm
import math
import requests
@@ -311,7 +352,6 @@ def getSinglePointElevationUtm(lat, lon):
res = s['results']
print (res)
import utm
for r in res:
c = utm.from_latlon(r['location']['lat'], r['location']['lng'])
v = FreeCAD.Vector(
@@ -323,7 +363,6 @@ def getSinglePointElevationUtm(lat, lon):
def getElevationUTM(polygon, lat, lng, resolution = 10000):
import utm
geo = utm.from_latlon(lat, lng)
# result = (679434.3578335291, 4294023.585627955, 30, 'S')
# EASTING, NORTHING, ZONE NUMBER, ZONE LETTER
@@ -392,7 +431,7 @@ def getElevation1(polygon,resolution=10):
s = json.loads(ans)
res = s['results']
except:
except (json.JSONDecodeError, KeyError):
continue
#points = []
@@ -526,7 +565,7 @@ class _ImportPointsTaskPanel:
try:
PointGroups = FreeCAD.ActiveDocument.Point_Groups
except:
except AttributeError:
PointGroups = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Point_Groups')
PointGroups.Label = "Point Groups"
+2 -2
View File
@@ -1164,8 +1164,8 @@ from scipy.interpolate import LinearNDInterpolator
import Part
import FreeCAD
import FreeCADGui
from PySide2 import QtCore, QtGui
from PySide2.QtWidgets import QListWidgetItem
from PySide import QtCore, QtGui
from PySide.QtWidgets import QListWidgetItem
import os
import PVPlantResources
+35 -41
View File
@@ -182,16 +182,14 @@ def makeSolarDiagram(longitude, latitude, scale=1, complete=False, tz=None):
import ladybug
from ladybug import location
from ladybug import sunpath
except:
# TODO - remove pysolar dependency
# FreeCAD.Console.PrintWarning("Ladybug module not found, using pysolar instead. Warning, this will be deprecated in the future\n")
except ImportError:
ladybug = False
try:
import pysolar
except:
except ImportError:
try:
import Pysolar as pysolar
except:
except ImportError:
FreeCAD.Console.PrintError("The pysolar module was not found. Unable to generate solar diagrams\n")
return None
else:
@@ -361,7 +359,7 @@ def makeWindRose(epwfile, scale=1, sectors=24):
try:
import ladybug
from ladybug import epw
except:
except ImportError:
FreeCAD.Console.PrintError("The ladybug module was not found. Unable to generate solar diagrams\n")
return None
if not epwfile:
@@ -667,23 +665,22 @@ class _PVPlantSite(ArchSite._Site):
self.computeAreas(obj)
def computeAreas(self, obj):
"""
Compute areas, perimeter and volumes.
Override to add custom logic after parent computation.
"""
ArchSite._Site.computeAreas(self, obj)
return
if not obj.Shape:
return
if obj.Shape.isNull():
if obj.Shape.isNull() or not obj.Shape.isValid() or not obj.Shape.Faces:
return
if not obj.Shape.isValid():
return
if not obj.Shape.Faces:
return
if not hasattr(obj, "Perimeter"): # check we have a latest version site
if not hasattr(obj, "Perimeter"):
return
if not obj.Terrain:
return
# compute area
# Compute projected area (horizontal projection of all near-horizontal faces)
fset = []
for f in obj.Shape.Faces:
if f.normalAt(0, 0).getAngle(FreeCAD.Vector(0, 0, 1)) < 1.5707:
@@ -694,13 +691,11 @@ class _PVPlantSite(ArchSite._Site):
for f in fset:
try:
pf = Part.Face(Part.Wire(Drawing.project(f, FreeCAD.Vector(0, 0, 1))[0].Edges))
except Part.OCCError:
# error in computing the area. Better set it to zero than show a wrong value
if obj.ProjectedArea.Value != 0:
print("Error computing areas for ", obj.Label)
obj.ProjectedArea = 0
else:
pset.append(pf)
except Part.OCCError:
if getattr(obj, 'ProjectedArea', None) and obj.ProjectedArea.Value != 0:
FreeCAD.Console.PrintWarning(f"Error computing projected area for {obj.Label}\n")
obj.ProjectedArea = 0
if pset:
self.flatarea = pset.pop()
for f in pset:
@@ -708,28 +703,27 @@ class _PVPlantSite(ArchSite._Site):
self.flatarea = self.flatarea.removeSplitter()
if obj.ProjectedArea.Value != self.flatarea.Area:
obj.ProjectedArea = self.flatarea.Area
# compute perimeter
# Compute perimeter (border edges only)
lut = {}
for e in obj.Shape.Edges:
lut.setdefault(e.hashCode(), []).append(e)
l = 0
for e in lut.values():
if len(e) == 1: # keep only border edges
l += e[0].Length
if l:
if obj.Perimeter.Value != l:
obj.Perimeter = l
# compute volumes
if obj.Terrain.Shape.Solids:
shapesolid = obj.Terrain.Shape.copy()
else:
shapesolid = obj.Terrain.Shape.extrude(obj.ExtrusionVector)
addvol = 0
subvol = 0
for sub in obj.Subtractions:
subvol += sub.Shape.common(shapesolid).Volume
for sub in obj.Additions:
addvol += sub.Shape.cut(shapesolid).Volume
perimeter = sum(e[0].Length for e in lut.values() if len(e) == 1)
if perimeter and obj.Perimeter.Value != perimeter:
obj.Perimeter = perimeter
# Compute cut/fill volumes relative to terrain
try:
if obj.Terrain.Shape.Solids:
shapesolid = obj.Terrain.Shape.copy()
else:
shapesolid = obj.Terrain.Shape.extrude(obj.ExtrusionVector)
except Exception:
return
subvol = sum(sub.Shape.common(shapesolid).Volume for sub in obj.Subtractions)
addvol = sum(sub.Shape.cut(shapesolid).Volume for sub in obj.Additions)
if obj.SubtractionVolume.Value != subvol:
obj.SubtractionVolume = subvol
if obj.AdditionVolume.Value != addvol:
@@ -1056,7 +1050,7 @@ class _ViewProviderSite:
if hasattr(vobj.Object,"EPWFile") and vobj.Object.EPWFile:
try:
import ladybug
except:
except ImportError:
pass
else:
self.windrosenode = makeWindRose(vobj.Object.EPWFile,vobj.SolarDiagramScale)
+147 -71
View File
@@ -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)
@@ -192,6 +198,12 @@ class Terrain(ArchComponent.Component):
if prop == "InitialMesh":
obj.mesh = obj.InitialMesh.copy()
# Forzar actualización visual
obj.publishProperty("Mesh")
if prop == "mesh":
# La propiedad mesh cambió → forzar recompute para que updateData se dispare
pass
if prop == "DEM" or prop == "CuttingBoundary":
from datetime import datetime
@@ -237,7 +249,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 +281,95 @@ 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()
# Limpiar objetos mesh huérfanos previos
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
print(f'XYZ import: {len(pts_array)} puntos en {datetime.now()-t0}')
@@ -329,6 +401,11 @@ class Terrain(ArchComponent.Component):
if obj.DEM:
obj.DEM = None
obj.mesh = mesh
# Forzar actualización visual llamando a publishProperty
try:
obj.publishProperty("Mesh")
except:
pass
def execute(self, obj):
''''''
@@ -547,47 +624,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 +673,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 +686,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,52 +725,50 @@ 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 prop == "mesh" or prop == "Mesh":
if obj.mesh:
print("Mostrar mesh")
mesh = obj.mesh
vertices = [tuple(v) for v in mesh.Topology[0]]
faces = []
for face in mesh.Topology[1]:
faces.extend(face)
faces.append(-1)
try:
vertices = [tuple(v) for v in mesh.Topology[0]]
faces = []
for face in mesh.Topology[1]:
faces.extend(face)
faces.append(-1)
# Asignar a los nodos de visualización
self.geo_coords.point.values = vertices # <-- ¡Clave!
self.triangles.coordIndex.values = faces # <-- ¡Clave!
# Asignar a los nodos de visualización
self.geo_coords.point.values = vertices
self.triangles.coordIndex.values = faces
except Exception as e:
FreeCAD.Console.PrintError(f"Error actualizando mesh visual: {e}\n")
def getDisplayModes(self, vobj):
''' Return a list of display modes. '''
modes = ["Surface", "Boundary"]
return modes
return ["Surface", "Boundary", "Flat Lines", "Wireframe"]
def getDefaultDisplayMode(self):
'''
Return the name of the default display mode.
'''
return "Surface"
def claimChildren(self):
@@ -736,3 +811,4 @@ class ViewProviderTerrain:
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Terrain', _CommandTerrain())'''
+10 -30
View File
@@ -450,35 +450,18 @@ class ContourTaskPanel():
starttime = datetime.now()
if self.land is None:
print("No hay objetos para procesar")
FreeCAD.Console.PrintWarning("No hay objetos para procesar\n")
return False
else:
minor = FreeCAD.Units.Quantity(self.inputMinorContourMargin.currentText()).Value
mayor = FreeCAD.Units.Quantity(self.inputMayorContourMargin.currentText()).Value
i = 2
if i == 0:
makeContours(self.land, minor, mayor, self.MinorColor, self.MayorColor,
self.inputMinorContourThickness.value(), self.inputMayorContourThickness.value())
elif i == 1:
import multiprocessing
p = multiprocessing.Process(target=makeContours,
args=(self.land, minor, mayor,
self.MinorColor, self.MayorColor,
self.inputMinorContourThickness.value(),
self.inputMayorContourThickness.value(), ))
p.start()
p.join()
else:
import threading
hilo = threading.Thread(target = makeContours,
args = (self.land, minor, mayor,
self.MinorColor, self.MayorColor,
self.inputMinorContourThickness.value(),
self.inputMayorContourThickness.value()))
hilo.daemon = True
hilo.start()
makeContours(
self.land, minor, mayor,
self.MinorColor, self.MayorColor,
self.inputMinorContourThickness.value(),
self.inputMayorContourThickness.value()
)
total_time = datetime.now() - starttime
print(" -- Tiempo tardado:", total_time)
@@ -569,7 +552,7 @@ class SlopeTaskPanel(_generalTaskPanel):
land.ViewObject.DiffuseColor = colorlist
# TODO: check this code:
elif obj.isDerivedFrom("Mesh::Feature"):
elif hasattr(land, 'Mesh') and land.isDerivedFrom("Mesh::Feature"):
fMesh = Mest2FemMesh(land)
import math
setColors = []
@@ -602,10 +585,7 @@ class SlopeTaskPanel(_generalTaskPanel):
print("Everything OK (", datetime.now() - starttime, ")")
def accept(self):
# self.getPointSlope()
import threading
hilo = threading.Thread(target=self.getPointSlope(self.ranges))
hilo.start()
self.getPointSlope(self.ranges)
return True
# Orientation Analisys: ---------------------------------------------------------------------------------
@@ -809,4 +789,4 @@ if FreeCAD.GuiUp:
FreeCADGui.addCommand('SlopeAnalisys', _CommandSlopeAnalisys())
FreeCADGui.addCommand('HeightAnalisys', _CommandHeightAnalisys())
FreeCADGui.addCommand('OrientationAnalisys', _CommandOrientationAnalisys())
FreeCADGui.addCommand('TerrainAnalisys', CommandTerrainAnalisysGroup())'''
FreeCADGui.addCommand('TerrainAnalisys', CommandTerrainAnalisysGroup())'''
+2 -2
View File
@@ -1,8 +1,8 @@
# Script para FreeCAD - Procesador de Documentos Word con Carátula
import os
import glob
from PySide2 import QtWidgets, QtCore
from PySide2.QtWidgets import (QFileDialog, QMessageBox, QProgressDialog,
from PySide import QtWidgets, QtCore
from PySide.QtWidgets import (QFileDialog, QMessageBox, QProgressDialog,
QApplication, QVBoxLayout, QWidget, QPushButton,
QLabel, QTextEdit)
import FreeCAD
+1 -1
View File
@@ -159,7 +159,7 @@ def calculate_incenter(facet):
"""Calcula el incentro usando la función nativa de FreeCAD"""
try:
return facet.InCircle[0] # (x, y, z)
except:
except (IndexError, AttributeError):
return None
+2 -3
View File
@@ -2,8 +2,6 @@ numpy~=1.26.2
opencv-python~=4.8.1
matplotlib~=3.8.2
openpyxl~=3.1.2
utm~=0.7.0
PySide2~=5.15.8
requests~=2.31.0
setuptools~=68.2.2
laspy~=2.5.3
@@ -17,4 +15,5 @@ certifi~=2023.11.17
SciPy~=1.11.4
pycollada~=0.7.2
shapely
rtree
rtree
pandas