{ "sourceFile": "Project Notes/BLENDER_INTEGRATION_PLAN.md", "activeCommit": 0, "commits": [ { "activePatchIndex": 0, "patches": [ { "date": 1745229128322, "content": "Index: \n===================================================================\n--- \n+++ \n" } ], "date": 1745229128322, "name": "Commit-0", "content": "# Blender Integration Plan: Node Groups from Processed Assets\r\n\r\n**Objective:** Develop a Python script (`blenderscripts/create_nodegroups.py`) to run manually inside Blender. This script will scan the output directory generated by the Asset Processor Tool, read `metadata.json` files, and create/update corresponding PBR node groups in the active Blender file, leveraging the pre-calculated metadata.\r\n\r\n**Key Principles:**\r\n\r\n* **Leverage Existing Tool Output:** Rely entirely on the structured output and `metadata.json` from the Asset Processor Tool. Avoid reprocessing or recalculating data already available.\r\n* **Blender Environment:** The script is designed solely for Blender's Python environment (`bpy`).\r\n* **Manual Execution:** Users will manually run this script from Blender's Text Editor.\r\n* **Target Active File:** All operations modify the currently open `.blend` file.\r\n* **Assume Templates:** The script will assume node group templates (`Template_PBRSET`, `Template_PBRTYPE`) exist in the active file. Error handling will be added if they are missing.\r\n* **Focus on Node Groups:** The script's scope is limited to creating and updating the node groups, not materials.\r\n\r\n**Detailed Plan:**\r\n\r\n1. **Script Setup & Configuration:**\r\n * Create a new Python file named `create_nodegroups.py` (intended location: `blenderscripts/`).\r\n * Import necessary modules (`bpy`, `os`, `json`, `pathlib`).\r\n * Define user-configurable variables at the top:\r\n * `PROCESSED_ASSET_LIBRARY_ROOT`: Path to the root output directory of the Asset Processor Tool.\r\n * `PARENT_TEMPLATE_NAME`: Name of the parent node group template (e.g., `\"Template_PBRSET\"`).\r\n * `CHILD_TEMPLATE_NAME`: Name of the child node group template (e.g., `\"Template_PBRTYPE\"`).\r\n * `ASPECT_RATIO_NODE_LABEL`: Label of the Value node in the parent template for aspect ratio correction (e.g., `\"AspectRatioCorrection\"`).\r\n * `STATS_NODE_PREFIX`: Prefix for Combine XYZ nodes storing stats in the parent template (e.g., `\"Histogram-\"`).\r\n * `ENABLE_MANIFEST`: Boolean flag to enable/disable the manifest system (default: `True`).\r\n\r\n2. **Manifest Handling:**\r\n * **Location:** Use a separate JSON file named `[ActiveBlendFileName]_manifest.json`, located in the same directory as the active `.blend` file.\r\n * **Loading:** Implement `load_manifest(context)` that finds and reads the manifest JSON file. If not found or invalid, return an empty dictionary.\r\n * **Saving:** Implement `save_manifest(context, manifest_data)` that writes the `manifest_data` dictionary to the manifest JSON file.\r\n * **Checking:** Implement helper functions `is_asset_processed(manifest_data, asset_name)` and `is_map_processed(manifest_data, asset_name, map_type, resolution)` to check against the loaded manifest.\r\n * **Updating:** Update the manifest dictionary in memory as assets/maps are processed. Save the manifest once at the end of the script.\r\n\r\n3. **Core Logic - `process_library()` function:**\r\n * Get Blender context.\r\n * Load manifest data (if enabled).\r\n * Validate that `PROCESSED_ASSET_LIBRARY_ROOT` exists.\r\n * Validate that template node groups exist in `bpy.data.node_groups`. Exit gracefully with an error message if not found.\r\n * Initialize counters (new groups, updated groups, etc.).\r\n * **Scan Directory:** Use `os.walk` or `pathlib.rglob` to find all `metadata.json` files within the `PROCESSED_ASSET_LIBRARY_ROOT`.\r\n * **Iterate Metadata:** For each `metadata.json` found:\r\n * Parse the JSON data. Extract key information: `asset_name`, `supplier_name`, `archetype`, `maps` (dictionary of maps with resolutions, paths, stats), `aspect_ratio_change_string`.\r\n * Check manifest if asset is already processed (if enabled). Skip if true.\r\n * **Parent Group Handling:**\r\n * Determine target parent group name (e.g., `f\"PBRSET_{asset_name}\"`).\r\n * Find existing group or create a copy from `PARENT_TEMPLATE_NAME`.\r\n * Mark group as asset (`asset_mark()`) if not already.\r\n * **Apply Metadata:**\r\n * Find the aspect ratio node using `ASPECT_RATIO_NODE_LABEL`. Calculate the correction factor based on the `aspect_ratio_change_string` from metadata (using helper function `calculate_factor_from_string`) and set the node's default value.\r\n * For relevant map types (e.g., ROUGH, DISP), find the stats node (`STATS_NODE_PREFIX` + map type). Set the X, Y, Z inputs using the `min`, `max`, `mean` values stored in the map's metadata entry for the reference resolution.\r\n * **Apply Asset Tags:** Use `asset_data.tags.new()` to add the `supplier_name` and `archetype` tags (checking for existence first).\r\n * **Child Group Handling (Iterate through `maps` in metadata):**\r\n * For each `map_type` (e.g., \"COL\", \"NRM\") and its data in the metadata:\r\n * Determine target child group name (e.g., `f\"PBRTYPE_{asset_name}_{map_type}\"`).\r\n * Find existing child group or create a copy from `CHILD_TEMPLATE_NAME`.\r\n * Find the corresponding placeholder node in the *parent* group (by label matching `map_type`). Assign the child node group to this placeholder (`placeholder_node.node_tree = child_group`).\r\n * Link the child group's output to the corresponding parent group's output socket. Ensure the parent output socket type is `NodeSocketColor`.\r\n * **Image Node Handling (Iterate through resolutions for the map type):**\r\n * For each `resolution` (e.g., \"4K\", \"2K\") and its `image_path` in the metadata:\r\n * Check manifest if this specific map/resolution is processed (if enabled). Skip if true.\r\n * Find the corresponding Image Texture node in the *child* group (by label matching `resolution`, e.g., \"4K\").\r\n * Load the image using `bpy.data.images.load(image_path, check_existing=True)`. Handle potential file-not-found errors.\r\n * Assign the loaded image to the `image_node.image`.\r\n * Set the `image_node.image.colorspace_settings.name` based on the `map_type` (using a helper function `get_color_space`).\r\n * Update manifest dictionary for this map/resolution (if enabled).\r\n * Update manifest dictionary for the processed asset (if enabled).\r\n * Save manifest data (if enabled and changes were made).\r\n * Print summary (duration, groups created/updated, etc.).\r\n\r\n4. **Helper Functions:**\r\n * `find_nodes_by_label(node_tree, label, node_type)`: Reusable function to find nodes.\r\n * `calculate_factor_from_string(aspect_string)`: Parses the `aspect_ratio_change_string` from metadata and returns the appropriate UV X-scaling factor.\r\n * `get_color_space(map_type)`: Returns the appropriate Blender color space name for a given map type string.\r\n * `add_tag_if_new(asset_data, tag_name)`: Adds a tag if it doesn't exist.\r\n * Manifest loading/saving/checking functions.\r\n\r\n5. **Execution Block (`if __name__ == \"__main__\":`)**\r\n * Add pre-run checks (templates exist, library path valid, blend file saved if manifest enabled).\r\n * Call the main `process_library()` function.\r\n * Include basic timing and print statements for start/end.\r\n\r\n**Mermaid Diagram:**\r\n\r\n```mermaid\r\ngraph TD\r\n A[Start Script in Blender] --> B(Load Config: Lib Path, Template Names, Node Labels);\r\n B --> C{Check Templates Exist};\r\n C -- Templates OK --> D(Load Manifest from adjacent .json file);\r\n C -- Templates Missing --> X(Error & Exit);\r\n D --> E{Scan Processed Library for metadata.json};\r\n E --> F{For each metadata.json};\r\n F --> G{Parse Metadata (Asset Name, Supplier, Archetype, Maps, Aspect Str, Stats)};\r\n G --> H{Is Asset in Manifest?};\r\n H -- Yes --> F;\r\n H -- No --> I{Find/Create Parent Group (PBRSET_)};\r\n I --> J(Mark as Asset & Apply Supplier + Archetype Tags);\r\n J --> K(Find Aspect Node & Set Value from Aspect String);\r\n K --> M{For each Map Type in Metadata};\r\n M --> N(Find Stats Node & Set Values from Stats in Metadata);\r\n N --> O{Find/Create Child Group (PBRTYPE_)};\r\n O --> P(Assign Child to Parent Placeholder);\r\n P --> Q(Link Child Output to Parent Output);\r\n Q --> R{For each Resolution of Map};\r\n R --> S{Is Map/Res in Manifest?};\r\n S -- Yes --> R;\r\n S -- No --> T(Find Image Node in Child);\r\n T --> U(Load Processed Image);\r\n U --> V(Assign Image to Node);\r\n V --> W(Set Image Color Space);\r\n W --> W1(Update Manifest Dict for Map/Res);\r\n W1 --> R;\r\n R -- All Resolutions Done --> M;\r\n M -- All Map Types Done --> X1(Update Manifest Dict for Asset);\r\n X1 --> F;\r\n F -- All metadata.json Processed --> Y(Save Manifest Dict to adjacent .json file);\r\n Y --> Z(Print Summary & Finish);\r\n\r\n subgraph \"Manifest Operations (External File)\"\r\n D; H; S; W1; X1; Y;\r\n end\r\n\r\n subgraph \"Node/Asset Operations\"\r\n I; J; K; N; O; P; Q; T; U; V; W;\r\n end" } ] }