diff --git a/PVPlantPlacement.py b/PVPlantPlacement.py index 3203ec4..011d92f 100644 --- a/PVPlantPlacement.py +++ b/PVPlantPlacement.py @@ -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):