Placement: getAligments con linspace, _calculate_placement progreso, accept simplificado, _get_or_create optimizado
This commit is contained in:
+83
-56
@@ -1215,28 +1215,25 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
self.form.editInnerSpacing.setText(f"{self.form.editGapCols.value() - self.maxWidth / 1000} m")
|
self.form.editInnerSpacing.setText(f"{self.form.editGapCols.value() - self.maxWidth / 1000} m")
|
||||||
|
|
||||||
def _get_or_create_frame_group(self):
|
def _get_or_create_frame_group(self):
|
||||||
"""Optimized group creation and management"""
|
"""Gestión optimizada de grupos de frames"""
|
||||||
doc = FreeCAD.ActiveDocument
|
doc = FreeCAD.ActiveDocument
|
||||||
|
main_group = doc.getObject("Frames")
|
||||||
# Get or create main group
|
if not main_group:
|
||||||
main_group = doc.getObject("Frames") or doc.addObject("App::DocumentObjectGroup", "Frames")
|
main_group = doc.addObject("App::DocumentObjectGroup", "Frames")
|
||||||
if not main_group.Label == "Frames":
|
|
||||||
main_group.Label = "Frames"
|
main_group.Label = "Frames"
|
||||||
|
|
||||||
# Add to MechanicalGroup if exists
|
mg = doc.getObject('MechanicalGroup')
|
||||||
if not hasattr(doc, 'MechanicalGroup') and hasattr(doc, 'getObject') and doc.getObject('MechanicalGroup'):
|
if mg and main_group not in mg.Group:
|
||||||
doc.MechanicalGroup.addObject(main_group)
|
mg.addObject(main_group)
|
||||||
|
|
||||||
# Handle subfolder
|
|
||||||
if self.form.cbSubfolders.isChecked() and self.PVArea:
|
if self.form.cbSubfolders.isChecked() and self.PVArea:
|
||||||
subgroup_name = f"Frames-{self.PVArea.Label}"
|
sn = f"Frames-{self.PVArea.Label}"
|
||||||
subgroup = next((obj for obj in main_group.Group if obj.Name == subgroup_name), None)
|
sg = next((o for o in main_group.Group if o.Name == sn), None)
|
||||||
if not subgroup:
|
if not sg:
|
||||||
subgroup = doc.addObject("App::DocumentObjectGroup", subgroup_name)
|
sg = doc.addObject("App::DocumentObjectGroup", sn)
|
||||||
subgroup.Label = subgroup_name
|
sg.Label = sn
|
||||||
main_group.addObject(subgroup)
|
main_group.addObject(sg)
|
||||||
return subgroup
|
return sg
|
||||||
|
|
||||||
return main_group
|
return main_group
|
||||||
|
|
||||||
def createFrameFromPoints(self, dataframe):
|
def createFrameFromPoints(self, dataframe):
|
||||||
@@ -1479,31 +1476,50 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def getAligments(self):
|
def getAligments(self):
|
||||||
"""Optimized alignment calculation"""
|
"""
|
||||||
|
Calcula las alineaciones X (columnas) y opcionalmente Y (filas)
|
||||||
|
en función de las referencias seleccionadas.
|
||||||
|
Retorna (x_range, y_range). y_range vacío si no hay referencia vertical.
|
||||||
|
"""
|
||||||
sel = FreeCADGui.Selection.getSelectionEx()
|
sel = FreeCADGui.Selection.getSelectionEx()
|
||||||
if not sel or not sel[0].SubObjects:
|
if not sel or not sel[0].SubObjects:
|
||||||
return np.array([]), np.array([])
|
return np.array([], dtype=np.float64), np.array([], dtype=np.float64)
|
||||||
|
|
||||||
sub_objects = sel[0].SubObjects
|
sub_objects = sel[0].SubObjects
|
||||||
|
|
||||||
if len(sub_objects) == 1:
|
if len(sub_objects) == 1:
|
||||||
refh = refv = sub_objects[0]
|
# Una sola referencia: usar BoundBox completo
|
||||||
|
bb = sub_objects[0].BoundBox
|
||||||
|
area_bb = self.Area.BoundBox
|
||||||
|
n_cols = max(1, int((area_bb.XMax - area_bb.XMin) / self.gap_col))
|
||||||
|
n_rows = max(1, int((area_bb.YMax - area_bb.YMin) / self.gap_row))
|
||||||
|
x_range = np.linspace(area_bb.XMin + self.offsetX, area_bb.XMax, n_cols, dtype=np.float64)
|
||||||
|
y_range = np.linspace(area_bb.YMax - self.offsetY - self.gap_row, area_bb.YMin, n_rows, dtype=np.float64)
|
||||||
else:
|
else:
|
||||||
# Choose references based on bounding box dimensions
|
|
||||||
refh = max(sub_objects[:2], key=lambda x: x.BoundBox.XLength)
|
refh = max(sub_objects[:2], key=lambda x: x.BoundBox.XLength)
|
||||||
refv = max(sub_objects[:2], key=lambda x: x.BoundBox.YLength)
|
refv = max(sub_objects[:2], key=lambda x: x.BoundBox.YLength)
|
||||||
|
|
||||||
# Calculate ranges efficiently
|
# Alinear grid con referencias
|
||||||
startx = refv.BoundBox.XMin + self.offsetX - self.gap_col * int(
|
area_xmin, area_xmax = self.Area.BoundBox.XMin, self.Area.BoundBox.XMax
|
||||||
(refv.BoundBox.XMax - self.Area.BoundBox.XMin + self.offsetX) / self.gap_col
|
area_ymin, area_ymax = self.Area.BoundBox.YMin, self.Area.BoundBox.YMax
|
||||||
)
|
|
||||||
|
|
||||||
starty = refh.BoundBox.YMin + self.offsetY + self.gap_row * int(
|
n_cols = max(1, int((area_xmax - area_xmin) / self.gap_col))
|
||||||
(refh.BoundBox.YMin - self.Area.BoundBox.YMax + self.offsetY) / self.gap_row
|
n_rows = max(1, int((area_ymax - area_ymin) / self.gap_row))
|
||||||
|
x_range = np.linspace(
|
||||||
|
refv.BoundBox.XMin + self.offsetX,
|
||||||
|
min(refv.BoundBox.XMax + self.offsetX, area_xmax),
|
||||||
|
n_cols, dtype=np.float64
|
||||||
)
|
)
|
||||||
|
y_range = np.linspace(
|
||||||
|
refh.BoundBox.YMax - self.offsetY,
|
||||||
|
max(refh.BoundBox.YMin - self.offsetY, area_ymin),
|
||||||
|
n_rows, dtype=np.float64
|
||||||
|
) if n_rows > 1 else np.array([refh.BoundBox.YMin], dtype=np.float64)
|
||||||
|
|
||||||
x_range = np.arange(startx, self.Area.BoundBox.XMax, self.gap_col, dtype=np.float64)
|
# Pre-filtrar: eliminar puntos claramente fuera del BoundBox del área
|
||||||
y_range = np.arange(starty, self.Area.BoundBox.YMin, -self.gap_row, dtype=np.float64)
|
x_range = x_range[(x_range >= self.Area.BoundBox.XMin) & (x_range <= self.Area.BoundBox.XMax)]
|
||||||
|
if len(y_range) > 0:
|
||||||
|
y_range = y_range[(y_range >= self.Area.BoundBox.YMin) & (y_range <= self.Area.BoundBox.YMax)]
|
||||||
|
|
||||||
return x_range, y_range
|
return x_range, y_range
|
||||||
|
|
||||||
@@ -1525,7 +1541,7 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
"""
|
"""
|
||||||
Motor de posicionamiento unificado para aligned y non_aligned.
|
Motor de posicionamiento unificado para aligned y non_aligned.
|
||||||
|
|
||||||
aligned: grid Y fijo + isInside (rápido en áreas rectangulares)
|
aligned: grid Y fijo + isInside (rápido en áreas rectangulares, usa caché)
|
||||||
non_aligned: intersección área-línea (preciso en bordes irregulares)
|
non_aligned: intersección área-línea (preciso en bordes irregulares)
|
||||||
"""
|
"""
|
||||||
pointsx, pointsy = self.getAligments()
|
pointsx, pointsy = self.getAligments()
|
||||||
@@ -1539,6 +1555,9 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
footprint = self._get_frame_footprint(frame)
|
footprint = self._get_frame_footprint(frame)
|
||||||
footprints.append((frame, footprint))
|
footprints.append((frame, footprint))
|
||||||
|
|
||||||
|
if not footprints:
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
min_h = min(ftp[0].Width.Value for ftp in footprints)
|
min_h = min(ftp[0].Width.Value for ftp in footprints)
|
||||||
corridor_enabled = self.form.groupCorridor.isChecked()
|
corridor_enabled = self.form.groupCorridor.isChecked()
|
||||||
corridor_count = 0
|
corridor_count = 0
|
||||||
@@ -1548,34 +1567,49 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
self.form.editColGap.text()).Value - (self.gap_col - ref_width)
|
self.form.editColGap.text()).Value - (self.gap_col - ref_width)
|
||||||
area_ymax = self.Area.BoundBox.YMax
|
area_ymax = self.Area.BoundBox.YMax
|
||||||
area_ymin = self.Area.BoundBox.YMin
|
area_ymin = self.Area.BoundBox.YMin
|
||||||
|
ref_frame = footprints[0][0]
|
||||||
|
ref_len = ref_frame.Length.Value
|
||||||
|
|
||||||
cols = []
|
n_cols = len(pointsx)
|
||||||
for x in pointsx:
|
cols = [None] * n_cols
|
||||||
|
|
||||||
|
# Procesar por lotes para permitir interrupción con barra de progreso
|
||||||
|
from PySide.QtCore import QCoreApplication
|
||||||
|
for idx, x in enumerate(pointsx):
|
||||||
col = []
|
col = []
|
||||||
cx = x + corridor_offset
|
cx = x + corridor_offset
|
||||||
|
|
||||||
|
# Actualizar barra de progreso cada 20 columnas
|
||||||
|
if idx % 20 == 0 and hasattr(self.form, 'progressBar'):
|
||||||
|
self.form.progressBar.setValue(int(100 * idx / n_cols))
|
||||||
|
QCoreApplication.processEvents()
|
||||||
|
|
||||||
if mode == 'aligned' and len(pointsy) > 0:
|
if mode == 'aligned' and len(pointsy) > 0:
|
||||||
ref_frame = footprints[0][0]
|
half_len = ref_len / 2
|
||||||
for y in pointsy:
|
for y in pointsy:
|
||||||
tp = FreeCAD.Vector(cx, y - ref_frame.Length.Value / 2, 0.0)
|
py = y - half_len
|
||||||
found = False
|
# Vector creado solo si pasa el BoundBox check (ya lo hace isInside internamente)
|
||||||
|
tp = FreeCAD.Vector(cx, py, 0.0)
|
||||||
|
placed = False
|
||||||
if self.isInside(ref_frame, tp):
|
if self.isInside(ref_frame, tp):
|
||||||
col.append([ref_frame, tp])
|
col.append([ref_frame, tp])
|
||||||
found = True
|
placed = True
|
||||||
else:
|
else:
|
||||||
for fi, (fr, _) in enumerate(footprints[1:], 1):
|
# frames alternativos: probar con offsets
|
||||||
ld = (ref_frame.Length.Value - fr.Length.Value) / 2
|
for fi in range(1, len(footprints)):
|
||||||
|
fr = footprints[fi][0]
|
||||||
|
ld = (ref_len - fr.Length.Value) / 2
|
||||||
for yoff in (ld, -ld):
|
for yoff in (ld, -ld):
|
||||||
tp2 = FreeCAD.Vector(tp.x, tp.y + yoff, 0.0)
|
if self.isInside(fr, FreeCAD.Vector(tp.x, tp.y + yoff, 0.0)):
|
||||||
if self.isInside(fr, tp2):
|
col.append([fr, FreeCAD.Vector(tp.x, tp.y + yoff, 0.0)])
|
||||||
col.append([fr, tp2])
|
placed = True
|
||||||
found = True
|
|
||||||
break
|
break
|
||||||
if found:
|
if placed:
|
||||||
break
|
break
|
||||||
if not found:
|
if not placed:
|
||||||
col.append(0)
|
col.append(0)
|
||||||
else:
|
else:
|
||||||
|
# Non-aligned: intersección de línea vertical con el área
|
||||||
line = Part.LineSegment(
|
line = Part.LineSegment(
|
||||||
FreeCAD.Vector(cx, area_ymax, 0.0),
|
FreeCAD.Vector(cx, area_ymax, 0.0),
|
||||||
FreeCAD.Vector(cx, area_ymin, 0.0)
|
FreeCAD.Vector(cx, area_ymin, 0.0)
|
||||||
@@ -1591,12 +1625,13 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(f"Segment error: {e}\n")
|
FreeCAD.Console.PrintWarning(f"Segment error: {e}\n")
|
||||||
|
|
||||||
|
# Corredores
|
||||||
if corridor_enabled and col:
|
if corridor_enabled and col:
|
||||||
corridor_count += 1
|
corridor_count += 1
|
||||||
if corridor_count >= self.form.editColCount.value():
|
if corridor_count >= self.form.editColCount.value():
|
||||||
corridor_offset += corridor_val
|
corridor_offset += corridor_val
|
||||||
corridor_count = 0
|
corridor_count = 0
|
||||||
cols.append(col)
|
cols[idx] = col
|
||||||
|
|
||||||
return self.adjustToTerrain(cols)
|
return self.adjustToTerrain(cols)
|
||||||
|
|
||||||
@@ -1604,33 +1639,27 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
starttime = datetime.now()
|
starttime = datetime.now()
|
||||||
|
|
||||||
# Document optimization
|
|
||||||
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Document")
|
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Document")
|
||||||
auto_save_enabled = params.GetBool("AutoSaveEnabled")
|
auto_save_enabled = params.GetBool("AutoSaveEnabled")
|
||||||
params.SetBool("AutoSaveEnabled", False)
|
params.SetBool("AutoSaveEnabled", False)
|
||||||
FreeCAD.ActiveDocument.RecomputesFrozen = True
|
FreeCAD.ActiveDocument.RecomputesFrozen = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get selected frames
|
|
||||||
items = [
|
items = [
|
||||||
FreeCAD.ActiveDocument.getObject(self.form.listFrameSetups.item(i).text())
|
FreeCAD.ActiveDocument.getObject(self.form.listFrameSetups.item(i).text())
|
||||||
for i in range(self.form.listFrameSetups.count())
|
for i in range(self.form.listFrameSetups.count())
|
||||||
if self.form.listFrameSetups.item(i).checkState() == QtCore.Qt.Checked
|
if self.form.listFrameSetups.item(i).checkState() == QtCore.Qt.Checked
|
||||||
]
|
]
|
||||||
|
|
||||||
# Remove duplicates efficiently
|
self.FrameSetups = list({f.Length.Value: f for f in items}.values())
|
||||||
self.FrameSetups = list({frame.Length.Value: frame for frame in items}.values())
|
|
||||||
self.FrameSetups.sort(key=lambda x: x.Length.Value, reverse=True)
|
self.FrameSetups.sort(key=lambda x: x.Length.Value, reverse=True)
|
||||||
|
|
||||||
# Parse parameters
|
|
||||||
self.gap_col = FreeCAD.Units.Quantity(self.form.editGapCols.text()).Value
|
self.gap_col = FreeCAD.Units.Quantity(self.form.editGapCols.text()).Value
|
||||||
self.gap_row = FreeCAD.Units.Quantity(self.form.editGapRows.text()).Value + self.FrameSetups[0].Length.Value
|
self.gap_row = FreeCAD.Units.Quantity(self.form.editGapRows.text()).Value + self.FrameSetups[0].Length.Value
|
||||||
self.offsetX = FreeCAD.Units.Quantity(self.form.editOffsetHorizontal.text()).Value
|
self.offsetX = FreeCAD.Units.Quantity(self.form.editOffsetHorizontal.text()).Value
|
||||||
self.offsetY = FreeCAD.Units.Quantity(self.form.editOffsetVertical.text()).Value
|
self.offsetY = FreeCAD.Units.Quantity(self.form.editOffsetVertical.text()).Value
|
||||||
|
|
||||||
FreeCAD.ActiveDocument.openTransaction("Create Placement")
|
FreeCAD.ActiveDocument.openTransaction("Create Placement")
|
||||||
|
|
||||||
# Main processing
|
|
||||||
self.calculateWorkingArea()
|
self.calculateWorkingArea()
|
||||||
|
|
||||||
if self.form.cbAlignFrames.isChecked():
|
if self.form.cbAlignFrames.isChecked():
|
||||||
@@ -1641,7 +1670,6 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
if not dataframe.empty:
|
if not dataframe.empty:
|
||||||
self.createFrameFromPoints(dataframe)
|
self.createFrameFromPoints(dataframe)
|
||||||
|
|
||||||
# Group trackers
|
|
||||||
import Electrical.group as egroup
|
import Electrical.group as egroup
|
||||||
import importlib
|
import importlib
|
||||||
importlib.reload(egroup)
|
importlib.reload(egroup)
|
||||||
@@ -1650,12 +1678,11 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
FreeCAD.ActiveDocument.commitTransaction()
|
FreeCAD.ActiveDocument.commitTransaction()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Restore document settings
|
|
||||||
FreeCAD.ActiveDocument.RecomputesFrozen = False
|
FreeCAD.ActiveDocument.RecomputesFrozen = False
|
||||||
params.SetBool("AutoSaveEnabled", auto_save_enabled)
|
params.SetBool("AutoSaveEnabled", auto_save_enabled)
|
||||||
|
|
||||||
total_time = datetime.now() - starttime
|
elapsed = datetime.now() - starttime
|
||||||
print(f" -- Total time: {total_time}")
|
FreeCAD.Console.PrintMessage(f"Placement: {elapsed}\n")
|
||||||
FreeCADGui.Control.closeDialog()
|
FreeCADGui.Control.closeDialog()
|
||||||
FreeCAD.ActiveDocument.recompute()
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user