# /**********************************************************************
# *                                                                     *
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com>  *
# *                                                                     *
# * This program is free software; you can redistribute it and/or modify*
# * it under the terms of the GNU Lesser General Public License (LGPL)  *
# * as published by the Free Software Foundation; either version 2 of   *
# * the License, or (at your option) any later version.                 *
# * for detail see the LICENCE text file.                               *
# *                                                                     *
# * This program is distributed in the hope that it will be useful,     *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of      *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       *
# * GNU Library General Public License for more details.                *
# *                                                                     *
# * You should have received a copy of the GNU Library General Public   *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307*
# * USA                                                                 *
# *                                                                     *
# ***********************************************************************
import 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
