Punto de restauración.

This commit is contained in:
2025-07-31 09:58:38 +02:00
parent e1e1441892
commit 5db8f5439d
14 changed files with 1382 additions and 497 deletions

View File

@@ -78,11 +78,12 @@ class _PVPlantPlacementTaskPanel:
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantPlacement.ui"))
self.form.setWindowIcon(QtGui.QIcon(os.path.join(PVPlantResources.DirIcons, "way.svg")))
self.form.buttonPVArea.clicked.connect(self.addPVArea)
#self.form.buttonAddFrame.clicked.connect(self.addFrames)
#self.form.buttonRemoveFrame.clicked.connect(self.removeFrame)
self.addFrames()
self.maxWidth = max([frame.Width.Value for frame in self.site.Frames])
self.form.buttonPVArea.clicked.connect(self.addPVArea)
self.form.editGapCols.valueChanged.connect(self.update_inner_spacing)
self.update_inner_spacing()
def addPVArea(self):
sel = FreeCADGui.Selection.getSelection()
@@ -95,6 +96,10 @@ class _PVPlantPlacementTaskPanel:
list_item = QListWidgetItem(frame_setup.Name, self.form.listFrameSetups)
list_item.setCheckState(QtCore.Qt.Checked)
def update_inner_spacing(self):
self.form.editInnerSpacing.setText(
("{} m".format((self.form.editGapCols.value() - self.maxWidth / 1000))))
def createFrameFromPoints(self, dataframe):
from Mechanical.Frame import PVPlantFrame
try:
@@ -104,22 +109,37 @@ class _PVPlantPlacementTaskPanel:
MechanicalGroup.Label = "Frames"
FreeCAD.ActiveDocument.MechanicalGroup.addObject(MechanicalGroup)
if self.form.cbSubfolders.checked:
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", self.PVArea.Label)
group.Label = self.PVArea.Label
MechanicalGroup.addObject(group)
MechanicalGroup = group
placements = dataframe["placement"].tolist()
types = dataframe["type"].tolist()
frames = []
for idx in range(len(placements)):
newrack = PVPlantFrame.makeTracker(setup=types[idx])
newrack.Label = "Tracker"
newrack.Visibility = False
newrack.Placement = placements[idx]
MechanicalGroup.addObject(newrack)
frames.append(newrack)
if self.form.cbSubfolders.isChecked:
label = "Frames-" + self.PVArea.Label
if label in [obj.Label for obj in FreeCAD.ActiveDocument.Frames.Group]:
MechanicalGroup = FreeCAD.ActiveDocument.getObject(label)[0]
else:
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", label)
group.Label = label
MechanicalGroup.addObject(group)
MechanicalGroup = group
try:
placements = dataframe["placement"].tolist()
types = dataframe["type"].tolist()
frames = []
for idx in range(len(placements)):
newrack = PVPlantFrame.makeTracker(setup=types[idx])
newrack.Label = "Tracker"
newrack.Visibility = False
newrack.Placement = placements[idx]
MechanicalGroup.addObject(newrack)
frames.append(newrack)
except:
placements = dataframe[0]
frames = []
for idx in placements:
print(idx)
newrack = PVPlantFrame.makeTracker(setup=idx[0])
newrack.Label = "Tracker"
newrack.Visibility = False
newrack.Placement = idx[1]
MechanicalGroup.addObject(newrack)
frames.append(newrack)
if self.PVArea.Name.startswith("FrameArea"):
self.PVArea.Frames = frames
@@ -179,7 +199,7 @@ class _PVPlantPlacementTaskPanel:
return np.arange(startx, self.Area.BoundBox.XMax, self.gap_col, dtype=np.int64), \
np.arange(starty, self.Area.BoundBox.YMin, -self.gap_row, dtype=np.int64)
def adjustToTerrain(self, coordinates):
def adjustToTerrain_old(self, coordinates):
mode = 1
terrain = self.Terrain.Mesh
@@ -276,6 +296,106 @@ class _PVPlantPlacementTaskPanel:
placeRegion(df)
return df
def _setup_terrain_interpolator(self):
"""Prepara interpolador del terreno para ajuste rápido"""
import numpy as np
from scipy.interpolate import LinearNDInterpolator
mesh = self.Terrain.Mesh
points = np.array([p.Vector for p in mesh.Points])
bbox = self.Area.BoundBox
# Filtrar puntos dentro del área de trabajo
in_bbox = [
p for p in points
if bbox.XMin <= p[0] <= bbox.XMax and
bbox.YMin <= p[1] <= bbox.YMax
]
if not in_bbox:
return None
coords = np.array(in_bbox)
return LinearNDInterpolator(coords[:, :2], coords[:, 2])
def adjustToTerrain(self, coordinates):
from scipy.ndimage import label as sclabel
import pandas as pd
import numpy as np
from scipy import stats
import MeshPart
# Crear matriz binaria
arr = np.array([[1 if obj != 0 else 0 for obj in col] for col in coordinates])
labeled_array, num_features = sclabel(arr)
# Construir DataFrame optimizado
data = []
terrain_interp = self._setup_terrain_interpolator()
for label in range(1, num_features + 1):
cols, rows = np.where(labeled_array == label)
for idx, (col, row) in enumerate(zip(cols, rows)):
frame_type, placement = coordinates[col][row]
data.append({
'ID': len(data) + 1,
'region': label,
'type': frame_type,
'column': col,
'row': row,
'placement': placement
})
df = pd.DataFrame(data)
# Ajustar al terreno
for idx, row in df.iterrows():
pl = row['placement']
yl = row['type'].Length.Value / 2
# Calcular puntos extremos
top_point = FreeCAD.Vector(pl.x, pl.y + yl, 0)
bot_point = FreeCAD.Vector(pl.x, pl.y - yl, 0)
# Usar interpolador si está disponible
if terrain_interp:
yy = np.linspace(bot_point.y, top_point.y, 10)
xx = np.full(10, pl.x)
zz = terrain_interp(xx, yy)
if not np.isnan(zz).all():
slope, intercept, *_ = stats.linregress(yy, zz)
z_top = slope * top_point.y + intercept
z_bot = slope * bot_point.y + intercept
else:
z_top = z_bot = 0
else:
# Fallback a proyección directa
line = Part.LineSegment(bot_point, top_point).toShape()
projected = MeshPart.projectShapeOnMesh(line, self.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))[0]
if len(projected) >= 2:
yy = [p.y for p in projected]
zz = [p.z for p in projected]
slope, intercept, *_ = stats.linregress(yy, zz)
z_top = slope * top_point.y + intercept
z_bot = slope * bot_point.y + intercept
else:
z_top = z_bot = 0
# Actualizar placement
new_top = FreeCAD.Vector(top_point.x, top_point.y, z_top)
new_bot = FreeCAD.Vector(bot_point.x, bot_point.y, z_bot)
new_pl = FreeCAD.Placement()
new_pl.Base = (new_top + new_bot) / 2
new_pl.Rotation = FreeCAD.Rotation(
FreeCAD.Vector(-1, 0, 0),
new_top - new_bot
)
df.at[idx, 'placement'] = new_pl
return df
def isInside(self, frame, point):
if self.Area.isInside(point, 10, True):
frame.Placement.Base = point
@@ -349,86 +469,68 @@ class _PVPlantPlacementTaskPanel:
if countcols == self.form.editColCount.value():
offsetcols += valcols
countcols = 0
print("/n/n")
print(cols)
return self.adjustToTerrain(cols)
def calculateNonAlignedArray(self):
gap_col = FreeCAD.Units.Quantity(self.form.editGapCols.text()).Value
gap_row = FreeCAD.Units.Quantity(self.form.editGapRows.text()).Value + max(self.Rack.Shape.BoundBox.XLength,
self.Rack.Shape.BoundBox.YLength)
offset_x = FreeCAD.Units.Quantity(self.form.editOffsetHorizontal.text()).Value
offset_y = FreeCAD.Units.Quantity(self.form.editOffsetVertical.text()).Value
pointsx, pointsy = self.getAligments()
Area = self.calculateWorkingArea()
footprints = []
for frame in self.FrameSetups:
xx = frame.Length.Value
yy = frame.Width.Value
xx_med = xx / 2
yy_med = yy / 2
rec = Part.makePolygon([FreeCAD.Vector(-xx_med, -yy_med, 0),
FreeCAD.Vector(xx_med, -yy_med, 0),
FreeCAD.Vector(xx_med, yy_med, 0),
FreeCAD.Vector(-xx_med, yy_med, 0),
FreeCAD.Vector(-xx_med, -yy_med, 0)])
rec.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), FreeCAD.Vector(0, 1, 0))
footprints.append([frame, rec])
ref = footprints.pop(0)
xx = ref[0].Length.Value
yy = ref[0].Width.Value
xx_med = xx / 2
yy_med = yy / 2
rec = Part.makePlane(self.Rack.Shape.BoundBox.YLength, self.Rack.Shape.BoundBox.XLength)
# TODO: revisar todo esto: -----------------------------------------------------------------
sel = FreeCADGui.Selection.getSelectionEx()[0]
refh = None
refv = None
if len(sel.SubObjects) == 0:
refh = refv = Area.Edges[0]
if len(sel.SubObjects) == 1:
refh = refv = sel.SubObjects[0]
if len(sel.SubObjects) == 2:
if sel.SubObjects[0].BoundBox.XLength > sel.SubObjects[1].BoundBox.XLength:
refh = sel.SubObjects[0]
else:
refh = sel.SubObjects[1]
if sel.SubObjects[0].BoundBox.YLength > sel.SubObjects[1].BoundBox.YLength:
refv = sel.SubObjects[0]
else:
refv = sel.SubObjects[1]
steps = int((refv.BoundBox.XMax - Area.BoundBox.XMin + offset_x) / gap_col)
startx = refv.BoundBox.XMax + offset_x - gap_col * steps
# todo end ----------------------------------------------------------------------------------
start = FreeCAD.Vector(startx, 0.0, 0.0)
pointsx = np.arange(start.x, Area.BoundBox.XMax, gap_col)
if self.form.groupCorridor.isChecked():
if (self.form.editColCount.value() > 0):
xlen = len(pointsx)
count = self.form.editColCount.value()
val = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (
gap_col - min(self.Rack.Shape.BoundBox.XLength, self.Rack.Shape.BoundBox.YLength))
while count <= xlen:
for i, point in enumerate(pointsx):
if i >= count:
pointsx[i] += val
count += self.form.editColCount.value()
# variables for corridors:
countcols = 0
countrows = 0
offsetcols = 0 # ??
offsetrows = 0 # ??
valcols = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (self.gap_col - yy)
pl = []
for point in pointsx:
p1 = FreeCAD.Vector(point, Area.BoundBox.YMax, 0.0)
p2 = FreeCAD.Vector(point, Area.BoundBox.YMin, 0.0)
p1 = FreeCAD.Vector(point, self.Area.BoundBox.YMax, 0.0)
p2 = FreeCAD.Vector(point, self.Area.BoundBox.YMin, 0.0)
line = Part.makePolygon([p1, p2])
inter = Area.section([line])
inter = self.Area.section([line])
pts = [ver.Point for ver in inter.Vertexes] # todo: sort points
for i in range(0, len(pts), 2):
line = Part.LineSegment(pts[i], pts[i + 1])
if line.length() >= rec.BoundBox.YLength:
y1 = pts[i].y - rec.BoundBox.YLength
cp = rec.copy()
cp.Placement.Base = FreeCAD.Vector(pts[i].x - rec.BoundBox.XLength / 2, y1, 0.0)
inter = cp.cut([Area])
y1 = min([ver.Point.y for ver in inter.Vertexes])
pointsy = np.arange(y1, pts[i + 1].y, -gap_row)
for point in pointsy:
cp = rec.copy()
cp.Placement.Base = FreeCAD.Vector(pts[i].x - rec.BoundBox.XLength / 2, point, 0.0)
cut = cp.cut([Area], 0)
if len(cut.Vertexes) == 0:
Part.show(cp)
pl.append(point)
if line.length() >= ref[1].BoundBox.YLength:
y1 = pts[i].y - ref[1].BoundBox.YLength / 2
cp = ref[1].copy()
cp.Placement.Base = FreeCAD.Vector(pts[i].x, y1, 0.0)
Part.show(cp)
inter = cp.cut([self.Area])
pts1 = [ver.Point for ver in inter.Vertexes]
if len(pts1) == 0:
continue
y1 = min(pts1, key=lambda p: p.y).y
pointsy = np.arange(y1, pts[i + 1].y, -self.gap_row)
continue
for pointy in pointsy:
cp = ref[1].copy()
cp.Placement.Base = FreeCAD.Vector(pts[i].x + ref[1].BoundBox.XLength / 2, pointy, 0.0)
cut = cp.cut([self.Area], 0)
#print(y1, " - ", pointy, " - ", len(cut.Vertexes))
#if len(cut.Vertexes) == 0:
Part.show(cp)
pl.append([ref[0], pointy])
return pl
def accept(self):
@@ -446,21 +548,6 @@ class _PVPlantPlacementTaskPanel:
if (item := self.form.listFrameSetups.item(i)).checkState() == QtCore.Qt.Checked
]
"""seen_lengths = set()
tmpframes = []
for frame in sorted(items, key=lambda rack: rack.Length, reverse=True):
if frame.Length not in seen_lengths:
seen_lengths.add(frame.Length)
tmpframes.append(frame)
'''found = False
for tmp in tmpframes:
if tmp.Length == frame.Length:
found = True
break
if not found:
tmpframes.append(frame)'''
self.FrameSetups = tmpframes.copy()"""
unique_frames = {frame.Length.Value: frame for frame in items}
self.FrameSetups = sorted(list(unique_frames.values()), key=lambda rack: rack.Length, reverse=True)
@@ -479,8 +566,14 @@ class _PVPlantPlacementTaskPanel:
dataframe = self.calculateNonAlignedArray()
# 3. Adjust to terrain:
self.createFrameFromPoints(dataframe)
FreeCAD.ActiveDocument.commitTransaction()
import Electrical.group as egroup
import importlib
importlib.reload(egroup)
egroup.groupTrackersToTransformers(5000000, self.gap_row + self.FrameSetups[0].Length.Value)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.RecomputesFrozen = False
params.SetBool("AutoSaveEnabled", auto_save_enabled)
@@ -489,6 +582,8 @@ class _PVPlantPlacementTaskPanel:
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
# ----------------------------------------------------------------------------------------------------------------------
# function AdjustToTerrain
# Take a group of objects and adjust it to the slope and altitude of the terrain mesh. It detects the terrain mesh