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:

