# /********************************************************************** # * * # * Copyright (c) 2021 Javier Braña * # * * # * 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_old: '''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() class _PVPlantPlacementTaskPanel_new1: '''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 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(col) 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() import numpy as np import pandas as pd from scipy.ndimage import label as sclabel from scipy import stats from scipy.interpolate import LinearNDInterpolator import Part import FreeCAD import FreeCADGui from PySide2 import QtCore, QtGui from PySide2.QtWidgets import QListWidgetItem import os import PVPlantResources 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) self._terrain_interpolator = None self._frame_footprints_cache = {} # UI setup 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 sel: 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(f"{self.form.editGapCols.value() - self.maxWidth / 1000} m") def _get_or_create_frame_group(self): """Optimized group creation and management""" doc = FreeCAD.ActiveDocument # Get or create main group main_group = doc.getObject("Frames") or doc.addObject("App::DocumentObjectGroup", "Frames") if not main_group.Label == "Frames": main_group.Label = "Frames" # Add to MechanicalGroup if exists if not hasattr(doc, 'MechanicalGroup') and hasattr(doc, 'getObject') and doc.getObject('MechanicalGroup'): doc.MechanicalGroup.addObject(main_group) # Handle subfolder if self.form.cbSubfolders.isChecked() and self.PVArea: subgroup_name = f"Frames-{self.PVArea.Label}" 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) return subgroup return main_group def createFrameFromPoints(self, dataframe): from Mechanical.Frame import PVPlantFrame doc = FreeCAD.ActiveDocument group = self._get_or_create_frame_group() frames = [] placements_key = "placement" if "placement" in dataframe.columns else 0 if placements_key == "placement": placements = dataframe["placement"].tolist() types = dataframe["type"].tolist() for idx, (placement, frame_type) in enumerate(zip(placements, types)): newrack = PVPlantFrame.makeTracker(setup=frame_type) newrack.Label = "Tracker" newrack.Visibility = False newrack.Placement = placement group.addObject(newrack) frames.append(newrack) if self.PVArea and self.PVArea.Name.startswith("FrameArea"): self.PVArea.Frames = frames def getProjected(self, shape): """Optimized projection calculation""" 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 and wire.isClosed() else Part.Face(wire) def calculateWorkingArea(self): """Optimized working area calculation""" 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 and face.isValid(): prohibited_faces.append(face) if prohibited_faces: self.Area = self.Area.cut(prohibited_faces) # Clear terrain interpolator cache when area changes self._terrain_interpolator = None def _setup_terrain_interpolator(self): """Cached terrain interpolator""" if self._terrain_interpolator is not None: return self._terrain_interpolator mesh = self.Terrain.Mesh points = np.array([v.Vector for v in mesh.Points]) bbox = self.Area.BoundBox # Filter points within working area efficiently mask = ((points[:, 0] >= bbox.XMin) & (points[:, 0] <= bbox.XMax) & (points[:, 1] >= bbox.YMin) & (points[:, 1] <= bbox.YMax)) filtered_points = points[mask] if len(filtered_points) == 0: self._terrain_interpolator = None return None try: self._terrain_interpolator = LinearNDInterpolator( filtered_points[:, :2], filtered_points[:, 2] ) except: self._terrain_interpolator = None return self._terrain_interpolator def _get_frame_footprint(self, frame): """Cached footprint calculation""" frame_key = (frame.Length.Value, frame.Width.Value) if frame_key not in self._frame_footprints_cache: l, w = frame.Length.Value, frame.Width.Value l_med, w_med = l / 2, w / 2 footprint = 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) ]) footprint.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), FreeCAD.Vector(0, 1, 0)) self._frame_footprints_cache[frame_key] = footprint return self._frame_footprints_cache[frame_key] def _calculate_terrain_adjustment_batch(self, points_data): """Process terrain adjustments in batches for better performance""" terrain_interp = self._setup_terrain_interpolator() results = [] for frame_type, base_point in points_data: yl = frame_type.Length.Value / 2 top_point = FreeCAD.Vector(base_point.x, base_point.y + yl, 0) bot_point = FreeCAD.Vector(base_point.x, base_point.y - yl, 0) if terrain_interp: # Use interpolator for faster elevation calculation yy = np.linspace(bot_point.y, top_point.y, 6) # Reduced points for speed xx = np.full_like(yy, base_point.x) try: 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 except: z_top = z_bot = 0 else: # Fallback to direct projection (slower) line = Part.LineSegment(bot_point, top_point).toShape() try: import MeshPart 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 except: z_top = z_bot = 0 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 ) results.append((frame_type, new_pl)) return results def adjustToTerrain(self, coordinates): """Unified terrain adjustment function for both aligned and non-aligned arrays""" # Create binary array efficiently arr = np.array([[int(obj != 0) for obj in col] for col in coordinates], dtype=np.uint8) labeled_array, num_features = sclabel(arr) # Build DataFrame efficiently data = [] for label in range(1, num_features + 1): cols, rows = np.where(labeled_array == label) for col, row in 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 }) if not data: return pd.DataFrame(columns=['ID', 'region', 'type', 'column', 'row', 'placement']) df = pd.DataFrame(data) # Process terrain adjustments in batches points_data = [(row['type'], row['placement']) for _, row in df.iterrows()] adjusted_results = self._calculate_terrain_adjustment_batch(points_data) # Update placements in DataFrame for idx, (frame_type, new_placement) in enumerate(adjusted_results): df.at[idx, 'placement'] = new_placement return df def isInside(self, frame, point): """Optimized inside check with early termination""" if not self.Area.isInside(point, 1e-6, True): # Reduced tolerance for speed return False frame_footprint = self._get_frame_footprint(frame) frame_footprint.Placement.Base = point try: cut = frame_footprint.cut([self.Area]) return len(cut.Vertexes) == 0 except: return False def getAligments(self): """Optimized alignment calculation""" sel = FreeCADGui.Selection.getSelectionEx() if not sel or not sel[0].SubObjects: return np.array([]), np.array([]) sub_objects = sel[0].SubObjects if len(sub_objects) == 1: refh = refv = sub_objects[0] else: # Choose references based on bounding box dimensions refh = max(sub_objects[:2], key=lambda x: x.BoundBox.XLength) refv = max(sub_objects[:2], key=lambda x: x.BoundBox.YLength) # Calculate ranges efficiently startx = refv.BoundBox.XMin + self.offsetX - self.gap_col * int( (refv.BoundBox.XMax - self.Area.BoundBox.XMin + self.offsetX) / self.gap_col ) starty = refh.BoundBox.YMin + self.offsetY + self.gap_row * int( (refh.BoundBox.YMin - self.Area.BoundBox.YMax + self.offsetY) / self.gap_row ) x_range = np.arange(startx, self.Area.BoundBox.XMax, self.gap_col, dtype=np.float64) y_range = np.arange(starty, self.Area.BoundBox.YMin, -self.gap_row, dtype=np.float64) return x_range, y_range def calculateAlignedArray(self): """Optimized aligned array calculation""" pointsx, pointsy = self.getAligments() if len(pointsx) == 0 or len(pointsy) == 0: return pd.DataFrame() # Precompute footprints once footprints = [] for frame in self.FrameSetups: footprint = self._get_frame_footprint(frame) footprints.append((frame, footprint)) ref_frame, ref_footprint = footprints[0] ref_length = ref_frame.Length.Value ref_width = ref_frame.Width.Value # Corridor variables countcols = 0 valcols = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (self.gap_col - ref_width) corridor_enabled = self.form.groupCorridor.isChecked() cols = [] for x in pointsx: col = [] corridor_offset = 0 for y in pointsy: point = FreeCAD.Vector(x + ref_width / 2 + corridor_offset, y - ref_length / 2, 0.0) found = False # Check reference frame first (most common case) if self.isInside(ref_frame, point): col.append([ref_frame, point]) found = True else: # Check alternative frames for frame, footprint in footprints[1:]: length_diff = (ref_frame.Length.Value - frame.Length.Value) / 2 for offset in [length_diff, -length_diff]: test_point = FreeCAD.Vector(point.x, point.y + offset, 0.0) if self.isInside(frame, test_point): col.append([frame, test_point]) found = True break if found: break if not found: col.append(0) # Handle corridors if corridor_enabled and col: countcols += 1 if countcols >= self.form.editColCount.value(): corridor_offset += valcols countcols = 0 cols.append(col) return self.adjustToTerrain(cols) def calculateNonAlignedArray(self): """Optimized non-aligned array calculation""" pointsx, pointsy = self.getAligments() if len(pointsx) == 0: FreeCAD.Console.PrintWarning("No X alignments found.\n") return pd.DataFrame() # Precompute footprints footprints = [] for frame in self.FrameSetups: footprint = self._get_frame_footprint(frame) footprints.append((frame, footprint)) corridor_enabled = self.form.groupCorridor.isChecked() corridor_count = 0 corridor_offset = 0 cols = [] for x in pointsx: col = [] current_x = x + corridor_offset # Create vertical line for intersection p1 = FreeCAD.Vector(current_x, self.Area.BoundBox.YMax, 0.0) p2 = FreeCAD.Vector(current_x, self.Area.BoundBox.YMin, 0.0) line = Part.LineSegment(p1, p2).toShape() # Get intersections with area try: inter = self.Area.section(line) pts = sorted([v.Point for v in inter.Vertexes], key=lambda p: p.y, reverse=True) for i in range(0, len(pts) - 1, 2): top, bottom = pts[i], pts[i + 1] available_height = top.y - bottom.y if available_height > footprints[-1][0].Width.Value: # Use optimized placement algorithm self._place_frames_in_segment(col, footprints, current_x, top, bottom) except Exception as e: FreeCAD.Console.PrintWarning(f"Error in segment processing: {e}\n") # Handle corridor offset if corridor_enabled and col: corridor_count += 1 if corridor_count >= self.form.editColCount.value(): corridor_offset += 12000 # 12m corridor corridor_count = 0 cols.append(col) return self.adjustToTerrain(cols) def _place_frames_in_segment(self, col, footprints, x, top, bottom): """Optimized frame placement within a segment""" current_y = top.y frame_heights = [ftp[0].Width.Value for ftp in footprints] min_frame_height = min(frame_heights) while current_y - bottom.y > min_frame_height: placed = False for frame, footprint in footprints: test_y = current_y - frame.Width.Value / 2 test_point = FreeCAD.Vector(x, test_y, 0.0) if self.isInside(frame, test_point): col.append([frame, test_point]) current_y -= frame.Width.Value placed = True break if not placed: break def accept(self): from datetime import datetime starttime = datetime.now() # Document optimization params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Document") auto_save_enabled = params.GetBool("AutoSaveEnabled") params.SetBool("AutoSaveEnabled", False) FreeCAD.ActiveDocument.RecomputesFrozen = True try: # Get selected frames items = [ FreeCAD.ActiveDocument.getObject(self.form.listFrameSetups.item(i).text()) for i in range(self.form.listFrameSetups.count()) if self.form.listFrameSetups.item(i).checkState() == QtCore.Qt.Checked ] # Remove duplicates efficiently self.FrameSetups = list({frame.Length.Value: frame for frame in items}.values()) self.FrameSetups.sort(key=lambda x: x.Length.Value, reverse=True) # Parse parameters 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") # Main processing self.calculateWorkingArea() if self.form.cbAlignFrames.isChecked(): dataframe = self.calculateAlignedArray() else: dataframe = self.calculateNonAlignedArray() if not dataframe.empty: self.createFrameFromPoints(dataframe) # Group trackers import Electrical.group as egroup import importlib importlib.reload(egroup) egroup.groupTrackersToTransformers(5000000, self.gap_row) FreeCAD.ActiveDocument.commitTransaction() finally: # Restore document settings FreeCAD.ActiveDocument.RecomputesFrozen = False params.SetBool("AutoSaveEnabled", auto_save_enabled) total_time = datetime.now() - starttime print(f" -- Total time: {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())'''