This commit is contained in:
2026-06-24 20:23:12 -07:00
parent 6b704ff469
commit 37c8779d12
10 changed files with 512 additions and 399 deletions

View File

@ -11,6 +11,8 @@ import sys
import os
import array
import atexit
from mathutils import Quaternion, Matrix, Vector
import lnx.make_state as state
HAS_GPU_STATE = bpy.app.version >= (3, 0, 0)
if not HAS_GPU_STATE:
@ -24,22 +26,6 @@ try:
except ImportError:
HAS_NUMPY = False
# TODO: Fix correctly
def _build_srgb_to_linear_lut():
lut = []
for i in range(256):
srgb = i / 255.0
if srgb <= 0.04045:
linear = srgb / 12.92
else:
linear = ((srgb + 0.055) / 1.055) ** 2.4
lut.append(linear)
return lut
_srgb_to_linear_lut_list = _build_srgb_to_linear_lut()
if HAS_NUMPY:
_srgb_to_linear_lut = np.array(_srgb_to_linear_lut_list, dtype=np.float32)
if sys.platform == 'win32':
import mmap
else:
@ -50,8 +36,8 @@ else:
except ImportError:
HAS_POSIX_IPC = False
VIEWPORT_BUILD = 0x4B524F4D
VIEWPORT_VERSION = 1
VIEWPORT_BUILD = 0x52554E54
VIEWPORT_VERSION = 2
VIEWPORT_SHMEM_NAME_BASE = "KROM_VIEWPORT_FB"
def _get_viewport_shmem_name(viewport_id=None):
@ -118,6 +104,7 @@ MOUSE_BUTTON_MIDDLE = 2
_rendered_mode_pending = False
_rendered_mode_retries = 0
_msgbus_owner = object()
_make_module = None
@bpy.app.handlers.persistent
def _on_depsgraph_update(scene, depsgraph):
@ -184,9 +171,10 @@ class KromViewportEngine(bpy.types.RenderEngine):
bl_idname = 'KROM_VIEWPORT'
bl_label = 'Krom Viewport'
bl_use_preview = False
bl_use_gpu_context = True
bl_use_shading_nodes_custom = False
bl_use_eevee_viewport = True
if bpy.app.version >= (2, 90, 0):
bl_use_gpu_context = True
bl_use_eevee_viewport = True
def _ensure_initialized(self):
@ -200,6 +188,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._initialized = False
self._shader = None
self._batch = None
self._batch_dims = None
self._viewport_init_done = True
self._rendered_mode_set = False
self._last_shading_mode = None
@ -208,6 +197,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._krom_proc = None
self._krom_launch_attempted = False
self._pending_camera_sync = False
self._input_enabled_state = None
self._last_region_dims = None
self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
self._engine_id = id(self)
# print(f"New engine instance created: {self._engine_id}")
@ -221,8 +213,34 @@ class KromViewportEngine(bpy.types.RenderEngine):
if current != 'RENDERED':
space.shading.type = 'RENDERED'
self._rendered_mode_set = True
# Disable viewport overlay so Krom output is unobstructed
if space and hasattr(space, 'overlay'):
if not hasattr(self, '_overlay_saved'):
self._overlay_saved = space.overlay.show_overlays
space.overlay.show_overlays = False
except Exception as e:
print(f"Failed to set rendered mode: {e}")
def _restore_overlay(self, context=None):
try:
if not hasattr(self, '_overlay_saved'):
return
spaces = []
if context and context.space_data:
spaces = [context.space_data]
else:
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for sp in area.spaces:
if sp.type == 'VIEW_3D':
spaces.append(sp)
for sp in spaces:
if hasattr(sp, 'overlay'):
sp.overlay.show_overlays = self._overlay_saved
del self._overlay_saved
except:
pass
def _handle_mode_switch(self, context):
"""Detect shading mode changes and restart Krom on Solid->Rendered switch if needed."""
@ -326,6 +344,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
except:
pass
self._krom_proc = None
self._krom_launch_attempted = False
self._initialized = False
self._cleanup_shared_memory()
def _init_shared_memory(self, context=None):
self._ensure_initialized()
@ -493,6 +514,27 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._initialized = False
def _peek_frame_id(self):
"""Quick check if a new frame is available without reading full header."""
self._ensure_initialized()
if not self._initialized:
return None
try:
if sys.platform == 'win32':
if not self._shm_ptr:
return None
frame_id = struct.unpack('<Q', ctypes.string_at(self._shm_ptr + OFFSET_FRAME_ID, 8))[0]
ready_flag = struct.unpack('<I', ctypes.string_at(self._shm_ptr + OFFSET_READY_FLAG, 4))[0]
return frame_id, ready_flag
else:
self._shm.seek(OFFSET_FRAME_ID)
frame_id = struct.unpack('<Q', self._shm.read(8))[0]
self._shm.seek(OFFSET_READY_FLAG)
ready_flag = struct.unpack('<I', self._shm.read(4))[0]
return frame_id, ready_flag
except Exception:
return None
def _read_header(self):
"""Read and parse the shared memory header."""
self._ensure_initialized()
@ -503,10 +545,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
if sys.platform == 'win32':
if not self._shm_ptr:
return None
# use memmove for safety
header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
ctypes.memmove(header_buffer, self._shm_ptr, VIEWPORT_HEADER_SIZE)
header_data = bytes(header_buffer)
ctypes.memmove(self._header_buffer, self._shm_ptr, VIEWPORT_HEADER_SIZE)
header_data = bytes(self._header_buffer)
else:
self._shm.seek(0)
header_data = self._shm.read(VIEWPORT_HEADER_SIZE)
@ -516,7 +556,6 @@ class KromViewportEngine(bpy.types.RenderEngine):
ready_flag, fmt = struct.unpack('<II', header_data[24:32])
if build != VIEWPORT_BUILD:
# shared memory may have been recreated
self._handle_shmem_invalid()
return None
@ -538,21 +577,30 @@ class KromViewportEngine(bpy.types.RenderEngine):
"""Handle invalid/stale shared memory by cleaning up and allowing re-init."""
self._cleanup_shared_memory()
self._initialized = False
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try:
bgl.glDeleteTextures(1, self._gl_texture_buf)
except:
pass
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
self._texture = None
self._last_frame_id = 0
def _read_framebuffer(self):
"""Read framebuffer from shared memory."""
# Fast path: peek frame_id and ready_flag without reading full header
peek = self._peek_frame_id()
if peek is None:
return None
frame_id, ready_flag = peek
if frame_id == self._last_frame_id or ready_flag == 0:
return None
# New frame available - read full header for dimensions
header = self._read_header()
if not header:
return None
if header['frame_id'] == self._last_frame_id:
return None
if header['ready_flag'] == 0:
return None
width = header['width']
height = header['height']
@ -565,15 +613,26 @@ class KromViewportEngine(bpy.types.RenderEngine):
if sys.platform == 'win32':
if not self._shm_ptr:
return None
pixel_buffer = (ctypes.c_ubyte * pixel_size)()
ctypes.memmove(pixel_buffer, self._shm_ptr + VIEWPORT_HEADER_SIZE, pixel_size)
pixels = bytes(pixel_buffer)
if HAS_NUMPY:
# Single copy: shared memory -> numpy array
pixel_array = np.ctypeslib.as_array(
(ctypes.c_ubyte * pixel_size).from_address(
self._shm_ptr + VIEWPORT_HEADER_SIZE)
).copy()
else:
pixel_buffer = (ctypes.c_ubyte * pixel_size)()
ctypes.memmove(pixel_buffer, self._shm_ptr + VIEWPORT_HEADER_SIZE, pixel_size)
pixel_array = bytes(pixel_buffer)
ready_bytes = struct.pack('<I', 0)
ctypes.memmove(self._shm_ptr + OFFSET_READY_FLAG, ready_bytes, 4)
else:
self._shm.seek(VIEWPORT_HEADER_SIZE)
pixels = self._shm.read(pixel_size)
raw = self._shm.read(pixel_size)
if HAS_NUMPY:
pixel_array = np.frombuffer(raw, dtype=np.uint8).copy()
else:
pixel_array = raw
self._shm.seek(OFFSET_READY_FLAG)
self._shm.write(struct.pack('<I', 0))
@ -581,7 +640,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._width = width
self._height = height
return pixels
return pixel_array
except Exception as e:
self._handle_shmem_invalid()
print(f"Failed to read framebuffer: {e}")
@ -658,8 +717,6 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._shm.seek(OFFSET_KROM_CAMERA_DIRTY)
self._shm.write(clear_data)
from mathutils import Quaternion, Matrix, Vector
rv3d = context.space_data.region_3d if context.space_data else None
if not rv3d:
return
@ -734,6 +791,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._ensure_initialized()
if not self._initialized:
return
if self._input_enabled_state == enabled:
return
try:
data = struct.pack('<I', 1 if enabled else 0)
@ -744,6 +803,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
else:
self._shm.seek(OFFSET_INPUT_ENABLED)
self._shm.write(data)
self._input_enabled_state = enabled
except Exception as e:
pass
@ -793,7 +853,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._shader = gpu.shader.from_builtin(SHADER_IMAGE)
def _update_texture(self, pixels, width, height):
"""Create or update the GPU texture with new pixel data."""
"""Create or update the GPU texture with new pixel data.
Uses SRGB8_A8 format for GPU-side sRGB-to-linear conversion."""
self._ensure_initialized()
try:
num_pixels = width * height * 4
@ -801,28 +862,46 @@ class KromViewportEngine(bpy.types.RenderEngine):
if len(pixels) < num_pixels:
return
# clear old texture if dimensions changed
if hasattr(self, '_tex_width') and hasattr(self, '_tex_height'):
if self._tex_width != width or self._tex_height != height:
self._texture = None
# TODO: re-investigate LUT for linearization
if HAS_NUMPY:
pixel_array = np.frombuffer(pixels[:num_pixels], dtype=np.uint8).copy()
pixel_array[3::4] = 255
float_array = _srgb_to_linear_lut[pixel_array]
float_array[3::4] = 1.0
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
else:
pixel_array = array.array('B', pixels[:num_pixels])
float_pixels = []
for i, p in enumerate(pixel_array):
if i % 4 == 3: # alpha channel
float_pixels.append(1.0)
else:
float_pixels.append(_srgb_to_linear_lut_list[p])
buffer = gpu.types.Buffer('FLOAT', len(float_pixels), float_pixels)
if HAS_GPU_TEXTURE:
if HAS_NUMPY:
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:
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
bgl.glGenTextures(1, self._gl_texture_buf)
tex_id = self._gl_texture_buf[0]
bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex_id)
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
if HAS_NUMPY:
pixels[3::4] = 255
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, pixels.tobytes())
else:
pixel_array = array.array('B', pixels[:num_pixels])
for i in range(3, len(pixel_array), 4):
pixel_array[i] = 255
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array))
internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8)
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)
self._texture = tex_id
self._texture = gpu.types.GPUTexture((width, height), format='RGBA32F', data=buffer)
self._tex_width = width
self._tex_height = height
@ -833,9 +912,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
def render(self, depsgraph):
"""Disabled render method for final renders (F12)."""
pass
def free(self):
"""Called by Blender when the engine instance is destroyed."""
self._stop_krom_process()
self._restore_overlay()
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try:
bgl.glDeleteTextures(1, self._gl_texture_buf)
except:
pass
if self._viewport_id and self._viewport_id in _active_krom_engines:
del _active_krom_engines[self._viewport_id]
def view_update(self, context, depsgraph):
"""Called when the scene is updated."""
if state.is_exporting:
return
self._ensure_initialized()
if not self._init_shared_memory(context):
return
@ -848,6 +941,11 @@ class KromViewportEngine(bpy.types.RenderEngine):
def view_draw(self, context, depsgraph):
"""Called to draw the viewport."""
global _active_krom_engine, _active_krom_engines, _active_viewport_id, _input_operator_running, _make_module
if state.is_exporting:
return
self._ensure_initialized()
self._handle_mode_switch(context)
@ -872,10 +970,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._pending_camera_sync = False
self._sync_camera_once(context)
# Check if Krom process has died - allow relaunch
if self._krom_launch_attempted and self._viewport_id:
if _make_module is None:
import lnx.make
_make_module = lnx.make
proc = _make_module._viewport_processes.get(self._viewport_id)
if proc is None or proc.poll() is not None:
print(f"Krom process for viewport {self._viewport_id} is gone, resetting for relaunch")
self._krom_launch_attempted = False
self._initialized = False
self._cleanup_shared_memory()
if self._viewport_id in _active_krom_engines:
del _active_krom_engines[self._viewport_id]
self._set_rendered_mode(context)
# TODO: input forwarding when viewport is active
global _active_krom_engine, _active_krom_engines, _active_viewport_id, _input_operator_running
_active_krom_engine = self # legacy single-engine reference
if self._viewport_id:
@ -886,23 +997,19 @@ class KromViewportEngine(bpy.types.RenderEngine):
if not _input_operator_running:
bpy.app.timers.register(_start_input_capture, first_interval=0.1)
else:
if not hasattr(self, '_input_check_counter'):
self._input_check_counter = 0
self._input_check_counter += 1
if self._input_check_counter > 60:
self._input_check_counter = 0
bpy.app.timers.register(_start_input_capture, first_interval=0.5)
region = context.region
if region and region.width > 0 and region.height > 0:
self._request_resize(region.width, region.height)
dims = (region.width, region.height)
if self._last_region_dims != dims:
self._last_region_dims = dims
self._request_resize(region.width, region.height)
self._read_krom_camera(context)
pixels = self._read_framebuffer()
if pixels:
if pixels is not None:
self._update_texture(pixels, self._width, self._height)
if not self._texture:
@ -917,26 +1024,36 @@ class KromViewportEngine(bpy.types.RenderEngine):
bgl.glDisable(bgl.GL_BLEND)
region = context.region
vertices = (
(0, 0),
(region.width, 0),
(region.width, region.height),
(0, region.height),
)
tex_coords = (
(0, 1), # Flip Y
(1, 1),
(1, 0),
(0, 0),
)
indices = ((0, 1, 2), (0, 2, 3))
dims = (region.width, region.height)
if self._batch is None or self._batch_dims != dims:
vertices = (
(0, 0),
(region.width, 0),
(region.width, region.height),
(0, region.height),
)
tex_coords = (
(0, 1),
(1, 1),
(1, 0),
(0, 0),
)
indices = ((0, 1, 2), (0, 2, 3))
self._batch = batch_for_shader(self._shader, 'TRIS', {"pos": vertices, "texCoord": tex_coords}, indices=indices)
self._batch_dims = dims
self._shader.bind()
self._shader.uniform_sampler("image", self._texture)
if HAS_GPU_TEXTURE:
self._shader.uniform_sampler("image", self._texture)
else:
bgl.glActiveTexture(bgl.GL_TEXTURE0)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._texture)
self._shader.uniform_int("image", [0])
batch = batch_for_shader(self._shader, 'TRIS', {"pos": vertices, "texCoord": tex_coords}, indices=indices)
batch.draw(self._shader)
self._batch.draw(self._shader)
if not HAS_GPU_TEXTURE:
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
if HAS_GPU_STATE:
gpu.state.blend_set('NONE')
@ -969,12 +1086,14 @@ class KromViewportEngine(bpy.types.RenderEngine):
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
batch.draw(shader)
if HAS_GPU_STATE:
gpu.state.blend_set('NONE')
else:
bgl.glDisable(bgl.GL_BLEND)
self.tag_redraw()
class KROM_OT_viewport_input(bpy.types.Operator):
"""Modal operator to capture and forward input to Krom viewport"""
@ -988,8 +1107,7 @@ class KROM_OT_viewport_input(bpy.types.Operator):
def modal(self, context, event):
global _input_operator_running
# check if we should stop
if event.type == 'ESC':
try:
if self._engine:
@ -1002,22 +1120,13 @@ class KROM_OT_viewport_input(bpy.types.Operator):
if event.type in {'SCREEN_SET', 'SCREEN_CHANGED'}:
return {'PASS_THROUGH'}
if not context.space_data or not context.area:
# Re-derive area/region/engine from mouse position each event
result = self._find_viewport_under_mouse(event)
if result is None:
return {'PASS_THROUGH'}
if context.space_data.type == 'VIEW_3D':
if context.engine != 'KROM_VIEWPORT':
try:
if self._engine:
self._engine._set_input_enabled(False)
except ReferenceError:
pass
_input_operator_running = False
return {'CANCELLED'}
current_engine = self._get_engine_for_area(context, event)
if current_engine:
self._engine = current_engine
engine, area, region = result
self._engine = engine
try:
if not self._engine or not self._engine._initialized:
@ -1026,37 +1135,19 @@ class KROM_OT_viewport_input(bpy.types.Operator):
_input_operator_running = False
return {'CANCELLED'}
region = context.region
if not region:
return {'PASS_THROUGH'}
# Check if mouse is over a UI region (toolbar, header, panel)
mouse_x_abs = event.mouse_x
mouse_y_abs = event.mouse_y
for rgn in area.regions:
if rgn.type == 'WINDOW':
continue
if (rgn.x <= mouse_x_abs <= rgn.x + rgn.width and
rgn.y <= mouse_y_abs <= rgn.y + rgn.height):
return {'PASS_THROUGH'}
if region.type != 'WINDOW':
return {'PASS_THROUGH'}
mouse_in_region = (0 <= event.mouse_region_x <= region.width and
0 <= event.mouse_region_y <= region.height)
if not mouse_in_region:
return {'PASS_THROUGH'}
area = context.area
if area:
mouse_x_abs = event.mouse_x
mouse_y_abs = event.mouse_y
for rgn in area.regions:
# skip the main window region where we actually want input
if rgn.type == 'WINDOW':
continue
# check if mouse is over toolbar, header, UI panel, etc.
if (rgn.x <= mouse_x_abs <= rgn.x + rgn.width and
rgn.y <= mouse_y_abs <= rgn.y + rgn.height):
# mouse is over a UI region - pass through
return {'PASS_THROUGH'}
mouse_x = event.mouse_region_x
# flip Y for Krom
mouse_y = region.height - event.mouse_region_y
# Compute mouse position relative to the WINDOW region
mouse_x = mouse_x_abs - region.x
mouse_y = region.height - (mouse_y_abs - region.y)
try:
if event.type == 'MOUSEMOVE':
@ -1128,49 +1219,63 @@ class KROM_OT_viewport_input(bpy.types.Operator):
}
return key_map.get(blender_key)
def _get_engine_for_area(self, context, event=None):
"""Get the correct engine for the viewport under the mouse cursor."""
# Use mouse coordinates to find which viewport the mouse is over
if event:
mouse_x = event.mouse_x
mouse_y = event.mouse_y
def _find_viewport_under_mouse(self, event):
"""Find the area, WINDOW region, and engine under the mouse cursor.
Returns (engine, area, region) or None."""
mouse_x = event.mouse_x
mouse_y = event.mouse_y
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type != 'VIEW_3D':
continue
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type != 'VIEW_3D':
continue
if (area.x <= mouse_x < area.x + area.width and
if not (area.x <= mouse_x < area.x + area.width and
area.y <= mouse_y < area.y + area.height):
continue
for space in area.spaces:
if space.type == 'VIEW_3D':
try:
viewport_id = hex(space.as_pointer())[-6:]
if viewport_id in _active_krom_engines:
return _active_krom_engines[viewport_id]
except:
pass
# Find the WINDOW region for this area
win_region = None
for rgn in area.regions:
if rgn.type == 'WINDOW':
win_region = rgn
break
if context.space_data:
try:
viewport_id = hex(context.space_data.as_pointer())[-6:]
if viewport_id in _active_krom_engines:
return _active_krom_engines[viewport_id]
except:
pass
if not win_region:
continue
if _active_viewport_id and _active_viewport_id in _active_krom_engines:
return _active_krom_engines[_active_viewport_id]
# Check if mouse is actually within the WINDOW region bounds
if not (win_region.x <= mouse_x < win_region.x + win_region.width and
win_region.y <= mouse_y < win_region.y + win_region.height):
continue
return _active_krom_engine
for space in area.spaces:
if space.type == 'VIEW_3D':
try:
viewport_id = hex(space.as_pointer())[-6:]
if viewport_id in _active_krom_engines:
return (_active_krom_engines[viewport_id], area, win_region)
except:
pass
break
return None
def invoke(self, context, event):
self._engine = self._get_engine_for_area(context)
if not self._engine:
result = self._find_viewport_under_mouse(event)
if result is None:
# Fallback: use context's viewport
if context.space_data:
try:
viewport_id = hex(context.space_data.as_pointer())[-6:]
if viewport_id in _active_krom_engines:
self._engine = _active_krom_engines[viewport_id]
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
except:
pass
return {'CANCELLED'}
self._engine = result[0]
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
@ -1192,8 +1297,12 @@ def _start_input_capture():
for region in area.regions:
if region.type == 'WINDOW':
override = {'window': window, 'area': area, 'region': region}
with bpy.context.temp_override(**override):
bpy.ops.krom.viewport_input('INVOKE_DEFAULT')
if bpy.app.version >= (3, 0, 0):
with bpy.context.temp_override(**override):
bpy.ops.krom.viewport_input('INVOKE_DEFAULT')
_input_operator_running = True
else:
bpy.ops.krom.viewport_input(override, 'INVOKE_DEFAULT')
_input_operator_running = True
return None
except Exception as e:
@ -1208,7 +1317,9 @@ def get_panels():
'VIEWLAYER_PT_layer_passes',
}
compatible_engines = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
compatible_engines = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
if bpy.app.version >= (4, 2, 0):
compatible_engines.add('BLENDER_EEVEE_NEXT')
panels = []
for panel in bpy.types.Panel.__subclasses__():
@ -1262,6 +1373,12 @@ class KROM_OT_stop_viewport(bpy.types.Operator):
make.stop_viewport(viewport_id)
global _active_krom_engines
if viewport_id in _active_krom_engines:
engine = _active_krom_engines[viewport_id]
try:
engine._stop_krom_process()
engine._restore_overlay(context)
except:
pass
del _active_krom_engines[viewport_id]
if context.space_data and hasattr(context.space_data, 'shading'):
context.space_data.shading.type = 'SOLID'