Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,19 @@ jobs:
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
sed -i "s/^version = \"[^\"]\+\"$/version=\"${VERSION}\"/g" src/quickbake/blender_manifest.toml
sed -i "s/^version = \"[^\"]\+\"$/version=\"${VERSION}\"/g" quickbake/blender_manifest.toml

- name: Create Archive
env:
VERSION: ${{ steps.version.outputs.version }}
working-directory: ./src/quickbake
working-directory: ./quickbake
run: |
zip quickbake-${VERSION}.zip *

- name: Create Release
uses: ncipollo/release-action@v1.14.0
with:
artifacts: src/quickbake/quickbake-${{ steps.version.outputs.version }}.zip
artifacts: quickbake/quickbake-${{ steps.version.outputs.version }}.zip
makeLatest: true
generateReleaseNotes: true
tag: ${{ steps.version.outputs.version }}
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"python.testing.pytestArgs": [
"tests",
"--cov=src",
"--cov=branch"
"--cov",
"--cov-branch"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ setup:

build_dev:
@mkdir -p release/
@cd src/quickbake && zip $(PWD)/release/$(ZIP_ARCHIVE_NAME) *
@cd quickbake/ && zip $(PWD)/release/$(ZIP_ARCHIVE_NAME) *

install:
rm -rf $(INSTALL_DIR)/*
cp -r src/quickbake/* $(INSTALL_DIR)/
cp -r quickbake/* $(INSTALL_DIR)/

run: install
blender
Expand Down
4 changes: 0 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ authors = [{ name = "Thomas Harrison", email = "theharrisoncrafter@gmail.com" }]
requires-python = ">=3.12"
dependencies = []

[build-system]
requires = ["uv_build>=0.9.27,<0.10.0"]
build-backend = "uv_build"

[dependency-groups]
dev = [
"coverage>=7.13.2",
Expand Down
File renamed without changes.
96 changes: 96 additions & 0 deletions quickbake/bake.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Baking helper functions."""

import logging

import bpy

_l = logging.getLogger(__name__)


def setup_bake_nodes(obj):
"""Create material nodes required for baking."""
_l.info('Creating bake nodes for object %s', obj.name)

bake_nodes = []
for mat in obj.data.materials:
_l.debug('Creating nodes for material %s', mat.name)

mat.use_nodes = True
nodes = mat.node_tree.nodes
texture_node = nodes.new('ShaderNodeTexImage')
texture_node.name = 'Bake_node'
texture_node.select = True
nodes.active = texture_node
bake_nodes.append(texture_node)

return bake_nodes


def cleanup_bake_nodes(obj):
"""Remove material nodes created for baking by setup_bake_nodes."""
_l.info('Cleaning up bake nodes for object %s', obj.name)

for mat in obj.data.materials:
_l.debug('Clean up nodes for material %s', mat.name)

for n in mat.node_tree.nodes:
if n.name == 'Bake_node':
_l.debug('Remove bake node %s', n.name)
mat.node_tree.nodes.remove(n)


def setup_bake_uv(obj, name):
"""Create a uv layer to unwrap obj for baking."""
_l.info('Creating uv layer %s for baking', name)

def unwrap_uv(obj, uv):
_l.info('Unwrapping object %s to layer %s', obj.name, uv.name)

active_layer = None
for layer in obj.data.uv_layers:
if layer.active:
_l.debug('Found active layer %s', layer.name)
active_layer = layer
break

uv.active = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.smart_project(island_margin=0.001)
bpy.ops.object.mode_set(mode='OBJECT')
uv.active = False

if active_layer is not None:
_l.debug('Restoring active layer %s', active_layer.name)
active_layer.active = True # type: ignore

bake_uv = obj.data.uv_layers.get(name)
if bake_uv is None:
bake_uv = obj.data.uv_layers.new(name=name)
unwrap_uv(obj, bake_uv)

else:
_l.debug('Using existing uv layer')

return bake_uv


def setup_bake_image(
obj, bake_nodes, bake_name, bake_size, pass_name, reuse_tex, is_data=False
):
_l.info('Creating image for baking object %s', obj.name)

image_name = obj.name + '_' + bake_name + '_' + pass_name
_l.debug('Image name %s', image_name)

img = bpy.data.images.get(image_name)
if img is None or not reuse_tex:
img = bpy.data.images.new(image_name, bake_size, bake_size, is_data=is_data)

else:
_l.debug('Using existing image')

for node in bake_nodes:
node.image = img

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

# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "quickbake"
version = "0.0.0"
name = "Quick Bake"
tagline = "Fast baking for blender"
maintainer = "Thomas Harrison <theharrisoncrafter@gmail.com>"
type = "add-on"

# TODO replace with gh pages link
# Optional: link to documentation, support, source files, etc
website = "https://github.com/automas-dev/quickbake/"

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

blender_version_min = "2.28.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-3.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]
files = "Export baked texture images to disk"
clipboard = "Copy and paste bone transforms"

# Optional: advanced build settings.
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
[build]
# These are the default build excluded patterns.
# You only need to edit them if you want different options.
paths_exclude_pattern = ["__pycache__/", "/.git/", "/*.zip"]
72 changes: 72 additions & 0 deletions quickbake/material.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Material helper functions."""

import logging

import bpy
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper

_l = logging.getLogger(__name__)


def setup_bake_material(
obj, name, bake_uv_name, diffuse=None, roughness=None, normal=None
):
_l.info('Creating material %s for object %s', name, obj.name)

mat = bpy.data.materials.get(name)
if mat is not None:
_l.debug('Found existing material, skipping')
return mat

mat = bpy.data.materials.new(name=name)
mat.use_nodes = True
obj.data.materials.append(mat)

principled_mat = PrincipledBSDFWrapper(mat, is_readonly=False)
principled_mat.roughness = 1.0

principled_node = principled_mat.node_principled_bsdf

nodes = mat.node_tree.nodes
links = mat.node_tree.links

uv_node = nodes.new(type='ShaderNodeUVMap')
uv_node.uv_map = bake_uv_name
uv_node.location.x -= 1000
# uv_node.location.y += 300

mapping_node = nodes.new(type='ShaderNodeMapping')
mapping_node.location.x -= 800
# mapping_node.location.y += 300
links.new(uv_node.outputs['UV'], mapping_node.inputs['Vector'])

def make_tex_node(img, y):
tex_node = nodes.new(type='ShaderNodeTexImage')
tex_node.image = img
tex_node.location.x -= 500
tex_node.location.y += y

links.new(mapping_node.outputs['Vector'], tex_node.inputs['Vector'])

# TODO: color space if not set by default
# tex_node.image.colorspace_settings.name = '...'

return tex_node

if diffuse is not None:
diff_node = make_tex_node(diffuse, 400)
links.new(diff_node.outputs['Color'], principled_node.inputs['Base Color'])

if roughness is not None:
rough_node = make_tex_node(roughness, 100)
links.new(rough_node.outputs['Color'], principled_node.inputs['Roughness'])

if normal is not None:
norm_node = make_tex_node(normal, -200)
norm_map_node = nodes.new(type='ShaderNodeNormalMap')
norm_map_node.location.x -= 200
norm_map_node.location.y -= 200
links.new(norm_node.outputs['Color'], norm_map_node.inputs['Color'])
links.new(norm_map_node.outputs['Normal'], principled_node.inputs['Normal'])

return mat
File renamed without changes.
101 changes: 101 additions & 0 deletions quickbake/panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""QuickBake n Menu."""

import bpy

from .op import QuickBake_OT_bake


class QuickBake_PT_main(bpy.types.Panel):
"""Creates a Sub-Panel in the Property Area of the 3D View."""

bl_label = 'Quick Bake'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tool'
bl_context = 'objectmode'

def draw(self, context):
"""Override Panel draw method."""
layout = self.layout
assert layout is not None, 'Missing layout'

assert context.scene is not None, 'Missing scene from context'

row = layout.row()
row.operator(QuickBake_OT_bake.bl_idname)
layout.separator()

props = context.scene.QuickBakeToolPropertyGroup

layout.label(text='Texture')
row = layout.row()
row.prop(props, 'bake_name')

row = layout.row()
row.prop(props, 'bake_uv')

row = layout.row()
row.prop(props, 'bake_size')

layout.separator()
layout.label(text='Material')
row = layout.row()
row.prop(props, 'create_mat')

row = layout.row()
row.prop(props, 'mat_name')

layout.separator()
layout.label(text='Options')
row = layout.row()
row.enabled = not props.create_mat
row.prop(props, 'reuse_tex')

row = layout.row()
row.enabled = not props.create_mat
row.prop(props, 'clean_up')

# layout.separator()
# layout.label(text='Output')
# row = layout.row()
# row.prop(props, "save_img")

# row = layout.row()
# row.prop(props, "image_path")

layout.separator()
layout.label(text='Layers')
row = layout.row()
row.prop(props, 'diffuse_enabled')

row = layout.row()
row.prop(props, 'normal_enabled')

row = layout.row()
row.prop(props, 'roughness_enabled')

row = layout.row()
row.prop(props, 'ao_enabled')

row = layout.row()
row.prop(props, 'shadow_enabled')

row = layout.row()
row.prop(props, 'position_enabled')

row = layout.row()
row.prop(props, 'uv_enabled')

row = layout.row()
row.prop(props, 'emit_enabled')

row = layout.row()
row.prop(props, 'environment_enabled')

row = layout.row()
row.prop(props, 'glossy_enabled')

row = layout.row()
row.prop(props, 'transmission_enabled')

row = layout.row()
Loading