# /********************************************************************** # * * # * 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.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.Terrain = PVPlantSite.get().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.form.buttonPVArea.clicked.connect(self.addPVArea) self.form.buttonAddFrame.clicked.connect(self.addFrame) self.form.buttonRemoveFrame.clicked.connect(self.removeFrame) def addPVArea(self): sel = FreeCADGui.Selection.getSelection() if len(sel) > 0: self.PVArea = sel[0] self.form.editPVArea.setText(self.PVArea.Label) def addFrame(self): from Mechanical.Frame import PVPlantFrame selection = FreeCADGui.Selection.getSelection() self.FrameSetup = selectionFilter(selection, PVPlantFrame.TrackerSetup) if len(selection) > 0: items = [] for x in range(self.form.listFrameSetups.count()): items.append(self.form.listFrameSetups.item(x).text()) if not (selection[0].Name in items): self.form.listFrameSetups.addItem(selection[0].Name) def removeFrame(self): ''' remove select item from list ''' self.form.listFrameSetups.takeItem(self.form.listFrameSetups.currentRow()) 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) 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] MechanicalGroup.addObject(newrack) frames.append(newrack) if self.PVArea.Name.startswith("FrameArea"): self.PVArea.Frames = frames # TODO: else def getProjected(self, shape): """ returns projected edges from a shape and a direction """ if shape.BoundBox.ZLength == 0: edges = shape.Edges return Part.Face(Part.Wire(edges)) else: from Utils import PVPlantUtils as utils wire = utils.simplifyWire(utils.getProjected(shape)) if wire.isClosed(): wire = wire.removeSplitter() return Part.Face(wire) def calculateWorkingArea(self): self.Area = self.getProjected(self.PVArea.Shape) tmp = FreeCAD.ActiveDocument.findObjects(Name="ExclusionArea") if len(tmp): ProhibitedAreas = list() for obj in tmp: face = self.getProjected(obj.Base.Shape) if face.isValid(): ProhibitedAreas.append(face) self.Area = self.Area.cut(ProhibitedAreas) 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(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 placeonregion_old(frames): # old for colnum, col in enumerate(frames): groups = list() groups.append([col[0]]) for i in range(1, len(col)): group = groups[-1] long = (col[i].sub(group[-1])).Length long -= width if long <= dist: group.append(col[i]) else: groups.append([col[i]]) for group in groups: points = list() points.append(group[0].sub(vec1)) for ind in range(0, len(group) - 1): points.append((group[ind].sub(vec1) + group[ind + 1].add(vec1)) / 2) points.append(group[-1].add(vec1)) points3D = list() ''' # v0 for ind in range(len(points) - 1): line = Part.LineSegment(points[ind], points[ind + 1]) tmp = terrain.makeParallelProjection(line.toShape(), FreeCAD.Vector(0, 0, 1)) if len(tmp.Vertexes) > 0: if ind == 0: points3D.append(tmp.Vertexes[0].Point) points3D.append(tmp.Vertexes[-1].Point) ''' # V1 if type == 0: # Mesh: import MeshPart as mp for point in points: point3D = mp.projectPointsOnMesh([point, ], terrain, FreeCAD.Vector(0, 0, 1)) if len(point3D) > 0: points3D.append(point3D[0]) else: # Shape: line = Part.LineSegment(points[0], points[-1]) tmp = terrain.makeParallelProjection(line.toShape(), FreeCAD.Vector(0, 0, 1)) if len(tmp.Vertexes) > 0: tmppoints = [ver.Point for ver in tmp.Vertexes] if mode == 1: # mode = normal for point in points: '''# OPTION 1: line = Part.Line(point, point + FreeCAD.Vector(0, 0, 10)) for i in range(len(tmppoints) - 1): seg = Part.LineSegment(tmppoints[i], tmppoints[i + 1]) inter = line.intersect(seg) print(inter) if len(inter) > 0: points3D.append(FreeCAD.Vector(inter[0].X, inter[0].Y, inter[0].Z)) ''' # OPTION 2: plane = Part.Plane(point, self.Dir) for i in range(len(tmppoints) - 1): seg = Part.LineSegment(tmppoints[i], tmppoints[i + 1]) inter = plane.intersect(seg) if len(inter) > 0: if len(inter[0]): inter = inter[0] points3D.append(FreeCAD.Vector(inter[0].X, inter[0].Y, inter[0].Z)) break else: # TODO: mode = Trend # TODO: check: from scipy import stats xx = list() yy = list() zz = list() for pts in tmppoints: xx.append(pts.x) yy.append(pts.y) zz.append(pts.z) slope, intercept, r, p, std_err = stats.linregress(yy, zz) def myfunc(x): return slope * x + intercept x = list() x.append(yy[0]) x.append(yy[-1]) newzz = list(map(myfunc, x)) points3D.append(FreeCAD.Vector(xx[0], yy[0], newzz[0])) points3D.append(FreeCAD.Vector(xx[-1], yy[-1], newzz[1])) for ind in range(0, len(points3D) - 1): pl = FreeCAD.Placement() vec = points3D[ind] - points3D[ind + 1] pl.Base = FreeCAD.Vector(group[ind]) p = (points3D[ind] + points3D[ind + 1]) / 2 pl.Base.z = p.z rot = FreeCAD.Rotation(FreeCAD.Vector(-1, 0, 0), vec) pl.Rotation = FreeCAD.Rotation(rot.toEuler()[0], rot.toEuler()[1], 0) placements.append(pl) return placements""" 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 print("/n/n") print(cols) return self.adjustToTerrain(cols) def calculateNonAlignedArray(self): gap_col = FreeCAD.Units.Quantity(self.form.editGapCols.text()).Value gap_row = FreeCAD.Units.Quantity(self.form.editGapRows.text()).Value + max(self.Rack.Shape.BoundBox.XLength, self.Rack.Shape.BoundBox.YLength) offset_x = FreeCAD.Units.Quantity(self.form.editOffsetHorizontal.text()).Value offset_y = FreeCAD.Units.Quantity(self.form.editOffsetVertical.text()).Value Area = self.calculateWorkingArea() rec = Part.makePlane(self.Rack.Shape.BoundBox.YLength, self.Rack.Shape.BoundBox.XLength) # TODO: revisar todo esto: ----------------------------------------------------------------- sel = FreeCADGui.Selection.getSelectionEx()[0] refh = None refv = None if len(sel.SubObjects) == 0: refh = refv = Area.Edges[0] if len(sel.SubObjects) == 1: refh = refv = sel.SubObjects[0] if len(sel.SubObjects) == 2: 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 - Area.BoundBox.XMin + offset_x) / gap_col) startx = refv.BoundBox.XMax + offset_x - gap_col * steps # todo end ---------------------------------------------------------------------------------- start = FreeCAD.Vector(startx, 0.0, 0.0) pointsx = np.arange(start.x, Area.BoundBox.XMax, gap_col) if self.form.groupCorridor.isChecked(): if (self.form.editColCount.value() > 0): xlen = len(pointsx) count = self.form.editColCount.value() val = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - ( gap_col - min(self.Rack.Shape.BoundBox.XLength, self.Rack.Shape.BoundBox.YLength)) while count <= xlen: for i, point in enumerate(pointsx): if i >= count: pointsx[i] += val count += self.form.editColCount.value() pl = [] for point in pointsx: p1 = FreeCAD.Vector(point, Area.BoundBox.YMax, 0.0) p2 = FreeCAD.Vector(point, Area.BoundBox.YMin, 0.0) line = Part.makePolygon([p1, p2]) inter = Area.section([line]) pts = [ver.Point for ver in inter.Vertexes] # todo: sort points for i in range(0, len(pts), 2): line = Part.LineSegment(pts[i], pts[i + 1]) if line.length() >= rec.BoundBox.YLength: y1 = pts[i].y - rec.BoundBox.YLength cp = rec.copy() cp.Placement.Base = FreeCAD.Vector(pts[i].x - rec.BoundBox.XLength / 2, y1, 0.0) inter = cp.cut([Area]) y1 = min([ver.Point.y for ver in inter.Vertexes]) pointsy = np.arange(y1, pts[i + 1].y, -gap_row) for point in pointsy: cp = rec.copy() cp.Placement.Base = FreeCAD.Vector(pts[i].x - rec.BoundBox.XLength / 2, point, 0.0) cut = cp.cut([Area], 0) if len(cut.Vertexes) == 0: Part.show(cp) pl.append(point) return pl 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 = [] for x in range(self.form.listFrameSetups.count()): items.append(FreeCAD.ActiveDocument.getObject(self.form.listFrameSetups.item(x).text())) tmpframes = list() for frame in sorted(items, key=lambda rack: rack.Length, reverse=True): found = False for tmp in tmpframes: if tmp.Length == frame.Length: found = True break if not found: tmpframes.append(frame) self.FrameSetups = tmpframes.copy() longerFrame = self.FrameSetups[0] self.gap_col = FreeCAD.Units.Quantity(self.form.editGapCols.text()).Value self.gap_row = FreeCAD.Units.Quantity(self.form.editGapRows.text()).Value + longerFrame.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) 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() #return True # ---------------------------------------------------------------------------------------------------------------------- # 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())'''