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):
# Crear la esfera que representará el CT
ct_sphere = FreeCAD.ActiveDocument.addObject("Part::Sphere", f"CT_{i + 1}")
ct_sphere.Radius = 5000 # 2m de radio
ct_sphere.Placement.Base = FreeCAD.Vector(group['center'][0], group['center'][1], 0)
ct_shape = FreeCAD.ActiveDocument.addObject("Part::Box", f"CT_{i + 1}")
ct_shape.Length = 6058
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
ct_sphere.addProperty("App::PropertyLinkList", "Trackers", "CT",
ct_shape.addProperty("App::PropertyLinkList", "Trackers", "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)")
ct_sphere.addProperty("App::PropertyFloat", "NominalPower", "CT",
ct_shape.addProperty("App::PropertyFloat", "NominalPower", "CT",
"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)")
# Establecer valores de las propiedades
ct_sphere.Trackers = group['trackers']
ct_sphere.TotalPower = group['total_power'].Value
ct_sphere.NominalPower = transformer_power
ct_sphere.Utilization = (group['total_power'].Value / transformer_power) * 100
ct_shape.Trackers = group['trackers']
ct_shape.TotalPower = group['total_power'].Value
ct_shape.NominalPower = transformer_power
ct_shape.Utilization = (group['total_power'].Value / transformer_power) * 100
# Configurar visualización
# Calcular color basado en utilización (verde < 100%, amarillo < 110%, rojo > 110%)
utilization = ct_sphere.Utilization
utilization = ct_shape.Utilization
if utilization <= 100:
color = (0.0, 1.0, 0.0) # Verde
elif utilization <= 110:
@@ -171,15 +173,15 @@ def groupTrackersToTransformers(transformer_power, max_distance):
else:
color = (1.0, 0.0, 0.0) # Rojo
ct_sphere.ViewObject.ShapeColor = color
ct_sphere.ViewObject.Transparency = 40 # 40% de transparencia
ct_shape.ViewObject.ShapeColor = color
ct_shape.ViewObject.Transparency = 40 # 40% de transparencia
# Añadir etiqueta con información
ct_sphere.ViewObject.DisplayMode = "Shaded"
ct_sphere.Label = f"CT {i + 1} ({ct_sphere.TotalPower / 1000:.1f}kW/{ct_sphere.NominalPower / 1000:.1f}kW)"
ct_shape.ViewObject.DisplayMode = "Shaded"
ct_shape.Label = f"CT {i + 1} ({ct_shape.TotalPower / 1000:.1f}kW/{ct_shape.NominalPower / 1000:.1f}kW)"
# 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")
onSelectGatePoint()
@@ -195,7 +197,7 @@ class InternalPathCreator:
self.gate_point = gate_point
self.strategy = strategy
self.path_width = path_width
self.ct_spheres = []
self.ct_shapes = []
self.ct_positions = []
def get_transformers(self):
@@ -204,13 +206,13 @@ class InternalPathCreator:
FreeCAD.Console.PrintError("No se encontró el grupo 'Transformers'\n")
return False
self.ct_spheres = transformers_group.Group
if not self.ct_spheres:
self.ct_shapes = transformers_group.Group
if not self.ct_shapes:
FreeCAD.Console.PrintWarning("No hay Centros de Transformación en el grupo\n")
return False
# Obtener las posiciones de los CTs
for sphere in self.ct_spheres:
for sphere in self.ct_shapes:
base = sphere.Placement.Base
self.ct_positions.append(FreeCAD.Vector(base.x, base.y, 0))
return True
@@ -263,6 +265,8 @@ class InternalPathCreator:
y_proj = slope * x_proj + intercept
return FreeCAD.Vector(x_proj, y_proj, 0)
# return slope * x + intercept --> desde placement
projected_points = [project_point(p) for p in all_points]
# 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),
("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 Cadastral Plot", 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):
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

View File

@@ -54,30 +54,6 @@ class CommandPVPlantSite:
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:
@staticmethod
def GetResources():

View File

@@ -224,14 +224,20 @@ def getProjected(shape, direction=FreeCAD.Vector(0, 0, 1)): # Based on Draft / s
return ow
def findObjects(classtype):
'''def findObjects(classtype):
objects = FreeCAD.ActiveDocument.Objects
objlist = list()
for object in objects:
if hasattr(object, "Proxy"):
if object.Proxy.Type == classtype:
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):
'''