UVPACKMASTER 4

Blender Edition Documentation 4.1.1

UVPackmaster logo

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.

The AI assistant is based on a third-party AI service provider. Currently supported providers are:

  • Cladue
  • ChatGPT
  • Gemini.

In order to use a given provider, you need to set your API key for it in the Preferences multi panel.

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 the current values of the main packer options
  • 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).

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

  • packing
  • 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]
  • 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 Rotation Step to 90 for all islands whose bounding box is wider than it is tall

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 (Int, Float, Bool or Str) and value. 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.

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 1.
  • 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. 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) - the other value fields are ignored, but all of them must be present in the entry.
  • 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": 1,
  "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
for island in sc.selected_islands:
    new_island = island.offset(0.0, offset)
    sc.transformed_islands.add(new_island)

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:

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.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 a int-based per UV island parameter

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

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

    default_value : str # Default value of the per-island parameter
    def mark_dirty(self) # Notifies that a parameter value has been changed for at least one UV island

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 (behaves as a list)

    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.

Other classes (in the global namespace)

class AppState:

    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) -> list[ItemType] # Returns the internal collection which stores 
    
    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: # 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: # 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: # 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]

class EngineSceneProps:

    tdensity_tier_access : IdCollectionAccess[TDensityTier] # Stores texel density tiers defined by the user. Read-ony - you cannot modify this container!

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 TileTargetMode: # Enum class
    TILE_GRID : EnumValue
    TILE_RANGE : EnumValue
    DYNAMIC_TILES : EnumValue

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
    UNIFORM : EnumValue
    AUTOMATIC : EnumValue
    CUSTOM : EnumValue

class GroupLayoutMode: # Enum class
    AUTOMATIC : EnumValue
    AUTOMATIC_HORI : EnumValue
    AUTOMATIC_VERT : EnumValue
    TILE_GRID : EnumValue
    TEXTURE_ATLAS : EnumValue
    MANUAL : EnumValue

class PackStrategyProps(EngineParamTarget): 

    strategy : UvpmPackStrategy
    start_corner : UvpmBoxCorner

class SplitOverlapProps(EngineParamTarget):

    detection_mode : UvpmOverlapDetectionMode    
    max_tile_x : int    
    dont_split_priorities : bool

class TrackGroupsProps(EngineParamTarget):

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

    mode : TileTargetMode

    use_editor_grid : bool
    
    tile_count_x : int
    tile_count_y : int
    
    start_tile_x : int
    start_tile_y : int
    
    tile_count : int
    tiles_in_row : int

class SimilarityProps(EngineParamTarget):

    simi_mode : UvpmSimilarityMode
    threshold : float
    
    check_holes : bool
    adjust_scale : bool
    non_uniform_scaling_tolerance : float
    match_3d_axis : UvpmAxis
    match_3d_axis_space : UvpmCoordSpace
    correct_vertices : bool
    vertex_threshold : float

class OrientTo3dProps(EngineParamTarget):

    prim_3d_axis : UvpmAxis
    prim_uv_axis : UvpmAxis
    sec_3d_axis : UvpmAxis
    sec_uv_axis : UvpmAxis
    axes_space : UvpmCoordSpace
    prim_sec_bias : int

class MainProps(EngineParamTarget):

    track_groups_props : TrackGroupsProps
    pack_strategy_props : PackStrategyProps

    precision : int
    margin : float

    pixel_margin_enable : bool
    pixel_margin : int
    pixel_border_margin_enable : bool

    pixel_border_margin : int
    extra_pixel_margin_to_others : int
    pixel_margin_tex_size : int
    pixel_perfect_align : bool

    pixel_perfect_align_target : UvpmPixelPerfectAlignTarget
    pixel_perfect_vert_align_mode : UvpmPixelPerfectVertAlignMode

    rotation_enable : bool
    pre_rotation_disable : bool
    flipping_enable : bool

    normalize_scale : bool
    normalize_space : UvpmCoordSpace
    
    tdensity_set_before_pack : bool
    tdensity_to_set : TDensityTierValue
    tdensity_packing : TDensityTierValue
    island_tdensity_set_before_pack : bool
    
    island_normalize_multiplier_enable : bool
    island_normalize_multiplier : int

    scale_mode : UvpmScaleMode

    rotation_step : int
    island_rot_step_enable : bool
    island_rot_step : int
    non_square_packing : bool

    lock_overlapping_enable : bool
    lock_overlapping_mode : UvpmOverlapDetectionMode

    heuristic_enable : bool
    heuristic_search_time : int
    heuristic_max_wait_time : int
    heuristic_allow_mixed_scales : bool
    advanced_heuristic_mode : UvpmAdvancedHeuristicMode
    fully_inside : bool

    custom_target_box_enable : bool
    custom_target_box : Box

    tile_target_props : TileTargetProps
    
    tile_filling_method : UvpmTileFillingMethod
    split_props : SplitOverlapProps

    simi_props : SimilarityProps
    
    align_priority_enable : bool
    align_priority : int

    orient_to3d_props : OrientTo3dProps

    def to_uc_simi_params(self) -> uc.SimilarityParams # Get similarity params to drive similarity based operations from the current main packer options

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 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

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

    transformed_islands = set() # Empty set 
    iparam_dirty_islands = set() # Empty set
    selection_dirty_islands = set() # Empty set

    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

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):

{}

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.

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. +)

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 to add the given island to sc.iparam_dirty_islands.

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.

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 add all transformed islands (islands returned from the offset, scale and similar methods) to sc.transformed_islands.

To select an island, set the SELECTED flag, to unselect - clear the flag. You have to add every island whose selection state changed to sc.selection_dirty_islands.

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.

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.