UVPACKMASTER 4

Maya Edition Documentation 4.1.3

UVPackmaster logo
The final version of the documentation for Maya is still under construction. This temporary version is based on the Blender documentation, though its contents is relevant to Maya as well (both variants use the analogical UI).

AI Assistant

IMPORTANT: the AI features of the packer are disabled by default until the user explicitly enables them using a global setting. After enabling, they will only run when requested by the user - they never run in background.

The AI assistant is available in the AI Assistant multi panel:

To learn more about multi panels read this page.

General info

When provided with a prompt, the AI assistant will perform certain action on the UVs.

NOTE: UVPackmaster does not send any of your 3D data to your AI provider. You describe what you want to do with your UVs using a prompt and your AI provider generates a script to perform the given action. The script is then executed in a restriced engine environment locally on your machine. Read the How it works under the hood section below for more details.

Providers

The AI assistant is based on an AI provider. Currently supported providers are:

  • Claude (Anthropic)
  • ChatGPT (OpenAI)
  • Gemini (Google)
  • Ollama (Local)
  • Llama (Local).

The provider is selected using the Provider option at the top of the panel. The model used by the selected provider is set right below it and it is saved with the blend file, so different scenes may use different models.

The first three providers are third-party online services. In order to use one of them, you need to set your API key for it in the Preferences multi panel.

Custom model

The Model list of every online provider contains a few models the packer was released with, followed by the Custom entry. Select Custom in order to use a model which is not on the list - a model released after this version of the packer, in particular - without waiting for an addon update.

With Custom selected, an additional ID field is displayed below the list. Enter the id of the model into the field, exactly as the provider names it in its API (e.g. claude-sonnet-5-5 for Claude). A model id is not the name displayed on the provider website, so use the button next to the field: it opens the model documentation of the provider in your web browser, where you can check which models are currently offered and what their ids are. If the field is left empty, the packer will report an error instead of sending the prompt.

NOTE: the packer does not verify a custom model id - it is passed to the provider as it is. In case of a typo (or a model your account has no access to) the provider will respond with an error which the packer will display.

Ollama provider

Ollama (Local) is different than the first three providers: Ollama is a server you run yourself, usually on the very same machine, so it needs no API key and no internet connection - with this provider selected, nothing is sent outside your computer at all. Instead of an API key, set the address of the server in the Preferences multi panel - the default http://localhost:11434 is correct if Ollama runs on your machine with its standard configuration.

The Ollama provider also offers two options in the AI Assistant panel:

  • Model: the name of a model you have pulled (ollama pull) on the server, exactly as printed by the ollama list command (e.g. qwen3-coder:30b). A model trained for coding gives distinctly better results than a general purpose one of a comparable size. You don’t have to type the name by hand: click the arrow button next to the field to open a menu listing all models currently available on the server, then click a model to put its name into the field. The server is asked for the list every time the menu is opened, so it must be running - otherwise the menu shows an error message instead of the models.
  • Context Length: the size of the context window (in tokens) the model is loaded with. Ollama defaults to a small window and silently truncates everything above it, so the value must be large enough to fit the entire prompt - the description of the scripting API alone takes a few thousand tokens. Note that a larger window also requires more memory on the machine running the model.

WARNING: the addon was tested using the Ollama model qwen3.6:27b. Using a weaker model will likely introduce incorrect results.

Llama provider

Llama (Local) is the second local provider: Llama is a desktop application built on llama.cpp, which runs AI models on your own machine and serves them through a local server, just like Ollama - with this provider selected and the server running on your machine, nothing is sent outside your computer. Set the address of the server in the Preferences multi panel - the default http://localhost:9931 is the address of the server run by the Llama application on your machine. The provider also works with a llama.cpp server started manually (llama-server): it listens on port 8080 unless told otherwise, so set the address to http://localhost:8080 in that case. The API Key field below the address is optional: fill it only if the server was started with the --api-key option.

The Llama provider offers a single option in the AI Assistant panel:

  • Model: the name of the model the server should use, exactly as the server lists it (e.g. ggml-org/Qwen3.8-27B-GGUF:Q4_K_M). The name is required: the packer reports an error if the field is left empty. Just like with the Ollama provider, you can click the arrow button next to the field to open a menu listing all models currently available on the server and click a model to put its name into the field. The server must be running (and the API Key set, if the server requires one) for the menu to show the models.

Note that there is no Context Length option for this provider: a Llama server sets the size of the context window when it loads the model, so it has to be configured when starting the server (the --ctx-size option). The window must be large enough to fit the entire prompt - the description of the scripting API alone takes a few thousand tokens. If the response does not fit in the window, the packer will report an error instead of running a truncated script.

Capabilities

Current assistant capabilities:

  • reading the list of selected and unselected UV islands
  • selecting / delecting islands
  • reading UV area, 3D area (in local and global spaces), vertex count, UV bounding boxes of islands
  • transforming islands: moving, scaling, rotating
  • reading islands bounding boxes in the UV space
  • reading the object (mesh) names a given islands is part of
  • reading the material name a given islands is assigned to
  • reading the mesh part a given island belongs to
  • checking if islands are overlapping each other
  • finding islands which are similar in shape to each other
  • aligning (stacking) similar islands on each other
  • processing texel density: calculating the current texel density of an island, scaling an island to a given texel density, accessing the Texel Density (Packing) per-island parameter
  • reading and modifying texel density tiers
  • reading the current values of the main packer options
  • packing islands: using the packing mode and the options currently set in the packer or using a packing mode, target boxes and options requested in the prompt (the options set in the UI are never changed - the script packs with its own modified copy)
  • running several packing operations in a single script and applying only the selected result (e.g. the best one)
  • reading and modifying grouping schemes: creating and removing schemes and groups, changing group target boxes, options and overrides, assigning islands to groups
  • packing islands in groups, using an existing grouping scheme or a temporary scheme created by the script
  • reading user-defined parameters (read the Parametrizing the prompt section for more details)
  • returning text output to the user through the packer log system
  • writing and reading per-island parameters.

Per-island parameters currently supported by the assistant:

  • Lock Group
  • Track Group
  • Stack Group
  • Normalize Group
  • Island Scale Multiplier
  • Rotation Step
  • Align Priority
  • Texel Density (Packing)
  • group assignment in a grouping scheme.

More capabilities will be implemented in future versions of the packer, including:

  • accessing and modifying UV primitives: vertices, edges, faces
  • and more.

Example prompts

  • Select all islands assigned to Stack Group greater than group_num. [Uses an int parameter group_num - check the next section for details]
  • Stack selected islands but only if they belong to the same mesh part.
  • Select 10 biggest islands (by UV area) belonging to the material mat_name and move them one tile up. [Uses a string parameter mat_name - check the next section for details]
  • Create texel density tier per every object (name tiers according to object names). Do not create a tier if it already exists. Assign islands to corresponding tiers.
  • Assign all islands which are closer to each other than 0.2 in the UV space to the same Lock Group (but only if they are part of the same object).
  • Select all overlapping islands but only if they are assigned to a different material.
  • Select all islands which have texel density greater than 1.0 px/cm
  • Select all islands whose texel density differs by more than 10% from the average of all islands
  • Equalize texel density of the selected islands to the density of the biggest one
  • Assign each mesh part to its own separate Stack Group
  • Set per-island rotation step to 90 for all islands whose bounding box is wider than it is tall
  • Pack the selected islands using the current packer options with rotation steps 10, 45 and 90, then apply the result with the best area and print the rotation step used

Parametrizing the prompt

You can parametrize a prompt using the Parameters list located below the prompt field. Press the Add Parameter button to define a new parameter, then set its name, type and value. The basic types are Int, Float, Bool and Str. A parameter may also be a Box (a rectangle in the UV space, e.g. a packing target), a Packing Mode or one of the enumerated packer options (e.g. Scale Mode, Pack Strategy, Axis). You can then refer to the parameter by its name in the prompt, for example - after defining a Float parameter named offset, you could use a prompt like:

  • Move all selected islands up by offset.

The generated script will read the offset parameter instead of using a hardcoded number.

Check the image below for an example of using prompt parameters:

A parameter name must be unique and must be a valid Python identifier: it may only contain letters, digits and underscores, it cannot start with a digit or an underscore and it cannot collide with a name already used by the script environment.

NOTE: only parameter names and types are sent to your AI provider - parameter values are never included in the request. A value is only applied locally on your machine, when the script is executed.

The main advantage of parameters is that after a script is generated, you can change a parameter value and run the script again with the new value - without sending another request to the AI provider. In particular, when you save a generated script to a file, the parameters used by the script are saved together with it - after selecting such a script in the Run Assistant From Script subpanel, its parameters will be listed with editable values, so every run of the script may use different values (read the next section for more details on running the assistant from a script).

Note that the AI may also ask you to add a new parameter on its own, if it decides a parameter is needed to fulfill your request.

Running assistant from script

After script is generated by AI, you can instruct the packer to save it to a file. You can then rerun the script later in the Run Assistant From Script subpanel without resending the prompt.

All scripts are saved in the assist_scripts subfolder in the user directory.

Writing own scripts

A user with a coding knowledge may write scripts on thier own, using the Python environment described at the end of this page. In such a case relying on a third-party AI provider is not necessary. After creating a script, save it into assist_scripts subfolder as described in the previous section.

Note that the environment is restricted: only a few modules and built-ins are available, in-place operators (e.g. +=) and the format method of str are not allowed and no name used by the script may start with an underscore. All restrictions are listed at the end of the prompt header printed below. In order to finish the operation before the end of the code, raise the OpExit exception - the exit function is not available.

In order to be accepted by the packer, a script file must begin with a metadata header, otherwise the packer will report the Invalid script format error when the script is selected. The header is delimited by the #!UVPM4_META_BEGIN and #!UVPM4_META_END markers and contains a JSON structure with the following fields:

  • env_version: the version of the script environment the script was written for. The packer will refuse to run a script with an environment version different from the version the packer currently provides - it prevents a situation where a script is run in an incompatible environment. The environment version provided by this version of the packer is 3. This field is required and it must be a plain number.
  • env_subversion: the sub version of the script environment the script was written for. The sub version is increased when the environment is extended in a way which does not break the existing scripts - a new class or a new method, for instance. In consequence a script is accepted as long as its sub version is not greater than the sub version the packer provides: a script written for an older sub version simply uses less than the environment currently offers, whereas a greater sub version means the script was written for a newer packer and may use functionalities this version does not know. The sub version provided by this version of the packer is 0. This field is required and it must be a plain number.
  • params: parameters used by the script (read the Parametrizing the prompt section for more details). The type field of a parameter entry accepts the following values: "0" - Int, "1" - Float, "2" - Bool, "3" - Str, "4" - Box, "5" - Packing Mode (greater values stand for the enumerated packer options). Only the value field corresponding to the parameter type is used (i_val for Int, f_val for Float, b_val for Bool, s_val for Str, box_val for Box, pack_mode_val for Packing Mode) - the other value fields are ignored and a field which is missing from the entry takes its default value.
  • prompt: the prompt the script was generated from (may be empty for a hand-written script).
  • model: the name of the AI model which generated the script (may be empty for a hand-written script).

An example of a complete script file - it defines a single Float parameter named offset and moves all selected islands up by the parameter value:

#!UVPM4_META_BEGIN
{
  "env_version": 3,
  "env_subversion": 0,
  "params": {
    "name": "This field is ignored - can be empty",
    "entries": [
      {
        "name": "offset",
        "type": "1",
        "i_val": 0,
        "f_val": 1.0,
        "b_val": false,
        "s_val": ""
      }
    ]
  },
  "prompt": "The packer stores the prompt used to generate a script here. You can write your own description here",
  "model": ""
}
#!UVPM4_META_END
transformed_islands = uc.IslandSet()

for island in sc.selected_islands:
    transformed_islands.append(island.offset(0.0, offset))

sc.send_transform(transformed_islands)

HINT: instead of crafting the metadata header by hand, you can ask the AI assistant to generate any simple script (using the parameters you need), save it to a file and then simply replace the code part of the file with your own code - keeping the header intact. If your script stops using some of the parameters listed in the header, remove them from the params field, so that they are not displayed in the UI unnecessarily.

How it works under the hood

UVPackmaster does not send any of your 3D data to your AI provider. You describe what you want to do with you UVs using a prompt. The packer then combines your prompt with a built-in prompt header and asks AI to generate a Python code which performs the given operatorn.

The prompt header desctibes the Python environment the script will be executed in. The header is a fixed text which does not contain any of your 3D data. You can examine the exact contents of the prompt header at the end of this section.

After the script code is generated by AI, it is executed in a restricted Python environment inside the engine, locally on your machine. While the environment is restricted e.g. it does not provide easy access to any part of your system except the UV data inside the engine, you should always examine the script code before running it.

UVPackmaster is not responsible for the code returned by your AI provider.

The prompt header used in this version of the packer:

Assume the following Python env specification for a UV packer (you can directly use all of the following classes):

uc module (already imported - access these classes using uc. prefix):

class uc.LogType:

    INFO : int

class uc.Packer: # Singleton class for communication with the user

    def send_log(log_type : uc.LogType, log_str : str) # Sends info to the user

uc.packer : uc.Packer # Singleton object for accessing uc.Packer functionalities

class uc.RetCode: # Enum class for operation result

    NOT_SET : int
    SUCCESS : int
    FATAL_ERROR : int
    NO_SPACE : int
    CANCELLED : int
    INVALID_ISLANDS : int
    NO_SIUTABLE_DEVICE : int
    NO_UVS : int
    INVALID_INPUT : int
    WARNING : int

class uc.CoordSpace: # Enum for 3D coord space type

    LOCAL : int
    GLOBAL : int

class uc.Point: # Encodes 2D point

    x : float # Property
    y : float # Property

    def __init__(self, x : float, y : float)
    def __add__(self, other : uc.Point) -> uc.Point
    def __iadd__(self, other : uc.Point)

    def __sub__(self, other : uc.Point) -> uc.Point
    def __isub__(self, other : uc.Point)

    def __imul__(self, scalar : float)

class uc.Box: # Encodes box in 2d space

    min_corner : uc.Point # Property
    max_corner : uc.Point # Property

    def __init__(self, min_corner : uc.Point, max_corner : uc.Point)
    def center(self) -> uc.Point # Returns the center of the box
    def within(self, other : uc.Box) -> bool # Checks if the box is fully within other
    def width(self) -> float # Box width
    def height(self) -> float # Box height
    def area(self) -> float # Box area
    def combine(self, other : uc.Box) # Expands the box to minimal box which contains original box and other
    def intersects(self, other : uc.Box) # Checks if two boxes intersect each other

    @staticmethod
    def unit_box() -> uc.Box # Returns unit box ([0, 0]-[1, 1])

class uc.IntIParamDesc: # Descriptor of an int-based per UV island parameter

    name : str # String which uniquely identifies the given parameter. Read-only
    default_value : int # Default value of the per-island parameter. Read-only
    min_value : int # Min value of the per-island parameter - assigning a parameter value lower than this will raise an error. Read-only
    max_value : int # Max value of the per-island parameter - assigning a parameter value greater than this will raise an error. Read-only
    def mark_dirty(self) # Notifies that a parameter value has been changed for at least one UV island

class uc.IntIParamsManager: # Manager of int-based per UV island parameters

    def iparam_desc(self, name : str) -> uc.IntIParamDesc # Returns the descriptor of the per-island parameter of the given name. Raises IndexError if no such parameter exists

class uc.StrIParamDesc: # Descriptor of a string-based per UV island parameter

    name : str # String which uniquely identifies the given parameter. Read-only
    default_value : str # Default value of the per-island parameter. Read-only
    def mark_dirty(self) # Notifies that a parameter value has been changed for at least one UV island

class uc.StrIParamsManager: # Manager of string-based per UV island parameters

    def iparam_desc(self, name : str) -> uc.StrIParamDesc # Returns the descriptor of the per-island parameter of the given name. Raises IndexError if no such parameter exists

class uc.IslandFlag: # Enum class for island flags

    SELECTED : int

class uc.SimilarityParams: # Class for driving similarity based operations

class uc.Island: # Class holding info about a singe UV island (in a 3D graphics application)

    def area(self) -> float # UV area of the island. Areas of overlapping UV faces are not added twice into the result (calculates area as if all UV faces are merged)
    def faces_area(self) -> float # UV area of all faces. Areas of overlapping UV faces are added twice into the result
    def faces_3d_area(self, space : uc.CoordSpace) -> float # area of island 3D faces in the given coord space. Overlapping faces are added twice into the result

    def vert_count(self) -> int # Returns vertex count of the island

    def set_iparam(self, iparam_desc : uc.IntIParamDesc, value : int) # Sets int-valued per-island parameter
    def get_iparam(self, iparam_desc : uc.IntIParamDesc) -> int # Gets int-valued per-island parameter assigned to the island
    def set_iparam(self, iparam_desc : uc.StrIParamDesc, value : str) # Sets string-valued per-island parameter
    def get_iparam(self, iparam_desc : uc.StrIParamDesc) -> str # Gets string-valued per-island parameter assigned to the island

    # Transform methods: every method returns a new uc.Island object. The returned object refers to the same UVs but transformed accordingly in the UV space
    def offset(self, x : float, y : float) -> uc.Island # Offsets the island
    def scale(self, scale_x : float, scale_y : float) -> uc.Island # Scales the island (pivot being the UC space origin) 
    def scale(self, scale_x : float, scale_y : float, pivot : uc.Point) # Scales the island with a custom pivot
    def rotate(self, angle : float, pivot : uc.Point) # Rotates the island with pivot. Angle in radians

    def set_flags(self, flag : int) # Set flags for the given island (use uc.IslandFlag attributes as argument)
    def clear_flags(self, flag : int) # Clear flags for the given island (use uc.IslandFlag attributes as argument)

    def bbox(self) -> uc.Box # Island bounding box in the UV space
    def smallest_bounding_box_angle() -> float # Returns the angle (in radians) which rotates the island to the smallest bounding box possible in the UV space

    def overlaps(self, other : uc.Island) -> bool # Check if self and other overlap each other in the UV space (check based on exact UV shape - not island bounding box)
    def overlaps(self, box : uc.Box) -> bool # Check if self and other overlap each other in the UV space (check based on exact UV shape - not island bounding box)

class uc.IslandSet: # Container for islands (it is not subscriptable)

    def append(self, island : uc.Island) # Adds an island to the container
    def __iter__(self) # Iterator to iterate over the islands in the container

    def overlapping_islands(self, other : uc.IslandSet) -> tuple[uc.IslandSet, uc.IslandSet] # Finds all islands from self which overlap (in UV space) at least one island from other and vice versa. ret[0] contians all overlapping islands from self, ret[1] - all overlapping islands from other. It's possible that self is passed in the other argument - in such case ret[0] provides all isladns which overlap another island from self and ret[1] is always empty. Overlap check is based on exact UV island shapes (not bounding box).

    def align_similar(self, target_islands : uc.IslandSet, simi_params : uc.SimilarityParams) -> tuple[list[tuple[uc.Island, uc.IslandSet]], uc.IslandSet] # Aligns (stacks) islands from self onto islands from target_islands using simi_params to drive the process, based on island shape similarity. output[0] provides a list of pairs where pair[0] is an island from target_islands and pair[1] contains islands from self transformed so that they are stacked on pair[0]. output[1] contains all islands from self which cound't be stacked due to no match was found for them in target_islands. self may also be passed as target_islands - in such a case islands from self will be stacked onto each other.

    def find_similar(self, simi_params : uc.SimilarityParams, other : uc.IslandSet) -> uc.IslandSet # Finds all islands form other which are similar in shape to at least one island from self. It uses simi_params to drive the process.

    def split_by_similarity(self, simi_params : uc.SimilarityParams) -> list[uc.IslandSet] # Splits islands from self into similarity groups - every returned uc.IslandSet contains islands which are similar in shape to each other. It uses simi_params to drive the process. An island not similar to any other island forms a group containing that island only. Islands are not transformed by this method - use align_similar if they should also be stacked

Other classes (in the global namespace)

class PropertyGroupSA: # Base class for holding properties. It is always used through a dervived class - never directly

    def __init__(self, _copy_from : PropertyGroupSA = None) # If _copy_from is provided, recursively initializes all new object properties by copying values from _copy_from object.
      Otherwise new object properties are recursively initialized to reasonable default values. Note: type(self) == type(_copy_from) must always hold. _copy_from must always be passed as a keyword argument

    def copy_from(self, other) # Recursively copies all other object properties to self properties. Note: type(self) == type(_copy_from) must always hold.

class AppState(PropertyGroupSA):

    scale_length : float

class IdCollectionAccess: # Container for managing a uuid-identified and index-identified items of a fixed type ItemType. A single item may be set as active

    # ItemType always provides two attrs: ItemType.name : str, ItemType.uuid : str. You MUST NEVER modify the uuid attr - it is read-only.

    def create_item(self, set_active=True) -> ItemType # Creates a new item (automatically generates a new uuid value for the new item and also sets name to a default value)
    def remove_item(self, idx : int) # Removes the item at the specified index

    def remove_active_item(self) # Removes the active item (at the same sets another item as active if present)
    def get_active_item_uuid(self) -> str # Returns the active item uuid
    
    def get_item_by_uuid(self, uuid : str) -> ItemType # Returns the item of the given uuid or None if no such item is present
    def get_active_item_idx(self) -> int # Returns the index of the active item or -1 if no active item is selected

    def get_items(self) -> Iterable # Returns the internal collection which stores the items
    
    def get_active_item(self) -> ItemType # Returns the active item or None if no active item is selected
    def set_active_item_uuid(self, uuid : str) # Sets a new item as active (by uuid)

    def __len__(self) -> int # Returns the number of items in the container

class EnumValue: # Class for storing enum values. 
    
    def __eq__(self, other : EnumValue) -> bool

class TexelDensityUnit: # Enum class
    PX_M : EnumValue
    PX_CM : EnumValue
    PX_IN : EnumValue
    PX_FT : EnumValue

class TDensityValue(PropertyGroupSA): # Class for storing a texel density value

    def __eq__(self, other : TDensityValue) -> bool # Checks whether two objects hold the same value
    def __ne__(self, other: TDensityValue) -> bool

    @classmethod
    def unit(cls) -> TexelDensityUnit # Returns the current texel density unit to use (selected by the user in preferences)
    
    @classmethod
    def from_f_abs(cls, f_abs : float) -> TDensityValue # Creates the object based on a float value, where f_abs is always in px/m unit
    
    @classmethod
    def from_s_abs(cls, s_abs : str) -> TDensityValue # Creates the object based on a string value, where s_abs stores a float number in px/m unit
    
    @classmethod
    def undefined(cls) -> TDensityValue # Returns the object which encodes an undefined texel density value (e.g. because the corresponding 3D area is zero)
    def is_defined(self) -> bool # Checks whether the object holds a defined texel density value

    def set_s_abs(self, s_abs : str) # Assigns a value to the object based on a string value, where s_abs stores a float number in px/m unit
    def set_f_abs(self, f_abs : float) # Assigns a value to the object based on a float value, where f_abs is always in px/m unit

    def set_f_unit(self, f_unit : float) # Assigns a value to the object based on a float value, where f_unit is in the unit returned from TDensityValue.unit()

    def to_f_abs(self) -> float # Converts the object to a float value where output is always in px/m unit
    def to_f_unit(self) -> float # Converts the object to a float value where output is in the unit returned from TDensityValue.unit()
    
    def to_s_abs(self) -> str # Converts the object to a string value where output is always in px/m unit 
    def to_s_exact(self) -> str # Equivalent of to_s_abs
    
    def to_s_unit(self) -> str # Converts the object to a string value where output is in the unit returned from TDensityValue.unit()
    def __str__(self) -> str # Returns a human-readable representation of the object

class TDensityTierValue(PropertyGroupSA): # Class for storing either a direct texel densiy value or indication of being assigned to a texel density tier

    @classmethod
    def from_s_exact(cls, s_abs : str) -> TDensityTierValue # Creates the object based on a string value, where s_abs either stores a float number in px/m unit or the uuid of a texel denisty tier
    
    def to_s_unit(self) -> str -> # Converts the object to a string value where output is in the unit returned from TDensityValue.unit(). If the object is assigned to a tier - it returns texel density value assigned to the tier
    
    def to_s_abs(self) -> str # Converts the object to a string value where output is always in the px/m unit. If the object is assigned to a tier - it returns texel density value assigned to the tier

    def to_f_abs(self) -> float # Converts the object to a float value where output is always in px/m unit. If the object is assigned to a tier - it returns texel density value assigned to the tier

    def is_defined(self) -> bool # Checks whether the object holds a defined texel density value. If the object is assigned to a tier, it checks whether the tier texel density value is defined

    def to_s_exact(self) -> str # If the object holds a direct texel density value, it converts the object to a string value where output is always in the px/m unit. If the object is assigned to a tier - it returns the tier uuid

    def __str__(self) -> # Returns a human-readable representation of the object

class TDensityTier(PropertyGroupSA): # Stores into of a texel density tier

    val : TDensityValue # The texel density value assigned to the tier
    name : str
    uuid : str
    color : tuple[float, float, float] # Automatically initialized to a valid value when creating a new tier

class GroupingOptionsBase(PropertyGroupSA): # Options of a grouping scheme shared by all group layout modes

    tiles_in_row : int # Determines the maximum number of tiles in a single tile row. After all tiles in the given row are used, further UV islands will be packed into the tiles from the next row. Range: 1-100. Used only if group_layout_mode is AUTOMATIC or TEXTURE_ATLAS
    tile_count_x : int # Number of tiles to use in the X direction. Range: 1-100. Used only if group_layout_mode is TILE_GRID
    tile_count_y : int # Number of tiles to use in the Y direction. Range: 1-100. Used only if group_layout_mode is TILE_GRID
    atlas_size_x : int # Range: 2-100. Used only if group_layout_mode is TEXTURE_ATLAS
    atlas_size_y : int # Range: 2-100. Used only if group_layout_mode is TEXTURE_ATLAS
    last_group_complementary : bool # Automatically pack the last group on the top of all other groups
    groups_together : bool # When enabled, islands belonging to the same group will be packed into a single box so that they are neighbors in the resulting layout. The option can be overridden on a per-group basis. WARNING: enabling this option forces islands from a single group always being packed into a single target box - one of the boxes configured for the group, where the packer sees fit. Used only with the GROUPS_TO_TILES packing mode
    grouping_compactness : float # A value from 0 to 1 specifying how much the packer should prefer solutions providing more compact grouping, when packing groups together. A lower value means the packer will strive less to achieve compact grouping, at the same time it will prioritize achieving better coverage of the overall packing. With a greater value of the parameter, groups will be packed more compactly, but the coverage of the entire solution might be worse. WARNING: use this parameter with care - a small increase of its value might considerably change the result you will get
    pack_to_single_box : bool # Force every group to be packed into a single target box (one of the target boxes configured for the group). Note that different groups may still be packed into different boxes, also different groups may be packed into the same box. Used only with the GROUPS_TO_TILES packing mode
    dynamic_tiles_all : bool # Enable the Dynamic Tiles functionality for all groups. When UVs are packed with Dynamic Tiles on, the packer will automatically determine the number of target tiles to use - it will use as many tiles as possible to pack the UVs efficiently. Used only with the GROUPS_TO_TILES packing mode
    probing_resolution : int # Determines how many splits of the target box into group subboxes the packer tries. The higher the value, the more splits are tried - the result may be better, but packing takes longer. Range: 1-10. Used only with the GROUPS_TO_SINGLE_TILE packing mode
    priority_mode : GroupPriorityMode # Determines which groups get larger subboxes. Used only with the GROUPS_TO_SINGLE_TILE packing mode

class GroupingOptions(PropertyGroupSA): # Options of a grouping scheme

    base : GroupingOptionsBase
    tdensity_policy : TexelDensityGroupPolicy # Determines how relative texel density of particular groups is processed. Used only with the GROUPS_TO_TILES and GROUPS_TO_SINGLE_TILE packing modes
    group_layout_mode : GroupLayoutMode # Determines where in the UV space the particular groups will be packed. Used only with the GROUPS_TO_TILES packing mode

class GroupOverrides(PropertyGroupSA): # Per-group overrides of the global packing options (main_props or g_scheme.options). An option (e.g. rotation_step) is applied to the group only if both override_global_options and the matching override_ attribute (e.g. override_rotation_step) are True - otherwise the global value is used. Every override_ attribute: Override the given option for the given group. The same value ranges apply as for the corresponding global options

    override_global_options : bool # Enable overriding of the global options for the given group

    override_rotation_enable : bool
    rotation_enable : bool # Allow the packer to rotate islands in order to achieve a better result

    override_pre_rotation_disable : bool
    pre_rotation_disable : bool

    override_rotation_step : bool
    rotation_step : int

    override_scale_mode : bool
    scale_mode : UvpmScaleMode

    override_pixel_margin : bool
    pixel_margin : int

    override_pixel_border_margin : bool
    pixel_border_margin : int # Regardless of main_props.pixel_border_margin_enable

    override_extra_pixel_margin_to_others : bool
    extra_pixel_margin_to_others : int 

    override_pixel_margin_tex_size : bool
    pixel_margin_tex_size : int 

    override_tdensity_packing : bool
    tdensity_packing : TDensityTierValue

    override_groups_together : bool # Only used when packing with GROUPS_TO_TILES
    groups_together : bool # When enabled, islands belonging to the same group will be packed into a single box so that they are neighbors in the resulting layout. WARNING: enabling this option forces islands from a single group always being packed into a single target box - one of the boxes configured for the group, where the packer sees fit

    override_grouping_compactness : bool # Only used when packing with GROUPS_TO_TILES
    grouping_compactness : float

    override_pack_to_single_box : bool # Only used when packing with GROUPS_TO_TILES
    pack_to_single_box : bool

    override_pack_strategy : bool
    pack_strategy : PackStrategyProps

class GroupInfo: # A single group of a grouping scheme

    name : str # Cannot be set to an empty string
    num : int # Number identifying the group inside its scheme. Read-only
    color : tuple[float, float, float] # Color the group is shown with in the UI
    target_boxes : Iterable # Boxes the group is packed into when pack mode is GROUPS_TO_TILES. Use methods described below to modify this container
    tdensity_cluster : int # If given groups have the same value of this parameter set, then uniform scaling will be applied to them during packing (their relative texel density will be maintained). This per-group parameter is only used if texel_density_policy is set to CUSTOM and packing mode is GROUPS_TO_TILES
    tile_count : int # Determines the number of tiles the given group will be packed into. Range: 1-100. Used only if group_layout_mode is AUTOMATIC, AUTOMATIC_HORI, AUTOMATIC_VERT, TEXTURE_ATLAS
    dynamic_tiles : bool # Enable the Dynamic Tiles functionality for the given group. When UVs are packed with Dynamic Tiles on, the packer will automatically determine the number of target tiles to use - it will use as many tiles as possible to pack the UVs efficiently
    overrides : GroupOverrides # Per-group overrides of the global packing options. Overrides are only used in GROUPS_TO_TILES and GROUPS_INDEPENDENTLY packing modes

    def is_default(self) -> bool # Checks whether the group is the default group of its scheme (the group holding all the islands which were not assigned to any other group)
    def add_target_box(self, new_box : Box) # Adds a copy of new_box at the end of target_boxes container and makes it active
    def remove_target_box(self, box_idx : int) # Removes the target box at the given index. Removing the last box of a group raises RuntimeError

class GroupingScheme: # A named set of island groups defined by the user. A grouping scheme drives packing modes which pack islands in groups

    name : str # Cannot be set to an empty string
    uuid : str # Read-only
    groups : Iterable # Groups of the scheme. Use methods described below to modify this container
    options : GroupingOptions # Options of the scheme, used when packing in groups

    def get_iparam_desc(self) -> uc.IntIParamDesc # Get iparam desc for assigning islands to the groups of the scheme. An island is assiged to the group, if its iparam value is set to group.num. The default group num attribute always equals g_scheme.get_iparam_desc(self).default_value
    def group_count(self) -> int # Number of groups in the scheme
    def get_active_group(self) -> GroupInfo # The group currently selected by the user in the UI
    def get_group_by_num(self, g_num : int) -> GroupInfo # Returns the group of the given number or None if no such group is present
    def get_default_group(self) -> GroupInfo # Returns the default group of the scheme (the group every island belongs to unless assigned to another one). The default group is always present
    def add_group_with_target_box(self, name : str = None) -> GroupInfo # Adds a new group to the scheme, at the end of the groups container. The group is initialized with name, num, color and a single target box
    def remove_group(self, group_idx : int) # Removes the group at the given index. Removing the default group raises RuntimeError

class GroupingSchemeAccess: # Container holding all grouping schemes defined by the user. A single scheme may be set as active

    g_schemes : Iterable # All the schemes. Use methods described below to modify this container

    def get_active_g_scheme(self) -> GroupingScheme # Get the scheme currently selected by the user in the packing panel, None if no scheme is selected

    def get_g_scheme_by_uuid(self, uuid : str) -> GroupingScheme # Returns the scheme of the given uuid or None if no such scheme is present
    def get_g_scheme_idx_by_uuid(self, uuid : str) -> int # Returns the index of the scheme of the given uuid or -1 if no such scheme is present
    def get_active_g_scheme_uuid(self) -> str # Returns the uuid of the active scheme
    def get_active_g_scheme_idx(self) -> int # Returns the index of the active scheme or -1 if no scheme is active
    def set_active_g_scheme_uuid(self, uuid : str) # Sets the scheme of the given uuid as active
    def create_g_scheme(self, set_active : bool = True, standalone : bool = False) -> GroupingScheme # Creates a new scheme (generates its uuid, gives it a default name and a default group with a target box). If standalone is False, the new scheme will be added at the end of g_schemes container. Otherwise it will be standalone. Create a standalone scheme if you need it for temporary purposes. Create a non-standalone scheme in order to add it to the scene for later use. set_active is ignored when creating a standalone scheme. WARNING: island assignment to the groups of a scheme created during the operation cannot be sent to the application - see the note at the end of this prompt
    def remove_g_scheme(self, idx : int) # Removes the scheme at the given index. If the removed scheme was active, the scheme which takes its place (or the previous one) becomes active. Raises RuntimeError if the index is out of range
    def remove_g_scheme_by_uuid(self, uuid : str) # Removes the scheme of the given uuid, updating the active scheme the same way as remove_g_scheme. Raises RuntimeError if no such scheme is present

class EngineSceneProps(PropertyGroupSA):

    tdensity_tier_access : IdCollectionAccess[TDensityTier] # Stores texel density tiers defined by the user in the scene
    g_scheme_access : GroupingSchemeAccess # Stores grouping schemes defined by the user in the scene

class UvpmScaleMode: # Enum class
    MAX_SCALE : EnumValue
    FIXED_SCALE : EnumValue
    FIXED_SCALE_MAX_MARGIN : EnumValue

    @classmethod
    def fixed_scale_enabled(cls, mode : UvpmScaleMode) -> bool
    
class UvpmPackStrategy: # Enum class
    AUTOMATIC : EnumValue
    SIDE_TO_SIDE_VERT : EnumValue
    SIDE_TO_SIDE_HORI : EnumValue
    SQUARE : EnumValue

class UvpmBoxCorner: # Enum class
    BOTTOM_LEFT : EnumValue
    BOTTOM_RIGHT : EnumValue
    TOP_RIGHT : EnumValue
    TOP_LEFT : EnumValue

class UvpmTileFillingMethod: # Enum class
    SIMULTANEOUSLY : EnumValue
    ONE_BY_ONE : EnumValue
    
class UvpmOverlapDetectionMode: # Enum class
    DISABLED : EnumValue
    ANY_PART : EnumValue
    EXACT : EnumValue

class UvpmSimilarityMode: # Enum class
    BORDER_SHAPE : EnumValue
    VERTEX_POSITION : EnumValue
    TOPOLOGY : EnumValue

    @classmethod
    def is_vertex_based(cls, mode : UvpmSimilarityMode) -> bool

class UvpmAxis: # Enum class
    NONE : EnumValue
    X : EnumValue
    Y : EnumValue
    Z : EnumValue
    X_NEG : EnumValue
    Y_NEG : EnumValue
    Z_NEG : EnumValue

    @classmethod
    def is_positive(cls, axis : UvpmAxis) -> bool 

class UvpmCoordSpace: # Enum class
    LOCAL : EnumValue
    GLOBAL : EnumValue

class UvpmPixelPerfectVertAlignMode: # Enum class
    NONE : EnumValue
    BOUNDING_BOX_CORNERS : EnumValue
    BOUNDING_BOX : EnumValue
    BORDER_EDGES : EnumValue
    ALL : EnumValue
    
class UvpmPixelPerfectAlignTarget: # Enum class
    CORNER : EnumValue
    CENTER : EnumValue

class UvpmAdvancedHeuristicMode: # Enum class
    AUTOMATIC : EnumValue
    DISABLE : EnumValue
    ENABLE : EnumValue

class PackModeId: # Enum class holding the ids of the packing modes the user can select
    SINGLE_TILE : EnumValue # Pack into a single target box
    TILES : EnumValue # Pack into the UV tiles configured in tile_target_props
    GROUPS_TO_TILES : EnumValue # Pack islands in groups, each group into its own tiles, determined by grouping scheme configuration
    GROUPS_TOGETHER : EnumValue # Pack islands in groups, all groups into the same target configured in tile_target_props
    GROUPS_INDEPENDENTLY : EnumValue # Pack islands in groups, every group packed independently of the others, into target configured in tile_target_props
    GROUPS_TO_SINGLE_TILE : EnumValue # Pack islands in groups (at most 5 groups with islands), every group into its own part of a single target box (as in SINGLE_TILE). The packer tries many splits of the box into parts and applies the one giving the best coverage. With g_scheme.options.base.priority_mode set to GroupPriorityMode.GROUP_ORDER, the earlier the group in g_scheme.groups, the larger part it gets

class TileTargetMode: # Enum class
    TILE_GRID : EnumValue # Pack UVs into a tile grid of size X, Y
    TILE_RANGE : EnumValue # Pack UVs into a tile range. The range is defined by start tile, tile count and tiles in row values
    DYNAMIC_TILES : EnumValue # The packer will automatically determine the number of target tiles to use - it will use as many tiles as possible to pack the UVs efficiently

class GroupingMethod: # Enum class
    MATERIAL : EnumValue
    MESH : EnumValue
    OBJECT : EnumValue
    TILE : EnumValue
    VERTEX_COLOR : EnumValue
    COLLECTION : EnumValue
    MANUAL : EnumValue

    @classmethod
    def auto_grouping_enabled(cls, g_method) -> bool

class TexelDensityGroupPolicy: # Enum class
    INDEPENDENT : EnumValue # Relative texel density of the groups will NOT be maintained (all groups are scaled independently during packing)
    UNIFORM : EnumValue # Relative texel density of the groups will be maintained (uniform scale will be applied to all groups during packing)
    AUTOMATIC : EnumValue # Handle relative texel density automatically: two groups will have relative texel density maintained if and only if their target boxes intersect
    CUSTOM : EnumValue # Determine manually which groups will have relative texel density maintained. Given groups will have relative texel density maintained during packing (the same scale will be applied to them) if they share the same value of the 'Texel Density Cluster' per-group parameter

class GroupLayoutMode: # Enum class
    AUTOMATIC : EnumValue # Groups will be automatically packed one after another. The number of tiles for each group is determined by the tile_count per-group parameter. The maximum number of tiles in a single row is determined by the tiles_in_row parameter
    AUTOMATIC_HORI : EnumValue # Every group will be automatically packed into the distinct tile row (first group into the first row, second group into the second row etc). The number of tiles for each group is determined by the 'Tile Count' parameter
    AUTOMATIC_VERT : EnumValue # Every group will be automatically packed into the distinct tile column (first group into the first column, second group into the second column etc). The number of tiles for each group is determined by the tile_count per-group parameter
    TILE_GRID : EnumValue # All groups will be packed into a tile grid of the given dimensions
    TEXTURE_ATLAS : EnumValue # Groups will be packed into subtiles. The number of subtiles in a single tile is determined by the atlas size properties. When a given tile is out of subtiles, the packer will use subtiles of the next tile and so on
    MANUAL : EnumValue # Groups will be packed into thier target_boxes attributes

class GroupPriorityMode: # Enum class
    GROUP_ORDER : EnumValue # A group never gets a smaller subbox than any group after it on the list
    ISLAND_AREA : EnumValue # The larger the total area of the group islands, the larger subbox the group gets

class Box(PropertyGroupSA): # A rectangle in the UV space, used to define a packing target

    def __init__(self, p1_x : float = 0.0, p1_y : float = 0.0, p2_x : float = 1.0, p2_y : float = 1.0) # Creates a box spanned on two corner points. Defaults to the box covering the default UV space ([0, 0]-[1, 1])

    @classmethod
    def unit_box(cls) -> Box # Returns the box covering the default UV space ([0, 0]-[1, 1])

    p1_x : float
    p1_y : float
    p2_x : float
    p2_y : float

    width : float # Read-only
    height : float # Read-only

    def coords_tuple(self) -> tuple[float, float, float, float] # Returns the box coordinates as (min_x, min_y, max_x, max_y)

class PackStrategyProps(PropertyGroupSA): 

    strategy : UvpmPackStrategy
    start_corner : UvpmBoxCorner

class TrackGroupsProps(PropertyGroupSA):

    require_match_for_all : bool
    matching_mode : UvpmSimilarityMode
 
class TileTargetProps(PropertyGroupSA):

    mode : TileTargetMode

    use_editor_grid : bool # If enabled, the tile grid shape is determined by the UV editor settings (N-panel -> View tab). Otherwise, the shape is configured using the properties below
    
    tile_count_x : int # Number of tiles to use in the X direction. Used only if mode == TILE_GRID
    tile_count_y : int # Number of tiles to use in the Y direction. Used only if mode == TILE_GRID
    
    start_tile_x : int # Used only if mode == TILE_RANGE
    start_tile_y : int # Used only if mode == TILE_RANGE
    
    tile_count : int # Used only if mode == TILE_RANGE
    tiles_in_row : int # Used only if mode == TILE_RANGE

class SimilarityProps(PropertyGroupSA):

    simi_mode : UvpmSimilarityMode # Defines the way in which the packer determines whether two UV islands are similar
    threshold : float # A greater value of this parameter means island borders will be more likely recognized as a similar in shape. A lower value means more accurate distinction. Used only if simi_mode == BORDER_SHAPE 
    
    check_holes : bool # When enabled, the BORDER_SHAPE mode will also take into account shapes of inner holes when determining if two islands are similar

    adjust_scale : bool # Allow the packer to scale islands to the same size before determining whether they are similar

    non_uniform_scaling_tolerance : float # Determines to what extent the packer will be allowed to apply non-uniform scaling when determining if two islands are similar. If set to 0, non-uniform scaling will not be allowed at all. If the value is between 0 and 1, the packer will consider matches resulting from non-uniform scaling, but it will add a penalty for such matches when comparing them with uniformly scaled matches. A general rule is: the more non-uniformly a match is scaled, the greater the penalty which will be applied for the match during comparison. On the other hand the greater the value this option, the more tolerance the packer will have for non-uniform scaling i.e. overall penalties for non-uniformly scaled matches will be lower. If the option is set to the maximum tolerance (value 1), the packer will not distinguish non-uniform scaling from uniform scaling (both cases will be preferred equally)

    match_3d_axis : UvpmAxis # When performing the similarity check, accept only those UV island orientations which result in the same mapping (texture) direction along the given axis in the 3D space. If set to 'NONE' then the functionality is disabled. WARNING: make sure you don't choose an axis which is perpendicular to the 3D geometry being considered - in such a case the UV island corresponding to the given geometry will be excluded from the operation

    match_3d_axis_space : UvpmCoordSpace # The 3D space to consider when the 'Match 3D Axis' functionality is enabled

    correct_vertices : bool # Correct position of matching UV vertices of similar islands so they are placed on the top of each other after aligning. Used only if simi_mode != BORDER_SHAPE
    
    vertex_threshold : float # Maximum distance below which two vertices are considered as matching. Used only if simi_mode != BORDER_SHAPE

class OrientTo3dProps(PropertyGroupSA):

    prim_3d_axis : UvpmAxis # When orienting UVs to the 3D space, the packer first tries to match the primary 3D axis to the primary UV axis. This parameter defines the primary 3D axis
    prim_uv_axis : UvpmAxis # When orienting UVs to the 3D space, the packer first tries to match the primary 3D axis to the primary UV axis. This parameter defines the primary UV axis
    sec_3d_axis : UvpmAxis # When orienting UVs to the 3D space, if primary axes matching fails for a given UV island, the packer will try to match the secondary 3D axis to the secondary UV axis for that island. This parameter defines the secondary 3D axis. Note that the addon prevents setting 'Primary 3D Axis' and 'Secondary 3D Axis' to the same value
    sec_uv_axis : UvpmAxis # When orienting UVs to the 3D space, if primary axes matching fails for a given UV island, the packer will try to match the secondary 3D axis to the secondary UV axis for that island. This parameter defines the secondary UV axis
    axes_space : UvpmCoordSpace # The 3D space to consider when orienting UV islands to the 3D space
    prim_sec_bias : int # Angle (from 0 to 90 degrees) defining to what extent the packer favors orienting using the primary axes over the secondary axes. The greater value of this parameter, the more likely the packer will use the primary axes for orienting. The default value (80 degrees) should be optimal for most scenarios. Do not change the value of this parameter unless really needed. Technical explanation: the packer will orient UVs using secondary axes, only if the primary 3D axis is more perpendicular to the 3D geometry than the secondary 3D axis by the value of this parameter

class NumberedGroupsDescriptor(PropertyGroupSA): # Configuration of a group functionality (lock, stack, track or normalization groups)

    enable : bool # Enable the given group functionality
    iparam_name : str # Name of the int-based per-island parameter defining the groups the packer actually uses for the functionality. Empty string if the functionality is disabled (enable is False). Otherwise get the parameter descriptor using sc.int_iparams_manager.iparam_desc(iparam_name): islands with the same parameter value belong to the same group. Islands with the iparam_desc.default_value value do not belong to any group - the given functionality does not affect such islands. If requested, you can set this field to the name of any int-based parameter (iparam_desc.name).

class NumberedGroupsDescriptorContainer(PropertyGroupSA): # Holds the entire numbered groups configuration of a MainProps object. If the given functionality is enabled in the current packer options (sc.main_props), iparam_name will already hold a valid iparam name. The per-island parameter used will usually be equal to the corresponding parameter from sc (e.g. lock_group.iparam_name == sc.lock_group_iparam_desc.name), but this is not strictly required. If you set up a custom MainProps object and enable a group functionality in it, you need to set iparam_name to a valid value yourself.

    lock_group : NumberedGroupsDescriptor # Lock groups: islands assigned to the same group are locked together during packing (merged into a single island), even if they do not overlap each other. Islands not assigned to a group are not merged

    stack_group : NumberedGroupsDescriptor # Stack groups: before packing, islands assigned to the same group are aligned (stacked) on top of each other if they pass the similarity test (driven by main_props.simi_props) and stacked islands are packed together. Islands not assigned to a group are not stacked

    track_group : NumberedGroupsDescriptor # Track groups: unselected islands (duplicates) follow the transformation applied during packing to the selected islands of the same group they match. Applied only by the packing operation run from the UI - the Assist pack logic classes ignore track groups
    
    norm_group : NumberedGroupsDescriptor # Normalization groups: if main_props.normalize_scale is enabled, scale is normalized independently per every group. Islands not assigned to a group are not normalized

class MainProps(PropertyGroupSA):

    active_pack_mode_id : PackModeId # Determines how the packer processes the UV map. Read-only, it holds the packing mode currently selected by the user - compare it against the PackModeId values

    track_groups_props : TrackGroupsProps
    pack_strategy_props : PackStrategyProps
    numbered_groups_descriptors : NumberedGroupsDescriptorContainer

    precision : int # Number describing how exact the algorithm will be when searching for island placement during packing. Too low value may cause islands to overlap
    margin : float # Margin to apply during packing. Used only if pixel_margin_enable is false

    pixel_margin_enable : bool # Enable the pixel margin functionality. If this parameter is enabled, then the usual 'Margin' option will be ignored and the packer will determine the exact pixel margin

    # The following props are used only if pixel_margin_enable is true

    pixel_margin : int # Distance between distinct UV islands in pixels to apply during packing.
    pixel_border_margin_enable : bool # Border Margin allows to explicitly define the distance in pixels between UV islands and the UV target box border. When Border Margin is disabled, the distance is determined by the island margin value.

    pixel_border_margin : int # Distance in pixels between UV islands and the UV target box border

    extra_pixel_margin_to_others : int # Specifies an additional pixel margin between islands being packed and the static islands. In result the final margin between these islands will be calculated using the formula: pixel_margin + extra_pixel_margin_to_others. When using the GROUPS_TO_TILES mode and packing two groups into the same UV space, the islands from the other group are also treated as static. It means that the extra_pixel_margin_to_others parameter will also be taken into account when calculating the margin between islands belonging to different groups. Range: 0-128

    pixel_margin_tex_size : int # Texture size in pixels used for pixel margin calculation as well as in texel density operations. If the 'Non-Square Packing' option is enabled, then this property is ignored and the dimensions of the active texture are used for calculations

    pixel_perfect_align : bool # Align bounding boxes of the islands being packed to the nearest pixel coordinates

    pixel_perfect_align_target : UvpmPixelPerfectAlignTarget # Defines the pixel alignment target
    pixel_perfect_vert_align_mode : UvpmPixelPerfectVertAlignMode # Determines which vertices will be pixel aligned after packing. WARNING: be careful when setting this option as pixel aligning too many vertices may introduce degenerated faces in the UV map

    # End of pixel margin props

    rotation_enable : bool # Allow the packer to rotate islands in order to achieve a better result
    pre_rotation_disable : bool # Disable the initial rotation of islands before generating other orientations. The pre-rotation operation usually optimizes packing, use this option only if you have a good reason
    flipping_enable : bool # Allow the packer to flip islands when performing the operation

    normalize_scale : bool # Automatically scale selected islands before packing so that the average texel density is the same for every island. WARNING: if lock overlapping is enabled, then normalization takes place AFTER overlapping islands are locked. WARNING 2: islands packed with fixed scale will not be normalized
    normalize_space : UvpmCoordSpace # 3D space to use when normalizing islands
    
    tdensity_set_before_pack : bool # Enable automatic texel density setting for the input islands before packing
    tdensity_packing : TDensityTierValue # TD value to set before packing when tdensity_set_before_pack is enabled. If tdensity_packing.is_defined() == False, then setting TD is skipped
    island_tdensity_set_before_pack : bool # Used only when tdensity_set_before_pack is true. When enabled, TD to apply before packing may be defined on a per-island basis. TD to apply is stored in tdensity_packing_iparam_desc. If tdensity_packing_iparam_desc value for a given island is not defined (is_defined() == False) then the global tdensity_packing value will be applied for that island
    
    island_normalize_multiplier_enable : bool # When enabled, scale multiplier will be applied after normalization is done on a per-island basis. The multiplier is specified in percentages, for example: multiplier 100% (the default value for all islands) means scaling by 1 i.e. an island scale won't change after normalization; multiplier 200% means an island will be scaled by 2 after normalization, multiplier 50% means an island will be scaled by 0.5 after normalization etc. Multiplier to apply is stored in norm_multiplier_iparam_desc

    scale_mode : UvpmScaleMode # Determines how island scaling is performed during packing

    rotation_step : int # Rotation step (in degrees) to use when generating island orientations which will be considered during packing
    island_rot_step_enable : bool # Enable per-island rotation step. If enabled, rotation step to use is determined by rotation_step_iparam_desc. If the iparam value for a given island is rotation_step_iparam_desc.default_value, then the island will use the global rotation_step. If the iparam value is 0, then rotations will be disabled for the island

    lock_overlapping_enable : bool # Treat overlapping islands as a single island during packing (logically merge overlaing islands)
    lock_overlapping_mode : UvpmOverlapDetectionMode # Determines when the packer considers two islands as overlapping each other (when checking if two islands should be locked together)

    heuristic_enable : bool # Perform multiple packing iterations in order to find the optimal result

    heuristic_search_time : int # Specify a time in seconds for the heuristic search. After timeout is reached the packer will stop and the best result will be applied to the UV map. If the time is set to 0 the packer will perform the search continuously, until the user manually applies the result by pressing ESC

    heuristic_max_wait_time : int # Maximum time the packer will wait for a better result. If the heuristic algorithm is not able to find a tighter packing during that time, the operation will be automatically finished and the best result found so far will be applied to the UV map. If set to 0, then the functionality will be disabled

    heuristic_allow_mixed_scales : bool # Allow the packer to apply inconsistent scaling to islands during the search in order to improve UV space coverage. For example, when a single island in the UV map is much bigger than all other islands, such an island may limit final UV space coverage: the big island results in an upper limit for scale (because it has to be fit into the target box) and that limit also applies for all other islands when uniform scaling is used. When the 'Allow Mixed Scales' option is enabled, the packer is allowed to scale the big island down relatively to other islands - meaning the upper limit for scale no longer applies to the smaller islands. In result the smaller islands may be scaled up resulting in better overall UV space coverage. WARNING: when the option is enabled, the packer may scale islands independently. It means the relative texel density of islands may be changed after packing

    advanced_heuristic_mode : UvpmAdvancedHeuristicMode # Use an advanced method during a heuristic search. With this option enabled add-on will examine a broader set of solutions when searching for the optimal one. This method is most useful when packing a limited number of islands - in such case it allows to find a better solution than if using the simple method. Enabling this option is not recommended when packing a UV map containing a greater number of islands. When set to 'Automatic', the packer will automatically determine whether enabling the advanced method is desirable for a given UV map

    custom_target_box_enable : bool # Used only if active_pack_mode_id == SINGLE_TILE. If enabled, islands will be packed into custom_target_box. Otherwise they will be packed into the default unit box
    custom_target_box : Box

    tile_target_props : TileTargetProps
    
    tile_filling_method : UvpmTileFillingMethod # The SIMULTANEOUSLY method is much faster, when packing a huge number of islands (thousands of islands) to many tiles. When packing a moderate number of islands, performance of both methods is similar

    simi_props : SimilarityProps
    
    align_priority_enable : bool # Control which island is aligned (moved) onto another when two islands are considered similar by the packer. When two islands are considered similar by the packer, then the island having a lower priority value will be aligned (moved) to the island with a greater priority value. Align priority values for islands are stored in sc.align_priority_iparam_desc

    orient_to3d_props : OrientTo3dProps

    def to_uc_simi_params(self) -> uc.SimilarityParams # Get similarity params to drive similarity based operations

class IslandWrapper: # Wrapper class to easily calculate and set texel density for an island

    def __init__(self, island : uc.Island, scale_length : float) # Always pass sc.app_state.scale_length to scale_length

    def get(self) -> uc.Island # Get the wrapped island

    def calc_tdensity(self, tex_size : int) -> TDensityValue # Calculates current texel density of the island. Pass sc.main_props.pixel_margin_tex_size to tex_size if not asked otherwise

    def set_tdensity(self, tex_size : int, tdensity_value : TDensityTierValue, pivot : uc.Point = None) -> IslandWrapper # Scales the island to achieve the requested texel density. Pass sc.main_props.pixel_margin_tex_size to tex_size if not asked otherwise. If pivot is None, island bbox center will be used. If texel density cannot be set for the island (e.g. because island 3D area is 0), the method will raise ValueError

class AssistSingleTilePackLogic: # Holds all the packing logic and options - create it to pack islands into a single target box (the custom target box if the user enabled it, the default UV space otherwise). It behaves as if sc.main_props.active_pack_mode_id was set to PackModeId.SINGLE_TILE, whatever that attribute actually holds - the same applies to all the pack logic classes below, each with its own mode

    def __init__(self, main_props : MainProps, app_state : AppState, reporter : PackReporter = None) # Always create it as AssistSingleTilePackLogic(main_props, sc.app_state). Pass reporter as a keyword argument (reporter=...) in this and all the pack logic constructors below - it cannot be passed positionally

    def pack(self, islands_to_pack : uc.IslandSet, static_islands : uc.IslandSet) -> PackResult # Packs islands_to_pack. static_islands are not packed (they are not moved), but the packer makes sure the packed islands do not overlap them - pass an empty uc.IslandSet if there are no such islands. All the packing options set by the user (margin, rotation, scaling, heuristic search and so on) are applied automatically. A logic object performs a single packing operation - create a new one for every pack

class AssistTilesPackLogic(AssistSingleTilePackLogic): # The same, but packs into the UV tiles configured in the tile target options of the packer. It behaves as if active_pack_mode_id was set to PackModeId.TILES

class AssistTargetBoxesPackLogic(AssistSingleTilePackLogic): # The same, but packs into the boxes passed to the constructor. It behaves as if active_pack_mode_id was set to PackModeId.SINGLE_TILE, except that the passed boxes replace the target box

    def __init__(self, main_props : MainProps, app_state : AppState, target_boxes : list[Box], reporter : PackReporter = None)

class AssistGroupsToTilesPackLogic(AssistSingleTilePackLogic): # The same, but packs islands in groups of g_scheme - every group into its own target boxes (group.target_boxes). Islands are assigned to groups through g_scheme.get_iparam_desc(). It behaves as if active_pack_mode_id was set to PackModeId.GROUPS_TO_TILES

    def __init__(self, main_props : MainProps, app_state : AppState, g_scheme : GroupingScheme, reporter : PackReporter = None)

class AssistGroupsTogetherPackLogic(AssistGroupsToTilesPackLogic): # The same, but all groups are packed together into the target configured in the tile target options of the packer. It behaves as if active_pack_mode_id was set to PackModeId.GROUPS_TOGETHER

class AssistGroupsIndependentlyPackLogic(AssistGroupsToTilesPackLogic): # The same, but every group is packed independently of the others into the target configured in the tile target options of the packer. It behaves as if active_pack_mode_id was set to PackModeId.GROUPS_INDEPENDENTLY

class AssistGroupsToSingleTilePackLogic(AssistGroupsToTilesPackLogic): # The same, but every group is packed into its own part of a single target box (the custom target box if the user enabled it, the default UV space otherwise), trying many splits of the box and applying the best one. group.target_boxes are ignored. Supports at most 5 groups with islands. It behaves as if active_pack_mode_id was set to PackModeId.GROUPS_TO_SINGLE_TILE

class AssistStandardPackLogic(AssistSingleTilePackLogic): # The logic to use if asked to pack fully complying to main_props settings. Unlike the classes above, it respects main_props.active_pack_mode_id. Always use this logic unless explicitly asked to use a specific packing mode

    def __init__(self, main_props : MainProps, app_state : AppState, g_scheme : GroupingScheme = None, reporter : PackReporter = None) # Always pass sc.e_scene_props.g_scheme_access.active_g_scheme as g_scheme unless asked to use another scheme. g_scheme is ignored by non-grouping modes. Raises uc.InputError if a grouping mode is selected and g_scheme is None

class PackReporter: # Streams the progress of a packing operation to the application (intermediate results and tips). Never create it directly - get it from sc.create_pack_reporter. Without a reporter, nothing is shown to the user until sc.send_result is called

class PackResult: # Result of a packing operation

    ret_code : uc.RetCode # Return code of the operation
    packed_islands : uc.IslandSet # Islands which were packed - they are already transformed to their new location
    non_packed_islands : uc.IslandSet # Islands which could not be packed (they were not transformed) due to lack of free UV space. Set only when ret_code == uc.RetCode.NO_SPACE
    invalid_islands : uc.InvalidIslandSet # Islands which were rejected by the packer. Set only when ret_code == uc.RetCode.INVALID_ISLANDS

class Scenario: # Singleton class holding objects for performing the operation scenario

    app_state : AppState # General application state. Read-only
    e_scene_props : EngineSceneProps

    main_props : MainProps # Stores all main options of the packer currently set by the user in the UI. For getting default options call MainProps()

    selected_islands : uc.IslandSet # Selected islands in the application (do not modify or overwrite this attribute)
    unselected_islands : uc.IslandSet # Unselected islands in the application (do not modify or overwrite this attribute)
    all_islands : uc.IslandSet # Stores all islands: all_islands == (selected_islands + unselected_islands, do not modify or overwrite this attribute)

    int_iparams_manager : uc.IntIParamsManager # Use it to get a descriptor of an int-based per-island parameter by name (e.g. main_props.numbered_groups_descriptors.lock_group.iparam_name)
    str_iparams_manager : uc.StrIParamsManager # Use it to get a descriptor of a string-based per-island parameter by name

    lock_group_iparam_desc : uc.IntIParamDesc # Descriptor for Lock Group per-island parameter
    track_group_iparam_desc : uc.IntIParamDesc # Descriptor for Track Group per-island parameter
    stack_group_iparam_desc : uc.IntIParamDesc # Descriptor for Stack Group per-island parameter
    norm_group_iparam_desc : uc.IntIParamDesc # Descriptor for Normalization Group per-island parameter

    norm_multiplier_iparam_desc : uc.IntIParamDesc # Descriptor for Normalization Multiplier per-island parameter
    rotation_step_iparam_desc : uc.IntIParamDesc # Descriptor for Rotation Step per-island parameter
    align_priority_iparam_desc : uc.IntIParamDesc # Descriptor for Align Priority per-island parameter

    tdensity_packing_iparam_desc : : uc.StrIParamDesc # Descriptor of a per-island texel density to be applied automatically before packing (so called TD Packing). Its format is either a string encoding a direct texel density value (number, always in px/m unit) or the uuid of a texel density tier. You may manipulate this parameter using TDensityTierValue. Note it doesn't store the current island texel density, only texel density to be applied just before packing

    object_name_iparam_desc : uc.StrIParamDesc # Descriptor for object (mesh) name the island is part of. You can only read it - do not set this param for any island
    material_name_iparam_desc : uc.StrIParamDesc # Descriptor for material name the island is assigned to. You can only read it - do not set this param for any island
    mesh_part_iparam_desc : uc.StrIParamDesc # Descriptor for mesh part the island is a part of. You can only read it - do not set this param for any island. Two islands are parts of the same mesh part if and only if they have equal values of this parameter assigned

    def mark_dirty(self, obj : PropertyGroupSA) # Notifies that obj was modified by the script, so that the application applies the change

    def send_transform(self, islands : uc.IslandSet) # Sends the given islands to the application immediately, applying their new UV coordinates. Call it for every island you transformed
    def send_iparam(self, islands : uc.IslandSet) # Sends the given islands to the application immediately, applying the per-island parameters you modified for them
    def send_selection(self, islands : uc.IslandSet) # Sends the given islands to the application immediately, applying the selection state you modified for them

    def create_pack_reporter(self, immediate_result_sending : bool) -> PackReporter # Creates a reporter to pass to a pack logic constructor. immediate_result_sending controls whether a packing result is automatically sent to the application by the underlaying packing code as soon as it is found (it may happen multiple times when a heuristic search is enabled) - pass True if the result should be send to the application unconditionally. Pass False if you perform several packing operations and only a subset of results should be applied

    def send_result(self, pack_result : PackResult) # Applies the result of a packing operation. Validates the result and if it provides a solution - sends all packed islands using send_transform. You don't need to call this method if immediate_result_sending was True during packing

class OpExit(Exception): # Raise this exception for finishing the operation in the middle of the code - do not use exit()
    pass
    
Variables available (all in the global namespace):

sc : Scenario # Singleton object holding objects for performing the operation scenario

User defined parameters (also in global namespace). A parameter is of one of the following types: int, float, bool, str, Box or PackModeId:

{}

Generate a Python code running in the env as described above, performing an operation described at the end of this prompt.
In case the operation description is clear and achievable, you should output Python code only, so that the result may be directly passed into the exec function.
If the description asks you for a clarification of which functionalities are avaialble in the env, output a text "OP_INFO[[__msg__]]" where __msg__ is a human-readable answer. 
If you cannot create a code performing the requested operation, output a text "OP_ERROR[[__msg__]]" where __msg__ is a human-readable description of the problem. 
If the operation description is ambiguous or not clear, output a text "OP_AMBIGUOUS[[__msg__]]" where __msg__ by a human-readable description of the problem.
You can ask the user to add a user-defined parameter if it may solve the problem - when doing it, tell the user the name and the type the parameter should have.

Always assume the user of the OP_ messages does not have coding knowledge. In OP_ messages, output pure text only using ASCII chars only, without markdowns. Keep the messages short: less than 2000 characters in total, less than 15 lines.

General guidelines:

You can only import these modules: math, random, string

You can only use the following built-ins: abs, bool, bytes, callable, chr, complex, divmod, float, hash, hex, id, int, isinstance, issubclass, len, oct, ord, pow, range, repr, round, slice, sorted, str, tuple, zip, ValueError, RuntimeError, list, dict, set, enumerate, min, max, next, iter, None, True, False

You are not allowed to use inplace operators (e.g. +=) - use standard operators instead (e.g. +)

No variable name can start with underscore.

You are not allowed to use the format and format_map methods of str (e.g. 'value: {}'.format(x)) - they are blocked by the execution sandbox. Format strings using f-strings or the % operator. Note that inside an f-string you can only access attributes which do not start with an underscore.

Every time you modify a per-island parameter for an island, you need to call mark_dirty for the corresponding parameter descriptor. You also have to collect all islands with modified parameters in a uc.IslandSet and pass it to sc.send_iparam.

You are not allowed to check uc.Island or uc.IslandSet identies using id because id will only point to a wrapper object which is temporary. Compare identies of those objects using __eq__. Both types can be added to set and dict directly.

If asked to perform a similarity based operation, use sc.main_props.to_uc_simi_params() to get parameters driving the operation.

If asked to pack islands, create a pack logic object, call its pack method and pass the result to sc.send_result. If asked to pack fully complying to the packer settings (e.g. using the packing mode selected by the user), use AssistStandardPackLogic. Otherwise use AssistSingleTilePackLogic unless asked to pack into tiles (then use AssistTilesPackLogic) or into specific boxes of the UV space (then use AssistTargetBoxesPackLogic). If asked to pack islands in groups, use AssistGroupsToTilesPackLogic, AssistGroupsTogetherPackLogic or AssistGroupsIndependentlyPackLogic, passing sc.e_scene_props.g_scheme_access.active_g_scheme as g_scheme unless asked to use another scheme. Example of packing the selected islands:

pack_logic = AssistSingleTilePackLogic(sc.main_props, sc.app_state, reporter=sc.create_pack_reporter(True))
pack_result = pack_logic.pack(sc.selected_islands, uc.IslandSet())
sc.send_result(pack_result)

Always pass a reporter created by sc.create_pack_reporter.

Islands sent by sc.send_result must not be transformed again afterwards.

Grouping schemes created during the operation (using create_g_scheme) are a special case: you can assign islands to the groups of such a scheme and pack using the assignment, but you must not send that assignment to the application - do not pass islands with the iparam of such a scheme modified to sc.send_iparam and do not call mark_dirty for the iparam descriptor of such a scheme. Therefore, if a scheme is only needed to perform a packing operation, create it as standalone (create_g_scheme(standalone=True)) and do not call sc.mark_dirty(sc.e_scene_props) for it. If asked to permanently assign islands to groups, use a scheme which already exists in sc.e_scene_props.g_scheme_access.g_schemes instead of creating a new one.

For lock, track, stack, norm per-island parameters: if the parameter for an island is equal to iparam_desc.default_value, it means the island is not assigned to a group. If asked to assign an island to a group, use values larger than iparam_desc.default_value. If asked to unset / reset group assignment, set the value to iparam_desc.default_value.

You have to collect all transformed islands (islands returned from the offset, scale and similar methods) in a uc.IslandSet and pass it to sc.send_transform.

To select an island, set the SELECTED flag, to unselect - clear the flag. You have to collect every island whose selection state changed in a uc.IslandSet and pass it to sc.send_selection.

You cannot write any output to stdout (e.g. using print). If the description asks you write an output, call uc.packer.send_log(uc.LogType.INFO, msg) to send a message to the user.

If you ever modify sc.e_scene_props (or its submembers), you need to call sc.mark_dirty(sc.e_scene_props)

YOU CAN ONLY WRITE CODE AFFECTING ENVIRONMENT DESCRIBED ABOVE - REFUSE TO GENERATE A CODE PERFORMING ANY OTHER ACTION!

The operation description starts here:

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.