Skip to content

Commit

Permalink
Started working on lightmapper extension.
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonNordon4 committed Sep 11, 2024
1 parent 4d8db46 commit ddc1ee2
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 0 deletions.
20 changes: 20 additions & 0 deletions entry_lightmapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import importlib
import os
import sys

script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, script_dir)
__package__ = os.path.basename(script_dir)

import lightmapper.__init__
import lightmapper.lightmapper_properties
import lightmapper.lightmapper_operators
import lightmapper.lightmapper_panel

# TODO: Make this autoload everything.
importlib.reload(lightmapper.__init__)
importlib.reload(lightmapper.lightmapper_properties)
importlib.reload(lightmapper.lightmapper_operators)
importlib.reload(lightmapper.lightmapper_panel)

lightmapper.__init__.register()
16 changes: 16 additions & 0 deletions lightmapper/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from . import lightmapper_operators
from . import lightmapper_panel
from . import lightmapper_properties

print("Running lightmapper/__init__.py")

def register():
lightmapper_properties.register()
lightmapper_operators.register()
lightmapper_panel.register()

def unregister():
lightmapper_properties.unregister()
lightmapper_operators.unregister()
lightmapper_panel.unregister()

74 changes: 74 additions & 0 deletions lightmapper/blender_manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
schema_version = "1.0.0"

# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "lightmapper"
version = "1.0.0"
name = "Lightmapper"
tagline = "Lightmapper Blender Addon"
maintainer = "Simon Nordon <[email protected]>"
# Supported types: "add-on", "theme"
type = "add-on"

# Optional link to documentation, support, source files, etc
# Optional link to documentation, support, source files, etc
# website = "https://extensions.blender.org/add-ons/my-example-package/"

# Optional list defined by Blender and server, see:
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
tags = ["Bake"]

blender_version_min = "4.2.0"
# # Optional: Blender version that the extension does not support, earlier versions are supported.
# # This can be omitted and defined later on the extensions platform if an issue is found.
# blender_version_max = "5.1.0"

# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
license = [
"SPDX:GPL-2.0-or-later",
]
# Optional: required by some licenses.
# copyright = [
# "2002-2024 Developer Name",
# "1998 Company Name",
# ]

# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
# platforms = ["windows-x64", "macos-arm64", "linux-x64"]
# Other supported platforms: "windows-arm64", "macos-x64"

# Optional: bundle 3rd party Python modules.
# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
# wheels = [
# "./wheels/hexdump-3.3-py3-none-any.whl",
# "./wheels/jsmin-3.0.1-py3-none-any.whl",
# ]

# # Optional: add-ons can list which resources they will require:
# # * files (for access of any filesystem operations)
# # * network (for internet access)
# # * clipboard (to read and/or write the system clipboard)
# # * camera (to capture photos and videos)
# # * microphone (to capture audio)
# #
# # If using network, remember to also check `bpy.app.online_access`
# # https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
# #
# # For each permission it is important to also specify the reason why it is required.
# # Keep this a single short sentence without a period (.) at the end.
# # For longer explanations use the documentation or detail page.
#
# [permissions]
# network = "Need to sync motion-capture data to server"
# files = "Import/export FBX from/to disk"
# clipboard = "Copy and paste bone transforms"

# Optional: build settings.
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
# [build]
# paths_exclude_pattern = [
# "__pycache__/",
# "/.git/",
# "/*.zip",
# ]
133 changes: 133 additions & 0 deletions lightmapper/lightmapper_operators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import bpy

import bpy.utils



class LIGHTMAPPER_OT_create_lightmap_uv(bpy.types.Operator):
bl_idname = "lightmapper.create_lightmap_uv"
bl_label = "Create Lightmap UV"
bl_description = "Create a second UV channel called 'Lightmap' and select it"
bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):
mesh_objects = [obj for obj in context.selected_objects if obj.type == 'MESH']

if not mesh_objects:
self.report({'ERROR'}, "No mesh objects selected.")
return {'CANCELLED'}

for obj in mesh_objects:
uv_layers = obj.data.uv_layers
if len(uv_layers) < 2:
uv_layer = uv_layers.new(name="Lightmap")
uv_layers.active = uv_layer
self.report({'INFO'}, f"Lightmap UV created for {obj.name}.")
else:
self.report({'INFO'}, f"{obj.name} already has a Lightmap UV.")

# Perform lightmap unwrap
print(f"Performing lightmap unwrap for {obj.name}")
bpy.context.view_layer.objects.active = obj
bpy.ops.uv.lightmap_pack()

return {'FINISHED'}




class LIGHTMAPPER_OT_bake_lightmap(bpy.types.Operator):
bl_idname = "lightmapper.bake_lightmap"
bl_label = "Bake Lightmap"
bl_description = "Bake lightmap for selected objects"
bl_options = {'REGISTER', 'UNDO'}

def check_context(self, context):
""" Ensure the object is in a state that supports baking. """
mesh_objects = [obj for obj in context.selected_objects if obj.type == 'MESH']
# Ensure we're in object mode.
if context.mode != 'OBJECT':
self.report({'ERROR'}, "Lightmap baking requires object mode.")
return False

# Ensure we have at least one object selected.
if not mesh_objects:
self.report({'ERROR'}, "No mesh objects selected.")
return False

# Ensure there are no disabled for rendering.
for obj in mesh_objects:
if obj.hide_render or obj.hide_viewport:
self.report({'ERROR'}, f"Object {obj.name} is disabled for rendering or hidden in viewport.")
return False

# Ensure each mesh has 2 uv channels.
for obj in mesh_objects:
if len(obj.data.uv_layers) < 2:
self.report({'ERROR'}, f"Object {obj.name} does not have a Lightmap channel.")
return False

return True

def correct_context(self, context):
""" Change the state of the selected objects to work for baking."""
mesh_objects = [obj for obj in context.selected_objects if obj.type == 'MESH']

self.report({'INFO'}, "Correcting UV Selections.")
# Ensure that the first UVMap is set to render, and that "Lightmap" UV is selected.
for obj in mesh_objects:
# If the lightmap is renderable, set it to the first UVMap. Otherwise respect user choice.
obj.data.uv_layers[0].active_render = True
# Ensure the lightmap is selected, as that's the UV we're baking to.
obj.data.uv_layers["Lightmap"].active = True

def execute(self, context):
if not self.check_context(context):
return {'CANCELLED'}

self.correct_context(context)

self.report({'INFO'}, "Lightmap baking started.")
return {'FINISHED'}

for obj in context.selected_objects:
if obj.type == 'MESH':
# Set up lightmap UV if not present
if len(obj.data.uv_layers) < 2:
obj.data.uv_layers.new(name="Lightmap")

# Set up image for baking
image = bpy.data.images.new(name=f"{obj.name}_Lightmap", width=1024, height=1024)

# Set up material for baking
material = obj.active_material
if not material:
material = bpy.data.materials.new(name=f"{obj.name}_Lightmap_Material")
obj.data.materials.append(material)

# Set up node for baking
material.use_nodes = True
node_tree = material.node_tree
texture_node = node_tree.nodes.new('ShaderNodeTexImage')
texture_node.image = image

# Perform bake
bpy.ops.object.bake(type='COMBINED')

self.report({'INFO'}, "Lightmap baking completed")
return {'FINISHED'}

from bpy.utils import register_class, unregister_class
from .lightmapper_properties import LIGHTMAPPER_PT_properties

def register():
print("Registering lightmapper_operators")
bpy.utils.register_class(LIGHTMAPPER_OT_create_lightmap_uv)
bpy.utils.register_class(LIGHTMAPPER_OT_bake_lightmap)

def unregister():
bpy.utils.unregister_class(LIGHTMAPPER_OT_create_lightmap_uv)
bpy.utils.unregister_class(LIGHTMAPPER_OT_bake_lightmap)

if __name__ == "__main__":
register()
36 changes: 36 additions & 0 deletions lightmapper/lightmapper_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import bpy

class LightmapperPanel(bpy.types.Panel):
bl_idname = "OBJECT_PT_lightmapper_panel"
bl_label = "Lightmapper Panel"
bl_description = "This is a lightmapper panel, for starting a new addon."
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Lightmapper"
bl_order = 0

def draw_header(self, context):
layout = self.layout
layout.label(text="", icon="OUTLINER_DATA_LIGHTPROBE")

def draw(self, context):
layout = self.layout
scene = context.scene
lightmapper_props = scene.lightmapper_properties



layout.operator("lightmapper.create_lightmap_uv")


layout.prop(scene.lightmapper_properties, "lightmap_resolution")
layout.prop(scene.lightmapper_properties, "export_path")
layout.operator("lightmapper.bake_lightmap")

def register():
bpy.utils.register_class(LightmapperPanel)

def unregister():
bpy.utils.unregister_class(LightmapperPanel)


33 changes: 33 additions & 0 deletions lightmapper/lightmapper_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import bpy
from bpy.props import EnumProperty, StringProperty # type: ignore

class LIGHTMAPPER_PT_properties(bpy.types.PropertyGroup):
lightmap_resolution: EnumProperty(
name="Lightmap Resolution",
description="Choose the resolution for the lightmap",
items=[
('512', "512", "512x512 pixels"),
('1024', "1k", "1024x1024 pixels"),
('2048', "2k", "2048x2048 pixels"),
('4096', "4k", "4096x4096 pixels"),
('8192', "8k", "8192x8192 pixels"),
],
default='2048'
) # type: ignore

export_path: StringProperty(
name="Export Path",
description="Path to export the lightmap",
default="",
maxlen=1024,
subtype='DIR_PATH'
) # type: ignore

def register():
print("Registering lightmapper_properties")
bpy.utils.register_class(LIGHTMAPPER_PT_properties)
bpy.types.Scene.lightmapper_properties = bpy.props.PointerProperty(type=LIGHTMAPPER_PT_properties)

def unregister():
del bpy.types.Scene.lightmapper_properties
bpy.utils.unregister_class(LIGHTMAPPER_PT_properties)

0 comments on commit ddc1ee2

Please sign in to comment.