-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Utility for lightmap preparation #48
Open
rawnsley
wants to merge
23
commits into
Hubs-Foundation:master
Choose a base branch
from
LearnHub:lightmap-render-utils
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
7893d13
Added "Hubs" panel to Render with a utility function for lightmap pre…
rawnsley 8612ec8
Better error feedback to the user
rawnsley 29eaac6
Fixed error when in EDIT mode
rawnsley 33ef09e
Fix selection where objects are hidden
rawnsley 32402ad
select_all would fail sometimes depending on the UI context. This met…
rawnsley a7b4845
Support for multiple different lightmap target images
rawnsley c7bd3c7
Mesh face selection for UV packing
rawnsley 35ab016
Disable all lightmap image textures that are not targeted in an attem…
rawnsley f96943d
Shader graph nodes can be active even if the UI doesn't show it and t…
rawnsley 0c3d49a
Refactor code slightly for clarity
rawnsley 856307c
Sort lightmaps for clearer layout
rawnsley c303b94
Check for mixed materials to avoid image corruption
rawnsley f3c9f15
Merge pull request #1 from LearnHub/hubs-upload-instructions
rawnsley 8cbe9a9
Decoy textures for supporting mixed material scenes
rawnsley b545782
Exception catch when mode_set fails (context in Blender is mercurial)
rawnsley b551a0d
Only show the button if there are any lightmaps to process
rawnsley 71b0d1e
Merge branch 'master' of https://github.com/MozillaReality/hubs-blend…
rawnsley 6a3faf2
Merge branch 'master' into lightmap-render-utils
rawnsley ebb746e
Merge commit '192df9673927ab7348cec3743d7d09e729f95380'
rawnsley ecc8472
Merge branch 'master' into lightmap-render-utils
rawnsley ed85009
Merge branch 'master' into lightmap-render-utils
rawnsley 8324827
Fixed merge with master
rawnsley 11b9703
Incorporated linter suggestions
rawnsley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
import bpy | ||
import bmesh | ||
|
||
# Label for decoy texture | ||
HUBS_DECOY_IMAGE_TEXTURE = "HUBS_DECOY_IMAGE_TEXTURE" | ||
|
||
|
||
# Find and select the image texture associated with a MOZ_lightmap settings | ||
def findImageTexture(lightmapNode): | ||
for inputs in lightmapNode.inputs: | ||
for links in inputs.links: | ||
return links.from_node | ||
return None | ||
|
||
|
||
# Find the UV map associated with an image texture | ||
def findUvMap(imageTexture, material): | ||
# Search for the parent UV Map | ||
for inputs in imageTexture.inputs: | ||
for links in inputs.links: | ||
# Is this node a MOZ lightmap node? | ||
if links.from_node.bl_idname == "ShaderNodeUVMap": | ||
return links.from_node.uv_map | ||
else: | ||
raise ValueError(f"Unexpected node type '{links.from_node.bl_idname}' instead of 'ShaderNodeUVMap' on material '{material.name}'") | ||
return None | ||
|
||
|
||
# Selects all the faces of the mesh that have been assigned the given material (important for UV packing for lightmaps) | ||
def selectMeshFacesFromMaterial(object, mesh, material): | ||
materialSlotIndex = object.material_slots.find(material.name) | ||
if materialSlotIndex < 0: | ||
raise ValueError(f"Failed to find a slot with material '{material.name}' in '{mesh.name}' attached to object '{object.name}'") | ||
bm = bmesh.new() | ||
bm.from_mesh(object.data) | ||
for f in bm.faces: | ||
if f.material_index == materialSlotIndex: | ||
f.select = True | ||
bm.to_mesh(object.data) | ||
bm.free() | ||
|
||
|
||
# Select the object that holds this mesh | ||
def selectObjectFromMesh(mesh, material): | ||
for object in bpy.context.scene.objects: | ||
if object.type == "MESH": | ||
if object.data.name == mesh.name: | ||
# Objects cannot be selected if they are hidden | ||
object.hide_set(False) | ||
object.select_set(True) | ||
print(f" --- selected object '{object.name}' because it uses mesh '{mesh.name}'") | ||
selectMeshFacesFromMaterial(object, mesh, material) | ||
|
||
|
||
# Select the UV input to the image texture for every mesh that uses the given material | ||
def selectUvMaps(imageTexture, material): | ||
# Select the lightmap UVs on the associated mesh | ||
uvMap = findUvMap(imageTexture, material) | ||
if uvMap: | ||
print(f" -- found UV Map Node '{uvMap}'") | ||
# Search for meshes that use this material (can't find a parent property so this will have to do) | ||
for mesh in bpy.data.meshes: | ||
if mesh.materials.find(material.name) != -1: | ||
print(f" -- found mesh '{mesh.name}' that uses this material") | ||
selectObjectFromMesh(mesh, material) | ||
if mesh.uv_layers.find(uvMap) != -1: | ||
uvLayer = mesh.uv_layers[uvMap] | ||
mesh.uv_layers.active = uvLayer | ||
print(f" --- UV layer '{uvMap}' is now active on '{mesh.name}'") | ||
else: | ||
raise ValueError(f"Failed to find UV layer '{uvMap}' for mesh '{mesh.name}' using material '{material.name}'") | ||
else: | ||
raise ValueError(f"No UV map found for image texture '{imageTexture.name}' with image '{imageTexture.image.name}' in material '{material.name}'") | ||
|
||
|
||
# Selects all MOZ lightmap related components ready for baking | ||
def selectLightmapComponents(target): | ||
# Force UI into OBJECT mode so scripts can manipulate meshes | ||
try: | ||
bpy.ops.object.mode_set(mode='OBJECT') | ||
except Exception as e: | ||
print(f"Failed to enter OBJECT mode (usually non-fatal): {str(e)}") | ||
# Deslect all objects to start with (bake objects will then be selected) | ||
for o in bpy.context.scene.objects: | ||
o.select_set(False) | ||
# Deselect and show all mesh faces (targetted faces will then be selected) | ||
if o.type == "MESH": | ||
bm = bmesh.new() | ||
bm.from_mesh(o.data) | ||
for f in bm.faces: | ||
f.hide = False | ||
f.select = False | ||
bm.to_mesh(o.data) | ||
bm.free() | ||
# For every material | ||
for material in bpy.data.materials: | ||
if material.node_tree: | ||
# Deactivate and unselect all nodes in the shader graph | ||
# - they can be active even if the UI doesn't show it and they will be baked | ||
material.node_tree.nodes.active = None | ||
for n in material.node_tree.nodes: | ||
n.select = False | ||
# For every node in the material graph | ||
for shadernode in material.node_tree.nodes: | ||
# Is this node a MOZ lightmap node? | ||
if shadernode.bl_idname == "moz_lightmap.node": | ||
print(f"found '{shadernode.name}' ({shadernode.label}) on material '{material.name}'") | ||
imageTexture = findImageTexture(shadernode) | ||
if imageTexture: | ||
# Check image texture actually has an image | ||
if imageTexture.image is None: | ||
raise ValueError(f"No image found on image texture '{imageTexture.name}' ('{imageTexture.label}') in material '{material.name}'") | ||
# Is this lightmap texture image being targetted? | ||
if target == "" or target == imageTexture.image.name: | ||
# Select and activate the image texture node so it will be targetted by the bake | ||
imageTexture.select = True | ||
material.node_tree.nodes.active = imageTexture | ||
print(f" - selected image texture '{imageTexture.name}' ({imageTexture.label})") | ||
|
||
selectUvMaps(imageTexture, material) | ||
else: | ||
print(f" - ignoring image texture '{imageTexture.name}' because it uses image '{imageTexture.image.name}' and the target is '{target}'") | ||
else: | ||
raise ValueError(f"No image texture found on material '{material.name}'") | ||
# Is it a decoy node? | ||
elif shadernode.bl_idname == "ShaderNodeTexImage" and shadernode.label == "HUBS_DECOY_IMAGE_TEXTURE": | ||
# Select and activate the image texture node so it will be targetted by the bake | ||
shadernode.select = True | ||
material.node_tree.nodes.active = shadernode | ||
|
||
|
||
# List all the lightmap textures images | ||
def listLightmapImages(): | ||
result = set() | ||
# For every material | ||
for material in bpy.data.materials: | ||
if material.node_tree: | ||
# For every node in the material graph | ||
for shadernode in material.node_tree.nodes: | ||
# Is this node a MOZ lightmap node? | ||
if shadernode.bl_idname == "moz_lightmap.node": | ||
imageTexture = findImageTexture(shadernode) | ||
if imageTexture: | ||
if imageTexture.image: | ||
result.add(imageTexture.image) | ||
return result | ||
|
||
|
||
# Check for selected objects with non-lightmapped materials. They might get baked and have corrupted image textures | ||
def assertSelectedObjectsAreSafeToBake(addDecoyRatherThanThrow): | ||
decoyCount = 0 | ||
for object in bpy.context.scene.objects: | ||
if object.select_get(): | ||
for materialSlot in object.material_slots: | ||
material = materialSlot.material | ||
hasLightmapNode = False | ||
hasAtRiskImageTexture = False | ||
hasDecoyImageTexture = False | ||
for shadernode in material.node_tree.nodes: | ||
if shadernode.bl_idname == "moz_lightmap.node": | ||
hasLightmapNode = True | ||
elif shadernode.bl_idname == "ShaderNodeTexImage": | ||
if shadernode.label == HUBS_DECOY_IMAGE_TEXTURE: | ||
hasDecoyImageTexture = True | ||
else: | ||
hasAtRiskImageTexture = True | ||
if hasAtRiskImageTexture and not (hasLightmapNode or hasDecoyImageTexture): | ||
if addDecoyRatherThanThrow: | ||
decoy_image_texture = material.node_tree.nodes.new("ShaderNodeTexImage") | ||
decoy_image_texture.label = HUBS_DECOY_IMAGE_TEXTURE | ||
decoyCount += 1 | ||
else: | ||
raise ValueError(f"Multi-material object '{object.name}' uses '{materialSlot.name}' with no lightmap or decoy texture. It will be corrupted by baking") | ||
return decoyCount | ||
|
||
|
||
def removeAllDecoyImageTextures(): | ||
decoyCount = 0 | ||
# For every material | ||
for material in bpy.data.materials: | ||
if material.node_tree: | ||
# For every node in the material graph | ||
for shadernode in material.node_tree.nodes: | ||
# Is this a decoy image? | ||
if shadernode.bl_idname == "ShaderNodeTexImage" and shadernode.label == HUBS_DECOY_IMAGE_TEXTURE: | ||
material.node_tree.nodes.remove(shadernode) | ||
decoyCount += 1 | ||
return decoyCount |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A material slot isn't guaranteed to have a material in it, so an
if material
check is needed here.