Compare commits

...

15 Commits

Author SHA1 Message Date
0b4184ccc2 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-07-24 17:02:15 -07:00
ddc12e8607 BSDF Shaders 2026-07-24 17:02:00 -07:00
6ad647ae56 Merge pull request 'main' (#132) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#132
2026-07-24 09:22:31 +00:00
b77aca926a Refactor shader sockets 2026-07-24 01:59:51 -07:00
c2bb20f905 Moises Jpelaez: 5.2 Updates 2026-07-23 23:41:13 -07:00
14c6a7be03 Merge pull request 'main' (#131) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#131
2026-07-24 06:13:01 +00:00
85d63e8413 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-07-23 23:08:56 -07:00
78452aaf67 Finished Viewport 2026-07-23 23:02:14 -07:00
1f72636350 Merge pull request 'main' (#130) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#130
2026-07-23 07:00:06 +00:00
62433ce86a merge upstream 2026-07-23 06:58:51 +00:00
2be36398f7 Merge branch 'main' of https://dev.leenkx.com/Onek8/LNXSDK 2026-07-22 21:04:07 -07:00
2675138ddc Windows RunT/Krom WebView2 DX11 and OpenGL 2026-07-22 21:03:43 -07:00
572665e8e6 Merge pull request 'main' (#129) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#129
2026-07-22 04:01:11 +00:00
0839f39dfa merge upstream 2026-07-22 03:59:30 +00:00
c52ae2e4f1 Merge pull request 'main' (#128) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#128
2026-07-21 03:26:52 +00:00
13 changed files with 540 additions and 227 deletions

Binary file not shown.

Binary file not shown.

View File

@ -7,7 +7,7 @@ bl_info = {
"description": "Full Stack SDK", "description": "Full Stack SDK",
"author": "Leenkx.com", "author": "Leenkx.com",
"version": (2026, 5, 0), "version": (2026, 5, 0),
"blender": (4, 5, 0), "blender": (5, 2, 0),
"doc_url": "https://leenkx.com/", "doc_url": "https://leenkx.com/",
"tracker_url": "https://leenkx.com/support" "tracker_url": "https://leenkx.com/support"
} }

View File

@ -136,7 +136,10 @@ def always() -> float:
# Force ui redraw # Force ui redraw
if state.redraw_ui: if state.redraw_ui:
if bpy.context.screen is not None: if bpy.context.screen is not None:
krom_active = bool(lnx.render_engine._active_krom_engines)
for area in bpy.context.screen.areas: for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D' and krom_active:
continue
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'): if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
area.tag_redraw() area.tag_redraw()
state.redraw_ui = False state.redraw_ui = False

View File

@ -1,3 +1,4 @@
import bpy
import importlib import importlib
import inspect import inspect
import pkgutil import pkgutil

View File

@ -804,41 +804,71 @@ def viewport_build_done():
_viewport_pending_launches.clear() _viewport_pending_launches.clear()
log.error('Viewport build failed, check console') log.error('Viewport build failed, check console')
def play_viewport(viewport_id, width=1920, height=1080): def play(viewport_id=None, width=1920, height=1080):
"""Launch Krom in a viewport""" """Launch player external or embedded when viewport_id is passed"""
global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build, scripts_mtime
if not viewport_id: if viewport_id and 'Lnx' not in bpy.data.worlds:
log.error('No viewport_id: play_viewport requires an id')
return
if 'Lnx' not in bpy.data.worlds:
log.error('No Lnx world found - cannot start viewport server') log.error('No Lnx world found - cannot start viewport server')
return return
wrd = bpy.data.worlds['Lnx'] wrd = bpy.data.worlds['Lnx']
target = 'krom' if viewport_id else runtime_to_target()
krom_js_path = lnx.utils.get_fp_build() + '/debug/krom/krom.js' khajs_path = get_khajs_path(target)
if os.path.exists(krom_js_path) and not wrd.lnx_recompile: if not wrd.lnx_cache_build or \
run_viewport_runtime(viewport_id, width, height) not os.path.isfile(khajs_path) or \
return assets.khafile_defs_last != assets.khafile_defs or \
state.last_target != state.target:
wrd.lnx_recompile = True
pending_entry = (viewport_id, width, height) state.last_target = state.target
if pending_entry not in _viewport_pending_launches:
_viewport_pending_launches.append(pending_entry)
if _viewport_build_in_progress: state.mod_scripts = []
if _viewport_proc_build is not None and _viewport_proc_build.poll() is None: script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
log.info(f'Build in progress, viewport {viewport_id} will launch when ready') if os.path.isdir(script_path):
new_mtime = scripts_mtime
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
mtime = os.path.getmtime(fn)
if scripts_mtime < mtime:
lnx.utils.fetch_script_props(fn)
fn = fn.split('Sources/')[1]
fn = fn[:-3]
fn = fn.replace('/', '.')
state.mod_scripts.append(fn)
wrd.lnx_recompile = True
if new_mtime < mtime:
new_mtime = mtime
scripts_mtime = new_mtime
if len(state.mod_scripts) > 0:
lnx.utils.fetch_trait_props()
if viewport_id:
krom_js_path = lnx.utils.get_fp_build() + '/debug/krom/krom.js'
if os.path.exists(krom_js_path) and not wrd.lnx_recompile:
run_viewport_runtime(viewport_id, width, height)
return return
else:
_viewport_build_in_progress = False
_viewport_build_in_progress = True pending_entry = (viewport_id, width, height)
if pending_entry not in _viewport_pending_launches:
_viewport_pending_launches.append(pending_entry)
build(target='krom', is_viewport=True, viewport_width=width, viewport_height=height) if _viewport_build_in_progress:
if _viewport_proc_build is not None and _viewport_proc_build.poll() is None:
log.info(f'Build in progress, viewport {viewport_id} will launch when ready')
return
else:
_viewport_build_in_progress = False
compile_viewport(assets_only=(not wrd.lnx_recompile)) _viewport_build_in_progress = True
assets.invalidate_enabled = False
build(target='krom', is_viewport=True, viewport_width=width, viewport_height=height)
compile_viewport(assets_only=(not wrd.lnx_recompile))
assets.invalidate_enabled = True
else:
build(target=target, is_play=True)
compile(assets_only=(not wrd.lnx_recompile))
def stop_viewport(viewport_id=None): def stop_viewport(viewport_id=None):
@ -905,43 +935,6 @@ def get_khajs_path(target):
return lnx.utils.build_dir() + '/debug/krom/krom.js' return lnx.utils.build_dir() + '/debug/krom/krom.js'
return lnx.utils.build_dir() + '/debug/html5/kha.js' return lnx.utils.build_dir() + '/debug/html5/kha.js'
def play():
global scripts_mtime
wrd = bpy.data.worlds['Lnx']
build(target=runtime_to_target(), is_play=True)
khajs_path = get_khajs_path(state.target)
if not wrd.lnx_cache_build or \
not os.path.isfile(khajs_path) or \
assets.khafile_defs_last != assets.khafile_defs or \
state.last_target != state.target:
wrd.lnx_recompile = True
state.last_target = state.target
# Trait sources modified
state.mod_scripts = []
script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
if os.path.isdir(script_path):
new_mtime = scripts_mtime
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
mtime = os.path.getmtime(fn)
if scripts_mtime < mtime:
lnx.utils.fetch_script_props(fn) # Trait props
fn = fn.split('Sources/')[1]
fn = fn[:-3] #.hx
fn = fn.replace('/', '.')
state.mod_scripts.append(fn)
wrd.lnx_recompile = True
if new_mtime < mtime:
new_mtime = mtime
scripts_mtime = new_mtime
if len(state.mod_scripts) > 0: # Trait props
lnx.utils.fetch_trait_props()
compile(assets_only=(not wrd.lnx_recompile))
def build_success(): def build_success():
log.clear() log.clear()
wrd = bpy.data.worlds['Lnx'] wrd = bpy.data.worlds['Lnx']

View File

@ -219,7 +219,15 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
curshader = state.frag curshader = state.frag
state.curshader = curshader state.curshader = curshader
out_basecol, out_roughness, out_metallic, out_occlusion, out_specular, out_opacity, out_ior, out_emission_col = parse_shader_input(node.inputs[0]) outs = parse_shader_input(node.inputs[0])
out_basecol = outs[0]
out_roughness = outs[1]
out_metallic = outs[2]
out_occlusion = outs[3]
out_specular = outs[4]
out_opacity = outs[5]
out_ior = outs[6]
out_emission_col = outs[7]
if parse_surface: if parse_surface:
curshader.write(f'basecol = {out_basecol};') curshader.write(f'basecol = {out_basecol};')
curshader.write(f'roughness = {out_roughness};') curshader.write(f'roughness = {out_roughness};')
@ -227,6 +235,21 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
curshader.write(f'occlusion = {out_occlusion};') curshader.write(f'occlusion = {out_occlusion};')
curshader.write(f'specular = {out_specular};') curshader.write(f'specular = {out_specular};')
curshader.write(f'emissionCol = {out_emission_col};') curshader.write(f'emissionCol = {out_emission_col};')
curshader.write(f'subsurface = {outs[8]};')
curshader.write(f'subsurfaceRadius = {outs[9]};')
curshader.write(f'subsurfaceColor = {outs[10]};')
curshader.write(f'specularTint = {outs[11]};')
curshader.write(f'anisotropy = {outs[12]};')
curshader.write(f'anisoRot = {outs[13]};')
curshader.write(f'sheen = {outs[14]};')
curshader.write(f'sheenRough = {outs[15]};')
curshader.write(f'sheenTint = {outs[16]};')
curshader.write(f'clearcoat = {outs[17]};')
curshader.write(f'clearcoatRough = {outs[18]};')
curshader.write(f'transmission = {outs[19]};')
curshader.write(f'transmissionRough = {outs[20]};')
curshader.write(f'thinWall = {outs[21]};')
curshader.write(f'tangent = {outs[22]};')
if mat_state.emission_type == mat_state.EmissionType.SHADELESS: if mat_state.emission_type == mat_state.EmissionType.SHADELESS:
if '_EmissionShadeless' not in wrd.world_defs: if '_EmissionShadeless' not in wrd.world_defs:
@ -330,6 +353,10 @@ def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[st
'BSDF_GLASS', 'BSDF_GLASS',
'HOLDOUT', 'HOLDOUT',
'SUBSURFACE_SCATTERING', 'SUBSURFACE_SCATTERING',
'BSDF_REFRACTION',
'BSDF_TOON',
'BSDF_HAIR',
'BSDF_HAIR_PRINCIPLED',
'BSDF_TRANSLUCENT', 'BSDF_TRANSLUCENT',
'BSDF_TRANSPARENT', 'BSDF_TRANSPARENT',
'BSDF_VELVET', 'BSDF_VELVET',

View File

@ -16,19 +16,59 @@ if lnx.is_reload(__name__):
else: else:
lnx.enable_reload(__name__) lnx.enable_reload(__name__)
# Principled BSDF socket names that differ between Blender versions
if bpy.app.version < (4, 0, 0):
EMISSION_COLOR = 'Emission'
SUBSURFACE = 'Subsurface'
SUBSURFACE_RADIUS = 'Subsurface Radius'
SUBSURFACE_COLOR = 'Subsurface Color'
SPECULAR = 'Specular'
SPECULAR_TINT = 'Specular Tint'
ANISOTROPIC = 'Anisotropic'
ANISOTROPIC_ROT = 'Anisotropic Rotation'
SHEEN = 'Sheen'
SHEEN_TINT = 'Sheen Tint'
CLEARCOAT = 'Clearcoat'
CLEARCOAT_ROUGHNESS = 'Clearcoat Roughness'
CLEARCOAT_NORMAL = 'Clearcoat Normal'
TRANSMISSION = 'Transmission'
TRANSMISSION_ROUGHNESS = 'Transmission Roughness'
TANGENT = 'Tangent'
else:
EMISSION_COLOR = 'Emission Color'
SUBSURFACE = 'Subsurface Weight'
SUBSURFACE_RADIUS = 'Subsurface Radius'
SUBSURFACE_COLOR = 'Subsurface Color'
SPECULAR = 'Specular IOR Level'
SPECULAR_TINT = 'Specular Tint'
ANISOTROPIC = 'Anisotropic'
ANISOTROPIC_ROT = 'Anisotropic Rotation'
SHEEN = 'Sheen Weight'
SHEEN_TINT = 'Sheen Tint'
SHEEN_ROUGHNESS = 'Sheen Roughness'
CLEARCOAT = 'Coat Weight'
CLEARCOAT_ROUGHNESS = 'Coat Roughness'
CLEARCOAT_NORMAL = 'Coat Normal'
COAT_IOR = 'Coat IOR'
COAT_TINT = 'Coat Tint'
TRANSMISSION = 'Transmission Weight'
TRANSMISSION_ROUGHNESS = 'Transmission Roughness'
TANGENT = 'Tangent'
THIN_WALL = 'Thin Wall'
def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state: ParserState) -> None: def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state: ParserState) -> None:
# Skip mixing if only one input is effectively used # Skip mixing if only one input is effectively used
if not node.inputs[0].is_linked: if not node.inputs['Fac'].is_linked:
if node.inputs[0].default_value <= 0.0: if node.inputs['Fac'].default_value <= 0.0:
c.parse_shader_input(node.inputs[1]) c.parse_shader_input(node.inputs[1])
return return
elif node.inputs[0].default_value >= 1.0: elif node.inputs['Fac'].default_value >= 1.0:
c.parse_shader_input(node.inputs[2]) c.parse_shader_input(node.inputs[2])
return return
prefix = '' if node.inputs[0].is_linked else 'const ' prefix = '' if node.inputs['Fac'].is_linked else 'const '
fac = c.parse_value_input(node.inputs[0]) fac = c.parse_value_input(node.inputs['Fac'])
fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix() fac_var = c.node_name(node.name) + '_fac' + state.get_parser_pass_suffix()
fac_inv_var = c.node_name(node.name) + '_fac_inv' fac_inv_var = c.node_name(node.name) + '_fac_inv'
state.curshader.write('{0}float {1} = clamp({2}, 0.0, 1.0);'.format(prefix, fac_var, fac)) state.curshader.write('{0}float {1} = clamp({2}, 0.0, 1.0);'.format(prefix, fac_var, fac))
@ -36,134 +76,193 @@ def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket,
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
sss_before_1 = mat_state.needs_sss sss_before_1 = mat_state.needs_sss
bc1, rough1, met1, occ1, spec1, opac1, ior1, emi1 = c.parse_shader_input(node.inputs[1]) o1 = c.parse_shader_input(node.inputs[1])
sss_1 = mat_state.needs_sss sss_1 = mat_state.needs_sss
ek1 = mat_state.emission_type ek1 = mat_state.emission_type
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 1 mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 1
bc2, rough2, met2, occ2, spec2, opac2, ior2, emi2 = c.parse_shader_input(node.inputs[2]) o2 = c.parse_shader_input(node.inputs[2])
sss_2 = mat_state.needs_sss sss_2 = mat_state.needs_sss
ek2 = mat_state.emission_type ek2 = mat_state.emission_type
mat_state.needs_sss = sss_1 or sss_2 mat_state.needs_sss = sss_1 or sss_2
if state.parse_surface: if state.parse_surface:
state.out_basecol = '({0} * {3} + {1} * {2})'.format(bc1, bc2, fac_var, fac_inv_var) fm = '{0} * {3} + {1} * {2}' # fac mix template: a*fac_inv + b*fac
state.out_roughness = '({0} * {3} + {1} * {2})'.format(rough1, rough2, fac_var, fac_inv_var) fv = (fac_var, fac_inv_var)
state.out_metallic = '({0} * {3} + {1} * {2})'.format(met1, met2, fac_var, fac_inv_var) state.out_basecol = fm.format(o1[0], o2[0], fv[0], fv[1])
state.out_occlusion = '({0} * {3} + {1} * {2})'.format(occ1, occ2, fac_var, fac_inv_var) state.out_roughness = fm.format(o1[1], o2[1], fv[0], fv[1])
state.out_specular = '({0} * {3} + {1} * {2})'.format(spec1, spec2, fac_var, fac_inv_var) state.out_metallic = fm.format(o1[2], o2[2], fv[0], fv[1])
state.out_emission_col = '({0} * {3} + {1} * {2})'.format(emi1, emi2, fac_var, fac_inv_var) state.out_occlusion = fm.format(o1[3], o2[3], fv[0], fv[1])
state.out_specular = fm.format(o1[4], o2[4], fv[0], fv[1])
state.out_emission_col = fm.format(o1[7], o2[7], fv[0], fv[1])
state.out_subsurface = fm.format(o1[8], o2[8], fv[0], fv[1])
state.out_subsurface_radius = fm.format(o1[9], o2[9], fv[0], fv[1])
state.out_subsurface_color = fm.format(o1[10], o2[10], fv[0], fv[1])
state.out_specular_tint = fm.format(o1[11], o2[11], fv[0], fv[1])
state.out_anisotropy = fm.format(o1[12], o2[12], fv[0], fv[1])
state.out_aniso_rot = fm.format(o1[13], o2[13], fv[0], fv[1])
state.out_sheen = fm.format(o1[14], o2[14], fv[0], fv[1])
state.out_sheen_rough = fm.format(o1[15], o2[15], fv[0], fv[1])
state.out_sheen_tint = fm.format(o1[16], o2[16], fv[0], fv[1])
state.out_clearcoat = fm.format(o1[17], o2[17], fv[0], fv[1])
state.out_clearcoat_rough = fm.format(o1[18], o2[18], fv[0], fv[1])
state.out_transmission = fm.format(o1[19], o2[19], fv[0], fv[1])
state.out_transmission_rough = fm.format(o1[20], o2[20], fv[0], fv[1])
state.out_thin_wall = fm.format(o1[21], o2[21], fv[0], fv[1])
state.out_tangent = fm.format(o1[22], o2[22], fv[0], fv[1])
mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2) mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2)
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '({0} * {3} + {1} * {2})'.format(opac1, opac2, fac_var, fac_inv_var) fm = '{0} * {3} + {1} * {2}'
state.out_ior = '({0} * {3} + {1} * {2})'.format(ior1, ior2, fac_var, fac_inv_var) fv = (fac_var, fac_inv_var)
state.out_opacity = fm.format(o1[5], o2[5], fv[0], fv[1])
state.out_ior = fm.format(o1[6], o2[6], fv[0], fv[1])
def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket, state: ParserState) -> None: def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket, state: ParserState) -> None:
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
sss_before_1 = mat_state.needs_sss sss_before_1 = mat_state.needs_sss
bc1, rough1, met1, occ1, spec1, opac1, ior1, emi1 = c.parse_shader_input(node.inputs[0]) o1 = c.parse_shader_input(node.inputs[0])
sss_1 = mat_state.needs_sss sss_1 = mat_state.needs_sss
ek1 = mat_state.emission_type ek1 = mat_state.emission_type
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 0 mat_state.needs_sss = sss_before_1 # Reset to state before parsing input 0
bc2, rough2, met2, occ2, spec2, opac2, ior2, emi2 = c.parse_shader_input(node.inputs[1]) o2 = c.parse_shader_input(node.inputs[1])
sss_2 = mat_state.needs_sss sss_2 = mat_state.needs_sss
ek2 = mat_state.emission_type ek2 = mat_state.emission_type
mat_state.needs_sss = sss_1 or sss_2 mat_state.needs_sss = sss_1 or sss_2
if state.parse_surface: if state.parse_surface:
state.out_basecol = '({0} + {1})'.format(bc1, bc2) am = '{0} * 0.5 + {1} * 0.5' # add mix template (average)
state.out_roughness = '({0} * 0.5 + {1} * 0.5)'.format(rough1, rough2) state.out_basecol = '({0} + {1})'.format(o1[0], o2[0])
state.out_metallic = '({0} * 0.5 + {1} * 0.5)'.format(met1, met2) state.out_roughness = am.format(o1[1], o2[1])
state.out_occlusion = '({0} * 0.5 + {1} * 0.5)'.format(occ1, occ2) state.out_metallic = am.format(o1[2], o2[2])
state.out_specular = '({0} * 0.5 + {1} * 0.5)'.format(spec1, spec2) state.out_occlusion = am.format(o1[3], o2[3])
state.out_emission_col = '({0} + {1})'.format(emi1, emi2) state.out_specular = am.format(o1[4], o2[4])
state.out_emission_col = '({0} + {1})'.format(o1[7], o2[7])
state.out_subsurface = am.format(o1[8], o2[8])
state.out_subsurface_radius = am.format(o1[9], o2[9])
state.out_subsurface_color = am.format(o1[10], o2[10])
state.out_specular_tint = am.format(o1[11], o2[11])
state.out_anisotropy = am.format(o1[12], o2[12])
state.out_aniso_rot = am.format(o1[13], o2[13])
state.out_sheen = am.format(o1[14], o2[14])
state.out_sheen_rough = am.format(o1[15], o2[15])
state.out_sheen_tint = am.format(o1[16], o2[16])
state.out_clearcoat = am.format(o1[17], o2[17])
state.out_clearcoat_rough = am.format(o1[18], o2[18])
state.out_transmission = am.format(o1[19], o2[19])
state.out_transmission_rough = am.format(o1[20], o2[20])
state.out_thin_wall = am.format(o1[21], o2[21])
state.out_tangent = am.format(o1[22], o2[22])
mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2) mat_state.emission_type = mat_state.EmissionType.get_effective_combination(ek1, ek2)
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '({0} * 0.5 + {1} * 0.5)'.format(opac1, opac2) am = '{0} * 0.5 + {1} * 0.5'
state.out_ior = '({0} * 0.5 + {1} * 0.5)'.format(ior1, ior2) state.out_opacity = am.format(o1[5], o2[5])
state.out_ior = am.format(o1[6], o2[6])
# TODO: Refactor using c.get_*_input() # TODO: Refactor using c.get_*_input()
if bpy.app.version < (2, 92, 0): if bpy.app.version < (2, 91, 0):
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[20]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Base Color'])
if node.inputs[1].is_linked or node.inputs[1].default_value > 0.0: if node.inputs[SUBSURFACE].is_linked or node.inputs[SUBSURFACE].default_value > 0.0:
mat_state.needs_sss = True mat_state.needs_sss = True
state.out_metallic = c.parse_value_input(node.inputs[4]) state.out_subsurface = c.parse_value_input(node.inputs[SUBSURFACE])
state.out_specular = c.parse_value_input(node.inputs[5]) state.out_subsurface_radius = c.parse_vector_input(node.inputs[SUBSURFACE_RADIUS])
state.out_roughness = c.parse_value_input(node.inputs[7]) state.out_subsurface_color = c.parse_vector_input(node.inputs[SUBSURFACE_COLOR])
if node.inputs['Emission'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission'], (0.0, 0.0, 0.0), comp_alpha=False): state.out_metallic = c.parse_value_input(node.inputs['Metallic'])
emission_col = c.parse_vector_input(node.inputs[17]) state.out_specular = c.parse_value_input(node.inputs[SPECULAR])
state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SPECULAR_TINT]))
state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_anisotropy = c.parse_value_input(node.inputs[ANISOTROPIC])
state.out_aniso_rot = c.parse_value_input(node.inputs[ANISOTROPIC_ROT])
state.out_sheen = c.parse_value_input(node.inputs[SHEEN])
state.out_sheen_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SHEEN_TINT]))
state.out_clearcoat = c.parse_value_input(node.inputs[CLEARCOAT])
state.out_clearcoat_rough = c.parse_value_input(node.inputs[CLEARCOAT_ROUGHNESS])
state.out_transmission = c.parse_value_input(node.inputs[TRANSMISSION])
trans_rough_socket = node.inputs.get(TRANSMISSION_ROUGHNESS)
if trans_rough_socket is not None:
state.out_transmission_rough = c.parse_value_input(trans_rough_socket)
state.out_tangent = c.parse_vector_input(node.inputs[TANGENT])
if node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False):
emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR])
state.out_emission_col = emission_col state.out_emission_col = emission_col
mat_state.emission_type = mat_state.EmissionType.SHADED mat_state.emission_type = mat_state.EmissionType.SHADED
else: else:
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
if state.parse_opacity: if state.parse_opacity:
state.out_ior = c.parse_value_input(node.inputs[14]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
# In Blender 2.83, Alpha socket is at index 18, not 19
if 'Alpha' in node.inputs: if 'Alpha' in node.inputs:
state.out_opacity = c.parse_value_input(node.inputs['Alpha']) state.out_opacity = c.parse_value_input(node.inputs['Alpha'])
else: else:
state.out_opacity = '1.0' state.out_opacity = '1.0'
if bpy.app.version >= (2, 92, 0) and bpy.app.version <= (4, 1, 0): if bpy.app.version >= (2, 91, 0) and bpy.app.version < (4, 0, 0):
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[22]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Base Color'])
if node.inputs[1].is_linked or node.inputs[1].default_value > 0.0: if node.inputs[SUBSURFACE].is_linked or node.inputs[SUBSURFACE].default_value > 0.0:
mat_state.needs_sss = True mat_state.needs_sss = True
# subsurface_radius = c.parse_vector_input(node.inputs[2]) state.out_subsurface = c.parse_value_input(node.inputs[SUBSURFACE])
# subsurface_color = c.parse_vector_input(node.inputs[3]) state.out_subsurface_radius = c.parse_vector_input(node.inputs[SUBSURFACE_RADIUS])
state.out_metallic = c.parse_value_input(node.inputs[6]) state.out_subsurface_color = c.parse_vector_input(node.inputs[SUBSURFACE_COLOR])
state.out_specular = c.parse_value_input(node.inputs[7]) state.out_metallic = c.parse_value_input(node.inputs['Metallic'])
# specular_tint = c.parse_vector_input(node.inputs[6]) state.out_specular = c.parse_value_input(node.inputs[SPECULAR])
state.out_roughness = c.parse_value_input(node.inputs[9]) state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SPECULAR_TINT]))
# aniso = c.parse_vector_input(node.inputs[8]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
# aniso_rot = c.parse_vector_input(node.inputs[9]) state.out_anisotropy = c.parse_value_input(node.inputs[ANISOTROPIC])
# sheen = c.parse_vector_input(node.inputs[10]) state.out_aniso_rot = c.parse_value_input(node.inputs[ANISOTROPIC_ROT])
# sheen_tint = c.parse_vector_input(node.inputs[11]) state.out_sheen = c.parse_value_input(node.inputs[SHEEN])
# clearcoat = c.parse_vector_input(node.inputs[12]) state.out_sheen_tint = 'vec3({})'.format(c.parse_value_input(node.inputs[SHEEN_TINT]))
# clearcoat_rough = c.parse_vector_input(node.inputs[13]) state.out_clearcoat = c.parse_value_input(node.inputs[CLEARCOAT])
# ior = c.parse_vector_input(node.inputs[14]) state.out_clearcoat_rough = c.parse_value_input(node.inputs[CLEARCOAT_ROUGHNESS])
# transmission = c.parse_vector_input(node.inputs[15]) state.out_transmission = c.parse_value_input(node.inputs[TRANSMISSION])
# transmission_roughness = c.parse_vector_input(node.inputs[16]) state.out_transmission_rough = c.parse_value_input(node.inputs[TRANSMISSION_ROUGHNESS])
state.out_tangent = c.parse_vector_input(node.inputs[TANGENT])
if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\ if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\
and (node.inputs['Emission'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission'], (0.0, 0.0, 0.0), comp_alpha=False)): and (node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False)):
emission_col = c.parse_vector_input(node.inputs[19]) emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR])
emission_strength = c.parse_value_input(node.inputs[20]) emission_strength = c.parse_value_input(node.inputs['Emission Strength'])
state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength) state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength)
mat_state.emission_type = mat_state.EmissionType.SHADED mat_state.emission_type = mat_state.EmissionType.SHADED
else: else:
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
# clearcoar_normal = c.parse_vector_input(node.inputs[21])
# tangent = c.parse_vector_input(node.inputs[22])
if state.parse_opacity: if state.parse_opacity:
state.out_ior = c.parse_value_input(node.inputs[16]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
if len(node.inputs) >= 21: if 'Alpha' in node.inputs:
state.out_opacity = c.parse_value_input(node.inputs[21]) state.out_opacity = c.parse_value_input(node.inputs['Alpha'])
if bpy.app.version > (4, 1, 0): else:
state.out_opacity = '1.0'
if bpy.app.version >= (4, 0, 0):
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[5]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Base Color'])
sss_input = node.inputs.get('Subsurface Weight') or node.inputs.get('Subsurface') sss_input = node.inputs.get(SUBSURFACE)
if sss_input is not None: if sss_input is not None:
if sss_input.is_linked or sss_input.default_value > 0.0: if sss_input.is_linked or sss_input.default_value > 0.0:
mat_state.needs_sss = True mat_state.needs_sss = True
state.out_subsurface = c.parse_value_input(sss_input)
subsurface = c.parse_value_input(node.inputs[7])
subsurface_radius = c.parse_vector_input(node.inputs[9])
subsurface_color = c.parse_vector_input(node.inputs[8])
state.out_metallic = c.parse_value_input(node.inputs[1])
specular_socket = node.inputs.get('Specular IOR Level') sss_radius_input = node.inputs.get(SUBSURFACE_RADIUS)
if sss_radius_input is not None:
state.out_subsurface_radius = c.parse_vector_input(sss_radius_input)
sss_color_input = node.inputs.get(SUBSURFACE_COLOR)
if sss_color_input is not None:
state.out_subsurface_color = c.parse_vector_input(sss_color_input)
else:
state.out_subsurface_color = c.parse_vector_input(node.inputs['Base Color'])
state.out_metallic = c.parse_value_input(node.inputs['Metallic'])
specular_socket = node.inputs.get(SPECULAR)
if specular_socket is not None: if specular_socket is not None:
state.out_specular = f'({c.parse_value_input(specular_socket)} * 2.0)' state.out_specular = f'({c.parse_value_input(specular_socket)} * 2.0)'
else: else:
@ -172,89 +271,119 @@ if bpy.app.version > (4, 1, 0):
state.out_specular = c.parse_value_input(specular_socket) state.out_specular = c.parse_value_input(specular_socket)
else: else:
state.out_specular = '1.0' state.out_specular = '1.0'
state.out_roughness = c.parse_value_input(node.inputs[2]) spec_tint_socket = node.inputs.get(SPECULAR_TINT)
if spec_tint_socket is not None:
state.out_specular_tint = c.parse_vector_input(spec_tint_socket)
state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
# Prevent black material when metal = 1.0 and roughness = 0.0 # Prevent black material when metal = 1.0 and roughness = 0.0
try: try:
if float(state.out_roughness) < 0.00101: if float(state.out_roughness) < 0.00101:
state.out_roughness = '0.001' state.out_roughness = '0.001'
except ValueError: except ValueError:
pass pass
aniso_socket = node.inputs.get(ANISOTROPIC)
if aniso_socket is not None:
state.out_anisotropy = c.parse_value_input(aniso_socket)
aniso_rot_socket = node.inputs.get(ANISOTROPIC_ROT)
if aniso_rot_socket is not None:
state.out_aniso_rot = c.parse_value_input(aniso_rot_socket)
sheen_socket = node.inputs.get(SHEEN)
if sheen_socket is not None:
state.out_sheen = c.parse_value_input(sheen_socket)
sheen_rough_socket = node.inputs.get(SHEEN_ROUGHNESS)
if sheen_rough_socket is not None:
state.out_sheen_rough = c.parse_value_input(sheen_rough_socket)
sheen_tint_socket = node.inputs.get(SHEEN_TINT)
if sheen_tint_socket is not None:
state.out_sheen_tint = c.parse_vector_input(sheen_tint_socket)
coat_socket = node.inputs.get(CLEARCOAT)
if coat_socket is not None:
state.out_clearcoat = c.parse_value_input(coat_socket)
coat_rough_socket = node.inputs.get(CLEARCOAT_ROUGHNESS)
if coat_rough_socket is not None:
state.out_clearcoat_rough = c.parse_value_input(coat_rough_socket)
trans_socket = node.inputs.get(TRANSMISSION)
if trans_socket is not None:
state.out_transmission = c.parse_value_input(trans_socket)
trans_rough_socket = node.inputs.get(TRANSMISSION_ROUGHNESS)
if trans_rough_socket is not None:
state.out_transmission_rough = c.parse_value_input(trans_rough_socket)
thin_wall_socket = node.inputs.get(THIN_WALL)
if thin_wall_socket is not None:
state.out_thin_wall = c.parse_value_input(thin_wall_socket)
tangent_socket = node.inputs.get(TANGENT)
if tangent_socket is not None:
state.out_tangent = c.parse_vector_input(tangent_socket)
if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\ if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\
and (node.inputs['Emission Color'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission Color'], (0.0, 0.0, 0.0), comp_alpha=False)): and (node.inputs[EMISSION_COLOR].is_linked or not mat_utils.equals_color_socket(node.inputs[EMISSION_COLOR], (0.0, 0.0, 0.0), comp_alpha=False)):
if bpy.app.version >= (4, 4, 0): emission_col = c.parse_vector_input(node.inputs[EMISSION_COLOR])
emission_col = c.parse_vector_input(node.inputs[27]) emission_strength = c.parse_value_input(node.inputs['Emission Strength'])
emission_strength = c.parse_value_input(node.inputs[28])
else:
emission_col = c.parse_vector_input(node.inputs[26])
emission_strength = c.parse_value_input(node.inputs[27])
state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength) state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength)
mat_state.emission_type = mat_state.EmissionType.SHADED mat_state.emission_type = mat_state.EmissionType.SHADED
else: else:
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
#state.out_occlusion = state.out_roughness
#state.out_aniso = c.parse_vector_input(node.inputs[14])
#state.out_aniso_rot = c.parse_vector_input(node.inputs[15])
#state.out_sheen = c.parse_vector_input(node.inputs[23])
#state.out_sheen_tint = c.parse_vector_input(node.inputs[25])
#state.out_clearcoat = c.parse_vector_input(node.inputs[18])
#state.out_clearcoat_rough = c.parse_vector_input(node.inputs[19])
#state.out_ior = c.parse_value_input(node.inputs[3])
#state.out_transmission = c.parse_vector_input(node.inputs[17])
#state.out_transmission_roughness = state.out_roughness
if state.parse_opacity: if state.parse_opacity:
state.out_ior = c.parse_value_input(node.inputs[3]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
state.out_opacity = c.parse_value_input(node.inputs[4]) state.out_opacity = c.parse_value_input(node.inputs['Alpha'])
def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[2]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_specular = '0.0' state.out_specular = '0.0'
if bpy.app.version >= (4, 0, 0): if bpy.app.version >= (4, 0, 0):
def parse_bsdfsheen(node: bpy.types.ShaderNodeBsdfSheen, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfsheen(node: bpy.types.ShaderNodeBsdfSheen, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[2]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
if bpy.app.version < (4, 1, 0): if bpy.app.version < (4, 0, 0):
def parse_bsdfglossy(node: bpy.types.ShaderNodeBsdfGlossy, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfglossy(node: bpy.types.ShaderNodeBsdfGlossy, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[2]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_metallic = '1.0' state.out_metallic = '1.0'
else: else:
def parse_bsdfglossy(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfglossy(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[4]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_metallic = '1.0' state.out_metallic = '1.0'
def parse_ambientocclusion(node: bpy.types.ShaderNodeAmbientOcclusion, out_socket: NodeSocket, state: ParserState) -> None: def parse_ambientocclusion(node: bpy.types.ShaderNodeAmbientOcclusion, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
# Single channel # Single channel
state.out_occlusion = c.parse_vector_input(node.inputs[0]) + '.r' state.out_occlusion = c.parse_vector_input(node.inputs['Distance']) + '.r'
def parse_bsdfanisotropic(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfanisotropic(node: bpy.types.ShaderNodeBsdfAnisotropic, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[4]) c.write_normal(node.inputs['Normal'])
# Revert to glossy # Revert to glossy
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
state.out_metallic = '1.0' state.out_metallic = '1.0'
def parse_emission(node: bpy.types.ShaderNodeEmission, out_socket: NodeSocket, state: ParserState) -> None: def parse_emission(node: bpy.types.ShaderNodeEmission, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
emission_col = c.parse_vector_input(node.inputs[0]) emission_col = c.parse_vector_input(node.inputs['Color'])
emission_strength = c.parse_value_input(node.inputs[1]) emission_strength = c.parse_value_input(node.inputs['Strength'])
state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength) state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength)
state.out_basecol = 'vec3(0.0)' state.out_basecol = 'vec3(0.0)'
state.out_specular = '0.0' state.out_specular = '0.0'
@ -264,16 +393,77 @@ def parse_emission(node: bpy.types.ShaderNodeEmission, out_socket: NodeSocket, s
def parse_bsdfglass(node: bpy.types.ShaderNodeBsdfGlass, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfglass(node: bpy.types.ShaderNodeBsdfGlass, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
c.write_normal(node.inputs[3]) c.write_normal(node.inputs['Normal'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '1.0' state.out_opacity = '1.0'
state.out_ior = c.parse_value_input(node.inputs[2]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
def parse_bsdfhair(node: bpy.types.ShaderNodeBsdfHair, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfhair(node: bpy.types.ShaderNodeBsdfHair, out_socket: NodeSocket, state: ParserState) -> None:
pass if state.parse_surface:
state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs['RoughnessU'])
state.out_specular = '0.0'
rough_v_socket = node.inputs.get('RoughnessV')
if rough_v_socket is not None:
state.out_aniso_rot = c.parse_value_input(rough_v_socket)
tangent_socket = node.inputs.get('Tangent')
if tangent_socket is not None:
state.out_tangent = c.parse_vector_input(tangent_socket)
if bpy.app.version >= (2, 83, 0):
def parse_bsdfhairprincipled(node: 'bpy.types.ShaderNodeBsdfHairPrincipled', out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface:
c.write_normal(node.inputs['Normal'])
parametrization = getattr(node, 'parametrization', 'COLOR')
if parametrization == 'COLOR':
color_socket = node.inputs.get('Color')
if color_socket is not None:
state.out_basecol = c.parse_vector_input(color_socket)
elif parametrization == 'MELANIN':
melanin_socket = node.inputs.get('Melanin')
if melanin_socket is not None:
state.out_subsurface = c.parse_value_input(melanin_socket)
melanin_red_socket = node.inputs.get('Melanin Redness')
if melanin_red_socket is not None:
state.out_specular_tint = 'vec3({})'.format(c.parse_value_input(melanin_red_socket))
elif parametrization == 'ABSORPTION':
absorption_socket = node.inputs.get('Absorption Coefficient')
if absorption_socket is not None:
state.out_specular_tint = c.parse_vector_input(absorption_socket)
tint_socket = node.inputs.get('Tint')
if tint_socket is not None:
state.out_subsurface_color = c.parse_vector_input(tint_socket)
rough_socket = node.inputs.get('Roughness')
if rough_socket is not None:
state.out_roughness = c.parse_value_input(rough_socket)
model = getattr(node, 'model', 'CHIANG')
if model == 'CHIANG':
radial_rough_socket = node.inputs.get('Radial Roughness')
if radial_rough_socket is not None:
state.out_aniso_rot = c.parse_value_input(radial_rough_socket)
coat_socket = node.inputs.get('Coat')
if coat_socket is not None:
state.out_clearcoat = c.parse_value_input(coat_socket)
random_color_socket = node.inputs.get('Random Color')
if random_color_socket is not None:
state.out_sheen = c.parse_value_input(random_color_socket)
random_rough_socket = node.inputs.get('Random Roughness')
if random_rough_socket is not None:
state.out_sheen_rough = c.parse_value_input(random_rough_socket)
offset_socket = node.inputs.get('Offset')
if offset_socket is not None:
state.out_anisotropy = c.parse_value_input(offset_socket)
state.out_specular = '0.0'
state.out_metallic = '0.0'
if state.parse_opacity:
ior_socket = node.inputs.get('IOR')
if ior_socket is not None:
state.out_ior = c.parse_value_input(ior_socket)
state.out_opacity = '1.0'
def parse_holdout(node: bpy.types.ShaderNodeHoldout, out_socket: NodeSocket, state: ParserState) -> None: def parse_holdout(node: bpy.types.ShaderNodeHoldout, out_socket: NodeSocket, state: ParserState) -> None:
@ -284,46 +474,50 @@ def parse_holdout(node: bpy.types.ShaderNodeHoldout, out_socket: NodeSocket, sta
def parse_bsdfrefraction(node: bpy.types.ShaderNodeBsdfRefraction, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfrefraction(node: bpy.types.ShaderNodeBsdfRefraction, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
c.write_normal(node.inputs[3]) c.write_normal(node.inputs['Normal'])
state.out_roughness = c.parse_value_input(node.inputs[1]) state.out_roughness = c.parse_value_input(node.inputs['Roughness'])
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '1.0' state.out_opacity = '1.0'
state.out_ior = c.parse_value_input(node.inputs[2]) state.out_ior = c.parse_value_input(node.inputs['IOR'])
def parse_subsurfacescattering(node: bpy.types.ShaderNodeSubsurfaceScattering, out_socket: NodeSocket, state: ParserState) -> None: def parse_subsurfacescattering(node: bpy.types.ShaderNodeSubsurfaceScattering, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
# Mark that this material needs SSS # Mark that this material needs SSS
mat_state.needs_sss = True mat_state.needs_sss = True
if bpy.app.version < (4, 1, 0): c.write_normal(node.inputs['Normal'])
c.write_normal(node.inputs[4]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
else: state.out_subsurface = c.parse_value_input(node.inputs['Scale'])
c.write_normal(node.inputs[6]) state.out_subsurface_radius = c.parse_vector_input(node.inputs['Radius'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_subsurface_color = c.parse_vector_input(node.inputs['Color'])
state.out_specular = '0.0'
def parse_bsdftoon(node: bpy.types.ShaderNodeBsdfToon, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdftoon(node: bpy.types.ShaderNodeBsdfToon, out_socket: NodeSocket, state: ParserState) -> None:
# c.write_normal(node.inputs[3]) if state.parse_surface:
pass c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = c.parse_value_input(node.inputs['Size'])
state.out_specular = '0.0'
def parse_bsdftranslucent(node: bpy.types.ShaderNodeBsdfTranslucent, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdftranslucent(node: bpy.types.ShaderNodeBsdfTranslucent, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[1]) c.write_normal(node.inputs['Normal'])
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '(1.0 - {0}.r)'.format(c.parse_vector_input(node.inputs[0])) state.out_opacity = '(1.0 - {0}.r)'.format(c.parse_vector_input(node.inputs['Color']))
state.out_ior = '1.0' state.out_ior = '1.0'
def parse_bsdftransparent(node: bpy.types.ShaderNodeBsdfTransparent, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdftransparent(node: bpy.types.ShaderNodeBsdfTransparent, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_opacity: if state.parse_opacity:
state.out_opacity = '(1.0 - {0}.r)'.format(c.parse_vector_input(node.inputs[0])) state.out_opacity = '(1.0 - {0}.r)'.format(c.parse_vector_input(node.inputs['Color']))
state.out_ior = '1.0' state.out_ior = '1.0'
if bpy.app.version < (4, 1, 0): if bpy.app.version < (4, 0, 0):
def parse_bsdfvelvet(node: bpy.types.ShaderNodeBsdfVelvet, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfvelvet(node: bpy.types.ShaderNodeBsdfVelvet, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[2]) c.write_normal(node.inputs['Normal'])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs['Color'])
state.out_roughness = '1.0' state.out_roughness = '1.0'
state.out_metallic = '1.0' state.out_metallic = '1.0'

View File

@ -856,6 +856,22 @@ def _write_material_attribs_default(frag: shader.Shader, parse_opacity: bool):
# We may not use emission, but the attribute will then be removed # We may not use emission, but the attribute will then be removed
# by the shader compiler # by the shader compiler
frag.write('vec3 emissionCol;') frag.write('vec3 emissionCol;')
# Extended BRDF parameters
frag.write('float subsurface = 0.0;')
frag.write('vec3 subsurfaceRadius = vec3(0.0);')
frag.write('vec3 subsurfaceColor = vec3(0.8);')
frag.write('vec3 specularTint = vec3(1.0);')
frag.write('float anisotropy = 0.0;')
frag.write('float anisoRot = 0.0;')
frag.write('float sheen = 0.0;')
frag.write('float sheenRough = 0.5;')
frag.write('vec3 sheenTint = vec3(1.0);')
frag.write('float clearcoat = 0.0;')
frag.write('float clearcoatRough = 0.03;')
frag.write('float transmission = 0.0;')
frag.write('float transmissionRough = 0.0;')
frag.write('float thinWall = 0.0;')
frag.write('vec3 tangent = vec3(0.0);')
if parse_opacity: if parse_opacity:
frag.write('float opacity;') frag.write('float opacity;')
frag.write('float ior = 1.45;') frag.write('float ior = 1.45;')

View File

@ -165,6 +165,10 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = {
'TRANSPARENT_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftransparent), 'TRANSPARENT_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftransparent),
'BSDF_REFRACTION': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction), 'BSDF_REFRACTION': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction),
'REFRACTION_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction), 'REFRACTION_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfrefraction),
'BSDF_TOON': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftoon),
'TOON_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftoon),
'BSDF_HAIR': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhair),
'HAIR_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhair),
'EMISSION': MaterialNodeMeta(parse_func=nodes_shader.parse_emission), 'EMISSION': MaterialNodeMeta(parse_func=nodes_shader.parse_emission),
'HOLDOUT': MaterialNodeMeta( 'HOLDOUT': MaterialNodeMeta(
parse_func=nodes_shader.parse_holdout, parse_func=nodes_shader.parse_holdout,
@ -205,11 +209,14 @@ ALL_NODES: Dict[str, MaterialNodeMeta] = {
if bpy.app.version > (3, 2, 0): if bpy.app.version > (3, 2, 0):
ALL_NODES['SEPARATE_COLOR'] = MaterialNodeMeta(parse_func=nodes_converter.parse_separate_color) ALL_NODES['SEPARATE_COLOR'] = MaterialNodeMeta(parse_func=nodes_converter.parse_separate_color)
ALL_NODES['COMBINE_COLOR'] = MaterialNodeMeta(parse_func=nodes_converter.parse_combine_color) ALL_NODES['COMBINE_COLOR'] = MaterialNodeMeta(parse_func=nodes_converter.parse_combine_color)
if bpy.app.version < (4, 1, 0): if bpy.app.version < (4, 0, 0):
ALL_NODES['BSDF_VELVET'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfvelvet) ALL_NODES['BSDF_VELVET'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfvelvet)
if bpy.app.version < (4, 1, 0):
ALL_NODES['TEX_MUSGRAVE'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_musgrave) ALL_NODES['TEX_MUSGRAVE'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_musgrave)
if bpy.app.version >= (4, 0, 0): if bpy.app.version >= (4, 0, 0):
ALL_NODES['BSDF_SHEEN'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfsheen) ALL_NODES['BSDF_SHEEN'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfsheen)
if bpy.app.version >= (2, 83, 0):
ALL_NODES['BSDF_HAIR_PRINCIPLED'] = MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfhairprincipled)
if bpy.app.version < (5, 0, 0): if bpy.app.version < (5, 0, 0):
ALL_NODES['TEX_POINTDENSITY'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_pointdensity, compute_dxdy_variants=ComputeDXDYVariant.NEVER) ALL_NODES['TEX_POINTDENSITY'] = MaterialNodeMeta(parse_func=nodes_texture.parse_tex_pointdensity, compute_dxdy_variants=ComputeDXDYVariant.NEVER)

View File

@ -99,6 +99,21 @@ class ParserState:
self.out_opacity: floatstr = '1.0' self.out_opacity: floatstr = '1.0'
self.out_ior: floatstr = '1.450' self.out_ior: floatstr = '1.450'
self.out_emission_col: vec3str = 'vec3(0.0)' self.out_emission_col: vec3str = 'vec3(0.0)'
self.out_subsurface: floatstr = '0.0'
self.out_subsurface_radius: vec3str = 'vec3(0.0)'
self.out_subsurface_color: vec3str = 'vec3(0.8)'
self.out_specular_tint: vec3str = 'vec3(1.0)'
self.out_anisotropy: floatstr = '0.0'
self.out_aniso_rot: floatstr = '0.0'
self.out_sheen: floatstr = '0.0'
self.out_sheen_rough: floatstr = '0.5'
self.out_sheen_tint: vec3str = 'vec3(1.0)'
self.out_clearcoat: floatstr = '0.0'
self.out_clearcoat_rough: floatstr = '0.03'
self.out_transmission: floatstr = '0.0'
self.out_transmission_rough: floatstr = '0.0'
self.out_thin_wall: floatstr = '0.0'
self.out_tangent: vec3str = 'vec3(0.0)'
def reset_outs(self): def reset_outs(self):
"""Reset the shader output values to their default values.""" """Reset the shader output values to their default values."""
@ -110,11 +125,34 @@ class ParserState:
self.out_opacity = '1.0' self.out_opacity = '1.0'
self.out_ior = '1.450' self.out_ior = '1.450'
self.out_emission_col = 'vec3(0.0)' self.out_emission_col = 'vec3(0.0)'
self.out_subsurface = '0.0'
self.out_subsurface_radius = 'vec3(0.0)'
self.out_subsurface_color = 'vec3(0.8)'
self.out_specular_tint = 'vec3(1.0)'
self.out_anisotropy = '0.0'
self.out_aniso_rot = '0.0'
self.out_sheen = '0.0'
self.out_sheen_rough = '0.5'
self.out_sheen_tint = 'vec3(1.0)'
self.out_clearcoat = '0.0'
self.out_clearcoat_rough = '0.03'
self.out_transmission = '0.0'
self.out_transmission_rough = '0.0'
self.out_thin_wall = '0.0'
self.out_tangent = 'vec3(0.0)'
def get_outs(self) -> Tuple[vec3str, floatstr, floatstr, floatstr, floatstr, floatstr, floatstr, vec3str]: def get_outs(self) -> Tuple:
"""Return the shader output values as a tuple.""" """Return the shader output values as a tuple."""
return (self.out_basecol, self.out_roughness, self.out_metallic, self.out_occlusion, self.out_specular, return (self.out_basecol, self.out_roughness, self.out_metallic,
self.out_opacity, self.out_ior, self.out_emission_col) self.out_occlusion, self.out_specular, self.out_opacity,
self.out_ior, self.out_emission_col,
self.out_subsurface, self.out_subsurface_radius,
self.out_subsurface_color, self.out_specular_tint,
self.out_anisotropy, self.out_aniso_rot,
self.out_sheen, self.out_sheen_rough, self.out_sheen_tint,
self.out_clearcoat, self.out_clearcoat_rough,
self.out_transmission, self.out_transmission_rough,
self.out_thin_wall, self.out_tangent)
def get_parser_pass_suffix(self) -> str: def get_parser_pass_suffix(self) -> str:

View File

@ -11,12 +11,16 @@ import sys
import os import os
import array import array
import atexit import atexit
import time
from mathutils import Quaternion, Matrix, Vector from mathutils import Quaternion, Matrix, Vector
import lnx.make_state as state import lnx.make_state as state
HAS_GPU_STATE = bpy.app.version >= (3, 0, 0) HAS_GPU_STATE = bpy.app.version >= (3, 0, 0)
if not HAS_GPU_STATE: try:
import bgl import bgl
HAS_BGL = callable(getattr(bgl, 'glGenTextures', None))
except ImportError:
HAS_BGL = False
SHADER_UNIFORM_COLOR = 'UNIFORM_COLOR' if bpy.app.version >= (3, 4, 0) else '2D_UNIFORM_COLOR' SHADER_UNIFORM_COLOR = 'UNIFORM_COLOR' if bpy.app.version >= (3, 4, 0) else '2D_UNIFORM_COLOR'
SHADER_IMAGE = 'IMAGE' if bpy.app.version >= (3, 4, 0) else '2D_IMAGE' SHADER_IMAGE = 'IMAGE' if bpy.app.version >= (3, 4, 0) else '2D_IMAGE'
HAS_GPU_TEXTURE = bpy.app.version >= (3, 0, 0) HAS_GPU_TEXTURE = bpy.app.version >= (3, 0, 0)
@ -124,6 +128,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_frame_id = 0 self._last_frame_id = 0
self._width = 0 self._width = 0
self._height = 0 self._height = 0
self._tex_width = 0
self._tex_height = 0
self._initialized = False self._initialized = False
self._shader = None self._shader = None
self._batch = None self._batch = None
@ -140,6 +146,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_region_dims = None self._last_region_dims = None
self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)() self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
self._engine_id = id(self) self._engine_id = id(self)
self._viewport_image = None
self._float_buffer = None
# print(f"New engine instance created: {self._engine_id}") # print(f"New engine instance created: {self._engine_id}")
def _restore_overlay(self, context=None): def _restore_overlay(self, context=None):
@ -247,7 +255,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
def deferred_launch(): def deferred_launch():
try: try:
import lnx.make as make import lnx.make as make
make.play_viewport(viewport_id, width, height) make.play(viewport_id, width, height)
except Exception as e: except Exception as e:
print(f"Failed to launch viewport: {e}") print(f"Failed to launch viewport: {e}")
import traceback import traceback
@ -498,7 +506,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
"""Handle invalid/stale shared memory by cleaning up and allowing re-init.""" """Handle invalid/stale shared memory by cleaning up and allowing re-init."""
self._cleanup_shared_memory() self._cleanup_shared_memory()
self._initialized = False self._initialized = False
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0: if HAS_BGL and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try: try:
bgl.glDeleteTextures(1, self._gl_texture_buf) bgl.glDeleteTextures(1, self._gl_texture_buf)
except: except:
@ -783,21 +791,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
if len(pixels) < num_pixels: if len(pixels) < num_pixels:
return return
if HAS_GPU_TEXTURE: if HAS_BGL:
if HAS_NUMPY: # Fast path: bgl with GL_UNSIGNED_BYTE, no float conversion, in-place update
pixels[3::4] = 255
float_array = pixels.astype(np.float32) / 255.0
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
else:
pixel_array = array.array('B', pixels[:num_pixels])
for i in range(3, len(pixel_array), 4):
pixel_array[i] = 255
float_pixels = [p / 255.0 for p in pixel_array]
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_pixels)
self._texture = gpu.types.GPUTexture((width, height), format='SRGB8_A8', data=buffer)
else:
# bgl path for Blender < 3.0
if not hasattr(self, '_gl_texture_buf') or self._gl_texture_buf[0] == 0: if not hasattr(self, '_gl_texture_buf') or self._gl_texture_buf[0] == 0:
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1) self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
bgl.glGenTextures(1, self._gl_texture_buf) bgl.glGenTextures(1, self._gl_texture_buf)
@ -817,12 +812,43 @@ class KromViewportEngine(bpy.types.RenderEngine):
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array)) pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array))
internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8) internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8)
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, internal_format, width, height, 0, if self._tex_width == width and self._tex_height == height:
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer) bgl.glTexSubImage2D(bgl.GL_TEXTURE_2D, 0, 0, 0, width, height,
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer)
else:
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, internal_format, width, height, 0,
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
self._texture = tex_id self._texture = tex_id
elif HAS_GPU_TEXTURE:
# Blender 4.x+ path: use bpy.Image + gpu.texture.from_image
# from_image returns a cached GPUTexture (no 20ms constructor)
# pixels updated via foreach_set (fast C-level copy) + update()
if self._viewport_image is None or self._viewport_image.size[0] != width or self._viewport_image.size[1] != height:
if self._viewport_image is not None:
bpy.data.images.remove(self._viewport_image)
self._viewport_image = bpy.data.images.new(
"_lnx_viewport", width, height, float_buffer=False)
self._float_buffer = None
if HAS_NUMPY:
pixels[3::4] = 255
if self._float_buffer is None or len(self._float_buffer) != num_pixels:
self._float_buffer = np.empty(num_pixels, dtype=np.float32)
self._float_buffer[:num_pixels] = pixels[:num_pixels]
self._float_buffer *= (1.0 / 255.0)
self._viewport_image.pixels.foreach_set(self._float_buffer)
else:
pixel_array = array.array('B', pixels[:num_pixels])
for i in range(3, len(pixel_array), 4):
pixel_array[i] = 255
float_pixels = [p / 255.0 for p in pixel_array]
self._viewport_image.pixels.foreach_set(float_pixels)
self._viewport_image.update()
self._texture = gpu.texture.from_image(self._viewport_image)
self._tex_width = width self._tex_width = width
self._tex_height = height self._tex_height = height
@ -838,11 +864,17 @@ class KromViewportEngine(bpy.types.RenderEngine):
"""Called by Blender when the engine instance is destroyed.""" """Called by Blender when the engine instance is destroyed."""
self._stop_krom_process() self._stop_krom_process()
self._restore_overlay() self._restore_overlay()
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0: if HAS_BGL and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try: try:
bgl.glDeleteTextures(1, self._gl_texture_buf) bgl.glDeleteTextures(1, self._gl_texture_buf)
except: except:
pass pass
if self._viewport_image is not None:
try:
bpy.data.images.remove(self._viewport_image)
except:
pass
self._viewport_image = None
if self._viewport_id and self._viewport_id in _active_krom_engines: if self._viewport_id and self._viewport_id in _active_krom_engines:
del _active_krom_engines[self._viewport_id] del _active_krom_engines[self._viewport_id]
@ -928,6 +960,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_region_dims = dims self._last_region_dims = dims
self._request_resize(region.width, region.height) self._request_resize(region.width, region.height)
self._write_camera_matrices(context)
self._read_krom_camera(context) self._read_krom_camera(context)
pixels = self._read_framebuffer() pixels = self._read_framebuffer()
@ -966,7 +999,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._batch_dims = dims self._batch_dims = dims
self._shader.bind() self._shader.bind()
if HAS_GPU_TEXTURE: _is_gl_texture = isinstance(self._texture, int)
if not _is_gl_texture:
self._shader.uniform_sampler("image", self._texture) self._shader.uniform_sampler("image", self._texture)
else: else:
bgl.glActiveTexture(bgl.GL_TEXTURE0) bgl.glActiveTexture(bgl.GL_TEXTURE0)
@ -975,7 +1009,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._batch.draw(self._shader) self._batch.draw(self._shader)
if not HAS_GPU_TEXTURE: if _is_gl_texture:
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
if HAS_GPU_STATE: if HAS_GPU_STATE:

View File

@ -875,7 +875,7 @@ def check_blender_version(op: bpy.types.Operator):
"""Check whether the Blender version is supported by Leenkx, """Check whether the Blender version is supported by Leenkx,
if not, report in UI. if not, report in UI.
""" """
if bpy.app.version[:2] not in [(4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]: if bpy.app.version[:2] not in [(5, 2), (4, 5), (4, 4), (4, 2), (3, 6), (3, 3)]:
op.report({'INFO'}, 'INFO: For Leenkx to work correctly, use a Blender LTS version') op.report({'INFO'}, 'INFO: For Leenkx to work correctly, use a Blender LTS version')