# -*- coding: utf-8 -*-

from GN.FunctionLibraries import SceneLibrary
from GN.Standard.Libraries.SceneContainer import SceneContainer
from polygonflow.math import Vector3f
from polygonflow.array import VectorArray
from polygonflow.array import IntArray
from polygonflow.array import BitArray
from GN.FunctionLibraries import TransformsLibrary
from GN.FunctionLibraries import ArrayLibrary
from polygonflow.array import FloatArray
from GN.FunctionLibraries import RandomLibrary
from GN.Standard.DCCUtils import DCCUtils
from GN.FunctionLibraries import BaseLibrary
from GN.FunctionLibraries import PointsLibrary
from GN.FunctionLibraries import NoiseLibrary
from GN.FunctionLibraries.NoiseLibrary import NoiseLib
from GN.Standard.ObjectsInterface import SceneObjectInterface


from GN.GNL.ToolCreation import ToolDescriptor, PropertyDescriptor, ToolRunner
from GN.DashModifiers.ScatterModifiers import ScaleModeModifier, PivotModeModifier

class PivotMode:
    DEFAULT = 0
    BOTTOM = 1
    CENTER = 2
    TOP = 3

class ScatterAxis:
    X = 0
    Y = 1 
    XY = 2
    XYZ = 3

class GraphNTool(ToolRunner):
    
    def __init__(self, *args):
        super(GraphNTool, self).__init__()
        self._id = "ba2a4c969338b0c8cc0410f345f7800c"
        self.graph_index = 9617001
    
    def run(self,
            grid_origin: SceneContainer,
            grid_scatter: SceneContainer,
            # Grid properties
            width = 10.0,
            depth = 10.0,
            height = 10.0,
            width_divisions = 5,
            depth_divisions = 5,
            height_divisions = 5,
            scatter_axis = ScatterAxis.XYZ,
            # Position properties
            x_pos_jitter = 0.0,
            y_pos_jitter = 0.0,
            z_pos_jitter = 0.0,
            uniform_pos_jitter = 0.0,
            jitter_multiplier = 1.0,
            # Scale properties
            min_scale = 1.0,
            max_scale = 1.0,
            # Rotation properties
            rot_override = False,
            incremental_spin = "",
            sort_increments = False,
            x_rot_override = 0.0,
            y_rot_override = 0.0,
            z_rot_override = 0.0,
            rand_angle = False,
            uniform_angle = 0.0,
            x_rot_jitter = 0.0,
            y_rot_jitter = 0.0,
            z_rot_jitter = 0.0,
            # Masking properties
            proximity_meshes=None,
            proximity_distance=1.0,
            proximity_sampling=64,
            proximity_width=0.0,
            intensity_noise=0.5,
            frequency_noise=0.25,
            smoothness_noise=0.15,
            warp_noise=0.0,
            noise_scale_x=0.5,
            noise_scale_y=0.5,
            noise_scale_z=0.5,
            noise_scale_multiplier=1.0,
            # Scale influence properties
            scale_influences=None,
            influences_distance=10,
            influences_mult=1.0,
            influences_falloff=5.0,
            # Misc settings
            end_cull_distance=0,
            receive_decals=False,
            enable_collision=False,
            pivot_mode=0,
            **kwargs):
        
        self.seed = kwargs['seed']

        # Update property visibility based on scatter axis
        self.updatePropertyVisibility(scatter_axis)

        # Filter input meshes
        grid_scatter = grid_scatter.filterType(SceneContainer.Types.MESH)
        if not grid_scatter:
            return

        # Create grid points
        grid_width = width * width_divisions
        grid_height = height * height_divisions
        grid_depth = depth * depth_divisions
        
        # Adjust divisions based on scatter axis
        if scatter_axis == ScatterAxis.X:
            depth_divisions = 0
            height_divisions = 0
        elif scatter_axis == ScatterAxis.Y:
            width_divisions = 0
            height_divisions = 0
        elif scatter_axis == ScatterAxis.XY:
            height_divisions = 0

        # Generate base grid
        grid_points = PointsLibrary.PointOps.createPointsGrid(
            width=grid_width, 
            height=grid_height,
            depth=grid_depth,
            width_divisions=width_divisions,
            height_divisions=height_divisions,
            depth_divisions=depth_divisions,
            scale_factor=1.0,
            divisions_factor=1,
            move_center=True,
            func_id=self.graph_index+849686
        )

        # Transform grid to origin
        grid_points = TransformsLibrary.Transform.transformPoints(
            points=grid_points,
            translate=Vector3f(0, 0, 0),
            rotate=Vector3f(0, 0, 0),
            scale=Vector3f(1, 1, 1),
            pivot=Vector3f(0, 0, 0),
            # func_id=self.graph_index+255310
        )

        # Get origin transform and apply to grid
        origin_transform, _, _, _ = TransformsLibrary.Transform.getTransform(
            data=grid_origin,
            mode=0,
            local_space=False,
            # func_id=self.graph_index+469502
        )

        self.points = TransformsLibrary.Transform.transformPoints(
            points=grid_points,
            translate=origin_transform,
            rotate=Vector3f(0, 0, 0),
            scale=Vector3f(1, 1, 1),
            pivot=Vector3f(0, 0, 0),
            # func_id=self.graph_index+806813
        )

        self.count = len(self.points)

        jitter_multiplier = jitter_multiplier*10.0
        # Apply position jitter
        if uniform_pos_jitter != 0.0:
            x_pos_jitter = uniform_pos_jitter
            y_pos_jitter = uniform_pos_jitter
            z_pos_jitter = uniform_pos_jitter

        x_jit = FloatArray.rand(self.count, -x_pos_jitter, x_pos_jitter, seed=self.seed+12345)
        y_jit = FloatArray.rand(self.count, -y_pos_jitter, y_pos_jitter, seed=self.seed+67890)
        z_jit = FloatArray.rand(self.count, -z_pos_jitter, z_pos_jitter, seed=self.seed+13579)

        jitter_vec = VectorArray([x_jit, y_jit, z_jit]) * jitter_multiplier
        self.points = self.points + jitter_vec

        # Generate scales and IDs
        scales = RandomLibrary.RandomGenerators.random_float_array(
            min_value=min_scale,
            max_value=max_scale,
            size=self.count,
            seed_value=self.seed
        )
        
        self.scale = ArrayLibrary.ArrayOps.makeVectorArrayFromXYZ(
            x=scales,
            y=scales,
            z=scales,
            relative=False
        )

        scatter_count = BaseLibrary.BaseOps.size(array=grid_scatter)
        self.ids = RandomLibrary.RandomGenerators.random_integer_array(
            min_value=0,
            max_value=scatter_count,
            size=self.scale,
            seed_value=self.seed
        )

        # Generate rotations
        self.rotations = self.rotationModifier(
            count=self.count,
            normals=VectorArray.fill(Vector3f(0.0, 0.0, 0.0), self.count),
            surface_align=0.0,
            uniform_angle=uniform_angle,
            rand_angle=rand_angle,
            x_override=x_rot_override,
            y_override=y_rot_override,
            z_override=z_rot_override,
            x_rot_jitter=x_rot_jitter,
            y_rot_jitter=y_rot_jitter,
            z_rot_jitter=z_rot_jitter,
            seed=self.seed
        )

        if incremental_spin:
            process_str = incremental_spin.split(" ")
            # if we have some values, keep the ones that are floats or ints
            incr_array = [float(i) for i in process_str if i.replace('.', '').isdigit()]
            if incr_array:
                incr_array = FloatArray(incr_array)
                if sort_increments:
                    rand_fill = FloatArray.fill(list(incr_array), self.count)
                    # keep the "count" number of values
                    rand_fill = rand_fill[:self.count]
                else:
                    rand_fill = incr_array[IntArray.rand(self.count, 0, len(incr_array)-1, seed=self.seed+34988)]
                self.rotations.z += rand_fill

        # Initialize masking
        self.mask = BitArray.fill(True, self.count)

        # Apply feature masking
        remove_mask = self.getPropertyValue('remove_mask')
        if remove_mask:
            rem_invert = self.getPropertyMask('remove_mask')
            rem_mask = ~self.removePoints(remove_mask) if rem_invert else self.removePoints(remove_mask)
            self.mask &= rem_mask

        # Apply bottom removal
        if self.getPropertyValue('Remove Bottom'):
            origin_height = grid_origin.getPositions()[0].z
            self.mask &= (self.points.z >= origin_height)

        # Proximity masking A
        moka = IntArray.fill(0, len(self.mask))
        moka[self.mask] = 1

        # offsets to the distances
        proximity_distance = proximity_distance/2

        if proximity_meshes and proximity_distance > 0.0:
            invert_a = self.getPropertyMask('proximity_distance')
            mask_a = self.objectProximity(proximity_meshes, proximity_distance, proximity_sampling, proximity_sampling/2, proximity_sampling*6, invert_a, 35791)
            if proximity_width > 0.0:
                value_ = proximity_distance + proximity_width if not invert_a else proximity_width
                width_mask_a = self.objectProximity(proximity_meshes, value_, proximity_sampling, proximity_sampling/2, proximity_sampling*6, False, 35791)
                if not invert_a:
                    width_mask_a = ~width_mask_a
                mask_a = mask_a | width_mask_a
            moka[mask_a] = 0

        if proximity_meshes and proximity_distance > 0.0:
            self.mask = self.mask & (moka == 1)

        # Apply noise masking
        if intensity_noise > 0.0:
            noise_mask = self.generateNoiseMask(
                intensity=intensity_noise,
                frequency=frequency_noise,
                smoothness=smoothness_noise,
                warp=warp_noise,
                scale_x=noise_scale_x,
                scale_y=noise_scale_y,
                scale_z=noise_scale_z,
                scale_multiplier=noise_scale_multiplier
            )
            self.mask &= noise_mask

        # Apply scale influences
        if scale_influences and influences_distance > 0.0:
            self.applyScaleInfluences(
                influences=scale_influences,
                distance=influences_distance,
                multiplier=influences_mult,
                falloff=influences_falloff
            )

        # Apply pivot mode
        self.points = self.runModifier(
            PivotModeModifier.NAME,
            scatter=grid_scatter,
            positions=self.points,
            rotations=self.rotations,
            normals=VectorArray.fill(Vector3f(0.0, 0.0, 0.0), self.count),
            scales=self.scale,
            ids=self.ids
        )

        # Create instances
        toolInstanceName = self.getActiveInstance().name
        instancer_container = SceneLibrary.Scene.createMeshInstancer(
            meshes=grid_scatter,
            positions=self.points,
            rotations=self.rotations,
            scales=self.scale,
            ids=self.ids,
            mask=self.mask,
            seed=self.seed,
            name=f"{toolInstanceName}_Scatter",
            identifier=self.graph_index+953503,
            func_id=self.graph_index+953503
        )

        # Apply instance settings
        for iface in instancer_container.toInstancerInterfaces():
            iface.end_cull_distance = end_cull_distance

        for obj in instancer_container:
            obj: SceneObjectInterface
            obj.set_receive_decals(receive_decals)
            obj.set_collision(enabled=enable_collision)

    def generateNoiseMask(self, intensity, frequency, smoothness, warp, 
                         scale_x, scale_y, scale_z, scale_multiplier):
        """Helper method to generate noise mask"""
        base_noise = NoiseLib.noise_value()
        terrace_noise = NoiseLib.noise_Terrace(base_noise, 0.5, 5.0)
        fractal_noise = NoiseLib.noise_FractalFBm(terrace_noise, 4, 0.5, smoothness*5)
        
        scaled_noise = NoiseLib.noise_DomainAxisScale(
            fractal_noise,
            scalex=scale_x*scale_multiplier,
            scaley=scale_y*scale_multiplier,
            scalez=scale_z*scale_multiplier,
            scalew=1.0
        )

        warp_noise = NoiseLib.noise_DomainWarpGradient(scaled_noise, warp, 0.5)
        noise_3d, _, _ = NoiseLib.generate_3d_noise(
            noise=warp_noise,
            points=self.points,
            frequency=frequency/10,
            seed=self.seed
        )

        remap_noise = noise_3d.remap(0.0, 1.0)
        noise_mask = (remap_noise >= intensity)
        
        if self.getPropertyMask('intensity_noise'):
            noise_mask = ~noise_mask
            
        return noise_mask

    def applyScaleInfluences(self, influences, distance, multiplier, falloff):
        """Helper method to apply scale influences"""
        scale_mask = self.objectProximity(
            influences, distance, 64, 32, 128, False, 4857294822
        )

        influences_falloff = max(falloff / 10.0, 0.0001)
        weighted_distance = PointsLibrary.PointOps.weighted_distance(
            positions=self.points[~scale_mask],
            search_pnts=self.points,
            mask=scale_mask,
            remap_range_min=0.0,
            remap_range_max=influences_falloff,
            func_id=self.graph_index + 768810
        )

        self.scale += (weighted_distance * multiplier)

    def objectProximity(self, objs, distance, curve_sample, instance_sample, mesh_sample, invert, identifier=0):

        container_pts = SceneLibrary.Scene.getPointData(container=objs, resolution=curve_sample,
                                                        instance_sample=instance_sample, mesh_sample=mesh_sample,
                                                        func_id=self.graph_index+identifier)
        bsphere = container_pts.bounding_sphere()[1]/100.0

        mask, _, _ = PointsLibrary.PointOps.getPointsProximity(source_pts=self.points, target_pts=container_pts, reverse=invert,
                                                 distance=bsphere*distance, func_id=self.graph_index+identifier+1)

        return mask

    def removePoints(self, percent=0.0):
        percent = percent*100
        rem_count = int((percent * self.count) / 100.0)

        indices = IntArray.arange(self.count).random_shuffle(self.seed)[0:rem_count]
        out = IntArray.fill(1, self.count)
        out[indices] = 0

        return out == 1

    #TODO: Move this to the upcoming modifier library
    def rotationModifier(
                        self,
                        count: int,
                        normals: VectorArray,
                        surface_align: float,
                        uniform_angle: float,
                        rand_angle: bool,
                        x_override: float,
                        y_override: float,
                        z_override: float,
                        x_rot_jitter: float,
                        y_rot_jitter: float,
                        z_rot_jitter: float,
                        seed: int
                        ):

        use_override = self.getPropertyValue('rot_override')

        if rand_angle:
            angle_override = FloatArray.rand(count, -180.0, 180.0, seed=seed)
        else:
            angle_override = FloatArray.fill(uniform_angle, count)

        if not rand_angle:
            if not use_override:
                self.getProperty("uniform_angle").setVisibility(True)
            else:
                self.getProperty("uniform_angle").setVisibility(False)
        else:
            self.getProperty("uniform_angle").setVisibility(False)

        # Add rotation overrides
        if use_override:
            self.getProperty("x_rot_override").setVisibility(True)
            self.getProperty("y_rot_override").setVisibility(True)
            self.getProperty("z_rot_override").setVisibility(True)

            x_rot_override = FloatArray.fill(x_override, count)
            y_rot_override = FloatArray.fill(y_override, count)
            z_rot_override = FloatArray.fill(z_override, count)

            rotations = VectorArray([x_rot_override, y_rot_override, z_rot_override])

            rotations.z += angle_override

            if x_rot_jitter:
                rotations.x += FloatArray.rand(count, -x_rot_jitter, x_rot_jitter, seed=seed+73856093)
            if y_rot_jitter:
                rotations.y += FloatArray.rand(count, -y_rot_jitter, y_rot_jitter, seed=seed+19349663)
            if z_rot_jitter:
                rotations.z += FloatArray.rand(count, -z_rot_jitter, z_rot_jitter, seed=seed+83492791)
        else:
            self.getProperty("x_rot_override").setVisibility(False)
            self.getProperty("y_rot_override").setVisibility(False)
            self.getProperty("z_rot_override").setVisibility(False)

            straight_normals = VectorArray.fill(Vector3f(0.0, 0.0, 1.0), count)
            lerp_normals = straight_normals.lerp(normals, max(0.01, surface_align))
            system = 1 if DCCUtils.isUnreal() else 0
            rotations = lerp_normals.to_angles2(DCCUtils.WORLD_UP_VECTOR, system=system).degrees()

            # Add rotation jitter
            if x_rot_jitter or y_rot_jitter or z_rot_jitter:
                # Use seed with large prime numbers so we don't get similar sequences for rotations
                x_jit = FloatArray.rand(count, -x_rot_jitter, x_rot_jitter, seed=seed+73856093)
                y_jit = FloatArray.rand(count, -y_rot_jitter, y_rot_jitter, seed=seed+19349663)
                z_jit = FloatArray.rand(count, -z_rot_jitter, z_rot_jitter, seed=seed+83492791)
                rotations += VectorArray([x_jit, y_jit, z_jit])

            # if rand_angle:
            rotations = rotations.rotate_around_axis2(lerp_normals, angle_override, system)

            # TODO: make sure to_angles2 is a bit more robust
            # Make sure to null out nans, sometimes rotations can be nan, due to weird handling of normals here
            rotations[rotations != rotations] = Vector3f(0, 0, 0)

        return rotations

    @staticmethod
    def getZCompensate(interface: SceneObjectInterface, position: PivotMode):
        """Returns the offset needed to move an object from the pivot to the bottom Z of it's bounding box"""
        min_pt, max_pt = interface.boundingBox
        height = abs(max_pt.x - min_pt.z)
        pivot = interface.pivot

        if position == PivotMode.BOTTOM:
            offset = pivot.z - min_pt.z
        elif position == PivotMode.TOP:
            offset = pivot.z - max_pt.z
        elif position == PivotMode.CENTER:
            center = (max_pt.z + min_pt.z) / 2.0
            offset = pivot.z - center
        else:
            offset = 0.0

        return offset, height

    def updatePropertyVisibility(self, scatter_axis):
        """Helper method to show/hide properties based on scatter axis"""
        # Get property references
        depth_prop = self.getProperty("depth")
        height_prop = self.getProperty("height")
        depth_div_prop = self.getProperty("depth_divisions")
        height_div_prop = self.getProperty("height_divisions")

        # Hide/show based on scatter axis
        if scatter_axis == ScatterAxis.X:  # 1D Line
            depth_prop.setVisibility(False)
            height_prop.setVisibility(False)
            depth_div_prop.setVisibility(False)
            height_div_prop.setVisibility(False)
        
        elif scatter_axis == ScatterAxis.XY:  # 2D Plane
            depth_prop.setVisibility(True)
            height_prop.setVisibility(False)
            depth_div_prop.setVisibility(True)
            height_div_prop.setVisibility(False)
        
        else:  # 3D Grid (ScatterAxis.XYZ)
            depth_prop.setVisibility(True)
            height_prop.setVisibility(True)
            depth_div_prop.setVisibility(True)
            height_div_prop.setVisibility(True)

    @staticmethod
    def createDescriptor():
        tool = ToolDescriptor(identifier=9617002, version='1.1', name='Grid Scatter', 
                             description='Creates an instance grid of the input objects.', access='')
        tool.setHasInstanceOutput(True)

        # Base Properties
        origin_prop = tool.createProperty(name='Origin', value=SceneContainer(), var_name='grid_origin', 
                                        description="Origin object used as the center of the grid")
        origin_prop.setRequired(True)
        
        scatter_prop = tool.createProperty(name='Scatter', value=SceneContainer(), var_name='grid_scatter', 
                                         description="Objects to instance on the grid")
        scatter_prop.setRequired(True)

        # Add the axis dropdown before other grid properties
        scatter_axis = tool.createProperty(
            name='Distribution', 
            value='2D Plane',
            var_name='scatter_axis',
            description="Controls how objects are distributed - as a line, plane or 3D grid"
        )
        scatter_axis.setDropdownOptions({
            '1D Line': ScatterAxis.X,
            '2D Plane': ScatterAxis.XY, 
            '3D Grid': ScatterAxis.XYZ
        })

        # Grid Properties (moved to base properties group)
        tool.createProperty(name='Width', value=(10.0, 0.0, 250.0, 0.0, 100000.0), var_name='width',
                           description="Width of the grid")
        tool.createProperty(name='Depth', value=(10.0, 0.0, 250.0, 0.0, 100000.0), var_name='depth',
                           description="Depth of the grid")
        tool.createProperty(name='Height', value=(10.0, 0.0, 250.0, 0.0, 100000.0), var_name='height',
                           description="Height of the grid")
        tool.createProperty(name='Width Divisions', value=(5, 1, 100, 0, 10000), var_name='width_divisions', 
                           description="Width Divisions of the grid")
        tool.createProperty(name='Depth Divisions', value=(5, 1, 100, 0, 10000), var_name='depth_divisions', 
                           description="Depth Divisions of the grid")
        tool.createProperty(name='Height Divisions', value=(0, 1, 100, 0, 10000), var_name='height_divisions', 
                           description="Height Divisions of the grid")


        tool.addModifier(ScaleModeModifier(parent=tool, identifier=546091))

        tool.createProperty(name='Min Scale', value=(1.0, 0.0, 2.5, 0.0, 25.0), var_name='min_scale',
                            description="Minimum scale of your scattered meshes")
        tool.createProperty(name='Max Scale', value=(1.0, 0.0, 2.5, 0.0, 25.0), var_name='max_scale',
                            description="Maximum scale of your scattered meshes")

        tool.createProperty(name='Seed', value=(0, 0, 1000000), var_name='seed', 
                           description="Randomly changes each object in the grid")

        # Feature Masking
        tool.addGroup('Feature Masking')
        tool.createProperty(name='Remove Mask', value=(0.0, 0.0, 1.0), var_name='remove_mask',
                           description="Randomly removes objects, ignoring any previous masking", mask_state=False)
        tool.createProperty(name='Remove Bottom', value=False, var_name='Remove Bottom',
                           description="If checked, the bottom half of the grid that's below the origin will be removed")

        # Proximity Mask
        tool.addGroup('Proximity Mask')
        tool.createProperty(name='Objects', value=SceneContainer(), var_name='proximity_meshes',
                           description="You can pass meshes, curves and even instancers to use them as masks")
        tool.createProperty(name='Distance', value=(10.0, 0.0, 1000.0, 0.0, 10000.0), var_name='proximity_distance',
                           description="Distance of the objects' proximity mask", mask_state=False)
        tool.createProperty(name='Sampling', value=(256, 1, 1024), var_name='proximity_sampling',
                           description="A higher value results in more precise proximity masking, but at a heavier cost")
        tool.createProperty(name="Width", value=(0.0, 0.0, 1000.0, 0.0, 10000.0), var_name="proximity_width",
                           description="Width of the proximity mask. This allows your masking to create strokes/slices of your scatter setup.")

        # Noise Mask
        tool.addGroup('Noise Mask')
        tool.createProperty(name='Breakup', value=(0.0, 0.0, 1.0), var_name='intensity_noise',
                           description="Controls the intensity of the noise/breakup.", mask_state=False)
        tool.createProperty(name='Breakup Scale', value=(0.5, 0.0, 2.5), var_name='frequency_noise',
                           description="Frequency of the noise mask. This basically controls the scale of the noise")
        tool.createProperty(name='Smoothness', value=(0.5, 0.0, 1.0), var_name='smoothness_noise',
                           description="This value controls how sharp/soft the noise mask is")
        tool.createProperty(name='Warp', value=(0.5, 0.0, 1.0), var_name='warp_noise',
                           description="Deforms the noise mask, which can result in some really interesting swirly mask shapes")
        tool.createProperty(name='Scale Multiplier', value=(1.0, 0.0, 10.0), var_name='noise_scale_multiplier')
        tool.createProperty(name='Scale X', value=(0.5, 0.0, 10.0), var_name='noise_scale_x')
        tool.createProperty(name='Scale Y', value=(0.5, 0.0, 10.0), var_name='noise_scale_y')
        tool.createProperty(name='Scale Z', value=(0.5, 0.0, 10.0), var_name='noise_scale_z')

        # Position Properties
        tool.addGroup('Position Properties')

        tool.createProperty(name='X Jitter', value=(0.0, -10.0, 10.0), var_name='x_pos_jitter',
                            description="Randomizes the position along the X axis")
        tool.createProperty(name='Y Jitter', value=(0.0, -10.0, 10.0), var_name='y_pos_jitter',
                            description="Randomizes the position along the Y axis")
        tool.createProperty(name='Z Jitter', value=(0.0, -10.0, 10.0), var_name='z_pos_jitter',
                            description="Randomizes the position along the Z axis")
        tool.createProperty(name='Jitter Multiplier', value=(1.0, 0.0, 10.0), var_name='jitter_multiplier',
                            description="Multiplies the jitter amount")
        tool.createProperty(name='Uniform Jitter', value=(0.0, -10.0, 10.0), var_name='uniform_pos_jitter',
                            description="Applies uniform jitter to all axes")

        # Rotation Properties
        tool.addGroup('Rotation Properties')

        tool.createProperty(name="Incremental Spin", value="", var_name="incremental_spin",
                            description="Write your incremental values as ints or floats, separated by spaces.Example:\n 0 90 180")

        tool.createProperty(name="Sort Increments", value=False, var_name="sort_increments",
                            description="If set to True, the incremental values will be applied in order, instead of randomly")

        tool.createProperty(name='Override Rotation', value=False, var_name='rot_override',
                           description="If set to True, the rotation will be overridden by the values below")
        rot_1 = tool.createProperty(name='X Override', value=(0.0, -180.0, 180.0), var_name='x_rot_override',
                           description="Overrides the rotation along the X axis")
        rot_1.setVisible(False)
        rot2 = tool.createProperty(name='Y Override', value=(0.0, -180.0, 180.0), var_name='y_rot_override',
                           description="Overrides the rotation along the Y axis")
        rot2.setVisible(False)
        rot3 = tool.createProperty(name='Z Override', value=(0.0, -180.0, 180.0), var_name='z_rot_override',
                           description="Overrides the rotation along the Z axis")
        rot3.setVisible(False)

        tool.createProperty(name='Random Spin', value=True, var_name='rand_angle',
                           description="This ensures that all instances have a random angle/spin to them")
        tool.createProperty(name='Uniform Angle', value=(0.0, -180.0, 180.0), var_name='uniform_angle',
                           description="Adjust the angle/spin of your instances. By default, they all have a random spin")

        tool.createProperty(name='X Jitter', value=(0.0, -180.0, 180.0), var_name='x_rot_jitter',
                           description="Randomizes the rotation along the X axis")
        tool.createProperty(name='Y Jitter', value=(0.0, -180.0, 180.0), var_name='y_rot_jitter',
                           description="Randomizes the rotation along the Y axis")
        tool.createProperty(name='Z Jitter', value=(0.0, -180.0, 180.0), var_name='z_rot_jitter',
                           description="Randomizes the rotation along the Z axis")

        # Object-Based Scaling
        tool.addGroup('Object-Based Scaling')
        tool.createProperty("Objects", value=SceneContainer(), var_name='scale_influences',
                           description="Add meshes or curves to affect the scale of the scattered objects")
        tool.createProperty("Distance", value=(10.0, 0.0, 1000.0, 0.0, 10000.0), var_name='influences_distance',
                           description="Controls the strength of the scale influences")
        tool.createProperty("Multiplier", value=(1.0, -10.0, 10.0, -100.0, 100.0), var_name='influences_mult',
                           description="Controls the distance from the inputs for the influence to apply")
        tool.createProperty("Falloff", value=(0.0, 0.0, 10.0), var_name='influences_falloff')

        # Add Pivot Mode modifier
        tool.addModifier(PivotModeModifier(parent=tool, identifier=556094))

        # Misc Settings
        tool.addGroup('Misc Settings')
        tool.createProperty(name='End Cull Distance', value=(0, 0, 2000000), var_name='end_cull_distance',
                           description="Distance from camera at which each instance completely fades out")
        tool.createProperty(name='Receive decals', value=False, var_name='receive_decals',
                           description='If set to true, instances receive decals')
        tool.createProperty(name='Enable collision', value=False, var_name='enable_collision',
                           description='If True - collision is set to "Enabled (Query and Physics)", if False - "NoCollision"')

        return tool










