This commit is contained in:
2025-08-17 13:34:09 +04:00
parent 3a188cc47d
commit 049898c939
6 changed files with 233 additions and 104 deletions

View File

@@ -102,7 +102,7 @@ class _PVPlantPlacementTaskPanel:
def createFrameFromPoints(self, dataframe):
from Mechanical.Frame import PVPlantFrame
try:
'''try:
MechanicalGroup = FreeCAD.ActiveDocument.Frames
except:
MechanicalGroup = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Frames')
@@ -110,14 +110,41 @@ class _PVPlantPlacementTaskPanel:
FreeCAD.ActiveDocument.MechanicalGroup.addObject(MechanicalGroup)
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]
name = "Frames-" + self.PVArea.Label
if name in [obj.Name for obj in FreeCAD.ActiveDocument.Frames.Group]:
MechanicalGroup = FreeCAD.ActiveDocument.getObject(name)[0]
else:
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", label)
group.Label = label
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
group.Label = name
MechanicalGroup.addObject(group)
MechanicalGroup = group
MechanicalGroup = group'''
doc = FreeCAD.ActiveDocument
# 1. Obtener o crear el grupo principal 'Frames'
main_group_name = "Frames"
main_group = doc.getObject(main_group_name)
if not main_group:
main_group = doc.addObject("App::DocumentObjectGroup", main_group_name)
main_group.Label = main_group_name
# Asumiendo que existe un grupo 'MechanicalGroup'
if hasattr(doc, 'MechanicalGroup'):
doc.MechanicalGroup.addObject(main_group)
# 2. Manejar subgrupo si es necesario
group = main_group # Grupo donde se añadirán los marcos
if self.form.cbSubfolders.isChecked(): # ¡Corregido: falta de paréntesis!
subgroup_name = f"Frames-{self.PVArea.Label}"
# Buscar subgrupo existente
subgroup = next((obj for obj in main_group.Group if obj.Name == subgroup_name), None)
if not subgroup:
subgroup = doc.addObject("App::DocumentObjectGroup", subgroup_name)
subgroup.Label = subgroup_name
main_group.addObject(subgroup)
group = subgroup
try:
placements = dataframe["placement"].tolist()
types = dataframe["type"].tolist()
@@ -127,7 +154,7 @@ class _PVPlantPlacementTaskPanel:
newrack.Label = "Tracker"
newrack.Visibility = False
newrack.Placement = placements[idx]
MechanicalGroup.addObject(newrack)
group.addObject(newrack)
frames.append(newrack)
except:
placements = dataframe[0]
@@ -138,7 +165,7 @@ class _PVPlantPlacementTaskPanel:
newrack.Label = "Tracker"
newrack.Visibility = False
newrack.Placement = idx[1]
MechanicalGroup.addObject(newrack)
groupq.addObject(newrack)
frames.append(newrack)
if self.PVArea.Name.startswith("FrameArea"):
@@ -160,7 +187,7 @@ class _PVPlantPlacementTaskPanel:
if exclusion_areas:
prohibited_faces = []
for obj in exclusion_areas:
face = self.getProjected(obj.Base.Shape)
face = self.getProjected(obj.Shape.SubShapes[1])
if face.isValid():
prohibited_faces.append(face)
self.Area = self.Area.cut(prohibited_faces)
@@ -474,64 +501,70 @@ class _PVPlantPlacementTaskPanel:
def calculateNonAlignedArray(self):
pointsx, pointsy = self.getAligments()
if len(pointsx) == 0:
FreeCAD.Console.PrintWarning("No se encontraron alineaciones X.\n")
return []
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)])
l = frame.Length.Value
w = frame.Width.Value
l_med = l / 2
w_med = w / 2
rec = Part.makePolygon([FreeCAD.Vector(-l_med, -w_med, 0),
FreeCAD.Vector( l_med, -w_med, 0),
FreeCAD.Vector( l_med, w_med, 0),
FreeCAD.Vector(-l_med, w_med, 0),
FreeCAD.Vector(-l_med, -w_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
# variables for corridors:
countcols = 0
countrows = 0
offsetcols = 0 # ??
offsetrows = 0 # ??
valcols = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (self.gap_col - yy)
corridor = self.form.groupCorridor.isChecked()
corridor_offset = 0
count = 0
pl = []
for point in pointsx:
p1 = FreeCAD.Vector(point, self.Area.BoundBox.YMax, 0.0)
p2 = FreeCAD.Vector(point, self.Area.BoundBox.YMin, 0.0)
cols = []
for x in pointsx:
col=[]
x += corridor_offset
p1 = FreeCAD.Vector(x, self.Area.BoundBox.YMax, 0.0)
p2 = FreeCAD.Vector(x, self.Area.BoundBox.YMin, 0.0)
line = Part.makePolygon([p1, p2])
inter = self.Area.section([line])
pts = [ver.Point for ver in inter.Vertexes] # todo: sort points
pts = [ver.Point for ver in inter.Vertexes]
pts = sorted(pts, key=lambda p: p.y, reverse=True)
for i in range(0, len(pts), 2):
line = Part.LineSegment(pts[i], pts[i + 1])
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)
top = pts[i]
bootom = pts[i + 1]
if top.distanceToPoint(bootom) > footprints[-1][1].BoundBox.YLength:
y1 = top.y - (footprints[-1][1].BoundBox.YLength / 2)
cp = footprints[-1][1].copy()
cp.Placement.Base = FreeCAD.Vector(x + footprints[-1][1].BoundBox.XLength / 2, y1, 0.0)
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
vtx = [ver.Point for ver in inter.Vertexes]
mod = top.y
if len(vtx) != 0:
mod = min(vtx, key=lambda p: p.y).y
#y1 = cp.Placement.Base.y - mod
tmp = optimized_cut(mod - bootom.y, [ftp[1].BoundBox.YLength for ftp in footprints], 500, 'greedy')
for opt in tmp[0]:
mod -= (footprints[opt][1].BoundBox.YLength / 2)
pl = FreeCAD.Vector(x + footprints[opt][1].BoundBox.XLength / 2, mod, 0.0)
cp = footprints[opt][1].copy()
if self.isInside(cp, pl):
col.append([footprints[opt][0], pl])
mod -= ((footprints[opt][1].BoundBox.YLength / 2) + 500)
Part.show(cp)
if corridor and len(col) > 0:
count += 1
if count == self.form.editColCount.value():
corridor_offset += 12000
count = 0
cols.append(cols)
return self.adjustToTerrain(cols)
def accept(self):
from datetime import datetime
@@ -583,6 +616,115 @@ class _PVPlantPlacementTaskPanel:
FreeCAD.ActiveDocument.recompute()
def optimized_cut(L_total, piezas, margen=0, metodo='auto'):
"""
Encuentra la combinación óptima de piezas para minimizar el desperdicio,
considerando un margen entre piezas.
Args:
L_total (int): Longitud total del material.
piezas (list): Lista de longitudes de los patrones de corte.
margen (int): Espacio perdido entre piezas consecutivas.
metodo (str): 'dp' para programación dinámica, 'greedy' para voraz, 'auto' para selección automática.
Returns:
tuple: (piezas_seleccionadas, desperdicio)
"""
# Filtrar piezas inválidas
piezas = [p for p in piezas if 0 < p <= L_total]
if not piezas:
return [], L_total
# Transformar longitudes y longitud total con margen
longitudes_aumentadas = [p + margen for p in piezas]
L_total_aumentado = L_total + margen
# Selección automática de método
if metodo == 'auto':
if L_total_aumentado <= 10000 and len(piezas) <= 100:
metodo = 'dp'
else:
metodo = 'greedy'
if metodo == 'dp':
n = len(piezas)
dp = [0] * (L_total_aumentado + 1)
parent = [-1] * (L_total_aumentado + 1) # Almacena índices de piezas usadas
# Llenar la tabla dp y parent
for j in range(1, L_total_aumentado + 1):
for i in range(n):
p_aum = longitudes_aumentadas[i]
if p_aum <= j:
if dp[j] < dp[j - p_aum] + p_aum:
dp[j] = dp[j - p_aum] + p_aum
parent[j] = i # Guardar índice de la pieza
# Reconstruir solución desde el final
current = L_total_aumentado
seleccion_indices = []
while current > 0 and parent[current] != -1:
i = parent[current]
seleccion_indices.append(i)
current -= longitudes_aumentadas[i]
# Calcular desperdicio real
k = len(seleccion_indices)
if k == 0:
desperdicio = L_total
else:
suma_original = sum(piezas[i] for i in seleccion_indices)
desperdicio = L_total - suma_original - margen * (k - 1)
return seleccion_indices, desperdicio
elif metodo == 'greedy':
# Crear lista con índices y longitudes aumentadas
lista_con_indices = [(longitudes_aumentadas[i], i) for i in range(len(piezas))]
lista_con_indices.sort(key=lambda x: x[0], reverse=True) # Ordenar descendente
seleccion_indices = []
restante = L_total_aumentado
# Seleccionar piezas vorazmente
for p_aum, i in lista_con_indices:
while restante >= p_aum:
seleccion_indices.append(i)
restante -= p_aum
# Calcular desperdicio real
k = len(seleccion_indices)
if k == 0:
desperdicio = L_total
else:
suma_original = sum(piezas[i] for i in seleccion_indices)
desperdicio = L_total - suma_original - margen * (k - 1)
return seleccion_indices, desperdicio
# Ejemplo de uso
'''if __name__ == "__main__":
L_total = 100
piezas = [25, 35, 40, 20, 15, 30, 50]
margen = 5
print("Solución óptima con margen (programación dinámica):")
seleccion, desperd = corte_optimizado(L_total, piezas, margen, 'dp')
print(f"Piezas usadas: {seleccion}")
print(f"Margen entre piezas: {margen} cm")
print(f"Material útil: {sum(seleccion)} cm")
print(f"Espacio usado por márgenes: {(len(seleccion) - 1) * margen} cm")
print(f"Desperdicio total: {desperd} cm")
print("\nSolución aproximada con margen (algoritmo voraz):")
seleccion_g, desperd_g = corte_optimizado(L_total, piezas, margen, 'greedy')
print(f"Piezas usadas: {seleccion_g}")
print(f"Margen entre piezas: {margen} cm")
print(f"Material útil: {sum(seleccion_g)} cm")
print(f"Espacio usado por márgenes: {(len(seleccion_g) - 1) * margen} cm")
print(f"Desperdicio total: {desperd_g} cm")'''
# ----------------------------------------------------------------------------------------------------------------------
# function AdjustToTerrain