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

import FreeCAD
import Part
import PVPlantSite
import Utils.PVPlantUtils as utils
import MeshPart as mp

import pivy
from pivy import coin

if FreeCAD.GuiUp:
    import FreeCADGui
    from DraftTools import translate
else:
    # \cond
    def translate(ctxt,txt):
        return txt
    def QT_TRANSLATE_NOOP(ctxt,txt):
        return txt
    # \endcond

import os
from PVPlantResources import DirIcons as DirIcons

__title__ = "PVPlant Areas"
__author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com"

import PVPlantResources
from PVPlantResources import DirIcons as DirIcons
Dir3dObjects = os.path.join(PVPlantResources.DirResources, "3dObjects")

''' Default Area: '''


def makeArea(points = None, type = 0):
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Area")
    if type == 0:
        _Area(obj)
        _ViewProviderArea(obj.ViewObject)
    elif type == 1:
        _ForbiddenArea(obj)
        _ViewProviderForbiddenArea(obj.ViewObject)
    if points:
        obj.Points = points
    return obj


class _Area:
    def __init__(self, obj):
        ''' Initialize the Area object '''
        self.Type = None
        self.obj = None
        self.setProperties(obj)

    def setProperties(self, obj):
        pl = obj.PropertiesList

        if not ("Base" in pl):
            obj.addProperty("App::PropertyLink",
                            "Base",
                            "Area",
                            "Base wire"
                            ).Base = None

        if not ("Type" in pl):
            obj.addProperty("App::PropertyString",
                            "Type",
                            "Area",
                            "Points that define the area"
                            ).Type = "Area"
        obj.setEditorMode("Type", 1)

        self.Type = obj.Type
        obj.Proxy = self

    def onDocumentRestored(self, obj):
        """ Method run when the document is restored """
        self.setProperties(obj)

    def __getstate__(self):
        return None  # No necesitamos guardar estado adicional

    def __setstate__(self, state):
        pass

    def execute(self, obj):
        ''' Execute the area object '''
        pass


class _ViewProviderArea:
    def __init__(self, vobj):
        vobj.Proxy = self

    def attach(self, vobj):
        ''' Create Object visuals in 3D view. '''
        self.ViewObject = vobj

    def getIcon(self):
        '''
        Return object treeview icon.
        '''

        return str(os.path.join(DirIcons, "area.svg"))

    '''
    def claimChildren(self):
        """
        Provides object grouping
        """
        return self.Object.Group
    '''

    def setEdit(self, vobj, mode=0):
        """
        Enable edit
        """
        return True

    def unsetEdit(self, vobj, mode=0):
        """
        Disable edit
        """
        return False

    def doubleClicked(self, vobj):
        """
        Detect double click
        """
        pass

    def setupContextMenu(self, obj, menu):
        """
        Context menu construction
        """
        pass

    def edit(self):
        """
        Edit callback
        """
        pass

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        pass

''' Frame Area '''

def makeFramedArea(base = None, select = None):
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "FrameArea")
    FrameArea(obj)
    ViewProviderFrameArea(obj.ViewObject)
    if base:
        obj.Base = base

    if select:
        frames = []
        for o in select:
            if hasattr(o, "Proxy") and (o.Proxy.Type == "Tracker"):
                if not (o in frames):
                    frames.append(o)
        if len(frames) > 0:
            print(frames)
            obj.Frames = frames

    try:
        group = FreeCAD.ActiveDocument.FrameZones
    except:
        group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'FrameZones')
        group.Label = "FrameZones"
    group.addObject(obj)

    return obj


class FrameArea(_Area):
    def __init__(self, obj):
        _Area.__init__(self, obj)
        self.setProperties(obj)
        self.obj = None

    def setProperties(self, obj):
        _Area.setProperties(self, obj)
        pl = obj.PropertiesList

        if not ("Frames" in pl):
            obj.addProperty("App::PropertyLinkList",
                            "Frames",
                            "Area",
                            "All the frames inside this area."
                            )

        if not ("FrameNumber" in pl):
            obj.addProperty("App::PropertyInteger",
                            "FrameNumber",
                            "Area",
                            "The number of frames inside this area."
                            )
        obj.setEditorMode("FrameNumber", 1)

        if not ("Type" in pl):
            obj.addProperty("App::PropertyString",
                            "Type",
                            "Base",
                            "The facemaker type to use to build the profile of this object"
                            ).Type = "FrameArea"
        obj.setEditorMode("Type", 1)

        self.Type = "FrameArea"
        obj.Proxy = self
        self.obj = obj

    def onDocumentRestored(self, obj):
        """Method run when the document is restored."""
        self.setProperties(obj)

    def onBeforeChange(self, obj, prop):
        ''' '''
        pass

    def onChanged(self, obj, prop):
        if prop == "Base":
            if obj.Base.Shape is None:
                obj.Shape = Part.Shape()
                return

            import Utils.PVPlantUtils as utils

            base = obj.Base.Shape
            land = PVPlantSite.get().Terrain.Mesh
            vec = FreeCAD.Vector(0,0,1)
            wire = utils.getProjected(base, vec)
            tmp = mp.projectShapeOnMesh(wire, land, vec)
            shape = Part.makeCompound([])
            for section in tmp:
                shape.add(Part.makePolygon(section))
            obj.Shape = shape

        if prop == "Frames":
            lf = []
            for o in obj.Frames:
                if not hasattr(o, "Proxy"):
                    continue
                if o.Proxy.Type == "Tracker":
                    lf.append(o)
            obj.Frames = lf
            obj.FramesNumber = len(obj.Frames)

    def addFrame(self, frame):
        list = self.obj.Frames.copy()
        list.append(frame)
        self.obj.Frames = sorted(list, key=lambda x: x.Name)

    def execute(self, obj):
        ''' execute '''
        
        if not hasattr(obj, "Frames"):
            return
        obj.FrameNumber = len(obj.Frames)

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        pass


class ViewProviderFrameArea(_ViewProviderArea):
    def __init__(self, vobj):
        ''' Set view properties. '''
        self.Object = vobj.Object
        vobj.Proxy = self

    def getIcon(self):
        ''' Return object treeview icon '''
        return str(os.path.join(DirIcons, "FrameArea.svg"))

    def claimChildren(self):
        """ Provides object grouping """
        children = []
        if self.Object.Base:
            children.append(self.Object.Base)
        return children


''' offsets '''
def makeOffsetArea(base = None, val=None):
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "OffsetArea")
    OffsetArea(obj)
    obj.Base = base
    ViewProviderOffsetArea(obj.ViewObject)
    if val:
        obj.OffsetDistance = val

    try:
        offsetsgroup = FreeCAD.ActiveDocument.Offsets
    except:
        offsetsgroup = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Offsets')
        offsetsgroup.Label = "Offsets"
    offsetsgroup.addObject(obj)

    return obj


class OffsetArea(_Area):
    def __init__(self, obj):
        '''_Area.__init__(self, obj)
        self.setProperties(obj)'''
        super().__init__(obj)  # Llama al constructor de _Area

    def setProperties(self, obj):
        super().setProperties(obj)  # Propiedades de la clase base

        pl = obj.PropertiesList
        if not ("OffsetDistance" in pl):
            obj.addProperty("App::PropertyDistance",
                            "OffsetDistance",
                            "OffsetArea",
                            "Base wire"
                            )

        self.Type = obj.Type = "Area_Offset"

    def onDocumentRestored(self, obj):
        """Method run when the document is restored."""
        self.setProperties(obj)

    def execute(self, obj):
        # Comprobar dependencias críticas
        if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
            return
        if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
            return

        base = obj.Base.Shape
        land = PVPlantSite.get().Terrain.Mesh
        vec = FreeCAD.Vector(0, 0, 1)

        wire = utils.getProjected(base, vec)
        wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
        sections = mp.projectShapeOnMesh(wire, land, vec)
        print(" javi ", sections)
        pts = []
        for section in sections:
            pts.extend(section)
        # Crear forma solo si hay resultados
        if len(pts)>0:
            obj.Shape = Part.makePolygon(pts)
        else:
            obj.Shape = Part.Shape()  # Forma vacía si falla


class ViewProviderOffsetArea(_ViewProviderArea):
    def getIcon(self):
        ''' Return object treeview icon. '''
        return str(os.path.join(DirIcons, "offset.svg"))

    def claimChildren(self):
        """ Provides object grouping """
        children = []
        if self.ViewObject and self.ViewObject.Object.Base:
            children.append(self.ViewObject.Object.Base)
        return children


''' Forbidden Area: '''
def makeProhibitedArea(base = None):
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ExclusionArea")
    ProhibitedArea(obj)
    ViewProviderForbiddenArea(obj.ViewObject)
    if base:
        obj.Base = base
    try:
        group = FreeCAD.ActiveDocument.getObject("Exclusions")
    except:
        group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Exclusions')
        group.Label = "Exclusions"
    group.addObject(obj)

    return obj


class ProhibitedArea(OffsetArea):
    def __init__(self, obj):
        OffsetArea.__init__(self, obj)
        self.setProperties(obj)

    def setProperties(self, obj):
        OffsetArea.setProperties(self, obj)
        self.Type = obj.Type = "ProhibitedArea"
        obj.Proxy = self

    def onDocumentRestored(self, obj):
        """Method run when the document is restored."""
        self.setProperties(obj)

    def execute(self, obj):
        # Comprobar dependencias
        if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
            return
        if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
            return

        base = obj.Base.Shape
        land = PVPlantSite.get().Terrain.Mesh
        vec = FreeCAD.Vector(0, 0, 1)

        # 1. Crear wire original
        original_wire = utils.getProjected(base, vec)
        sections_original = mp.projectShapeOnMesh(original_wire, land, vec)

        # 2. Crear wire offset
        offset_wire = original_wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
        sections_offset = mp.projectShapeOnMesh(offset_wire, land, vec)

        # Crear formas compuestas
        def make_polygon(sections):
            if not sections:
                return Part.Shape()
            pts = []
            for section in sections:
                pts.extend(section)
            return Part.makePolygon(pts)

        compounds = []
        if sections_original:
            compounds.append(make_polygon(sections_original))
        if sections_offset:
            compounds.append(make_polygon(sections_offset))

        if compounds:
            obj.Shape = Part.makeCompound(compounds)
        else:
            obj.Shape = Part.Shape()

        # Actualizar colores en la vista
        """if FreeCAD.GuiUp and obj.ViewObject:
            obj.ViewObject.Proxy.updateVisual()"""


class ViewProviderForbiddenArea_old:
    def __init__(self, vobj):
        vobj.Proxy = self
        self.setProperties(vobj)

    def setProperties(self, vobj):
        # Propiedades de color
        if not hasattr(vobj, "OriginalColor"):
            vobj.addProperty("App::PropertyColor",
                             "OriginalColor",
                             "ObjectStyle",
                             "Color for original wire")
            vobj.OriginalColor = (1.0, 0.0, 0.0)  # Rojo

        if not hasattr(vobj, "OffsetColor"):
            vobj.addProperty("App::PropertyColor",
                             "OffsetColor",
                             "ObjectStyle",
                             "Color for offset wire")
            vobj.OffsetColor = (1.0, 0.0, 0.0)  # Rojo

        # Propiedades de grosor
        if not hasattr(vobj, "OriginalWidth"):
            vobj.addProperty("App::PropertyFloat",
                             "OriginalWidth",
                             "ObjectStyle",
                             "Line width for original wire")
            vobj.OriginalWidth = 4.0

        if not hasattr(vobj, "OffsetWidth"):
            vobj.addProperty("App::PropertyFloat",
                             "OffsetWidth",
                             "ObjectStyle",
                             "Line width for offset wire")
            vobj.OffsetWidth = 4.0

        # Deshabilitar el color por defecto
        vobj.setPropertyStatus("LineColor", "Hidden")
        vobj.setPropertyStatus("PointColor", "Hidden")
        vobj.setPropertyStatus("ShapeAppearance", "Hidden")

    def attach(self, vobj):
        self.ViewObject = vobj
        self.Object = vobj.Object

        # Crear la estructura de escena Coin3D
        self.root = coin.SoGroup()

        # Switch para habilitar/deshabilitar la selección
        self.switch = coin.SoSwitch()
        self.switch.whichChild = coin.SO_SWITCH_ALL

        # Separador para el wire original
        self.original_sep = coin.SoSeparator()
        self.original_color = coin.SoBaseColor()
        self.original_coords = coin.SoCoordinate3()
        self.original_line_set = coin.SoLineSet()
        self.original_draw_style = coin.SoDrawStyle()

        # Separador para el wire offset
        self.offset_sep = coin.SoSeparator()
        self.offset_color = coin.SoBaseColor()
        self.offset_coords = coin.SoCoordinate3()
        self.offset_line_set = coin.SoLineSet()
        self.offset_draw_style = coin.SoDrawStyle()

        # Construir la jerarquía de escena
        self.original_sep.addChild(self.original_color)
        self.original_sep.addChild(self.original_draw_style)
        self.original_sep.addChild(self.original_coords)
        self.original_sep.addChild(self.original_line_set)

        self.offset_sep.addChild(self.offset_color)
        self.offset_sep.addChild(self.offset_draw_style)
        self.offset_sep.addChild(self.offset_coords)
        self.offset_sep.addChild(self.offset_line_set)

        self.switch.addChild(self.original_sep)
        self.switch.addChild(self.offset_sep)
        self.root.addChild(self.switch)

        vobj.addDisplayMode(self.root, "Wireframe")

        # Inicializar estilos de dibujo
        self.original_draw_style.style = coin.SoDrawStyle.LINES
        self.offset_draw_style.style = coin.SoDrawStyle.LINES

        # Actualizar visualización inicial
        if hasattr(self.Object, 'Shape'):
            self.updateData(self.Object, "Shape")
            self.updateVisual()

    def updateData(self, obj, prop):
        if prop == "Shape" and obj.Shape and not obj.Shape.isNull():
            self.updateGeometry()

    def updateGeometry(self):
        """Actualiza la geometría en la escena 3D"""
        if not hasattr(self, 'Object') or not self.Object.Shape or self.Object.Shape.isNull():
            return

        # Limpiar coordenadas existentes
        self.original_coords.point.deleteValues(0)
        self.offset_coords.point.deleteValues(0)

        # Obtener los sub-shapes
        subshapes = []
        if hasattr(self.Object.Shape, 'SubShapes') and self.Object.Shape.SubShapes:
            subshapes = self.Object.Shape.SubShapes
        elif hasattr(self.Object.Shape, 'ChildShapes') and self.Object.Shape.ChildShapes:
            subshapes = self.Object.Shape.ChildShapes

        # Procesar wire original (primer sub-shape)
        if len(subshapes) > 0:
            self.processShape(subshapes[0], self.original_coords, self.original_line_set)

        # Procesar wire offset (segundo sub-shape)
        if len(subshapes) > 1:
            self.processShape(subshapes[1], self.offset_coords, self.offset_line_set)

        # Actualizar colores y grosores
        self.updateVisual()

    def processShape(self, shape, coords_node, lineset_node):
        """Procesa una forma y la añade al nodo de coordenadas"""
        if not shape or shape.isNull():
            return

        points = []
        line_indices = []
        current_index = 0

        # Obtener todos los edges de la forma
        edges = []
        if hasattr(shape, 'Edges'):
            edges = shape.Edges
        elif hasattr(shape, 'ChildShapes'):
            for child in shape.ChildShapes:
                if hasattr(child, 'Edges'):
                    edges.extend(child.Edges)

        for edge in edges:
            try:
                # Discretizar la curva para obtener puntos
                vertices = edge.discretize(Number=50)

                for i, vertex in enumerate(vertices):
                    points.append([vertex.x, vertex.y, vertex.z])
                    line_indices.append(current_index)
                    current_index += 1

                # Añadir -1 para indicar fin de línea
                line_indices.append(-1)

            except Exception as e:
                print(f"Error processing edge: {e}")
                continue

        # Configurar coordenadas y líneas
        if points:
            coords_node.point.setValues(0, len(points), points)
            lineset_node.numVertices.deleteValues(0)
            lineset_node.numVertices.setValues(0, len(line_indices), line_indices)

    def updateVisual(self):
        """Actualiza colores y grosores según las propiedades"""
        if not hasattr(self, 'ViewObject') or not self.ViewObject:
            return

        vobj = self.ViewObject

        try:
            # Configurar wire original
            if hasattr(vobj, "OriginalColor"):
                original_color = vobj.OriginalColor
                self.original_color.rgb.setValue(original_color[0], original_color[1], original_color[2])

            if hasattr(vobj, "OriginalWidth"):
                self.original_draw_style.lineWidth = vobj.OriginalWidth

            # Configurar wire offset
            if hasattr(vobj, "OffsetColor"):
                offset_color = vobj.OffsetColor
                self.offset_color.rgb.setValue(offset_color[0], offset_color[1], offset_color[2])

            if hasattr(vobj, "OffsetWidth"):
                self.offset_draw_style.lineWidth = vobj.OffsetWidth

        except Exception as e:
            print(f"Error updating visual: {e}")

    def onChanged(self, vobj, prop):
        """Maneja cambios en propiedades"""
        if prop in ["OriginalColor", "OffsetColor", "OriginalWidth", "OffsetWidth"]:
            self.updateVisual()

    def getDisplayModes(self, obj):
        return ["Wireframe"]

    def getDefaultDisplayMode(self):
        return "Wireframe"

    def setDisplayMode(self, mode):
        return mode

    def claimChildren(self):
        """Proporciona agrupamiento de objetos"""
        children = []
        if hasattr(self, 'Object') and self.Object and hasattr(self.Object, "Base"):
            children.append(self.Object.Base)
        return children

    def getIcon(self):
        '''Return object treeview icon'''
        return str(os.path.join(DirIcons, "area_forbidden.svg"))

    def onDocumentRestored(self, vobj):
        """Método ejecutado cuando el documento es restaurado"""
        self.ViewObject = vobj
        self.Object = vobj.Object
        self.setProperties(vobj)
        self.attach(vobj)

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        return None


class ViewProviderForbiddenArea:
    def __init__(self, vobj):
        vobj.Proxy = self
        self.ViewObject = vobj

        # Inicializar propiedades PRIMERO
        self.setProperties(vobj)

        # Configurar colores iniciales
        self.updateColors(vobj)

    def setProperties(self, vobj):
        if not hasattr(vobj, "OriginalColor"):
            vobj.addProperty("App::PropertyColor",
                             "OriginalColor",
                             "Display",
                             "Color for original wire")
            vobj.OriginalColor = (1.0, 0.0, 0.0)  # Rojo

        if not hasattr(vobj, "OffsetColor"):
            vobj.addProperty("App::PropertyColor",
                             "OffsetColor",
                             "Display",
                             "Color for offset wire")
            vobj.OffsetColor = (1.0, 0.5, 0.0)  # Naranja

    def updateColors(self, vobj):
        """Actualiza los colores desde las propiedades"""
        try:
            if hasattr(vobj, "OriginalColor"):
                self.original_color.rgb.setValue(*vobj.OriginalColor)
            else:
                self.original_color.rgb.setValue(1.0, 0.0, 0.0)

            if hasattr(vobj, "OffsetColor"):
                self.offset_color.rgb.setValue(*vobj.OffsetColor)
            else:
                self.offset_color.rgb.setValue(1.0, 0.5, 0.0)
        except Exception as e:
            print(f"Error en updateColors: {e}")

    def onDocumentRestored(self, vobj):
        self.setProperties(vobj)
        # No llamar a __init__ de nuevo, solo actualizar propiedades
        self.updateColors(vobj)

    def getIcon(self):
        return str(os.path.join(DirIcons, "area_forbidden.svg"))

    def attach(self, vobj):
        self.ViewObject = vobj

        # Inicializar nodos Coin3D
        self.root = coin.SoGroup()
        self.original_coords = coin.SoCoordinate3()
        self.offset_coords = coin.SoCoordinate3()
        self.original_color = coin.SoBaseColor()
        self.offset_color = coin.SoBaseColor()
        self.original_lineset = coin.SoLineSet()
        self.offset_lineset = coin.SoLineSet()

        # Añadir un nodo de dibujo para establecer el estilo de línea
        self.draw_style = coin.SoDrawStyle()
        self.draw_style.style = coin.SoDrawStyle.LINES
        self.draw_style.lineWidth = 3.0

        # Construir la escena
        self.root.addChild(self.draw_style)

        # Grupo para el polígono original
        original_group = coin.SoGroup()
        original_group.addChild(self.original_color)
        original_group.addChild(self.original_coords)
        original_group.addChild(self.original_lineset)

        # Grupo para el polígono offset
        offset_group = coin.SoGroup()
        offset_group.addChild(self.offset_color)
        offset_group.addChild(self.offset_coords)
        offset_group.addChild(self.offset_lineset)

        self.root.addChild(original_group)
        self.root.addChild(offset_group)
        
        vobj.addDisplayMode(self.root, "Standard")
        # Asegurar que la visibilidad esté activada
        vobj.Visibility = True

    def updateData(self, obj, prop):
        if prop == "Shape":
            self.updateVisual(obj)

    def updateVisual(self, obj):
        """Actualiza la representación visual basada en la forma del objeto"""
        if not hasattr(obj, 'Shape') or not obj.Shape or obj.Shape.isNull():
            return

        try:
            # Obtener todos los bordes de la forma compuesta
            all_edges = obj.Shape.Edges

            # Separar bordes por polígono (asumimos que el primer polígono es el original)
            # Esto es una simplificación - podrías necesitar una lógica más sofisticada
            if len(all_edges) >= 2:
                # Polígono original - primer conjunto de bordes
                original_edges = [all_edges[0]]
                original_points = []
                for edge in original_edges:
                    for vertex in edge.Vertexes:
                        original_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))

                # Polígono offset - segundo conjunto de bordes
                offset_edges = [all_edges[1]]
                offset_points = []
                for edge in offset_edges:
                    for vertex in edge.Vertexes:
                        offset_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))

                # Asignar puntos a los nodos Coordinate3
                if original_points:
                    self.original_coords.point.setValues(0, len(original_points), original_points)
                    self.original_lineset.numVertices.setValue(len(original_points))

                if offset_points:
                    self.offset_coords.point.setValues(0, len(offset_points), offset_points)
                    self.offset_lineset.numVertices.setValue(len(offset_points))

                # Actualizar colores
                if hasattr(obj, 'ViewObject') and obj.ViewObject:
                    self.updateColors(obj.ViewObject)

        except Exception as e:
            print(f"Error en updateVisual: {e}")

    def onChanged(self, vobj, prop):
        if prop in ["OriginalColor", "OffsetColor"]:
            self.updateColors(vobj)
        elif prop == "Visibility" and vobj.Visibility:
            # Cuando la visibilidad cambia a True, actualizar visual
            self.updateVisual(vobj.Object)

    def getDisplayModes(self, obj):
        return ["Standard"]

    def getDefaultDisplayMode(self):
        return "Standard"

    def setDisplayMode(self, mode):
        return mode

    def claimChildren(self):
        children = []
        if hasattr(self, 'ViewObject') and self.ViewObject and hasattr(self.ViewObject.Object, 'Base'):
            children.append(self.ViewObject.Object.Base)
        return children

    def dumps(self):
        return None

    def loads(self, state):
        return None

''' PV Area: '''
def makePVSubplant():
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant")
    PVSubplant(obj)
    ViewProviderPVSubplant(obj.ViewObject)
    return obj


class PVSubplant:
    def __init__(self, obj):
        self.setProperties(obj)
        self.Type = None
        self.obj = None

    def setProperties(self, obj):
        pl = obj.PropertiesList

        if not "Setup" in pl:
            obj.addProperty("App::PropertyEnumeration",
                            "Setup",
                            "PVSubplant",
                            "The facemaker type to use to build the profile of this object"
                            ).Setup = ["String Inverter", "Central Inverter"]

        if not ("Frames" in pl):
            obj.addProperty("App::PropertyLinkList",
                            "Frames",
                            "PVSubplant",
                            "List of frames"
                            )

        if not ("Inverters" in pl):
            obj.addProperty("App::PropertyLinkList",
                            "Inverters",
                            "PVSubplant",
                            "List of Inverters"
                            )

        if not ("Cables" in pl):
            obj.addProperty("App::PropertyLinkList",
                            "Cables",
                            "PVSubplant",
                            "List of Cables"
                            )

        if not "TotalPVModules" in pl:
            obj.addProperty("App::PropertyQuantity",
                            "TotalPVModules",
                            "PVSubplant",
                            "The facemaker type to use to build the profile of this object"
                            )

        if not "TotalPowerDC" in pl:
            obj.addProperty("App::PropertyQuantity",
                            "TotalPowerDC",
                            "PVSubplant",
                            "The facemaker type to use to build the profile of this object"
                            )

        if not "Color" in pl:
            obj.addProperty("App::PropertyColor",
                            "Color",
                            "PVSubplant",
                            "Color"
                            )

        if not "Type" in pl:
            obj.addProperty("App::PropertyString",
                            "Type",
                            "Base",
                            "The facemaker type to use to build the profile of this object"
                            ).Type = "PVSubplant"
        obj.setEditorMode("Type", 1)

        self.Type = obj.Type
        self.obj = obj
        obj.Proxy = self

    def onDocumentRestored(self, obj):
        """Method run when the document is restored."""
        self.setProperties(obj)

    def onChanged(self, obj, prop):
        if prop == "Color":
            obj.ViewObject.LineColor = obj.Color
            obj.ViewObject.PointColor = obj.Color

        if prop == "Setup":
            if obj.Setup == "Central Inverter":
                if not ("StringBoxes" in obj.PropertiesList):
                    obj.addProperty("App::PropertyLinkList",
                                    "StringBoxes",
                                    "PVSubplant",
                                    "List of String-Boxes"
                                    )
            else:
                if hasattr(obj, "StringBoxes"):
                    obj.removeProperty("StringBoxes")

        if prop == "Frames":
            import numpy as np
            # 1. Dibujar contorno:
            maxdist = 6000
            if len(obj.Frames) > 0:
                onlyframes = []
                for object in obj.Frames:
                    if object.Name.startswith("Tracker"):
                        onlyframes.append(object)
                obj.Frames = onlyframes

                pts = []
                for frame in obj.Frames:
                    for panel in frame.Shape.SubShapes[0].SubShapes[0].SubShapes:
                        zm = panel.BoundBox.ZMax
                        for i in range(8):
                            pt = panel.BoundBox.getPoint(i)
                            if pt.z == zm:
                                pts.append(pt)
                import MeshTools.Triangulation as Triangulation
                import MeshTools.MeshGetBoundary as mgb
                m = Triangulation.Triangulate(np.array(pts), MaxlengthLE=maxdist, use3d=False)
                b = mgb.get_boundary(m)
                obj.Shape = b

                # 2. rellenar información
                power = 0
                modulenum = 0
                for frame in obj.Frames:
                    modules = frame.Setup.ModuleColumns * frame.Setup.ModuleRows
                    power += frame.Setup.ModulePower.Value * modules
                    modulenum += modules
                obj.TotalPowerDC = power
                obj.TotalPVModules = modulenum
            obj.ViewObject.LineWidth = 5

    def execute(self, obj):
        '''  '''
        pass

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        pass


class ViewProviderPVSubplant:
    def __init__(self, vobj):
        ''' Set view properties. '''
        self.Object = None
        vobj.Proxy = self

    def attach(self, vobj):
        ''' Create Object visuals in 3D view. '''
        self.Object = vobj.Object
        return

    def getIcon(self):
        ''' Return object treeview icon. '''
        return str(os.path.join(DirIcons, "subplant.svg"))

    def claimChildren(self):
        """ Provides object grouping """
        children = []
        if self.Object.Frames:
            children.extend(self.Object.Frames)
        return children

    def __getstate__(self):
        return None

    '''def onDelete(self, feature, subelements):
        try:
            for obj in self.claimChildren():
                obj.ViewObject.show()
        except Exception as err:
            FreeCAD.Console.PrintError("Error in onDelete: " + str(err))
        return True

    def canDragObjects(self):
        return True

    def canDropObjects(self):
        return True

    def canDragObject(self, dragged_object):
        return True

    def canDropObject(self, incoming_object):
        return hasattr(incoming_object, 'Shape')

    def dragObject(self, selfvp, dragged_object):
        objs = self.Object.Objects
        objs.remove(dragged_object)
        self.Object.Objects = objs

    def dropObject(self, selfvp, incoming_object):
        self.Object.Objects = self.Object.Objects + [incoming_object]

    def setEdit(self, vobj, mode=0):
        """
        Enable edit
        """
        return True

    def unsetEdit(self, vobj, mode=0):
        """
        Disable edit
        """
        return False

    def doubleClicked(self, vobj):
        """
        Detect double click
        """
        pass

    def setupContextMenu(self, obj, menu):
        """
        Context menu construction
        """
        pass

    def edit(self):
        """
        Edit callback
        """
        pass

    def __getstate__(self):
        """
        Save variables to file.
        """
        return None

    def __setstate__(self,state):
        """
        Get variables from file.
        """
        return None'''

# Comandos: -----------------------------------------------------------------------------------------------------------


class CommandDivideArea:
    def GetResources(self):
        return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "area.svg")),
                'Accel': "A, D",
                'MenuText': "Divide Area",
                'ToolTip': "Allowed Area"}

    def Activated(self):
        sel = FreeCADGui.Selection.getSelection()[0]


    def IsActive(self):
        if FreeCAD.ActiveDocument:
            return True
        else:
            return False


class CommandBoundary:

    def GetResources(self):
        return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "area.svg")),
                'Accel': "A, B",
                'MenuText': "Area",
                'ToolTip': "Allowed Area"}

    def Activated(self):
        sel = FreeCADGui.Selection.getSelection()[0]
        obj = makeArea([ver.Point for ver in sel.Shape.Vertexes])
        #taskd = _PVPlantPlacementTaskPanel()
        #FreeCADGui.Control.showDialog(taskd)

    def IsActive(self):
        if FreeCAD.ActiveDocument:
            return True
        else:
            return False


class CommandFrameArea:
    def GetResources(self):
        return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "FrameArea.svg")),
                'Accel': "A, F",
                'MenuText': "Frame Area",
                'ToolTip': "Frame Area"}

    def Activated(self):
        for base in FreeCADGui.Selection.getSelection():
            makeFramedArea(None, base)

    def IsActive(self):
        if FreeCAD.ActiveDocument:
            return True
        else:
            return False


class CommandProhibitedArea:
    def GetResources(self):
        return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "area_forbidden.svg")),
                'Accel': "A, F",
                'MenuText': "Prohibited Area",
                'ToolTip': "Prohibited Area"}

    def Activated(self):
        for base in FreeCADGui.Selection.getSelection():
            makeProhibitedArea(base)

    def IsActive(self):
        if FreeCAD.ActiveDocument:
            return True
        else:
            return False


class CommandPVSubplant:

    def GetResources(self):
        return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "subplant.svg")),
                'Accel': "A, P",
                'MenuText': "PV Subplant",
                'ToolTip': "PV Subplant"}

    def Activated(self):
        area = makePVSubplant()
        sel = FreeCADGui.Selection.getSelection()
        for obj in sel:
            if hasattr(obj, 'Proxy') and obj.Proxy.Type == "Tracker":
                frame_list = area.Frames
                frame_list.append(obj)
                area.Frames = frame_list

    def IsActive(self):
        if FreeCAD.ActiveDocument:
            return True
        else:
            return False


class CommandOffsetArea:
    def GetResources(self):
        return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "offset.svg")),
                'Accel': "A, O",
                'MenuText': "OffsetArea",
                'ToolTip': "OffsetArea"}

    def Activated(self):
        for base in FreeCADGui.Selection.getSelection():
            makeOffsetArea(base)

    def IsActive(self):
        if FreeCAD.ActiveDocument:
            return True
        else:
            return False


'''if FreeCAD.GuiUp:
    class CommandAreaGroup:

        def GetCommands(self):
            return tuple([#'Area',
                          'FrameArea',
                          'ForbiddenArea',
                          'PVSubplant',
                          'OffsetArea'
                          ])

        def GetResources(self):
            return {'MenuText': 'Areas',
                    'ToolTip': 'Areas'
                    }

        def IsActive(self):
            return not FreeCAD.ActiveDocument is None

    #FreeCADGui.addCommand('Area', CommandBoundary())
    FreeCADGui.addCommand('FrameArea', CommandFrameArea())
    FreeCADGui.addCommand('ForbiddenArea', CommandProhibitedArea())
    FreeCADGui.addCommand('PVSubplant', CommandPVSubplant())
    FreeCADGui.addCommand('OffsetArea', CommandOffsetArea())
    FreeCADGui.addCommand('PVPlantAreas', CommandAreaGroup())'''
