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

@@ -141,29 +141,31 @@ def groupTrackersToTransformers(transformer_power, max_distance):
for i, group in enumerate(transformer_groups): for i, group in enumerate(transformer_groups):
# Crear la esfera que representará el CT # Crear la esfera que representará el CT
ct_sphere = FreeCAD.ActiveDocument.addObject("Part::Sphere", f"CT_{i + 1}") ct_shape = FreeCAD.ActiveDocument.addObject("Part::Box", f"CT_{i + 1}")
ct_sphere.Radius = 5000 # 2m de radio ct_shape.Length = 6058
ct_sphere.Placement.Base = FreeCAD.Vector(group['center'][0], group['center'][1], 0) ct_shape.Width = 2438
ct_shape.Height = 2591
ct_shape.Placement.Base = FreeCAD.Vector(group['center'][0], group['center'][1], 0)
# Añadir propiedades personalizadas # Añadir propiedades personalizadas
ct_sphere.addProperty("App::PropertyLinkList", "Trackers", "CT", ct_shape.addProperty("App::PropertyLinkList", "Trackers", "CT",
"Lista de trackers asociados a este CT") "Lista de trackers asociados a este CT")
ct_sphere.addProperty("App::PropertyFloat", "TotalPower", "CT", ct_shape.addProperty("App::PropertyFloat", "TotalPower", "CT",
"Potencia total del grupo (W)") "Potencia total del grupo (W)")
ct_sphere.addProperty("App::PropertyFloat", "NominalPower", "CT", ct_shape.addProperty("App::PropertyFloat", "NominalPower", "CT",
"Potencia nominal del transformador (W)") "Potencia nominal del transformador (W)")
ct_sphere.addProperty("App::PropertyFloat", "Utilization", "CT", ct_shape.addProperty("App::PropertyFloat", "Utilization", "CT",
"Porcentaje de utilización (Total/Nominal)") "Porcentaje de utilización (Total/Nominal)")
# Establecer valores de las propiedades # Establecer valores de las propiedades
ct_sphere.Trackers = group['trackers'] ct_shape.Trackers = group['trackers']
ct_sphere.TotalPower = group['total_power'].Value ct_shape.TotalPower = group['total_power'].Value
ct_sphere.NominalPower = transformer_power ct_shape.NominalPower = transformer_power
ct_sphere.Utilization = (group['total_power'].Value / transformer_power) * 100 ct_shape.Utilization = (group['total_power'].Value / transformer_power) * 100
# Configurar visualización # Configurar visualización
# Calcular color basado en utilización (verde < 100%, amarillo < 110%, rojo > 110%) # Calcular color basado en utilización (verde < 100%, amarillo < 110%, rojo > 110%)
utilization = ct_sphere.Utilization utilization = ct_shape.Utilization
if utilization <= 100: if utilization <= 100:
color = (0.0, 1.0, 0.0) # Verde color = (0.0, 1.0, 0.0) # Verde
elif utilization <= 110: elif utilization <= 110:
@@ -171,15 +173,15 @@ def groupTrackersToTransformers(transformer_power, max_distance):
else: else:
color = (1.0, 0.0, 0.0) # Rojo color = (1.0, 0.0, 0.0) # Rojo
ct_sphere.ViewObject.ShapeColor = color ct_shape.ViewObject.ShapeColor = color
ct_sphere.ViewObject.Transparency = 40 # 40% de transparencia ct_shape.ViewObject.Transparency = 40 # 40% de transparencia
# Añadir etiqueta con información # Añadir etiqueta con información
ct_sphere.ViewObject.DisplayMode = "Shaded" ct_shape.ViewObject.DisplayMode = "Shaded"
ct_sphere.Label = f"CT {i + 1} ({ct_sphere.TotalPower / 1000:.1f}kW/{ct_sphere.NominalPower / 1000:.1f}kW)" ct_shape.Label = f"CT {i + 1} ({ct_shape.TotalPower / 1000:.1f}kW/{ct_shape.NominalPower / 1000:.1f}kW)"
# Añadir al grupo principal # Añadir al grupo principal
transformer_group.addObject(ct_sphere) transformer_group.addObject(ct_shape)
FreeCAD.Console.PrintMessage(f"Se crearon {len(transformer_groups)} centros de transformación\n") FreeCAD.Console.PrintMessage(f"Se crearon {len(transformer_groups)} centros de transformación\n")
onSelectGatePoint() onSelectGatePoint()
@@ -195,7 +197,7 @@ class InternalPathCreator:
self.gate_point = gate_point self.gate_point = gate_point
self.strategy = strategy self.strategy = strategy
self.path_width = path_width self.path_width = path_width
self.ct_spheres = [] self.ct_shapes = []
self.ct_positions = [] self.ct_positions = []
def get_transformers(self): def get_transformers(self):
@@ -204,13 +206,13 @@ class InternalPathCreator:
FreeCAD.Console.PrintError("No se encontró el grupo 'Transformers'\n") FreeCAD.Console.PrintError("No se encontró el grupo 'Transformers'\n")
return False return False
self.ct_spheres = transformers_group.Group self.ct_shapes = transformers_group.Group
if not self.ct_spheres: if not self.ct_shapes:
FreeCAD.Console.PrintWarning("No hay Centros de Transformación en el grupo\n") FreeCAD.Console.PrintWarning("No hay Centros de Transformación en el grupo\n")
return False return False
# Obtener las posiciones de los CTs # Obtener las posiciones de los CTs
for sphere in self.ct_spheres: for sphere in self.ct_shapes:
base = sphere.Placement.Base base = sphere.Placement.Base
self.ct_positions.append(FreeCAD.Vector(base.x, base.y, 0)) self.ct_positions.append(FreeCAD.Vector(base.x, base.y, 0))
return True return True
@@ -263,6 +265,8 @@ class InternalPathCreator:
y_proj = slope * x_proj + intercept y_proj = slope * x_proj + intercept
return FreeCAD.Vector(x_proj, y_proj, 0) return FreeCAD.Vector(x_proj, y_proj, 0)
# return slope * x + intercept --> desde placement
projected_points = [project_point(p) for p in all_points] projected_points = [project_point(p) for p in all_points]
# Calcular distancias a lo largo de la línea # Calcular distancias a lo largo de la línea

View File

@@ -626,6 +626,7 @@ layers = [
("Available area Names", QtGui.QColor(255, 255, 255), "Continuous", "1", True), ("Available area Names", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("Areas Exclusion", QtGui.QColor(255, 85, 0), "Continuous", "1", True), ("Areas Exclusion", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
("Areas Exclusion Offset", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
("Areas Exclusion Name", QtGui.QColor(255, 85, 0), "Continuous", "1", True), ("Areas Exclusion Name", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
("Areas Cadastral Plot", QtGui.QColor(255, 255, 255), "Continuous", "1", True), ("Areas Cadastral Plot", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("Areas Cadastral Plot Name", QtGui.QColor(255, 255, 255), "Continuous", "1", True), ("Areas Cadastral Plot Name", QtGui.QColor(255, 255, 255), "Continuous", "1", True),

View File

@@ -102,7 +102,7 @@ class _PVPlantPlacementTaskPanel:
def createFrameFromPoints(self, dataframe): def createFrameFromPoints(self, dataframe):
from Mechanical.Frame import PVPlantFrame from Mechanical.Frame import PVPlantFrame
try: '''try:
MechanicalGroup = FreeCAD.ActiveDocument.Frames MechanicalGroup = FreeCAD.ActiveDocument.Frames
except: except:
MechanicalGroup = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Frames') MechanicalGroup = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Frames')
@@ -110,14 +110,41 @@ class _PVPlantPlacementTaskPanel:
FreeCAD.ActiveDocument.MechanicalGroup.addObject(MechanicalGroup) FreeCAD.ActiveDocument.MechanicalGroup.addObject(MechanicalGroup)
if self.form.cbSubfolders.isChecked: if self.form.cbSubfolders.isChecked:
label = "Frames-" + self.PVArea.Label name = "Frames-" + self.PVArea.Label
if label in [obj.Label for obj in FreeCAD.ActiveDocument.Frames.Group]: if name in [obj.Name for obj in FreeCAD.ActiveDocument.Frames.Group]:
MechanicalGroup = FreeCAD.ActiveDocument.getObject(label)[0] MechanicalGroup = FreeCAD.ActiveDocument.getObject(name)[0]
else: else:
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", label) group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
group.Label = label group.Label = name
MechanicalGroup.addObject(group) 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: try:
placements = dataframe["placement"].tolist() placements = dataframe["placement"].tolist()
types = dataframe["type"].tolist() types = dataframe["type"].tolist()
@@ -127,7 +154,7 @@ class _PVPlantPlacementTaskPanel:
newrack.Label = "Tracker" newrack.Label = "Tracker"
newrack.Visibility = False newrack.Visibility = False
newrack.Placement = placements[idx] newrack.Placement = placements[idx]
MechanicalGroup.addObject(newrack) group.addObject(newrack)
frames.append(newrack) frames.append(newrack)
except: except:
placements = dataframe[0] placements = dataframe[0]
@@ -138,7 +165,7 @@ class _PVPlantPlacementTaskPanel:
newrack.Label = "Tracker" newrack.Label = "Tracker"
newrack.Visibility = False newrack.Visibility = False
newrack.Placement = idx[1] newrack.Placement = idx[1]
MechanicalGroup.addObject(newrack) groupq.addObject(newrack)
frames.append(newrack) frames.append(newrack)
if self.PVArea.Name.startswith("FrameArea"): if self.PVArea.Name.startswith("FrameArea"):
@@ -160,7 +187,7 @@ class _PVPlantPlacementTaskPanel:
if exclusion_areas: if exclusion_areas:
prohibited_faces = [] prohibited_faces = []
for obj in exclusion_areas: for obj in exclusion_areas:
face = self.getProjected(obj.Base.Shape) face = self.getProjected(obj.Shape.SubShapes[1])
if face.isValid(): if face.isValid():
prohibited_faces.append(face) prohibited_faces.append(face)
self.Area = self.Area.cut(prohibited_faces) self.Area = self.Area.cut(prohibited_faces)
@@ -474,64 +501,70 @@ class _PVPlantPlacementTaskPanel:
def calculateNonAlignedArray(self): def calculateNonAlignedArray(self):
pointsx, pointsy = self.getAligments() pointsx, pointsy = self.getAligments()
if len(pointsx) == 0:
FreeCAD.Console.PrintWarning("No se encontraron alineaciones X.\n")
return []
footprints = [] footprints = []
for frame in self.FrameSetups: for frame in self.FrameSetups:
xx = frame.Length.Value l = frame.Length.Value
yy = frame.Width.Value w = frame.Width.Value
xx_med = xx / 2 l_med = l / 2
yy_med = yy / 2 w_med = w / 2
rec = Part.makePolygon([FreeCAD.Vector(-xx_med, -yy_med, 0), rec = Part.makePolygon([FreeCAD.Vector(-l_med, -w_med, 0),
FreeCAD.Vector(xx_med, -yy_med, 0), FreeCAD.Vector( l_med, -w_med, 0),
FreeCAD.Vector(xx_med, yy_med, 0), FreeCAD.Vector( l_med, w_med, 0),
FreeCAD.Vector(-xx_med, yy_med, 0), FreeCAD.Vector(-l_med, w_med, 0),
FreeCAD.Vector(-xx_med, -yy_med, 0)]) FreeCAD.Vector(-l_med, -w_med, 0)])
rec.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), FreeCAD.Vector(0, 1, 0)) rec.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), FreeCAD.Vector(0, 1, 0))
footprints.append([frame, rec]) 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: corridor = self.form.groupCorridor.isChecked()
countcols = 0 corridor_offset = 0
countrows = 0 count = 0
offsetcols = 0 # ??
offsetrows = 0 # ??
valcols = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (self.gap_col - yy)
pl = [] cols = []
for point in pointsx: for x in pointsx:
p1 = FreeCAD.Vector(point, self.Area.BoundBox.YMax, 0.0) col=[]
p2 = FreeCAD.Vector(point, self.Area.BoundBox.YMin, 0.0) 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]) line = Part.makePolygon([p1, p2])
inter = self.Area.section([line]) 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): for i in range(0, len(pts), 2):
line = Part.LineSegment(pts[i], pts[i + 1]) top = pts[i]
if line.length() >= ref[1].BoundBox.YLength: bootom = pts[i + 1]
y1 = pts[i].y - ref[1].BoundBox.YLength / 2 if top.distanceToPoint(bootom) > footprints[-1][1].BoundBox.YLength:
cp = ref[1].copy() y1 = top.y - (footprints[-1][1].BoundBox.YLength / 2)
cp.Placement.Base = FreeCAD.Vector(pts[i].x, y1, 0.0) cp = footprints[-1][1].copy()
Part.show(cp) cp.Placement.Base = FreeCAD.Vector(x + footprints[-1][1].BoundBox.XLength / 2, y1, 0.0)
inter = cp.cut([self.Area]) inter = cp.cut([self.Area])
pts1 = [ver.Point for ver in inter.Vertexes] vtx = [ver.Point for ver in inter.Vertexes]
if len(pts1) == 0: mod = top.y
continue if len(vtx) != 0:
y1 = min(pts1, key=lambda p: p.y).y mod = min(vtx, key=lambda p: p.y).y
pointsy = np.arange(y1, pts[i + 1].y, -self.gap_row) #y1 = cp.Placement.Base.y - mod
continue
for pointy in pointsy: tmp = optimized_cut(mod - bootom.y, [ftp[1].BoundBox.YLength for ftp in footprints], 500, 'greedy')
cp = ref[1].copy() for opt in tmp[0]:
cp.Placement.Base = FreeCAD.Vector(pts[i].x + ref[1].BoundBox.XLength / 2, pointy, 0.0) mod -= (footprints[opt][1].BoundBox.YLength / 2)
cut = cp.cut([self.Area], 0) pl = FreeCAD.Vector(x + footprints[opt][1].BoundBox.XLength / 2, mod, 0.0)
#print(y1, " - ", pointy, " - ", len(cut.Vertexes)) cp = footprints[opt][1].copy()
#if len(cut.Vertexes) == 0: if self.isInside(cp, pl):
Part.show(cp) col.append([footprints[opt][0], pl])
pl.append([ref[0], pointy]) mod -= ((footprints[opt][1].BoundBox.YLength / 2) + 500)
return pl 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): def accept(self):
from datetime import datetime from datetime import datetime
@@ -583,6 +616,115 @@ class _PVPlantPlacementTaskPanel:
FreeCAD.ActiveDocument.recompute() 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 # function AdjustToTerrain

View File

@@ -54,30 +54,6 @@ class CommandPVPlantSite:
return return
'''class CommandPVPlantGeoreferencing:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "Location.svg")),
'Accel': "G, R",
'MenuText': QT_TRANSLATE_NOOP("Georeferencing","Georeferencing"),
'ToolTip': QT_TRANSLATE_NOOP("Georeferencing","Referenciar el lugar")}
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
@staticmethod
def Activated():
import PVPlantGeoreferencing
taskd = PVPlantGeoreferencing.MapWindow()
#taskd.setParent(FreeCADGui.getMainWindow())
#taskd.setWindowFlags(QtCore.Qt.Window)
taskd.show()#exec_()'''
class CommandProjectSetup: class CommandProjectSetup:
@staticmethod @staticmethod
def GetResources(): def GetResources():

View File

@@ -224,14 +224,20 @@ def getProjected(shape, direction=FreeCAD.Vector(0, 0, 1)): # Based on Draft / s
return ow return ow
def findObjects(classtype): '''def findObjects(classtype):
objects = FreeCAD.ActiveDocument.Objects objects = FreeCAD.ActiveDocument.Objects
objlist = list() objlist = list()
for object in objects: for object in objects:
if hasattr(object, "Proxy"): if hasattr(object, "Proxy"):
if object.Proxy.Type == classtype: if object.Proxy.Type == classtype:
objlist.append(object) objlist.append(object)
return objlist return objlist'''
def findObjects(classtype):
return [obj for obj in FreeCAD.ActiveDocument.Objects
if hasattr(obj, "Proxy")
and hasattr(obj.Proxy, "Type")
and obj.Proxy.Type == classtype]
def getClosePoints(sh1, angle): def getClosePoints(sh1, angle):
''' '''