forked from LeenkxTeam/LNXSDK
merge upstream
This commit is contained in:
@ -2843,6 +2843,18 @@ class LeenkxExporter:
|
||||
body_params['linearDeactivationThreshold'] = deact_lv
|
||||
body_params['angularDeactivationThrshold'] = deact_av
|
||||
body_params['deactivationTime'] = deact_time
|
||||
# New velocity limit properties
|
||||
body_params['linearVelocityMin'] = bobject.lnx_rb_linear_velocity_min
|
||||
body_params['linearVelocityMax'] = bobject.lnx_rb_linear_velocity_max
|
||||
body_params['angularVelocityMin'] = bobject.lnx_rb_angular_velocity_min
|
||||
body_params['angularVelocityMax'] = bobject.lnx_rb_angular_velocity_max
|
||||
# New lock properties
|
||||
body_params['lockTranslationX'] = bobject.lnx_rb_lock_translation_x
|
||||
body_params['lockTranslationY'] = bobject.lnx_rb_lock_translation_y
|
||||
body_params['lockTranslationZ'] = bobject.lnx_rb_lock_translation_z
|
||||
body_params['lockRotationX'] = bobject.lnx_rb_lock_rotation_x
|
||||
body_params['lockRotationY'] = bobject.lnx_rb_lock_rotation_y
|
||||
body_params['lockRotationZ'] = bobject.lnx_rb_lock_rotation_z
|
||||
body_flags = {}
|
||||
body_flags['animated'] = rb.kinematic
|
||||
body_flags['trigger'] = bobject.lnx_rb_trigger
|
||||
|
@ -2,7 +2,10 @@ import importlib
|
||||
import os
|
||||
import queue
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
from typing import Dict, Tuple, Callable, Set
|
||||
|
||||
import bpy
|
||||
from bpy.app.handlers import persistent
|
||||
@ -30,6 +33,10 @@ if lnx.is_reload(__name__):
|
||||
else:
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
# Module-level storage for active threads (eliminates re-queuing overhead)
|
||||
_active_threads: Dict[threading.Thread, Callable] = {}
|
||||
_last_poll_time = 0.0
|
||||
_consecutive_empty_polls = 0
|
||||
|
||||
@persistent
|
||||
def on_depsgraph_update_post(self):
|
||||
@ -135,35 +142,113 @@ def always() -> float:
|
||||
|
||||
|
||||
def poll_threads() -> float:
|
||||
"""Polls the thread callback queue and if a thread has finished, it
|
||||
is joined with the main thread and the corresponding callback is
|
||||
executed in the main thread.
|
||||
"""
|
||||
Improved thread polling with:
|
||||
- No re-queuing overhead
|
||||
- Batch processing of completed threads
|
||||
- Adaptive timing based on activity
|
||||
- Better memory management
|
||||
- Simplified logic flow
|
||||
"""
|
||||
global _last_poll_time, _consecutive_empty_polls
|
||||
current_time = time.time()
|
||||
|
||||
# Process all new threads from queue at once (batch processing)
|
||||
new_threads_added = 0
|
||||
try:
|
||||
thread, callback = make.thread_callback_queue.get(block=False)
|
||||
while True:
|
||||
thread, callback = make.thread_callback_queue.get(block=False)
|
||||
_active_threads[thread] = callback
|
||||
new_threads_added += 1
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
# Early return if no active threads
|
||||
if not _active_threads:
|
||||
_consecutive_empty_polls += 1
|
||||
# Adaptive timing: longer intervals when consistently empty
|
||||
if _consecutive_empty_polls > 10:
|
||||
return 0.5 # Back off when no activity
|
||||
return 0.25
|
||||
if thread.is_alive():
|
||||
try:
|
||||
make.thread_callback_queue.put((thread, callback), block=False)
|
||||
except queue.Full:
|
||||
return 0.5
|
||||
return 0.1
|
||||
|
||||
# Reset empty poll counter when we have active threads
|
||||
_consecutive_empty_polls = 0
|
||||
|
||||
# Find completed threads (single pass, no re-queuing)
|
||||
completed_threads = []
|
||||
for thread in list(_active_threads.keys()):
|
||||
if not thread.is_alive():
|
||||
completed_threads.append(thread)
|
||||
|
||||
# Batch process all completed threads
|
||||
if completed_threads:
|
||||
_process_completed_threads(completed_threads)
|
||||
|
||||
# Adaptive timing based on activity level
|
||||
active_count = len(_active_threads)
|
||||
if active_count == 0:
|
||||
return 0.25
|
||||
elif active_count <= 3:
|
||||
return 0.05 # Medium frequency for low activity
|
||||
else:
|
||||
return 0.01 # High frequency for high activity
|
||||
|
||||
def _process_completed_threads(completed_threads: list) -> None:
|
||||
"""Process a batch of completed threads with robust error handling."""
|
||||
for thread in completed_threads:
|
||||
callback = _active_threads.pop(thread) # Remove from tracking
|
||||
|
||||
try:
|
||||
thread.join()
|
||||
thread.join() # Should be instant since thread is dead
|
||||
callback()
|
||||
except Exception as e:
|
||||
# If there is an exception, we can no longer return the time to
|
||||
# the next call to this polling function, so to keep it running
|
||||
# we re-register it and then raise the original exception.
|
||||
try:
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
except ValueError:
|
||||
pass
|
||||
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
|
||||
# Quickly check if another thread has finished
|
||||
return 0.01
|
||||
# Robust error recovery
|
||||
_handle_callback_error(e)
|
||||
continue # Continue processing other threads
|
||||
|
||||
# Explicit cleanup for better memory management
|
||||
del thread, callback
|
||||
|
||||
def _handle_callback_error(exception: Exception) -> None:
|
||||
"""Centralized error handling with better recovery."""
|
||||
try:
|
||||
# Try to unregister existing timer
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
except ValueError:
|
||||
pass # Timer wasn't registered, that's fine
|
||||
|
||||
# Re-register timer with slightly longer interval for stability
|
||||
bpy.app.timers.register(poll_threads, first_interval=0.1, persistent=True)
|
||||
|
||||
# Re-raise the original exception after ensuring timer continuity
|
||||
raise exception
|
||||
|
||||
def cleanup_polling_system() -> None:
|
||||
"""Optional cleanup function for proper shutdown."""
|
||||
global _active_threads, _consecutive_empty_polls
|
||||
|
||||
# Wait for remaining threads to complete (with timeout)
|
||||
for thread in list(_active_threads.keys()):
|
||||
if thread.is_alive():
|
||||
thread.join(timeout=1.0) # 1 second timeout
|
||||
|
||||
# Clear tracking structures
|
||||
_active_threads.clear()
|
||||
_consecutive_empty_polls = 0
|
||||
|
||||
# Unregister timer
|
||||
try:
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def get_polling_stats() -> dict:
|
||||
"""Get statistics about the polling system for monitoring."""
|
||||
return {
|
||||
'active_threads': len(_active_threads),
|
||||
'consecutive_empty_polls': _consecutive_empty_polls,
|
||||
'thread_ids': [t.ident for t in _active_threads.keys()]
|
||||
}
|
||||
|
||||
|
||||
loaded_py_libraries: dict[str, types.ModuleType] = {}
|
||||
|
@ -2,100 +2,158 @@ from lnx.logicnode.lnx_nodes import *
|
||||
|
||||
|
||||
class MouseLookNode(LnxLogicTreeNode):
|
||||
"""Controls object rotation based on mouse movement for FPS-style camera control.
|
||||
"""MouseLookNode - Blender UI interface for FPS-style mouse look camera controller
|
||||
|
||||
This class defines the Blender node interface for the MouseLookNode logic node.
|
||||
It creates the visual node that appears in Blender's logic tree editor and
|
||||
defines all the properties that configure the mouse look behavior.
|
||||
|
||||
The node provides controls for:
|
||||
- Axis orientation configuration
|
||||
- Mouse cursor behavior
|
||||
- Movement inversion options
|
||||
- Rotation limiting/capping
|
||||
- Head rotation space behavior
|
||||
|
||||
Features:
|
||||
- Sub-pixel interpolation (always enabled) for optimal precision and smooth low-sensitivity movement
|
||||
- Resolution-adaptive scaling for consistent feel across different screen resolutions
|
||||
- Built-in resolution-adaptive scaling for consistent feel across different screen resolutions
|
||||
- Automatic physics synchronization for rigid bodies
|
||||
- Support for both single-object and dual-object (body/head) setups
|
||||
"""
|
||||
bl_idname = 'LNMouseLookNode'
|
||||
bl_label = 'Mouse Look'
|
||||
lnx_section = 'mouse'
|
||||
lnx_version = 1
|
||||
|
||||
# Blender node identification
|
||||
bl_idname = 'LNMouseLookNode' # Unique identifier for Blender's node system
|
||||
bl_label = 'Mouse Look' # Display name in node menu and header
|
||||
lnx_section = 'mouse' # Category section in node add menu
|
||||
lnx_version = 1 # Node version for compatibility tracking
|
||||
|
||||
# Front axis property
|
||||
# Property 0: Front Axis Configuration
|
||||
# Determines which 3D axis represents the "forward" direction of the character/camera
|
||||
# This affects how horizontal and vertical rotations are applied to the objects
|
||||
property0: HaxeEnumProperty(
|
||||
'property0',
|
||||
items=[('X', 'X Axis', 'X Axis as front'),
|
||||
('Y', 'Y Axis', 'Y Axis as front'),
|
||||
('Z', 'Z Axis', 'Z Axis as front')],
|
||||
name='Front', default='Y')
|
||||
items=[('X', 'X Axis', 'X Axis as front'), # X-forward (side-scrolling, specific orientations)
|
||||
('Y', 'Y Axis', 'Y Axis as front'), # Y-forward (most common for 3D games)
|
||||
('Z', 'Z Axis', 'Z Axis as front')], # Z-forward (top-down, specific orientations)
|
||||
name='Front',
|
||||
default='Y') # Y-axis is default as it's most common in 3D game development
|
||||
|
||||
# Hide Locked property
|
||||
# Property 1: Automatic Mouse Cursor Management
|
||||
# When enabled, automatically centers and locks the mouse cursor when mouse input starts
|
||||
# This is essential for FPS games to prevent cursor from leaving game window
|
||||
property1: HaxeBoolProperty(
|
||||
'property1',
|
||||
name='Hide Locked',
|
||||
description='Automatically center and lock the mouse cursor',
|
||||
default=True)
|
||||
description='Automatically center and lock the mouse cursor when mouse input begins',
|
||||
default=True) # Enabled by default for typical FPS behavior
|
||||
|
||||
# Invert X property
|
||||
# Property 2: Horizontal Movement Inversion
|
||||
# Allows users to invert horizontal mouse movement (left becomes right, right becomes left)
|
||||
# Some players prefer inverted controls for consistency with flight simulators
|
||||
property2: HaxeBoolProperty(
|
||||
'property2',
|
||||
name='Invert X',
|
||||
description='Invert horizontal mouse movement',
|
||||
default=False)
|
||||
description='Invert horizontal mouse movement - moving mouse right turns character left',
|
||||
default=False) # Most players expect non-inverted horizontal movement
|
||||
|
||||
# Invert Y property
|
||||
# Property 3: Vertical Movement Inversion
|
||||
# Allows users to invert vertical mouse movement (up becomes down, down becomes up)
|
||||
# More commonly used than horizontal inversion, especially by flight sim players
|
||||
property3: HaxeBoolProperty(
|
||||
'property3',
|
||||
name='Invert Y',
|
||||
description='Invert vertical mouse movement',
|
||||
default=False)
|
||||
description='Invert vertical mouse movement - moving mouse up looks down',
|
||||
default=False) # Most players expect non-inverted vertical movement
|
||||
|
||||
# Cap Left/Right property
|
||||
# Property 4: Horizontal Rotation Limiting
|
||||
# Prevents the character from rotating beyond specified horizontal limits
|
||||
# Useful for fixed-perspective games or when character shouldn't turn completely around
|
||||
property4: HaxeBoolProperty(
|
||||
'property4',
|
||||
name='Cap Left / Right',
|
||||
description='Limit horizontal rotation',
|
||||
default=False)
|
||||
description='Limit horizontal rotation to prevent full 360-degree turns',
|
||||
default=False) # Disabled by default - most FPS games allow full horizontal rotation
|
||||
|
||||
# Cap Up/Down property
|
||||
# Property 5: Vertical Rotation Limiting
|
||||
# Prevents looking too far up or down, simulating human neck movement limitations
|
||||
# Essential for realistic FPS games to prevent disorienting over-rotation
|
||||
property5: HaxeBoolProperty(
|
||||
'property5',
|
||||
name='Cap Up / Down',
|
||||
description='Limit vertical rotation',
|
||||
default=True)
|
||||
|
||||
# Strategy toggles
|
||||
description='Limit vertical rotation to simulate natural neck movement (±90 degrees)',
|
||||
default=True) # Enabled by default for realistic FPS behavior
|
||||
|
||||
# Property 6: Head Rotation Space Mode
|
||||
# Controls whether head rotation uses local or world space coordinates
|
||||
# Critical for preventing rotation issues when head object is child of body object
|
||||
property6: HaxeBoolProperty(
|
||||
'property6',
|
||||
name='Resolution Adaptive',
|
||||
description='Scale sensitivity based on screen resolution',
|
||||
default=False)
|
||||
name='Head Local Space',
|
||||
description='Use local space for head rotation - enable when Head is child of Body to avoid gimbal lock',
|
||||
default=False) # Disabled by default, enable when using parent-child object relationships
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def lnx_init(self, context):
|
||||
"""Initialize the node's input and output sockets
|
||||
|
||||
This method is called when the node is first created in Blender.
|
||||
It defines all the connection points (sockets) that other nodes can connect to.
|
||||
|
||||
Input Sockets:
|
||||
- Action In: Execution flow input (when this node should run)
|
||||
- Body: The main character/player object that rotates horizontally
|
||||
- Head: Optional camera/head object that rotates vertically (can be child of Body)
|
||||
- Sensitivity: Mouse sensitivity multiplier (0.5 = half sensitivity, 2.0 = double sensitivity)
|
||||
- Smoothing: Movement smoothing factor (0.0 = no smoothing, higher = more smoothing)
|
||||
|
||||
Output Sockets:
|
||||
- Action Out: Execution flow output (continues to next node after processing)
|
||||
"""
|
||||
|
||||
# Execution flow input - connects from previous logic node
|
||||
self.add_input('LnxNodeSocketAction', 'In')
|
||||
self.add_input('LnxNodeSocketObject', 'Body')
|
||||
self.add_input('LnxNodeSocketObject', 'Head')
|
||||
self.add_input('LnxFloatSocket', 'Sensitivity', default_value=0.5)
|
||||
self.add_input('LnxFloatSocket', 'Smoothing', default_value=0.0)
|
||||
|
||||
|
||||
# Object inputs - require 3D objects from the scene
|
||||
self.add_input('LnxNodeSocketObject', 'Body') # Main character object (required)
|
||||
self.add_input('LnxNodeSocketObject', 'Head') # Camera/head object (optional)
|
||||
|
||||
# Numeric inputs with sensible defaults
|
||||
self.add_input('LnxFloatSocket', 'Sensitivity', default_value=0.5) # Medium sensitivity
|
||||
self.add_input('LnxFloatSocket', 'Smoothing', default_value=0.0) # No smoothing by default
|
||||
|
||||
# Execution flow output - connects to next logic node
|
||||
self.add_output('LnxNodeSocketAction', 'Out')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0', text='Front')
|
||||
layout.prop(self, 'property1', text='Hide Locked')
|
||||
"""Draw the node's user interface in Blender's logic tree editor
|
||||
|
||||
# Invert XY section
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Invert XY:")
|
||||
row = col.row(align=True)
|
||||
row.prop(self, 'property2', text='X', toggle=True)
|
||||
row.prop(self, 'property3', text='Y', toggle=True)
|
||||
This method creates the visual controls that appear on the node in Blender.
|
||||
It organizes properties into logical groups for better usability.
|
||||
|
||||
# Cap rotations section
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, 'property4', text='Cap Left / Right')
|
||||
col.prop(self, 'property5', text='Cap Up / Down')
|
||||
Args:
|
||||
context: Blender context (current scene, selected objects, etc.)
|
||||
layout: UI layout object for arranging interface elements
|
||||
"""
|
||||
|
||||
# Separator
|
||||
layout.separator()
|
||||
# Basic configuration section
|
||||
layout.prop(self, 'property0', text='Front') # Front axis dropdown
|
||||
layout.prop(self, 'property1', text='Hide Locked') # Mouse locking checkbox
|
||||
|
||||
# Enhancement strategies section
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Enhancement Strategies:")
|
||||
col.prop(self, 'property6', text='Resolution Adaptive')
|
||||
# Movement inversion controls section
|
||||
# Group X and Y inversion together for logical organization
|
||||
col = layout.column(align=True) # Create aligned column
|
||||
col.label(text="Invert XY:") # Section header
|
||||
row = col.row(align=True) # Create horizontal row within column
|
||||
row.prop(self, 'property2', text='X', toggle=True) # X inversion toggle button
|
||||
row.prop(self, 'property3', text='Y', toggle=True) # Y inversion toggle button
|
||||
|
||||
# Rotation limiting controls section
|
||||
# Group rotation caps together since they're related functionality
|
||||
col = layout.column(align=True) # Create new aligned column
|
||||
col.prop(self, 'property4', text='Cap Left / Right') # Horizontal capping checkbox
|
||||
col.prop(self, 'property5', text='Cap Up / Down') # Vertical capping checkbox
|
||||
|
||||
# Advanced head behavior section
|
||||
# Separate advanced option that affects technical behavior
|
||||
col = layout.column(align=True) # Create new aligned column
|
||||
col.prop(self, 'property6', text='Head Local Space') # Head rotation space checkbox
|
15
leenkx/blender/lnx/logicnode/logic/LN_once.py
Normal file
15
leenkx/blender/lnx/logicnode/logic/LN_once.py
Normal file
@ -0,0 +1,15 @@
|
||||
from lnx.logicnode.lnx_nodes import *
|
||||
import bpy
|
||||
|
||||
class OnceNode(LnxLogicTreeNode):
|
||||
bl_idname = 'LNOnceNode'
|
||||
bl_label = 'Once'
|
||||
lnx_version = 1
|
||||
|
||||
def lnx_init(self, context):
|
||||
self.add_input('LnxNodeSocketAction', 'Run Once')
|
||||
self.add_input('LnxNodeSocketAction', 'Reset')
|
||||
|
||||
self.add_output('LnxNodeSocketAction', 'Out')
|
||||
|
||||
|
@ -0,0 +1,28 @@
|
||||
from lnx.logicnode.lnx_nodes import *
|
||||
import bpy
|
||||
from bpy.props import BoolProperty
|
||||
|
||||
class LNSetObjectDelayedLocationNode(LnxLogicTreeNode):
|
||||
bl_idname = 'LNSetObjectDelayedLocationNode'
|
||||
bl_label = 'Set Object Delayed Location'
|
||||
lnx_section = 'props'
|
||||
lnx_version = 1
|
||||
|
||||
use_local_space: BoolProperty(
|
||||
name="Use Local Space",
|
||||
description="Move follower in local space instead of world space",
|
||||
default=False
|
||||
)
|
||||
|
||||
def lnx_init(self, context):
|
||||
self.inputs.new('LnxNodeSocketAction', 'In')
|
||||
self.inputs.new('LnxNodeSocketObject', 'Source Object')
|
||||
self.inputs.new('LnxNodeSocketObject', 'Target Object')
|
||||
self.inputs.new('LnxFloatSocket', 'Delay')
|
||||
self.outputs.new('LnxNodeSocketAction', 'Out')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'use_local_space')
|
||||
|
||||
def draw_label(self):
|
||||
return "Set Object Delayed Location"
|
@ -369,6 +369,26 @@ def init_properties():
|
||||
default=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False),
|
||||
size=20,
|
||||
subtype='LAYER')
|
||||
|
||||
# Linear velocity limits
|
||||
bpy.types.Object.lnx_rb_linear_velocity_min = FloatProperty(name="Linear Velocity Min", description="Minimum linear velocity", default=0.0, min=0.0)
|
||||
bpy.types.Object.lnx_rb_linear_velocity_max = FloatProperty(name="Linear Velocity Max", description="Maximum linear velocity", default=0.0, min=0.0)
|
||||
|
||||
# Angular velocity limits
|
||||
bpy.types.Object.lnx_rb_angular_velocity_min = FloatProperty(name="Angular Velocity Min", description="Minimum angular velocity", default=0.0, min=0.0)
|
||||
bpy.types.Object.lnx_rb_angular_velocity_max = FloatProperty(name="Angular Velocity Max", description="Maximum angular velocity", default=0.0, min=0.0)
|
||||
|
||||
|
||||
# Lock translation axes
|
||||
bpy.types.Object.lnx_rb_lock_translation_x = BoolProperty(name="Lock Translation X", description="Lock movement along X axis", default=False)
|
||||
bpy.types.Object.lnx_rb_lock_translation_y = BoolProperty(name="Lock Translation Y", description="Lock movement along Y axis", default=False)
|
||||
bpy.types.Object.lnx_rb_lock_translation_z = BoolProperty(name="Lock Translation Z", description="Lock movement along Z axis", default=False)
|
||||
|
||||
# Lock rotation axes
|
||||
bpy.types.Object.lnx_rb_lock_rotation_x = BoolProperty(name="Lock Rotation X", description="Lock rotation around X axis", default=False)
|
||||
bpy.types.Object.lnx_rb_lock_rotation_y = BoolProperty(name="Lock Rotation Y", description="Lock rotation around Y axis", default=False)
|
||||
bpy.types.Object.lnx_rb_lock_rotation_z = BoolProperty(name="Lock Rotation Z", description="Lock rotation around Z axis", default=False)
|
||||
|
||||
bpy.types.Object.lnx_relative_physics_constraint = BoolProperty(name="Relative Physics Constraint", description="Add physics constraint relative to the parent object or collection when spawned", default=False)
|
||||
bpy.types.Object.lnx_animation_enabled = BoolProperty(name="Animation", description="Enable skinning & timeline animation", default=True)
|
||||
bpy.types.Object.lnx_tilesheet = StringProperty(name="Tilesheet", description="Set tilesheet animation", default='')
|
||||
@ -594,7 +614,7 @@ def update_leenkx_world():
|
||||
if bpy.data.filepath != '' and (file_version < sdk_version or wrd.lnx_commit != lnx_commit):
|
||||
# This allows for seamless migration from earlier versions of Leenkx
|
||||
for rp in wrd.lnx_rplist: # TODO: deprecated
|
||||
if rp.rp_gi != 'Off':
|
||||
if hasattr(rp, 'rp_gi') and rp.rp_gi != 'Off':
|
||||
rp.rp_gi = 'Off'
|
||||
rp.rp_voxels = rp.rp_gi
|
||||
|
||||
|
@ -240,6 +240,36 @@ class LNX_PT_PhysicsPropsPanel(bpy.types.Panel):
|
||||
layout.prop(obj, 'lnx_rb_linear_factor')
|
||||
layout.prop(obj, 'lnx_rb_angular_factor')
|
||||
layout.prop(obj, 'lnx_rb_angular_friction')
|
||||
|
||||
# Linear Velocity section
|
||||
layout.separator()
|
||||
layout.label(text="Linear Velocity:")
|
||||
layout.prop(obj, 'lnx_rb_linear_velocity_min')
|
||||
layout.prop(obj, 'lnx_rb_linear_velocity_max')
|
||||
|
||||
# Angular Velocity section
|
||||
layout.separator()
|
||||
layout.label(text="Angular Velocity:")
|
||||
layout.prop(obj, 'lnx_rb_angular_velocity_min')
|
||||
layout.prop(obj, 'lnx_rb_angular_velocity_max')
|
||||
|
||||
|
||||
# Lock Translation section
|
||||
layout.separator()
|
||||
layout.label(text="Lock Translation:")
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj, 'lnx_rb_lock_translation_x', text="X")
|
||||
row.prop(obj, 'lnx_rb_lock_translation_y', text="Y")
|
||||
row.prop(obj, 'lnx_rb_lock_translation_z', text="Z")
|
||||
|
||||
# Lock Rotation section
|
||||
layout.separator()
|
||||
layout.label(text="Lock Rotation:")
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj, 'lnx_rb_lock_rotation_x', text="X")
|
||||
row.prop(obj, 'lnx_rb_lock_rotation_y', text="Y")
|
||||
row.prop(obj, 'lnx_rb_lock_rotation_z', text="Z")
|
||||
|
||||
layout.prop(obj, 'lnx_rb_trigger')
|
||||
layout.prop(obj, 'lnx_rb_ccd')
|
||||
layout.prop(obj, 'lnx_rb_interpolate')
|
||||
|
Reference in New Issue
Block a user