forked from LeenkxTeam/LNXSDK
Patch_5
This commit is contained in:
@ -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'
|
||||
|
||||
Reference in New Issue
Block a user