1283 lines
49 KiB
Python
1283 lines
49 KiB
Python
# /**********************************************************************
|
|
# * *
|
|
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
|
|
# * *
|
|
# * This program is free software; you can redistribute it and/or modify*
|
|
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
# * as published by the Free Software Foundation; either version 2 of *
|
|
# * the License, or (at your option) any later version. *
|
|
# * for detail see the LICENCE text file. *
|
|
# * *
|
|
# * This program is distributed in the hope that it will be useful, *
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
# * GNU Library General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with this program; if not, write to the Free Software *
|
|
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307*
|
|
# * USA *
|
|
# * *
|
|
# ***********************************************************************
|
|
|
|
import FreeCAD
|
|
import Part
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui, os
|
|
from PySide import QtCore, QtGui
|
|
from PySide.QtGui import QListWidgetItem
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
else:
|
|
# \cond
|
|
def translate(ctxt, txt):
|
|
return txt
|
|
|
|
|
|
def QT_TRANSLATE_NOOP(ctxt, txt):
|
|
return txt
|
|
# \endcond
|
|
|
|
try:
|
|
_fromUtf8 = QtCore.QString.fromUtf8
|
|
except AttributeError:
|
|
def _fromUtf8(s):
|
|
return s
|
|
|
|
import PVPlantResources
|
|
import PVPlantSite
|
|
|
|
version = "0.1.0"
|
|
|
|
|
|
def selectionFilter(sel, objtype):
|
|
fil = []
|
|
for obj in sel:
|
|
if hasattr(obj, "Proxy"):
|
|
if obj.Proxy.__class__ is objtype:
|
|
fil.append(obj)
|
|
return fil
|
|
|
|
|
|
class _PVPlantPlacementTaskPanel:
|
|
'''The editmode TaskPanel for Schedules'''
|
|
|
|
def __init__(self, obj=None):
|
|
self.site = PVPlantSite.get()
|
|
self.Terrain = self.site.Terrain
|
|
self.FrameSetups = None
|
|
self.PVArea = None
|
|
self.Area = None
|
|
self.gap_col = .0
|
|
self.gap_row = .0
|
|
self.offsetX = .0
|
|
self.offsetY = .0
|
|
self.Dir = FreeCAD.Vector(0, -1, 0) # Norte a sur
|
|
|
|
# self.form:
|
|
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.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()
|
|
if len(sel) > 0:
|
|
self.PVArea = sel[0]
|
|
self.form.editPVArea.setText(self.PVArea.Label)
|
|
|
|
def addFrames(self):
|
|
for frame_setup in self.site.Frames:
|
|
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:
|
|
MechanicalGroup = FreeCAD.ActiveDocument.Frames
|
|
except:
|
|
MechanicalGroup = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Frames')
|
|
MechanicalGroup.Label = "Frames"
|
|
FreeCAD.ActiveDocument.MechanicalGroup.addObject(MechanicalGroup)
|
|
|
|
if self.form.cbSubfolders.isChecked:
|
|
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", name)
|
|
group.Label = name
|
|
MechanicalGroup.addObject(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()
|
|
frames = []
|
|
for idx in range(len(placements)):
|
|
newrack = PVPlantFrame.makeTracker(setup=types[idx])
|
|
newrack.Label = "Tracker"
|
|
newrack.Visibility = False
|
|
newrack.Placement = placements[idx]
|
|
group.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]
|
|
groupq.addObject(newrack)
|
|
frames.append(newrack)
|
|
|
|
if self.PVArea.Name.startswith("FrameArea"):
|
|
self.PVArea.Frames = frames
|
|
|
|
def getProjected(self, shape):
|
|
""" returns projected edges from a shape and a direction """
|
|
if shape.BoundBox.ZLength == 0:
|
|
return Part.Face(Part.Wire(shape.Edges))
|
|
|
|
from Utils import PVPlantUtils as utils
|
|
wire = utils.simplifyWire(utils.getProjected(shape))
|
|
return Part.Face(wire.removeSplitter()) if wire.isClosed() else Part.Face(wire)
|
|
|
|
def calculateWorkingArea(self):
|
|
self.Area = self.getProjected(self.PVArea.Shape)
|
|
exclusion_areas = FreeCAD.ActiveDocument.findObjects(Name="ExclusionArea")
|
|
|
|
if exclusion_areas:
|
|
prohibited_faces = []
|
|
for obj in exclusion_areas:
|
|
face = self.getProjected(obj.Shape.SubShapes[1])
|
|
if face.isValid():
|
|
prohibited_faces.append(face)
|
|
self.Area = self.Area.cut(prohibited_faces)
|
|
|
|
def getAligments(self):
|
|
# TODO: revisar todo esto: -----------------------------------------------------------------
|
|
sel = FreeCADGui.Selection.getSelectionEx()[0]
|
|
refh = None
|
|
refv = None
|
|
|
|
if len(sel.SubObjects) == 0:
|
|
return
|
|
|
|
elif len(sel.SubObjects) == 1:
|
|
# Todo: chequear que sea un edge. Si es otra cosa coger el edge[0] de la forma
|
|
refh = refv = sel.SubObjects[0]
|
|
|
|
elif len(sel.SubObjects) > 1:
|
|
# Todo: chequear que sea un edge. Si es otra cosa coger el edge[0] de la forma
|
|
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 - self.Area.BoundBox.XMin + self.offsetX) / self.gap_col)
|
|
startx = int(refv.BoundBox.XMin + self.offsetX - self.gap_col * steps)
|
|
steps = int((refh.BoundBox.YMin - self.Area.BoundBox.YMax + self.offsetY) / self.gap_row)
|
|
starty = int(refh.BoundBox.YMin + self.offsetY + self.gap_row * steps)
|
|
# todo end ----------------------------------------------------------------------------------
|
|
|
|
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_old(self, coordinates):
|
|
mode = 1
|
|
terrain = self.Terrain.Mesh
|
|
|
|
def placeRegion(df): # TODO: new
|
|
import MeshPart as mp
|
|
from scipy import stats
|
|
linregression = []
|
|
for colnum in df.column.unique().tolist():
|
|
dftmp = df[df["column"] == colnum]
|
|
for id in dftmp["ID"].tolist():
|
|
data = df.loc[df['ID'] == id]
|
|
frametype = data["type"].tolist()[0]
|
|
# col = data["column"]
|
|
# row = data["row"]
|
|
base = data["placement"].tolist()[0]
|
|
|
|
yl = frametype.Length.Value / 2
|
|
ptop = FreeCAD.Vector(base)
|
|
ptop.y += yl
|
|
pbot = FreeCAD.Vector(base)
|
|
pbot.y -= yl
|
|
line = Part.LineSegment(ptop, pbot).toShape()
|
|
profilepoints = mp.projectShapeOnMesh(line, terrain, FreeCAD.Vector(0, 0, 1))[0]
|
|
'''else: # Shape: sumamente lento por lo que quedaría eliminado si no se encuetra otro modo.
|
|
tmp = terrain.makeParallelProjection(line, FreeCAD.Vector(0, 0, 1))
|
|
profilepoints = [ver.Point for ver in tmp.Vertexes]'''
|
|
|
|
xx = list()
|
|
yy = list()
|
|
zz = list()
|
|
for pts in profilepoints:
|
|
xx.append(pts.x)
|
|
yy.append(pts.y)
|
|
zz.append(pts.z)
|
|
slope, intercept, r, p, std_err = stats.linregress(yy, zz)
|
|
|
|
# linregression.append(slope, intercept, r, p, std_err)
|
|
def myfunc(x):
|
|
return slope * x + intercept
|
|
|
|
newzz = list(map(myfunc, [yy[0], yy[-1]]))
|
|
points3D = list()
|
|
points3D.append(FreeCAD.Vector(xx[0], yy[0], newzz[0]))
|
|
points3D.append(FreeCAD.Vector(xx[-1], yy[-1], newzz[1]))
|
|
linregression.append(points3D)
|
|
|
|
# for ind in range(0, len(points3D) - 1):
|
|
pl = FreeCAD.Placement()
|
|
pl.Base = (points3D[0] + points3D[1]) / 2
|
|
rot = FreeCAD.Rotation(FreeCAD.Vector(-1, 0, 0), points3D[0] - points3D[1])
|
|
pl.Rotation = FreeCAD.Rotation(rot.toEuler()[0], rot.toEuler()[1], 0)
|
|
df.at[id - 1, "placement"] = pl
|
|
df["regression"] = linregression
|
|
|
|
# 01. Grouping:
|
|
from scipy.ndimage import label as sclabel
|
|
import pandas as pd
|
|
tmp = []
|
|
for c, col in enumerate(coordinates):
|
|
tmpcol = []
|
|
for n, obj in enumerate(col):
|
|
if obj != 0:
|
|
tmpcol.append(1)
|
|
else:
|
|
tmpcol.append(0)
|
|
tmp.append(tmpcol)
|
|
|
|
data = {"ID": [],
|
|
"region": [],
|
|
"type": [],
|
|
"column": [],
|
|
"row": [],
|
|
"placement": []}
|
|
|
|
arr = np.array(tmp)
|
|
labeled_array, num_features = sclabel(arr)
|
|
id = 1
|
|
for label in range(1, num_features + 1):
|
|
cols, rows = np.where(labeled_array == label)
|
|
unique, counts = np.unique(cols, return_counts=True)
|
|
result = np.column_stack((unique, counts))
|
|
cnt = 0
|
|
for val, count in result:
|
|
for c in range(count):
|
|
data["ID"].append(id)
|
|
data["region"].append(label)
|
|
data["type"].append(coordinates[val][rows[cnt]][0])
|
|
data["column"].append(val)
|
|
data["row"].append(rows[cnt])
|
|
data["placement"].append(coordinates[val][rows[cnt]][1])
|
|
cnt += 1
|
|
id += 1
|
|
df = pd.DataFrame(data)
|
|
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
|
|
cut = frame.cut([self.Area])
|
|
if len(cut.Vertexes) == 0:
|
|
return True
|
|
return False
|
|
|
|
def calculateAlignedArray(self):
|
|
import FreeCAD
|
|
pointsx, pointsy = self.getAligments()
|
|
|
|
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
|
|
|
|
# variables for corridors:
|
|
countcols = 0
|
|
countrows = 0
|
|
offsetcols = 0 # ??
|
|
offsetrows = 0 # ??
|
|
valcols = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (self.gap_col - yy)
|
|
|
|
cols = []
|
|
for x in pointsx:
|
|
col = []
|
|
for y in pointsy:
|
|
found = False
|
|
point = FreeCAD.Vector(x + yy_med + offsetcols, y - xx_med + offsetrows, 0.0)
|
|
if self.isInside(ref[1], point):
|
|
col.append([ref[0], point])
|
|
found = True
|
|
continue
|
|
else:
|
|
for footprint in footprints:
|
|
l = int((ref[0].Length - footprint[0].Length) / 2)
|
|
for i in range(2):
|
|
point1 = FreeCAD.Vector(point)
|
|
point1.y = point1.y + l
|
|
if self.isInside(footprint[1], point1):
|
|
col.append([footprint[0], point1])
|
|
found = True
|
|
break
|
|
l = -l
|
|
if found:
|
|
break
|
|
if not found:
|
|
col.append(0)
|
|
cols.append(col)
|
|
|
|
# if len(col) > 0:
|
|
# code for vertical corridors:
|
|
if self.form.groupCorridor.isChecked():
|
|
if self.form.editColCount.value() > 0:
|
|
countcols += 1
|
|
if countcols == self.form.editColCount.value():
|
|
offsetcols += valcols
|
|
countcols = 0
|
|
|
|
return self.adjustToTerrain(cols)
|
|
|
|
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:
|
|
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])
|
|
|
|
corridor = self.form.groupCorridor.isChecked()
|
|
corridor_offset = 0
|
|
count = 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]
|
|
pts = sorted(pts, key=lambda p: p.y, reverse=True)
|
|
for i in range(0, len(pts), 2):
|
|
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])
|
|
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
|
|
starttime = datetime.now()
|
|
|
|
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Document")
|
|
auto_save_enabled = params.GetBool("AutoSaveEnabled")
|
|
params.SetBool("AutoSaveEnabled", False)
|
|
FreeCAD.ActiveDocument.RecomputesFrozen = True
|
|
|
|
items = [
|
|
FreeCAD.ActiveDocument.getObject(item.text())
|
|
for i in range(self.form.listFrameSetups.count())
|
|
if (item := self.form.listFrameSetups.item(i)).checkState() == QtCore.Qt.Checked
|
|
]
|
|
|
|
unique_frames = {frame.Length.Value: frame for frame in items}
|
|
self.FrameSetups = sorted(list(unique_frames.values()), key=lambda rack: rack.Length, reverse=True)
|
|
|
|
self.gap_col = FreeCAD.Units.Quantity(self.form.editGapCols.text()).Value
|
|
self.gap_row = FreeCAD.Units.Quantity(self.form.editGapRows.text()).Value + self.FrameSetups[0].Length.Value
|
|
self.offsetX = FreeCAD.Units.Quantity(self.form.editOffsetHorizontal.text()).Value
|
|
self.offsetY = FreeCAD.Units.Quantity(self.form.editOffsetVertical.text()).Value
|
|
|
|
FreeCAD.ActiveDocument.openTransaction("Create Placement")
|
|
# 1. Calculate working area:
|
|
self.calculateWorkingArea()
|
|
# 2. Calculate aligned array:
|
|
if self.form.cbAlignFrames.isChecked():
|
|
dataframe = self.calculateAlignedArray()
|
|
else:
|
|
dataframe = self.calculateNonAlignedArray()
|
|
# 3. Adjust to terrain:
|
|
self.createFrameFromPoints(dataframe)
|
|
|
|
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)
|
|
|
|
total_time = datetime.now() - starttime
|
|
print(" -- Tiempo tardado:", total_time)
|
|
FreeCADGui.Control.closeDialog()
|
|
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
|
|
# Take a group of objects and adjust it to the slope and altitude of the terrain mesh. It detects the terrain mesh
|
|
#
|
|
# Inputs:
|
|
# 1. frames: group of objest to adjust
|
|
# ----------------------------------------------------------------------------------------------------------------------
|
|
class adjustToTerrainTaskPanel:
|
|
def __init__(self, obj=None):
|
|
self.obj = obj
|
|
self.form = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantPlacementAdjust.ui")
|
|
|
|
def accept(self):
|
|
frames = []
|
|
for obj in FreeCADGui.Selection.getSelection():
|
|
if obj.Name.startswith("Tracker"):
|
|
frames.append(obj)
|
|
elif obj.Name.startswith("FrameArea"):
|
|
frames.extend(obj.Frames)
|
|
adjustToTerrain(frames, self.form.comboMethod.currentIndex() == 0)
|
|
self.close()
|
|
return True
|
|
|
|
def reject(self):
|
|
self.close()
|
|
return False
|
|
|
|
def close(self):
|
|
FreeCADGui.Control.closeDialog()
|
|
|
|
import numpy as np
|
|
from scipy import stats
|
|
|
|
def get_trend(points):
|
|
"""Return the trend of a list of 3D points"""
|
|
x, y, z = zip(*[(point.x, point.y, point.z) for point in points])
|
|
slope, intercept, _, _, _ = stats.linregress(y, z)
|
|
new_z = slope * np.array([y[0], y[-1]]) + intercept
|
|
return [FreeCAD.Vector(x[0], y[0], new_z[0]), FreeCAD.Vector(x[-1], y[-1], new_z[1])]
|
|
|
|
def getTrend(points): # old
|
|
from scipy import stats
|
|
def getNewZ(x):
|
|
return slope * x + intercept
|
|
|
|
xx = list()
|
|
yy = list()
|
|
zz = list()
|
|
for point in points:
|
|
xx.append(point.x)
|
|
yy.append(point.y)
|
|
zz.append(point.z)
|
|
slope, intercept, r, p, std_err = stats.linregress(yy, zz)
|
|
newzz = list(map(getNewZ, [yy[0], yy[-1]]))
|
|
return [FreeCAD.Vector(xx[0], yy[0], newzz[0]),
|
|
FreeCAD.Vector(xx[-1], yy[-1], newzz[1])]
|
|
|
|
|
|
def adjustToTerrain(frames, individual=True):
|
|
from datetime import datetime
|
|
starttime = datetime.now()
|
|
|
|
import MeshPart as mp
|
|
|
|
FreeCAD.ActiveDocument.openTransaction("Adjust to terrain")
|
|
terrain = PVPlantSite.get().Terrain.Mesh
|
|
|
|
if individual:
|
|
for frame in frames:
|
|
length = frame.Setup.Length.Value / 2 + 5000
|
|
p1 = FreeCAD.Vector(-length, 0, 0, )
|
|
p2 = FreeCAD.Vector(length, 0, 0, )
|
|
line = Part.LineSegment(p1, p2).toShape()
|
|
line.Placement = frame.Placement.copy()
|
|
line.Placement.Base.z = 0
|
|
xyz = line.Placement.Rotation.toEulerAngles("XYZ")
|
|
line.Placement.Rotation.setEulerAngles("XYZ", 0, 0, xyz[2])
|
|
pro = mp.projectShapeOnMesh(line, terrain, FreeCAD.Vector(0, 0, 1))
|
|
pts = []
|
|
for points in pro:
|
|
pts.extend(points)
|
|
points3D = get_trend(pts)
|
|
|
|
pl = FreeCAD.Placement()
|
|
pl.Base = (points3D[0] + points3D[1]) / 2
|
|
rot = FreeCAD.Rotation(FreeCAD.Vector(-1, 0, 0), points3D[0] - points3D[1])
|
|
pl.Rotation = FreeCAD.Rotation(rot.toEuler()[0], rot.toEuler()[1], 0)
|
|
frame.Placement = pl
|
|
else:
|
|
import math
|
|
def getLineAngle(line):
|
|
# ángulo en grados = arctan(ángulo en porcentaje / 100%)
|
|
import math
|
|
p1 = FreeCAD.Vector(line.Vertexes[0].Point)
|
|
p2 = FreeCAD.Vector(line.Vertexes[1].Point)
|
|
hi = p2.z - p1.z
|
|
p1.z = 0
|
|
p2.z = 0
|
|
le = p2.sub(p1).Length
|
|
return math.degrees(math.atan2(hi, le))
|
|
|
|
cols = getCols(frames)
|
|
for col in cols:
|
|
for group in col:
|
|
# Ver 1 -----------------
|
|
lines = []
|
|
# 1. Generar las líneas de trabajo.
|
|
for frame in group:
|
|
# 1.1. Corregir los frames que estén fuera de tolerancia:
|
|
if frame.AngleY < FreeCAD.ActiveDocument.MaximumTiltNegative.Value:
|
|
frame.AngleY = FreeCAD.ActiveDocument.MaximumTiltNegative.Value
|
|
if frame.AngleY > FreeCAD.ActiveDocument.MaximumTiltPositive.Value:
|
|
frame.AngleY = FreeCAD.ActiveDocument.MaximumTiltPositive.Value
|
|
|
|
# 1.2. Generar las líneas con las que se trabajarán:
|
|
l = frame.Setup.Length / 2
|
|
pn = FreeCAD.Vector(-l, 0, 0)
|
|
ps = FreeCAD.Vector( l, 0, 0)
|
|
line = Part.LineSegment(pn, ps).toShape()
|
|
line.Placement = frame.Placement.copy()
|
|
lines.append([frame, line])
|
|
|
|
# 2. Poner los tracker en tolerancia:
|
|
cnt = len(lines)
|
|
if cnt > 1:
|
|
angleLine=[]
|
|
anglesTwoLines=[]
|
|
for frame in lines:
|
|
angleLine.append(frame[0].AngleY.Value)
|
|
for ind in range(cnt - 1):
|
|
frame1 = lines[ind]
|
|
frame2 = lines[ind + 1]
|
|
vec1 = frame1[1].Vertexes[1].Point.sub(frame1[1].Vertexes[0].Point)
|
|
vec2 = frame2[1].Vertexes[1].Point.sub(frame2[1].Vertexes[0].Point)
|
|
anglesTwoLines.append(math.degrees(vec2.getAngle(vec1)))
|
|
print(angleLine)
|
|
print(anglesTwoLines)
|
|
pass
|
|
|
|
for ind, frame in enumerate(lines):
|
|
frame0 = None
|
|
frame1 = None
|
|
if ind > 0:
|
|
frame0 = lines[ind - 1]
|
|
if ind < (len(group) - 1):
|
|
frame1 = lines[ind + 1]
|
|
|
|
if (frame0 is None) and (frame1 is None): # Caso 1: sólo 1 frame por fila
|
|
# no se hace nada. ya está con todos los parámetros dentro de tolerancia
|
|
pass
|
|
elif (frame0 is None) and not (frame1 is None): # Caso 2: frame es el primero y hay más frames
|
|
pass
|
|
elif not (frame0 is None) and (frame1 is None): # Caso 3: el frame es el último y hay más frames
|
|
pass
|
|
else: # Caso 4: el frame está en el médio de varios frames
|
|
pass
|
|
|
|
continue
|
|
|
|
# Ver 0 -----------------
|
|
points = []
|
|
# 1. Get lines/points to project on land
|
|
frame1 = group[0] # Norte
|
|
frame2 = group[-1] # Sur
|
|
# 1.1. Get the first and last points:
|
|
|
|
# TODO: revisar esta parte:
|
|
p0 = FreeCAD.Vector(frame1.Shape.BoundBox.Center.x, frame1.Shape.BoundBox.YMax, 0.0)
|
|
pf = FreeCAD.Vector(frame2.Shape.BoundBox.Center.x, frame2.Shape.BoundBox.YMin, 0.0)
|
|
|
|
vec = (pf - p0).normalize()
|
|
points.append(p0)
|
|
for ind in range(0, len(group) - 1):
|
|
frame1 = group[ind]
|
|
frame2 = group[ind + 1]
|
|
vec1 = FreeCAD.Vector(frame1.Placement.Base)
|
|
vec2 = FreeCAD.Vector(frame2.Placement.Base)
|
|
vec1.z = 0
|
|
vec2.z = 0
|
|
vec3 = vec2.sub(vec1)
|
|
c = vec3.Length / 2 + (frame1.Setup.Length.Value - frame2.Setup.Length.Value) / 4
|
|
v = FreeCAD.Vector(vec)
|
|
v.Length = c
|
|
v = vec1.add(v)
|
|
v.z = 0
|
|
points.append(v)
|
|
points.append(pf)
|
|
|
|
# 2. Calculate trend:
|
|
points3D = []
|
|
for ind in range(len(points) - 1):
|
|
line = Part.LineSegment(points[ind], points[ind + 1]).toShape()
|
|
pro = mp.projectShapeOnMesh(line, terrain, FreeCAD.Vector(0, 0, 1))
|
|
pts = []
|
|
for lp in pro:
|
|
pts.extend(lp)
|
|
points3D.extend(get_trend(pts))
|
|
# Todo: aplicar aproximación de los vertices:
|
|
# prueba:
|
|
for i in range(0, len(points3D) - 2, 2):
|
|
# p0 = points3D[i]
|
|
p1 = points3D[i + 1]
|
|
p2 = points3D[i + 2]
|
|
# p3 = points3D[i + 3]
|
|
|
|
l = p1.sub(p2).Length
|
|
if l > 250:
|
|
l = (l - 250) / 2
|
|
if p1.z > p2.z:
|
|
p1.z -= l
|
|
p2.z += l
|
|
else:
|
|
p1.z += l
|
|
p2.z -= l
|
|
|
|
# 3. Aplicar placement
|
|
for ind, frame in enumerate(group):
|
|
v1 = points3D[ind * 2]
|
|
v2 = points3D[ind * 2 + 1]
|
|
pl = frame.Placement.copy()
|
|
pl.Base.z = (v1.add(v2) / 2).z
|
|
rot = FreeCAD.Rotation(FreeCAD.Vector(-1, 0, 0), v1.sub(v2))
|
|
pl.Rotation = FreeCAD.Rotation(rot.toEuler()[0], rot.toEuler()[1], 0)
|
|
frame.Placement = pl
|
|
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
|
total_time = datetime.now() - starttime
|
|
print(" -- Tiempo tardado en ajustar al terreno:", total_time)
|
|
FreeCAD.activeDocument().recompute()
|
|
|
|
|
|
def getRows(objs):
|
|
''' '''
|
|
def countFrames(columns):
|
|
cnt = 0
|
|
for icol in columns:
|
|
cnt += len(icol)
|
|
return cnt
|
|
|
|
if len(objs) == 0:
|
|
return None, None
|
|
|
|
cols = getCols(list(objs))
|
|
tmpcols = []
|
|
for col in cols:
|
|
g = []
|
|
for group in col:
|
|
g.extend(group)
|
|
tmpcols.append(g)
|
|
|
|
rows = []
|
|
while countFrames(tmpcols) > 0:
|
|
firstCol = max(tmpcols, key=lambda col: col[0].Placement.Base.y)
|
|
compFrame = max(firstCol, key=lambda x: x.Placement.Base.y)
|
|
ind = tmpcols.index(firstCol)
|
|
group = [compFrame,]
|
|
tmpcols[ind].remove(compFrame)
|
|
for i in range(ind - 1, 0, -1):
|
|
if len(tmpcols[i]) == 0:
|
|
break
|
|
frame = tmpcols[i][0]
|
|
framelen = frame.Setup.Length / 2
|
|
compFramelen = compFrame.Setup.Length / 2
|
|
l = max([framelen, compFramelen])
|
|
if abs(compFrame.Placement.Base.y - frame.Placement.Base.y) <= l:
|
|
group.append(frame)
|
|
tmpcols[i].remove(frame)
|
|
compFrame = frame
|
|
else:
|
|
break
|
|
|
|
for i in range(ind + 1, len(cols)):
|
|
if len(tmpcols[i]) == 0:
|
|
break
|
|
frame = tmpcols[i][0]
|
|
framelen = frame.Setup.Length / 2
|
|
compFramelen = compFrame.Setup.Length / 2
|
|
l = max([framelen, compFramelen])
|
|
if abs(compFrame.Placement.Base.y - frame.Placement.Base.y) <= l:
|
|
group.append(frame)
|
|
tmpcols[i].remove(frame)
|
|
compFrame = frame
|
|
else:
|
|
break
|
|
|
|
if len(group) > 0:
|
|
group = sorted(group, key=lambda x: x.Placement.Base.x)
|
|
rows.append(group)
|
|
|
|
return rows, cols
|
|
|
|
|
|
def getCols(objs):
|
|
def getRound(num):
|
|
return round(num / 100, 0)
|
|
xx = set(getRound(obj.Placement.Base.x) for obj in objs)
|
|
xx = sorted(xx)
|
|
columns = []
|
|
|
|
for x in xx:
|
|
# 1. identificar los objetos de una columna
|
|
tmpcol = []
|
|
for obj in objs:
|
|
if getRound(obj.Placement.Base.x) == x:
|
|
tmpcol.append(obj)
|
|
tmpcol = sorted(tmpcol, key=lambda obj: getRound(obj.Placement.Base.y), reverse=True)
|
|
for obj in tmpcol:
|
|
objs.remove(obj)
|
|
|
|
# 2. dividir los objetos en grupos:
|
|
group = []
|
|
col = []
|
|
for i, f2 in enumerate(tmpcol):
|
|
if i > 0:
|
|
f1 = group[-1]
|
|
d = abs(f1.Placement.Base.y - f2.Placement.Base.y) - \
|
|
(f1.Setup.Length.Value + f2.Setup.Length.Value) / 2
|
|
if d > 1000:
|
|
col.append(group.copy())
|
|
group.clear()
|
|
group.append(f2)
|
|
col.append(group)
|
|
columns.append(col)
|
|
return columns
|
|
|
|
|
|
# en el caso de que no sean perpendiculares a x:
|
|
|
|
def getCols_old(sel, tolerance=4000, sort=True):
|
|
# TODO: get only frames from de selection
|
|
if not sel:
|
|
return
|
|
if len(sel) == 0:
|
|
return
|
|
|
|
cols = []
|
|
while len(sel) > 0:
|
|
obj = sel[0]
|
|
p = obj.Shape.BoundBox.Center
|
|
vec = obj.Shape.SubShapes[1].SubShapes[1].BoundBox.Center - \
|
|
obj.Shape.SubShapes[1].SubShapes[0].BoundBox.Center
|
|
n = FreeCAD.Vector(vec.y, -vec.x, 0)
|
|
|
|
# 1. Detectar los objetos que están en una misma columna
|
|
col = []
|
|
newsel = []
|
|
for obj1 in sel:
|
|
if obj1.Shape.BoundBox.isCutPlane(p, n): # todo: esto no es del todo correcto. buscar otra manera
|
|
col.append(obj1)
|
|
else:
|
|
newsel.append(obj1)
|
|
sel = newsel.copy()
|
|
col = sorted(col, key=lambda k: k.Placement.Base.y, reverse=True) # Orden Norte - Sur (Arriba a abajo)
|
|
|
|
# 2. Detectar y separar los grupos dentro de una misma columna:
|
|
group = []
|
|
newcol = []
|
|
group.append(col[0])
|
|
if len(col) > 1:
|
|
for ind in range(0, len(col) - 1):
|
|
vec1 = FreeCAD.Vector(col[ind].Placement.Base)
|
|
vec1.z = 0
|
|
vec2 = FreeCAD.Vector(col[ind + 1].Placement.Base)
|
|
vec2.z = 0
|
|
distance = abs((vec1 - vec2).Length) - (col[ind].Setup.Width.Value + col[ind + 1].Setup.Width.Value) / 2
|
|
if distance > tolerance:
|
|
newcol.append(group.copy())
|
|
group.clear()
|
|
group.append(col[ind + 1])
|
|
newcol.append(group)
|
|
cols.append(newcol)
|
|
|
|
if sort:
|
|
cols = sorted(cols, key=lambda k: k[0][0].Placement.Base.x, reverse=False)
|
|
|
|
return cols
|
|
|
|
|
|
# -----------------------------------------------------------------------------------------------------------------------
|
|
# Convert
|
|
# -----------------------------------------------------------------------------------------------------------------------
|
|
class _PVPlantConvertTaskPanel:
|
|
'''The editmode TaskPanel for Conversions'''
|
|
|
|
def __init__(self):
|
|
self.To = None
|
|
# self.form:
|
|
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantPlacementConvert.ui"))
|
|
self.form.setWindowIcon(QtGui.QIcon(os.path.join(PVPlantResources.DirIcons, "Trace.svg")))
|
|
self.form.buttonTo.clicked.connect(self.addTo)
|
|
|
|
def addTo(self):
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
if len(sel) > 0:
|
|
self.To = sel[0]
|
|
self.form.editTo.setText(self.To.Label)
|
|
|
|
def accept(self):
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
if sel == self.To:
|
|
return False
|
|
if len(sel) > 0 and self.To is not None:
|
|
FreeCAD.ActiveDocument.openTransaction("Convert to")
|
|
ConvertObjectsTo(sel, self.To)
|
|
return True
|
|
return False
|
|
|
|
|
|
def getHeadsAndSoil(frame=None):
|
|
if frame == None:
|
|
return None
|
|
import MeshPart as mp
|
|
|
|
data = {"heads": [],
|
|
"soil": []}
|
|
poles = frame.Shape.SubShapes[1].SubShapes[0].SubShapes
|
|
for pole in poles:
|
|
vec = pole.BoundBox.Center
|
|
vec.z = pole.BoundBox.ZMax
|
|
data["heads"].append(vec)
|
|
|
|
data["soil"].extend(mp.projectPointsOnMesh(data["heads"],
|
|
FreeCAD.ActiveDocument.Terrain.Mesh,
|
|
FreeCAD.Vector(0, 0, 1)))
|
|
return data
|
|
|
|
|
|
def moveFrameHead(obj, head=0, dist=0):
|
|
import math
|
|
print(dist)
|
|
dir = 1 if head == 0 else -1
|
|
base = obj.Placement.Base
|
|
dist /= 2
|
|
base.z += dist
|
|
angles = obj.Placement.Rotation.toEulerAngles("XYZ")
|
|
angley = math.degrees(math.asin(dist/(obj.Setup.Length.Value / 2))) * dir
|
|
print(angley)
|
|
rot = FreeCAD.Rotation(angles[2], angles[1] + angley, angles[0])
|
|
obj.Placement = FreeCAD.Placement(base, rot, FreeCAD.Vector(0, 0, 0))
|
|
obj.recompute()
|
|
|
|
# ---------------------------------------------------------------------------------------------------------------------
|
|
# function ConvertObjectsTo
|
|
#
|
|
# ---------------------------------------------------------------------------------------------------------------------
|
|
def ConvertObjectsTo(sel, objTo):
|
|
if hasattr(objTo, "Proxy"):
|
|
isFrame = objTo.Proxy.__class__ is PVPlantRack._Tracker
|
|
# isFrame = issubclass(objTo.Proxy.__class__, PVPlantRack._Frame)
|
|
isFrame = True
|
|
|
|
for obj in sel:
|
|
if isFrame:
|
|
if hasattr(obj, "Proxy"):
|
|
if obj.Proxy.__class__ is PVPlantRack._Tracker:
|
|
# if issubclass(obj.Proxy.__class__, PVPlantRack._Frame): # 1. Si los dos son Frames
|
|
cp = FreeCAD.ActiveDocument.copyObject(objTo, False)
|
|
cp.Placement = obj.Placement
|
|
cp.CloneOf = objTo
|
|
else: # 2. De un objeto no Frame a Frame
|
|
place = FreeCAD.Placement() # obj.Placement
|
|
place.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1),
|
|
90) # TODO: rotar conforme a lados más largos
|
|
bb = None
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
bb = obj.Shape.BoundBox
|
|
elif obj.isDerivedFrom("Mesh::Feature"):
|
|
bb = obj.Mesh.BoundBox
|
|
place.Base = bb.Center
|
|
cp = FreeCAD.ActiveDocument.copyObject(objTo, False)
|
|
cp.Placement = place
|
|
if isFrame:
|
|
cp.CloneOf = objTo
|
|
else: # 3. De un objeto a otro objeto (cualesquieran que sean)
|
|
place = FreeCAD.Placement() # obj.Placement
|
|
bb = None
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
bb = obj.Shape.BoundBox
|
|
elif obj.isDerivedFrom("Mesh::Feature"):
|
|
bb = obj.Mesh.BoundBox
|
|
place.Base = bb.Center
|
|
cp = FreeCAD.ActiveDocument.copyObject(objTo, False)
|
|
cp.Placement = place
|
|
if isFrame:
|
|
cp.CloneOf = objTo
|
|
FreeCAD.ActiveDocument.removeObject(obj.Name)
|
|
FreeCAD.activeDocument().recompute()
|
|
|
|
|
|
## Comandos: -----------------------------------------------------------------------------------------------------------
|
|
class CommandPVPlantPlacement:
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "way.svg")),
|
|
'Accel': "P,P",
|
|
'MenuText': QT_TRANSLATE_NOOP("Placement", "Placement"),
|
|
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Crear un campo fotovoltaico")}
|
|
|
|
def Activated(self):
|
|
taskd = _PVPlantPlacementTaskPanel(None)
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
|
|
def IsActive(self):
|
|
if FreeCAD.ActiveDocument:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
class CommandAdjustToTerrain:
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "adjust.svg")),
|
|
'Accel': "P, A",
|
|
'MenuText': QT_TRANSLATE_NOOP("Placement", "Adjust"),
|
|
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Adjust object to terrain")}
|
|
|
|
def Activated(self):
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
if len(sel) > 0:
|
|
# adjustToTerrain(sel)
|
|
FreeCADGui.Control.showDialog(adjustToTerrainTaskPanel())
|
|
else:
|
|
print("No selected object")
|
|
|
|
def IsActive(self):
|
|
if FreeCAD.ActiveDocument:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
class CommandConvert:
|
|
def GetResources(self):
|
|
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "convert.svg")),
|
|
'Accel': "P, C",
|
|
'MenuText': QT_TRANSLATE_NOOP("Placement", "Convert"),
|
|
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Convertir un objeto en otro")}
|
|
|
|
def IsActive(self):
|
|
return (not FreeCAD.ActiveDocument is None and
|
|
not FreeCAD.ActiveDocument.getObject("Site") is None and
|
|
not FreeCAD.ActiveDocument.getObject("Terrain") is None and
|
|
not FreeCAD.ActiveDocument.getObject("TrackerSetup") is None)
|
|
|
|
def Activated(self):
|
|
taskd = _PVPlantConvertTaskPanel()
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
|
|
|
|
'''if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand('PVPlantPlacement', _CommandPVPlantPlacement())
|
|
FreeCADGui.addCommand('PVPlantAdjustToTerrain', _CommandAdjustToTerrain())
|
|
FreeCADGui.addCommand('PVPlantConvertTo', _CommandConvert())'''
|