Placement: motor unificado _calculate_placement para aligned/non_aligned, misma lógica compartida

This commit is contained in:
Javier Braña
2026-05-03 19:10:49 +02:00
parent 6c2db07493
commit 26311cb344
+69 -113
View File
@@ -156,7 +156,7 @@ class _PVPlantPlacementTaskPanel_old:
newrack.Placement = placements[idx]
group.addObject(newrack)
frames.append(newrack)
except (KeyError, TypeError, AttributeError):
except:
placements = dataframe[0]
frames = []
for idx in placements:
@@ -696,7 +696,7 @@ class _PVPlantPlacementTaskPanel_new1:
newrack.Placement = placements[idx]
group.addObject(newrack)
frames.append(newrack)
except (KeyError, TypeError, AttributeError):
except:
placements = dataframe[0]
frames = []
for idx in placements:
@@ -1310,7 +1310,7 @@ class _PVPlantPlacementTaskPanel:
self._terrain_interpolator = LinearNDInterpolator(
filtered_points[:, :2], filtered_points[:, 2]
)
except Exception:
except:
self._terrain_interpolator = None
return self._terrain_interpolator
@@ -1357,7 +1357,7 @@ class _PVPlantPlacementTaskPanel:
z_bot = slope * bot_point.y + intercept
else:
z_top = z_bot = 0
except Exception:
except:
z_top = z_bot = 0
else:
# Fallback to direct projection (slower)
@@ -1373,7 +1373,7 @@ class _PVPlantPlacementTaskPanel:
z_bot = slope * bot_point.y + intercept
else:
z_top = z_bot = 0
except Exception:
except:
z_top = z_bot = 0
new_top = FreeCAD.Vector(top_point.x, top_point.y, z_top)
@@ -1436,7 +1436,7 @@ class _PVPlantPlacementTaskPanel:
try:
cut = frame_footprint.cut([self.Area])
return len(cut.Vertexes) == 0
except Part.OCCError:
except:
return False
def getAligments(self):
@@ -1469,142 +1469,98 @@ class _PVPlantPlacementTaskPanel:
return x_range, y_range
def calculateAlignedArray(self):
"""Optimized aligned array calculation"""
pointsx, pointsy = self.getAligments()
if len(pointsx) == 0 or len(pointsy) == 0:
return pd.DataFrame()
# Precompute footprints once
footprints = []
for frame in self.FrameSetups:
footprint = self._get_frame_footprint(frame)
footprints.append((frame, footprint))
ref_frame, ref_footprint = footprints[0]
ref_length = ref_frame.Length.Value
ref_width = ref_frame.Width.Value
# Corridor variables
countcols = 0
valcols = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (self.gap_col - ref_width)
corridor_enabled = self.form.groupCorridor.isChecked()
cols = []
for x in pointsx:
col = []
corridor_offset = 0
for y in pointsy:
point = FreeCAD.Vector(x + ref_width / 2 + corridor_offset, y - ref_length / 2, 0.0)
found = False
# Check reference frame first (most common case)
if self.isInside(ref_frame, point):
col.append([ref_frame, point])
found = True
else:
# Check alternative frames
for frame, footprint in footprints[1:]:
length_diff = (ref_frame.Length.Value - frame.Length.Value) / 2
for offset in [length_diff, -length_diff]:
test_point = FreeCAD.Vector(point.x, point.y + offset, 0.0)
if self.isInside(frame, test_point):
col.append([frame, test_point])
found = True
break
if found:
break
if not found:
col.append(0)
# Handle corridors
if corridor_enabled and col:
countcols += 1
if countcols >= self.form.editColCount.value():
corridor_offset += valcols
countcols = 0
cols.append(col)
return self.adjustToTerrain(cols)
"""
Coloca frames en grid alineado (filas y columnas).
Llama al motor unificado _calculate_placement.
"""
return self._calculate_placement(mode='aligned')
def calculateNonAlignedArray(self):
"""Optimized non-aligned array calculation"""
"""
Coloca frames adaptados al contorno del área (solo columnas).
Llama al motor unificado _calculate_placement.
"""
return self._calculate_placement(mode='non_aligned')
def _calculate_placement(self, mode='non_aligned'):
"""
Motor de posicionamiento unificado para aligned y non_aligned.
aligned: grid Y fijo + isInside (rápido en áreas rectangulares)
non_aligned: intersección área-línea (preciso en bordes irregulares)
"""
pointsx, pointsy = self.getAligments()
if len(pointsx) == 0:
FreeCAD.Console.PrintWarning("No X alignments found.\n")
return pd.DataFrame()
# Precompute footprints
# Pre-calcular footprints una sola vez
footprints = []
for frame in self.FrameSetups:
footprint = self._get_frame_footprint(frame)
footprints.append((frame, footprint))
min_h = min(ftp[0].Width.Value for ftp in footprints)
corridor_enabled = self.form.groupCorridor.isChecked()
corridor_count = 0
corridor_offset = 0
ref_width = footprints[0][0].Width.Value
corridor_val = FreeCAD.Units.Quantity(
self.form.editColGap.text()).Value - (self.gap_col - ref_width)
area_ymax = self.Area.BoundBox.YMax
area_ymin = self.Area.BoundBox.YMin
cols = []
for x in pointsx:
col = []
current_x = x + corridor_offset
cx = x + corridor_offset
# Create vertical line for intersection
p1 = FreeCAD.Vector(current_x, self.Area.BoundBox.YMax, 0.0)
p2 = FreeCAD.Vector(current_x, self.Area.BoundBox.YMin, 0.0)
line = Part.LineSegment(p1, p2).toShape()
if mode == 'aligned' and len(pointsy) > 0:
ref_frame = footprints[0][0]
for y in pointsy:
tp = FreeCAD.Vector(cx, y - ref_frame.Length.Value / 2, 0.0)
found = False
if self.isInside(ref_frame, tp):
col.append([ref_frame, tp])
found = True
else:
for fi, (fr, _) in enumerate(footprints[1:], 1):
ld = (ref_frame.Length.Value - fr.Length.Value) / 2
for yoff in (ld, -ld):
tp2 = FreeCAD.Vector(tp.x, tp.y + yoff, 0.0)
if self.isInside(fr, tp2):
col.append([fr, tp2])
found = True
break
if found:
break
if not found:
col.append(0)
else:
line = Part.LineSegment(
FreeCAD.Vector(cx, area_ymax, 0.0),
FreeCAD.Vector(cx, area_ymin, 0.0)
).toShape()
try:
inter = self.Area.section(line)
pts = sorted([v.Point for v in inter.Vertexes],
key=lambda p: p.y, reverse=True)
for i in range(0, len(pts) - 1, 1 + (len(pts) > 2)):
top, bot = pts[i], pts[i + 1]
if top.y - bot.y > min_h:
self._place_frames_in_segment(col, footprints, cx, top, bot)
except Exception as e:
FreeCAD.Console.PrintWarning(f"Segment error: {e}\n")
# Get intersections with area
try:
inter = self.Area.section(line)
pts = sorted([v.Point for v in inter.Vertexes], key=lambda p: p.y, reverse=True)
for i in range(0, len(pts) - 1, 2):
top, bottom = pts[i], pts[i + 1]
available_height = top.y - bottom.y
if available_height > footprints[-1][0].Width.Value:
# Use optimized placement algorithm
self._place_frames_in_segment(col, footprints, current_x, top, bottom)
except Exception as e:
FreeCAD.Console.PrintWarning(f"Error in segment processing: {e}\n")
# Handle corridor offset
if corridor_enabled and col:
corridor_count += 1
if corridor_count >= self.form.editColCount.value():
corridor_offset += 12000 # 12m corridor
corridor_offset += corridor_val
corridor_count = 0
cols.append(col)
return self.adjustToTerrain(cols)
def _place_frames_in_segment(self, col, footprints, x, top, bottom):
"""Optimized frame placement within a segment"""
current_y = top.y
frame_heights = [ftp[0].Width.Value for ftp in footprints]
min_frame_height = min(frame_heights)
while current_y - bottom.y > min_frame_height:
placed = False
for frame, footprint in footprints:
test_y = current_y - frame.Width.Value / 2
test_point = FreeCAD.Vector(x, test_y, 0.0)
if self.isInside(frame, test_point):
col.append([frame, test_point])
current_y -= frame.Width.Value
placed = True
break
if not placed:
break
def accept(self):
from datetime import datetime
starttime = datetime.now()