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:

