Placement: isInside optimizado con shapely + caché LRU + prefiltro BoundBox. Caché se limpia al cambiar área

This commit is contained in:
Javier Braña
2026-05-03 19:56:01 +02:00
parent 26311cb344
commit a67001bb88
+46 -7
View File
@@ -1186,6 +1186,8 @@ class _PVPlantPlacementTaskPanel:
self.Dir = FreeCAD.Vector(0, -1, 0)
self._terrain_interpolator = None
self._frame_footprints_cache = {}
self._isinside_cache = {} # LRU: (frame_name, x, y) -> bool
self._area_polygon = None # Caché shapely del área
# UI setup
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantPlacement.ui"))
@@ -1285,8 +1287,10 @@ class _PVPlantPlacementTaskPanel:
if prohibited_faces:
self.Area = self.Area.cut(prohibited_faces)
# Clear terrain interpolator cache when area changes
# Clear caches when area changes
self._terrain_interpolator = None
self._area_polygon = None
self._isinside_cache.clear()
def _setup_terrain_interpolator(self):
"""Cached terrain interpolator"""
@@ -1425,18 +1429,53 @@ class _PVPlantPlacementTaskPanel:
return df
def _get_area_polygon(self):
"""Convierte self.Area a shapely Polygon para comprobaciones rápidas"""
if self._area_polygon is None and self.Area:
from shapely.geometry import Polygon
verts = self.Area.Vertexes
if len(verts) >= 3:
self._area_polygon = Polygon([(v.x, v.y) for v in verts])
return self._area_polygon
def isInside(self, frame, point):
"""Optimized inside check with early termination"""
if not self.Area.isInside(point, 1e-6, True): # Reduced tolerance for speed
"""
Comprueba si un frame cabe en el área en un punto dado.
Usa shapely para la comprobación 2D (mucho más rápido que Part.cut).
"""
# Caché LRU: mismo frame + misma posición
key = (frame.Name, round(point.x, 0), round(point.y, 0))
if key in self._isinside_cache:
return self._isinside_cache[key]
# Prefiltro rápido por BoundBox
fw, fl = frame.Width.Value / 2, frame.Length.Value / 2
if (point.x - fw < self.Area.BoundBox.XMin or
point.x + fw > self.Area.BoundBox.XMax or
point.y - fl < self.Area.BoundBox.YMin or
point.y + fl > self.Area.BoundBox.YMax):
self._isinside_cache[key] = False
return False
frame_footprint = self._get_frame_footprint(frame)
frame_footprint.Placement.Base = point
# Comprobación precisa con shapely
ap = self._get_area_polygon()
if ap is not None:
from shapely.geometry import box
fp = box(point.x - fw, point.y - fl, point.x + fw, point.y + fl)
result = ap.contains(fp)
self._isinside_cache[key] = result
return result
# Fallback OCC (si shapely falla)
try:
frame_footprint = self._get_frame_footprint(frame)
frame_footprint.Placement.Base = point
cut = frame_footprint.cut([self.Area])
return len(cut.Vertexes) == 0
except:
result = len(cut.Vertexes) == 0
self._isinside_cache[key] = result
return result
except Part.OCCError:
self._isinside_cache[key] = False
return False
def getAligments(self):