# /********************************************************************** # * * # * 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 math import ArchComponent import FreeCAD import Part import PVPlantSite if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore from PySide.QtCore import QT_TRANSLATE_NOOP else: # \cond def translate(ctxt, txt): return txt def QT_TRANSLATE_NOOP(ctxt, txt): return txt # \endcond __title__ = "PVPlant Frames" __author__ = "Javier Braña" __url__ = "http://www.sogos-solar.com" import os import PVPlantResources from PVPlantResources import DirIcons as DirIcons class FrameSetup: "A Base Frame Setup Class" def __init__(self, obj): # Definición de Variables: self.obj = obj def setProperties(self, obj): ''' Definición de Propiedades: ''' pl = obj.PropertiesList # Modulo: ------------------------------------------------------------------------------------------------------ if not "ModuleThick" in pl: obj.addProperty("App::PropertyLength", "ModuleThick", "Module", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ModuleThick = 40 if not "ModuleWidth" in pl: obj.addProperty("App::PropertyLength", "ModuleWidth", "Module", QT_TRANSLATE_NOOP("App::Property", "The width of this object") ).ModuleWidth = 1130 if not "ModuleHeight" in pl: obj.addProperty("App::PropertyLength", "ModuleHeight", "Module", QT_TRANSLATE_NOOP("App::Property", "The Length of this object") ).ModuleHeight = 2250 if not "PoleCableLength" in pl: obj.addProperty("App::PropertyLength", "PoleCableLength", "Module", QT_TRANSLATE_NOOP("App::Property", "The Length of this object") ).PoleCableLength = 1200 if not "ModulePower" in pl: obj.addProperty("App::PropertyQuantity", "ModulePower", "Module", QT_TRANSLATE_NOOP("App::Property", "The Length of this object") ).ModulePower = 600 # Array de modulos: ------------------------------------------------------------------------------------------- if not "ModuleColumns" in pl: obj.addProperty("App::PropertyQuantity", "ModuleColumns", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ModuleColumns = 2 if not "ModuleRows" in pl: obj.addProperty("App::PropertyQuantity", "ModuleRows", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ModuleRows = 2 if not "ModuleColGap" in pl: obj.addProperty("App::PropertyDistance", "ModuleColGap", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ModuleColGap = 20 if not "ModuleRowGap" in pl: obj.addProperty("App::PropertyDistance", "ModuleRowGap", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ModuleRowGap = 20 if not "ModuleOffsetX" in pl: obj.addProperty("App::PropertyDistance", "ModuleOffsetX", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ModuleOffsetX = 0 if not "ModuleOffsetY" in pl: obj.addProperty("App::PropertyDistance", "ModuleOffsetY", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ModuleOffsetY = 0 if not "ModuleOrientation" in pl: obj.addProperty("App::PropertyEnumeration", "ModuleOrientation", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The facemaker type to use to build the profile of this object") ).ModuleOrientation = ["Portrait", "Landscape"] if not "ModuleViews" in pl: obj.addProperty("App::PropertyBool", "ModuleViews", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The facemaker type to use to build the profile of this object") ).ModuleViews = True if not "TotalPower" in pl: obj.addProperty("App::PropertyQuantity", "TotalPower", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The facemaker type to use to build the profile of this object") ) # Poles -------------------------------------------------------------------------------------------------------- if not "PoleType" in pl: obj.addProperty("App::PropertyLinkList", "PoleType", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ) # Frame -------------------------------------------------------------------------------------------------------- if not "MaxLengthwiseTilt" in pl: obj.addProperty("App::PropertyAngle", "MaxLengthwiseTilt", "Frame", QT_TRANSLATE_NOOP("App::Property", "Máxima inclinación longitudinal") ).MaxLengthwiseTilt = 15 if not "Width" in pl: obj.addProperty("App::PropertyDistance", "Width", "Frame", QT_TRANSLATE_NOOP("App::Property", "Largo de la estructura") ) obj.setEditorMode("Width", 1) if not "Length" in pl: obj.addProperty("App::PropertyDistance", "Length", "Frame", QT_TRANSLATE_NOOP("App::Property", "Ancho de la estructura") ) obj.setEditorMode("Length", 1) if not "TotalAreaShape" in pl: obj.addProperty("App::PropertyDistance", "TotalAreaShape", "Frame", QT_TRANSLATE_NOOP("Part::PropertyPartShape", "Total Area de los Paneles") ) obj.setEditorMode("TotalAreaShape", 1) ''' ------------------------------------------- Fixed Structure --------------------------------------------------- ''' def makeRack(name="Rack"): obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name) obj.Label = name FixedRack(obj) ViewProviderFixedRack(obj.ViewObject) return obj class FixedRack(FrameSetup): "A Fixed Rack Obcject" def __init__(self, obj): #FrameSetup.__init__(self, obj) super(FixedRack, self).__init__(obj) self.setProperties(obj) obj.ModuleColumns = 6 obj.ModuleRows = 2 obj.ModuleColGap = 20 obj.ModuleRowGap = 20 #obj.Tilt = 30 # Does a IfcType exist? # obj.IfcType = "Fence" # obj.MoveWithHost = False def setProperties(self, obj): FrameSetup.setProperties(self, obj) pl = obj.PropertiesList if not "ModuleElevation" in pl: obj.addProperty("App::PropertyDistance", "ModuleElevation", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ModuleElevation = 500 # Frame: ------------------------------------------------------------------------------------------------------ if not "Tilt" in pl: obj.addProperty("App::PropertyAngle", "Tilt", "Frame", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).Tilt = 0 if not "FrameType" in pl: obj.addProperty("App::PropertyEnumeration", "FrameType", "Frame", QT_TRANSLATE_NOOP("App::Property", "The facemaker type to use to build the profile of this object") ).FrameType = ["Simple", "West-Lest"] # Pole: ------------------------------------------------------------------------------------------------------ if not "BackPostWidth" in pl: obj.addProperty("App::PropertyLength", "BackPostWidth", "Pole", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BackPostWidth = 80 if not "BackPostHeight" in pl: obj.addProperty("App::PropertyLength", "BackPostHeight", "Pole", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BackPostHeight = 160 if not "BackPostLength" in pl: obj.addProperty("App::PropertyLength", "BackPostLength", "Pole", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BackPostLength = 3200 if not "FrontPostWidth" in pl: obj.addProperty("App::PropertyLength", "FrontPostWidth", "Pole", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).FrontPostWidth = 40 if not "FrontPostHeight" in pl: obj.addProperty("App::PropertyLength", "FrontPostHeight", "Pole", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).FrontPostHeight = 80 if not "FrontPostLength" in pl: obj.addProperty("App::PropertyLength", "FrontPostLength", "Pole", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).FrontPostLength = 2300 # Array of Posts: ------------------------------------------------------------------------------------------------------ if not "NumberPostsX" in pl: obj.addProperty("App::PropertyQuantity", "NumberPostsX", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).NumberPostsX = 3 if not "DistancePostsX" in pl: obj.addProperty("App::PropertyLength", "DistancePostsX", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).DistancePostsX = 3000 if not "FrontPost" in pl: obj.addProperty("App::PropertyBool", "FrontPost", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).FrontPost = False if not "DistancePostsY" in pl: obj.addProperty("App::PropertyLength", "DistancePostsY", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).DistancePostsY = 2000 if not "RammingDeep" in pl: obj.addProperty("App::PropertyLength", "RammingDeep", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).RammingDeep = 1500 # Correas: ---------------------------------------------------------------------------------------------------- if not "BeamHeight" in pl: obj.addProperty("App::PropertyLength", "BeamHeight", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BeamHeight = 80 if not "BeamWidth" in pl: obj.addProperty("App::PropertyLength", "BeamWidth", "Beam", QT_TRANSLATE_NOOP("App::Property", "The width of this object") ).BeamWidth = 50 if not "BeamOffset" in pl: obj.addProperty("App::PropertyLength", "BeamOffset", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BeamOffset = 50 if not "BeamSpacing" in pl: obj.addProperty("App::PropertyLength", "BeamSpacing", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BeamSpacing = 1000 self.Type = "Fixed Rack" obj.Proxy = self def onDocumentRestored(self, obj): #ArchComponent.Component.onDocumentRestored(self, obj) super(FixedRack, self).onDocumentRestored(obj) self.setProperties(obj) def __getstate__(self): return self.Type def __setstate__(self, state): if state: self.Type = state def onChanged(self, fp, prop): '''Do something when a property has changed''' def generate_modules(self, obj, h, w): modulos=[] for y in range(int(obj.ModuleRows.Value)): for x in range(int(obj.ModuleColumns.Value)): mod = Part.makeBox(w, h, obj.ModuleThick.Value) mod.Placement.Base = FreeCAD.Vector( x * (w + obj.ModuleColGap.Value), y * (h + obj.ModuleRowGap.Value), 0 ) modulos.append(mod) compound = Part.makeCompound(modulos) '''center = FreeCAD.Vector(compound.BoundBox.Center) for mod in modulos: mod.Placement.Base -= FreeCAD.Vector(center.x, center.y, 0) compound = Part.makeCompound(modulos)''' return compound def generate_posts(self, obj): postBack = Part.makeBox(obj.BackPostWidth.Value, obj.BackPostHeight.Value, obj.BackPostLength.Value) postFront = Part.makeBox(obj.FrontPostWidth.Value, obj.FrontPostHeight.Value, obj.FrontPostLength.Value) post_back = [] post_front = [] for x in range(int(obj.NumberPostsX.Value)): postCopy = postBack.copy() postCopy.Placement.Base = FreeCAD.Vector(x * obj.DistancePostsX.Value, obj.DistancePostsY.Value, 0) post_back.append(postCopy) if obj.FrontPost: postCopy = postFront.copy() postCopy.Placement.Base = FreeCAD.Vector(x * obj.DistancePostsX.Value, 0, 0) post_front.append(postCopy) return Part.makeCompound([Part.makeCompound(post_back), Part.makeCompound(post_front)]) def correct_placement(self, obj): center = FreeCAD.Vector(obj.BoundBox.Center) obj.Placement.Base -= FreeCAD.Vector(center.x, center.y, 0) def execute(self, obj): # obj.Shape: compound # |- Modules and Beams: compound # |-- Modules array: compound # |--- Modules: solid # |-- Beams: compound # |--- MainBeam: solid # |--- Secundary Beams: solid (if exist) # |- Poles array: compound # |-- Poles: solid pl = obj.Placement if obj.ModuleOrientation == "Portrait": w = obj.ModuleWidth.Value h = obj.ModuleHeight.Value else: h = obj.ModuleWidth.Value w = obj.ModuleHeight.Value totalh = h * obj.ModuleRows + obj.ModuleRowGap.Value * (obj.ModuleRows - 1) totalw = w * obj.ModuleColumns + obj.ModuleColGap.Value * (obj.ModuleColumns - 1) modules = self.generate_modules(obj, h, w) modules.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), obj.Tilt.Value) self.correct_placement(modules) modules.Placement.Base += FreeCAD.Vector(0, 0, obj.ModuleElevation.Value) posts = self.generate_posts(obj) self.correct_placement(posts) posts.Placement.Base -= FreeCAD.Vector(0, 0, obj.RammingDeep.Value) compound = Part.makeCompound([modules, posts]) obj.Shape = compound obj.Placement = pl angle = obj.Placement.Rotation.toEuler()[1] if angle > obj.MaxLengthwiseTilt: obj.ViewObject.ShapeColor = (1.0, 0.0, 0.0) obj.Width = totalw obj.Length = totalh class ViewProviderFixedRack: "A View Provider for the Pipe object" def __init__(self, vobj): vobj.Proxy = self def getIcon(self): """ Return the path to the appropriate icon. """ return str(os.path.join(DirIcons, "solar-fixed.svg")) def setEdit(self, vobj, mode): if (mode == 0) and hasattr(self, "Object"): taskd = _FixedRackTaskPanel(self.Object) taskd.obj = self.Object FreeCADGui.Control.showDialog(taskd) return True return False class _FixedRackTaskPanel: def __init__(self, obj=None): self.obj = obj # ------------------------------------------------------------------------------------------------------------- # Module widget form # ------------------------------------------------------------------------------------------------------------- self.form = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "Mechanical/Frame/PVPlantFixedFrame.ui") def selectionchange(self, i): vis = False if i == 1: vis = True self.formRack.widgetTracker.setVisible(vis) def editBreadthwaysNumOfPostChange(self): self.formPiling.tableBreadthwaysPosts.insertRow(self.formPiling.tableBreadthwaysPosts.rowCount) def editAlongNumOfPostChange(self): self.l1.setText("current value:" + str(self.sp.value())) def getValues(self): d = {} d["ModuleFrame"] = self.ModuleFrame d["ModuleHeight"] = self.ModuleHeight d["ModuleWidth"] = self.ModuleWidth d["ModuleThick"] = self.ModuleThick return d def accept(self): FreeCADGui.Control.closeDialog() return True def reject(self): FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCADGui.Control.closeDialog() return True ''' ------------------------------------------- Tracker Structure --------------------------------------------------- ''' def makeTrackerSetup(name="TrackerSetup"): obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "TrackerSetup") obj.Label = name TrackerSetup(obj) ViewProviderTrackerSetup(obj.ViewObject) FreeCAD.ActiveDocument.recompute() try: site = PVPlantSite.get() frame_list = site.Frames frame_list.append(obj) site.Frames = frame_list except: pass return obj def getarray(array, numberofpoles): if len(array) == 0: newarray = [0] * numberofpoles return newarray elif len(array) == 1: newarray = [array[0]] * numberofpoles return newarray elif len(array) == 2: newarray = [array[0]] * numberofpoles half = int(numberofpoles / 2) newarray[half] = array[1] if numberofpoles % 2 == 0: newarray[half - 1] = array[1] return newarray elif len(array) == 3: half = int(numberofpoles / 2) newarray = [array[0]] * half + [array[1]] + [array[2]] * half if numberofpoles % 2 == 0: newarray[half] = array[1] return newarray elif len(array) == numberofpoles: return array elif len(array) > numberofpoles: return array[0: numberofpoles] else: newarray = [array[0]] * numberofpoles return newarray class TrackerSetup(FrameSetup): "A 1 Axis Tracker Obcject" def __init__(self, obj): FrameSetup.__init__(self, obj) self.setProperties(obj) obj.ModuleColumns = 45 obj.ModuleRows = 2 obj.ModuleColGap = 20 obj.ModuleRowGap = 20 #obj.Tilt = 0 def setProperties(self, obj): FrameSetup.setProperties(self, obj) pl = obj.PropertiesList # Array de modulos: ------------------------------------------------------------------------------------------- if not "MotorGap" in pl: obj.addProperty("App::PropertyDistance", "MotorGap", "ModuleArray", QT_TRANSLATE_NOOP("App::Property", "Thse height of this object") ).MotorGap = 550 if not "UseGroupsOfModules" in pl: obj.addProperty("App::PropertyBool", "UseGroupsOfModules", "GroupsOfModules", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).UseGroupsOfModules = False # Array of Posts: ------------------------------------------------------------------------------------------------------ '''movido a la clase madre if not "PoleType" in pl: obj.addProperty("App::PropertyLinkList", "PoleType", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") )''' if not "PoleSequence" in pl: obj.addProperty("App::PropertyIntegerList", "PoleSequence", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ) if not "NumberPole" in pl: obj.addProperty("App::PropertyQuantity", "NumberPole", "Poles", "The total number of poles" ).NumberPole = 7 if not "DistancePole" in pl: obj.addProperty("App::PropertyIntegerList", # No list of Lenght so I use float list "DistancePole", "Poles", "Distance between poles starting from the left and from the first photovoltaic module " "without taking into account the offsets" ).DistancePole = [7000, 7000, 7000, 7000, 7000, 7000, 7000] if not "AerialPole" in pl: obj.addProperty("App::PropertyIntegerList", "AerialPole", "Poles", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).AerialPole = [1050] # Correas: ---------------------------------------------------------------------------------------------------- # 1. MainBeam: ------------------------------------------------------------------------------------------------- if not "MainBeamProfile" in pl: obj.addProperty("App::PropertyEnumeration", "MainBeamProfile", "Beam", "The height of this object" ).MainBeamProfile = ["Square", "Octagon", "Circle"] if not "MainBeamHeight" in pl: obj.addProperty("App::PropertyLength", "MainBeamHeight", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).MainBeamHeight = 120 if not "MainBeamWidth" in pl: obj.addProperty("App::PropertyLength", "MainBeamWidth", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).MainBeamWidth = 120 if not "MainBeamAxisPosition" in pl: obj.addProperty("App::PropertyLength", "MainBeamAxisPosition", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).MainBeamAxisPosition = 1278 # 2. Costillas: ---------------------------------------------------------------------------------------------------- if not "ShowBeams" in pl: obj.addProperty("App::PropertyBool", "ShowBeams", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).ShowBeams = False if not "BeamHeight" in pl: obj.addProperty("App::PropertyLength", "BeamHeight", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BeamHeight = 80 if not "BeamWidth" in pl: obj.addProperty("App::PropertyLength", "BeamWidth", "Beam", QT_TRANSLATE_NOOP("App::Property", "The width of this object") ).BeamWidth = 83.2 if not "BeamOffset" in pl: obj.addProperty("App::PropertyLength", "BeamOffset", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BeamOffset = 50 if not "BeamSpacing" in pl: obj.addProperty("App::PropertyLength", "BeamSpacing", "Beam", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).BeamSpacing = 1000 # Tracker -------------------------------------------------------------------------------------------------------- if not "MaxPhi" in pl: obj.addProperty("App::PropertyAngle", "MaxPhi", "Frame", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).MaxPhi = 60 if not "MinPhi" in pl: obj.addProperty("App::PropertyAngle", "MinPhi", "Frame", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).MinPhi = -60 if not "MaxNegativeLengthwiseTilt" in pl: obj.addProperty("App::PropertyAngle", "MaxNegativeLengthwiseTilt", "Frame", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).MaxNegativeLengthwiseTilt = 6 self.Type = "1 Axis Tracker" obj.Proxy = self def onDocumentRestored(self, obj): self.setProperties(obj) def onChanged(self, obj, prop): '''Do something when a property has changed''' '''if prop == "NumberPole": obj.AerialPole = self.calculateAerialArray(obj) obj.DistancePole = self.calculateDistanceArray(obj) obj.PoleSequence = self.calculatePostSequence(obj)''' if prop == "AerialPole": obj.AerialPole = self.calculateAerialArray(obj) if prop == "DistancePole": obj.DistancePole = self.calculateDistanceArray(obj) if prop == "PoleSequence": obj.PoleSequence = self.calculatePostSequence(obj) if prop == "UseGroupsOfModules": if obj.getPropertyByName(prop) == True: if not "ColumnsPerGroup" in obj.PropertiesList: obj.addProperty("App::PropertyIntegerList", "ColumnsPerGroup", "GroupsOfModules", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ) if not "GroupGaps" in obj.PropertiesList: obj.addProperty("App::PropertyIntegerList", "GroupGaps", "GroupsOfModules", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ) else: if "ColumnsPerGroup" in obj.PropertiesList: obj.removeProperty("ColumnsPerGroup") if "GroupGaps" in obj.PropertiesList: obj.removeProperty("GroupGaps") def calculatePostSequence(self, obj): return getarray(obj.PoleSequence, int(obj.NumberPole.Value)) def calculateAerialArray(self, obj): return getarray(obj.AerialPole, int(obj.NumberPole.Value)) def calculateDistanceArray(self, obj): return getarray(obj.DistancePole, int(obj.NumberPole.Value)) def CalculateModuleArray(self, obj, totalh, totalw, moduleh, modulew): module = Part.makeBox(modulew, moduleh, obj.ModuleThick.Value) compound = Part.makeCompound([]) offsetx = -totalw / 2 offsety = -totalh / 2 offsetz = obj.MainBeamHeight.Value + obj.BeamHeight.Value if obj.ModuleViews: mid = int(obj.ModuleColumns.Value / 2) for row in range(int(obj.ModuleRows.Value)): for col in range(int(obj.ModuleColumns.Value)): xx = offsetx + (modulew + obj.ModuleColGap.Value) * col if col >= mid: xx += obj.MotorGap.Value - obj.ModuleColGap.Value yy = offsety + (moduleh + obj.ModuleRowGap.Value) * row zz = offsetz moduleCopy = module.copy() moduleCopy.Placement.Base = FreeCAD.Vector(xx, yy, zz) compound.add(moduleCopy) else: totalArea = Part.makePlane(totalw, totalh) totalArea.Placement.Base = FreeCAD.Vector(offsetx, offsety, offsetz) compound.add(totalArea) return compound def calculateBeams(self, obj, totalh, totalw, moduleh, modulew): ''' make mainbeam and modules beams ''' compound = Part.makeCompound([]) if obj.MainBeamProfile == "Square": mainbeam = Part.makeBox(totalw + obj.ModuleOffsetX.Value * 2, obj.MainBeamWidth.Value, obj.MainBeamHeight.Value) mainbeam.Placement.Base.y = -obj.MainBeamWidth.Value / 2 elif obj.MainBeamProfile == "Octagon": # base on Draft.polygon: radius = obj.MainBeamWidth.Value / 2 angle = (math.pi * 2) / 8 angleoffset = angle/2 delta = radius / math.cos(angle / 2.0) pts = [] for i in range(8): ang = (i + 1) * angle + angleoffset point = FreeCAD.Vector(0, delta * math.cos(ang), delta * math.sin(ang)) point = point.add(FreeCAD.Vector(0, 0, radius)) pts.append(point) pts.append(pts[0]) profile = Part.makePolygon(pts) profile = Part.Face(profile) mainbeam = profile.extrude(FreeCAD.Vector(totalw + obj.ModuleOffsetX.Value * 2, 0, 0)) else: radius = obj.MainBeamWidth.Value / 2 profile = Part.Face(Part.Wire(Part.makeCircle(radius, FreeCAD.Vector(0,0,radius), FreeCAD.Vector(1,0,0)))) mainbeam = profile.extrude(FreeCAD.Vector(totalw + obj.ModuleOffsetX.Value * 2, 0, 0)) mainbeam.Placement.Base.x = -totalw / 2 - obj.ModuleOffsetX.Value compound.add(mainbeam) # Correa profile: if obj.ShowBeams: # TODO: make it in another function mid = int(obj.ModuleColumns.Value / 2) up = 27.8 # todo thi = 3.2 # todo p1 = FreeCAD.Vector(obj.BeamWidth.Value / 2 - up, 0, thi) p2 = FreeCAD.Vector(p1.x, 0, obj.BeamHeight.Value) p3 = FreeCAD.Vector(obj.BeamWidth.Value / 2, 0, p2.z) p4 = FreeCAD.Vector(p3.x, 0, obj.BeamHeight.Value - thi) p5 = FreeCAD.Vector(p4.x - up + thi, 0, p4.z) p6 = FreeCAD.Vector(p5.x, 0, 0) p7 = FreeCAD.Vector(-p6.x, 0, p6.z) p8 = FreeCAD.Vector(-p5.x, 0, p5.z) p9 = FreeCAD.Vector(-p4.x, 0, p4.z) p10 = FreeCAD.Vector(-p3.x, 0, p3.z) p11 = FreeCAD.Vector(-p2.x, 0, p2.z) p12 = FreeCAD.Vector(-p1.x, 0, p1.z) p = Part.makePolygon([p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p1]) p = Part.Face(p) profile = p.extrude(FreeCAD.Vector(0, 428, 0)) for col in range(int(obj.ModuleColumns.Value)): xx = totalArea.Placement.Base.x + (modulew + obj.ModuleColGap.Value) * col if col >= mid: xx += float(obj.MotorGap.Value) - obj.ModuleColGap.Value correaCopy = profile.copy() correaCopy.Placement.Base.x = xx correaCopy.Placement.Base.y = -428 / 2 correaCopy.Placement.Base.z = obj.MainBeamHeight.Value self.ListModules.append(correaCopy) compound.add(correaCopy) return compound def CalculatePosts(self, obj, totalh, totalw): # Temp: utilizar el uso de versiones: ver = 1 if ver == 0: # versión 0: '''posttmp = Part.makeBox(obj.PoleWidth.Value, obj.PoleHeight.Value, obj.PoleLength.Value) linetmp = Part.LineSegment(FreeCAD.Vector(0), FreeCAD.Vector(0, obj.PoleHeight.Value / 2, 0)).toShape() compoundPoles = Part.makeCompound([]) compoundAxis = Part.makeCompound([]) offsetX = - totalw / 2 offsetY = -obj.PoleHeight.Value / 2 arrayDistance = getarray(obj.DistancePole, int(obj.NumberPole.Value)) arrayAerial = getarray(obj.AerialPole, int(obj.NumberPole.Value)) for x in range(int(obj.NumberPole.Value)): offsetX += arrayDistance[x] - obj.PoleWidth.Value / 2 postCopy = posttmp.copy() postCopy.Placement.Base = FreeCAD.Vector(offsetX, offsetY, -(obj.PoleLength.Value - arrayAerial[x])) compoundPoles.add(postCopy) axis = linetmp.copy() axis.Placement.Base = FreeCAD.Vector(offsetX + obj.PoleWidth.Value / 2, offsetY, arrayAerial[x]) compoundAxis.add(axis) return compoundPoles, compoundAxis''' elif ver == 1: # versión 1: linetmp = Part.LineSegment(FreeCAD.Vector(0), FreeCAD.Vector(0, 10, 0)).toShape() compoundPoles = Part.makeCompound([]) compoundAxis = Part.makeCompound([]) offsetX = - totalw / 2 arrayDistance = obj.DistancePole arrayAerial = obj.AerialPole arrayPost = obj.PoleSequence for x in range(int(obj.NumberPole.Value)): postCopy = obj.PoleType[arrayPost[x]].Shape.copy() offsetX += arrayDistance[x] postCopy.Placement.Base = FreeCAD.Vector(offsetX, 0, -(postCopy.BoundBox.ZLength - arrayAerial[x])) compoundPoles.add(postCopy) axis = linetmp.copy() axis.Placement.Base = FreeCAD.Vector(offsetX, 0, arrayAerial[x]) compoundAxis.add(axis) return compoundPoles, compoundAxis def execute(self, obj): # obj.Shape: compound # |- Modules and Beams: compound # |-- Modules array: compound # |--- Modules: solid # |-- Beams: compound # |--- MainBeam: solid # |--- Secundary Beams: solid (if exist) # |- Poles array: compound # |-- Poles: solid # |-- Axis: Edge/line (if exist) if obj.ModuleOrientation == "Portrait": w = obj.ModuleWidth.Value h = obj.ModuleHeight.Value else: h = obj.ModuleWidth.Value w = obj.ModuleHeight.Value totalh = h * obj.ModuleRows + obj.ModuleRowGap.Value * (obj.ModuleRows - 1) totalw = w * obj.ModuleColumns + obj.ModuleColGap.Value * (obj.ModuleColumns - 1) + \ (obj.MotorGap.Value - obj.ModuleColGap.Value) if obj.MotorGap.Value > 0 else 0 modules = self.CalculateModuleArray(obj, totalh, totalw, h, w) beams = self.calculateBeams(obj, totalh, totalw, h, w) poles, poleaxis = self.CalculatePosts(obj, totalh, totalw) compound = Part.makeCompound([modules, beams]) compound.Placement.Base.z = obj.MainBeamAxisPosition.Value - (obj.MainBeamHeight.Value / 2) obj.Shape = Part.makeCompound([compound, Part.makeCompound([poles, poleaxis])]) obj.Width = min(obj.Shape.BoundBox.XLength, obj.Shape.BoundBox.YLength) obj.Length = max(obj.Shape.BoundBox.XLength, obj.Shape.BoundBox.YLength) obj.TotalPower = obj.ModulePower.Value * obj.ModuleRows * obj.ModuleColumns class ViewProviderTrackerSetup: "A View Provider for the TrackerSetup object" def __init__(self, obj): '''Set this object to the proxy object of the actual view provider''' obj.Proxy = self def getIcon(self): return str(os.path.join(DirIcons, "trackersetup.svg")) def setEdit(self, vobj, mode): if (mode == 0) and hasattr(self, "Object"): taskd = _TrackerTaskPanel(self.Object) taskd.obj = self.Object FreeCADGui.Control.showDialog(taskd) return True return False def makeTracker(name = "Tracker", setup = None): obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Tracker") obj.Label = name Tracker(obj) ViewProviderTracker(obj.ViewObject) if setup is not None: obj.Setup = setup return obj class Tracker(ArchComponent.Component): "A 1 Axis single row Tracker Obcject" def __init__(self, obj): # Definición de Variables: ArchComponent.Component.__init__(self, obj) self.Type = None self.oldTilt = 0 self.oldRotation = None self.setProperties(obj) def setProperties(self, obj): pl = obj.PropertiesList if not ("Setup" in pl): obj.addProperty("App::PropertyLink", "Setup", "Frame", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ) if not ("Tilt" in pl): obj.addProperty("App::PropertyAngle", "Tilt", "Frame", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).Tilt = 0 if not ("AngleX" in pl): obj.addProperty("App::PropertyAngle", "AngleX", "Outputs", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).AngleX = 0 if not ("AngleY" in pl): obj.addProperty("App::PropertyAngle", "AngleY", "Outputs", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).AngleX = 0 if not ("AngleZ" in pl): obj.addProperty("App::PropertyAngle", "AngleZ", "Outputs", QT_TRANSLATE_NOOP("App::Property", "The height of this object") ).AngleX = 0 self.Type = "Tracker" #obj.Type = self.Type obj.Proxy = self def onDocumentRestored(self, obj): ArchComponent.Component.onDocumentRestored(self, obj) self.setProperties(obj) def onBeforeChange(self, obj, prop): if prop == "Tilt": self.oldTilt = obj.Tilt.Value if prop == "Placement": self.oldRotation = obj.Placement.Rotation def onChanged(self, obj, prop): if prop.startswith("Angle"): base = obj.Placement.Base angles = obj.Placement.Rotation.toEulerAngles("XYZ") if prop == "AngleX": rot = FreeCAD.Rotation(angles[2], angles[1], obj.AngleX.Value) elif prop == "AngleY": rot = FreeCAD.Rotation(angles[2], obj.AngleY.Value, angles[0]) elif prop == "AngleZ": rot = FreeCAD.Rotation(obj.AngleZ.Value, angles[1], angles[0]) obj.Placement = FreeCAD.Placement(base, rot, FreeCAD.Vector(0,0,0)) if hasattr(FreeCAD.ActiveDocument, "FramesChecking"): from PVPlantRackChecking import checkSingleTracker checkSingleTracker(obj, FreeCAD.ActiveDocument.FramesChecking) def execute(self, obj): # obj.Shape: compound # |- Modules and Beams: compound # |-- Modules array: compound # |--- Modules: solid # |-- Beams: compound # |--- MainBeam: solid # |--- Secundary Beams: solid (if exist) # |- Poles array: compound # |-- Poles array: compound # |--- Pole: solid # |-- PoleAxes: Edge if obj.Setup is None: return pl = obj.Placement shape = obj.Setup.Shape.copy() p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass axis = p1 - p2 modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value) angle = obj.Placement.Rotation.toEuler()[1] newpoles = Part.makeCompound([]) for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)): pole = shape.SubShapes[1].SubShapes[0].SubShapes[i] axis = shape.SubShapes[1].SubShapes[1].SubShapes[i] base = axis.Vertexes[0].Point axis = axis.Vertexes[1].Point - axis.Vertexes[0].Point newpoles.add(pole.rotate(base, axis, -angle)) poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()]) obj.Shape = Part.makeCompound([modules, poles]) obj.Placement = pl obj.AngleX, obj.AngleY, obj.AngleZ = obj.Placement.Rotation.toEulerAngles("XYZ") class ViewProviderTracker(ArchComponent.ViewProviderComponent): "A View Provider for the Pipe object" def __init__(self, vobj): ArchComponent.ViewProviderComponent.__init__(self, vobj) self.Object = vobj.Object vobj.Proxy = self def getIcon(self): return str(os.path.join(DirIcons, "solar-tracker.svg")) def setEdit(self, vobj, mode): """Method called when the document requests the object to enter edit mode. Edit mode is entered when a user double clicks on an object in the tree view, or when they use the menu option [Edit -> Toggle Edit Mode]. Just display the standard FRAME SETUP task panel. Parameters ---------- mode: int or str The edit mode the document has requested. Set to 0 when requested via a double click or [Edit -> Toggle Edit Mode]. Returns ------- bool If edit mode was entered. """ '''if (mode == 0) and hasattr(self, "Object"): taskd = _TrackerTaskPanel(self.Object) taskd.obj = self.Object FreeCADGui.Control.showDialog(taskd) return True''' return False class _TrackerTaskPanel: def __init__(self, obj=None): if not (obj is None): self.new = True self.obj = makeRack() else: self.new = False self.obj = obj # ------------------------------------------------------------------------------------------------------------- # Module widget form # ------------------------------------------------------------------------------------------------------------- self.formRack = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantFrame.ui") self.formRack.widgetTracker.setVisible(False) self.formRack.comboFrameType.currentIndexChanged.connect(self.selectionchange) self.formPiling = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantRackFixedPiling.ui") self.formPiling.editBreadthwaysNumOfPost.valueChanged.connect(self.editBreadthwaysNumOfPostChange) self.formPiling.editAlongNumOfPost.valueChanged.connect(self.editAlongNumOfPostChange) self.form = [self.formRack, self.formPiling] def selectionchange(self, i): vis = False if i == 1: vis = True self.formRack.widgetTracker.setVisible(vis) def editBreadthwaysNumOfPostChange(self): self.formPiling.tableBreadthwaysPosts.insertRow(self.formPiling.tableBreadthwaysPosts.rowCount) def editAlongNumOfPostChange(self): self.l1.setText("current value:" + str(self.sp.value())) def getValues(self): d = {} d["ModuleFrame"] = self.ModuleFrame d["ModuleHeight"] = self.ModuleHeight d["ModuleWidth"] = self.ModuleWidth d["ModuleThick"] = self.ModuleThick return d def accept(self): FreeCADGui.Control.closeDialog() return True def reject(self): if self.new: FreeCAD.ActiveDocument.removeObject(self.obj.Name) FreeCADGui.Control.closeDialog() return True class _FrameTaskPanel: def __init__(self, obj=None): if not (obj is None): self.new = True self.ojb = makeRack() else: self.new = False self.obj = obj self.formRack = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantFrame.ui") self.formRack.widgetTracker.setVisible(False) self.formRack.comboFrameType.setEnable(self.new) self.formRack.comboFrameType.currentIndexChanged.connect(self.selectionchange) self.formPiling = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantRackFixedPiling.ui") self.formPiling.editBreadthwaysNumOfPost.valueChanged.connect(self.editBreadthwaysNumOfPostChange) self.formPiling.editAlongNumOfPost.valueChanged.connect(self.editAlongNumOfPostChange) self.form = [self.formRack, self.formPiling] def selectionchange(self, i): vis = False if i == 1: vis = True self.formRack.widgetTracker.setVisible(vis) def editBreadthwaysNumOfPostChange(self): self.formPiling.tableBreadthwaysPosts.insertRow(self.formPiling.tableBreadthwaysPosts.rowCount) def editAlongNumOfPostChange(self): self.l1.setText("current value:" + str(self.sp.value())) def getValues(self): d = {} d["ModuleFrame"] = self.ModuleFrame d["ModuleHeight"] = self.ModuleHeight d["ModuleWidth"] = self.ModuleWidth d["ModuleThick"] = self.ModuleThick return d def accept(self): FreeCADGui.Control.closeDialog() return True def reject(self): FreeCAD.ActiveDocument.removeObject(self.obj.Name) if self.new: FreeCADGui.Control.closeDialog() return True class CommandFixedRack: "the Arch Building command definition" def GetResources(self): return {'Pixmap': str(os.path.join(DirIcons, "solar-fixed.svg")), 'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantRack", "Fixed Rack"), 'Accel': "R, F", 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlantRack", "Creates a Fixed Rack object from setup dialog.")} def IsActive(self): return not FreeCAD.ActiveDocument is None def Activated(self): obj = makeRack() #self.TaskPanel = _FixedRackTaskPanel(obj) #FreeCADGui.Control.showDialog(self.TaskPanel) return class CommandTrackerSetup: "the Arch Building command definition" def GetResources(self): return {'Pixmap': str(os.path.join(DirIcons, "trackersetup.svg")), 'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantTracker", "TrackerSetup"), 'Accel': "R, F", 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlanTracker", "Creates a TrackerSetup object from setup dialog.")} def IsActive(self): return True return (not FreeCAD.ActiveDocument is None and not FreeCAD.ActiveDocument.getObject("Site") is None) def Activated(self): obj = makeTrackerSetup() self.TaskPanel = _FixedRackTaskPanel(obj) FreeCADGui.Control.showDialog(self.TaskPanel) return class CommandTracker: "the Arch Building command definition" def GetResources(self): return {'Pixmap': str(os.path.join(DirIcons, "solar-tracker.svg")), 'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantTracker", "Tracker"), 'Accel': "R, F", 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlanTracker", "Creates a Tracker object from setup dialog.")} def IsActive(self): return ((not (FreeCAD.ActiveDocument is None)) and (not (FreeCAD.ActiveDocument.getObject("Site")) is None) and (not (FreeCAD.ActiveDocument.getObject("TrackerSetup")) is None)) def Activated(self): sel = FreeCADGui.Selection.getSelection() setupobj = None for obj in sel: if obj.Name[:12] == "TrackerSetup": setupobj = obj break makeTracker(setup=setupobj) return