merge upstream
This commit is contained in:
		@ -324,6 +324,20 @@ class LeenkxExporter:
 | 
			
		||||
    def export_object_transform(self, bobject: bpy.types.Object, o):
 | 
			
		||||
        wrd = bpy.data.worlds['Lnx']
 | 
			
		||||
 | 
			
		||||
        # HACK: In Blender 4.2.x, each camera must be selected to ensure its matrix is correctly assigned
 | 
			
		||||
        if bpy.app.version >= (4, 2, 0) and bobject.type == 'CAMERA' and bobject.users_scene:
 | 
			
		||||
            current_scene = bpy.context.window.scene
 | 
			
		||||
 | 
			
		||||
            bpy.context.window.scene = bobject.users_scene[0]
 | 
			
		||||
            bpy.context.view_layer.update()
 | 
			
		||||
 | 
			
		||||
            bobject.select_set(True)
 | 
			
		||||
            bpy.context.view_layer.update()
 | 
			
		||||
            bobject.select_set(False)
 | 
			
		||||
 | 
			
		||||
            bpy.context.window.scene = current_scene
 | 
			
		||||
            bpy.context.view_layer.update()
 | 
			
		||||
 | 
			
		||||
        # Static transform
 | 
			
		||||
        o['transform'] = {'values': LeenkxExporter.write_matrix(bobject.matrix_local)}
 | 
			
		||||
 | 
			
		||||
@ -1552,8 +1566,7 @@ class LeenkxExporter:
 | 
			
		||||
                        log.error(e.message)
 | 
			
		||||
                    else:
 | 
			
		||||
                        # Assume it was caused because of encountering n-gons
 | 
			
		||||
                        log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping.
 | 
			
		||||
Make sure the mesh only has tris/quads.""")
 | 
			
		||||
                        log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping. Make sure the mesh only has tris/quads.""")
 | 
			
		||||
 | 
			
		||||
                tangdata = np.empty(num_verts * 3, dtype='<f4')
 | 
			
		||||
        if has_col:
 | 
			
		||||
@ -3026,16 +3039,16 @@ Make sure the mesh only has tris/quads.""")
 | 
			
		||||
            if rbw is not None and rbw.enabled:
 | 
			
		||||
                out_trait['parameters'] = [str(rbw.time_scale), str(rbw.substeps_per_frame), str(rbw.solver_iterations)]
 | 
			
		||||
 | 
			
		||||
                if phys_pkg == 'bullet':
 | 
			
		||||
                    debug_draw_mode = 1 if wrd.lnx_bullet_dbg_draw_wireframe else 0
 | 
			
		||||
                    debug_draw_mode |= 2 if wrd.lnx_bullet_dbg_draw_aabb else 0
 | 
			
		||||
                    debug_draw_mode |= 8 if wrd.lnx_bullet_dbg_draw_contact_points else 0
 | 
			
		||||
                    debug_draw_mode |= 2048 if wrd.lnx_bullet_dbg_draw_constraints else 0
 | 
			
		||||
                    debug_draw_mode |= 4096 if wrd.lnx_bullet_dbg_draw_constraint_limits else 0
 | 
			
		||||
                    debug_draw_mode |= 16384 if wrd.lnx_bullet_dbg_draw_normals else 0
 | 
			
		||||
                    debug_draw_mode |= 32768 if wrd.lnx_bullet_dbg_draw_axis_gizmo else 0
 | 
			
		||||
                if phys_pkg == 'bullet' or phys_pkg == 'oimo':
 | 
			
		||||
                    debug_draw_mode = 1 if wrd.lnx_physics_dbg_draw_wireframe else 0
 | 
			
		||||
                    debug_draw_mode |= 2 if wrd.lnx_physics_dbg_draw_aabb else 0
 | 
			
		||||
                    debug_draw_mode |= 8 if wrd.lnx_physics_dbg_draw_contact_points else 0
 | 
			
		||||
                    debug_draw_mode |= 2048 if wrd.lnx_physics_dbg_draw_constraints else 0
 | 
			
		||||
                    debug_draw_mode |= 4096 if wrd.lnx_physics_dbg_draw_constraint_limits else 0
 | 
			
		||||
                    debug_draw_mode |= 16384 if wrd.lnx_physics_dbg_draw_normals else 0
 | 
			
		||||
                    debug_draw_mode |= 32768 if wrd.lnx_physics_dbg_draw_axis_gizmo else 0
 | 
			
		||||
                    debug_draw_mode |= 65536 if wrd.lnx_physics_dbg_draw_raycast else 0
 | 
			
		||||
                    out_trait['parameters'].append(str(debug_draw_mode))
 | 
			
		||||
                    out_trait['parameters'].append(str(wrd.lnx_bullet_dbg_draw_raycast).lower())
 | 
			
		||||
 | 
			
		||||
            self.output['traits'].append(out_trait)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								leenkx/blender/lnx/logicnode/array/LN_array_index_list.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								leenkx/blender/lnx/logicnode/array/LN_array_index_list.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
class ArrayIndexNode(LnxLogicTreeNode):
 | 
			
		||||
    """Returns the array index list of the given value as an array."""
 | 
			
		||||
    bl_idname = 'LNArrayIndexListNode'
 | 
			
		||||
    bl_label = 'Array Index List'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketArray', 'Array')
 | 
			
		||||
        self.add_input('LnxDynamicSocket', 'Value')
 | 
			
		||||
 | 
			
		||||
        self.add_output('LnxNodeSocketArray', 'Array')
 | 
			
		||||
@ -170,32 +170,64 @@ vec3 random3(const vec3 c) {
 | 
			
		||||
    r.y = fract(512.0 * j);
 | 
			
		||||
    return r - 0.5;
 | 
			
		||||
}
 | 
			
		||||
float tex_musgrave_f(const vec3 p) {
 | 
			
		||||
 | 
			
		||||
float noise_tex(const vec3 p) {
 | 
			
		||||
    const float F3 = 0.3333333;
 | 
			
		||||
    const float G3 = 0.1666667;
 | 
			
		||||
 | 
			
		||||
    vec3 s = floor(p + dot(p, vec3(F3)));
 | 
			
		||||
    vec3 x = p - s + dot(s, vec3(G3));
 | 
			
		||||
    vec3 e = step(vec3(0.0), x - x.yzx);
 | 
			
		||||
    vec3 i1 = e*(1.0 - e.zxy);
 | 
			
		||||
    vec3 i2 = 1.0 - e.zxy*(1.0 - e);
 | 
			
		||||
    vec3 i1 = e * (1.0 - e.zxy);
 | 
			
		||||
    vec3 i2 = 1.0 - e.zxy * (1.0 - e);
 | 
			
		||||
 | 
			
		||||
    vec3 x1 = x - i1 + G3;
 | 
			
		||||
    vec3 x2 = x - i2 + 2.0*G3;
 | 
			
		||||
    vec3 x3 = x - 1.0 + 3.0*G3;
 | 
			
		||||
    vec4 w, d;
 | 
			
		||||
    w.x = dot(x, x);
 | 
			
		||||
    w.y = dot(x1, x1);
 | 
			
		||||
    w.z = dot(x2, x2);
 | 
			
		||||
    w.w = dot(x3, x3);
 | 
			
		||||
    w = max(0.6 - w, 0.0);
 | 
			
		||||
    vec3 x2 = x - i2 + 2.0 * G3;
 | 
			
		||||
    vec3 x3 = x - 1.0 + 3.0 * G3;
 | 
			
		||||
 | 
			
		||||
    vec4 w;
 | 
			
		||||
    w.x = max(0.6 - dot(x, x), 0.0);
 | 
			
		||||
    w.y = max(0.6 - dot(x1, x1), 0.0);
 | 
			
		||||
    w.z = max(0.6 - dot(x2, x2), 0.0);
 | 
			
		||||
    w.w = max(0.6 - dot(x3, x3), 0.0);
 | 
			
		||||
 | 
			
		||||
    w = w * w;
 | 
			
		||||
    w = w * w;
 | 
			
		||||
 | 
			
		||||
    vec4 d;
 | 
			
		||||
    d.x = dot(random3(s), x);
 | 
			
		||||
    d.y = dot(random3(s + i1), x1);
 | 
			
		||||
    d.z = dot(random3(s + i2), x2);
 | 
			
		||||
    d.w = dot(random3(s + 1.0), x3);
 | 
			
		||||
    w *= w;
 | 
			
		||||
    w *= w;
 | 
			
		||||
 | 
			
		||||
    d *= w;
 | 
			
		||||
    return clamp(dot(d, vec4(52.0)), 0.0, 1.0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float tex_musgrave_f(const vec3 p, float detail, float distortion) {
 | 
			
		||||
    // Apply distortion to the input coordinates smoothly with noise_tex
 | 
			
		||||
    vec3 distorted_p = p + distortion * vec3(
 | 
			
		||||
        noise_tex(p + vec3(5.2, 1.3, 7.1)),
 | 
			
		||||
        noise_tex(p + vec3(1.7, 9.2, 3.8)),
 | 
			
		||||
        noise_tex(p + vec3(8.3, 2.8, 4.5))
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    float value = 0.0;
 | 
			
		||||
    float amplitude = 1.0;
 | 
			
		||||
    float frequency = 1.0;
 | 
			
		||||
 | 
			
		||||
    // Use 'detail' as number of octaves, clamped between 1 and 8
 | 
			
		||||
    int octaves = int(clamp(detail, 1.0, 8.0));
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < octaves; i++) {
 | 
			
		||||
        value += amplitude * noise_tex(distorted_p * frequency);
 | 
			
		||||
        frequency *= 2.0;
 | 
			
		||||
        amplitude *= 0.5;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return clamp(value, 0.0, 1.0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# col: the incoming color
 | 
			
		||||
 | 
			
		||||
@ -254,10 +254,10 @@ if bpy.app.version < (4, 1, 0):
 | 
			
		||||
            co = 'bposition'
 | 
			
		||||
    
 | 
			
		||||
        scale = c.parse_value_input(node.inputs['Scale'])
 | 
			
		||||
        # detail = c.parse_value_input(node.inputs[2])
 | 
			
		||||
        # distortion = c.parse_value_input(node.inputs[3])
 | 
			
		||||
    
 | 
			
		||||
        res = f'tex_musgrave_f({co} * {scale} * 0.5)'
 | 
			
		||||
        detail = c.parse_value_input(node.inputs[3])
 | 
			
		||||
        distortion = c.parse_value_input(node.inputs[4])
 | 
			
		||||
 | 
			
		||||
        res = f'tex_musgrave_f({co} * {scale} * 0.5, {detail}, {distortion})'
 | 
			
		||||
    
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
@ -278,11 +278,11 @@ def parse_tex_noise(node: bpy.types.ShaderNodeTexNoise, out_socket: bpy.types.No
 | 
			
		||||
    distortion = c.parse_value_input(node.inputs[5])
 | 
			
		||||
    if bpy.app.version >= (4, 1, 0):
 | 
			
		||||
        if node.noise_type == "FBM":
 | 
			
		||||
            state.curshader.add_function(c_functions.str_tex_musgrave)
 | 
			
		||||
            if out_socket == node.outputs[1]:
 | 
			
		||||
                state.curshader.add_function(c_functions.str_tex_musgrave)
 | 
			
		||||
                res = 'vec3(tex_musgrave_f({0} * {1}), tex_musgrave_f({0} * {1} + 120.0), tex_musgrave_f({0} * {1} + 168.0))'.format(co, scale, detail, distortion)
 | 
			
		||||
                res = 'vec3(tex_musgrave_f({0} * {1}, {2}, {3}), tex_musgrave_f({0} * {1} + 120.0, {2}, {3}), tex_musgrave_f({0} * {1} + 168.0, {2}, {3}))'.format(co, scale, detail, distortion)
 | 
			
		||||
            else:
 | 
			
		||||
                res = f'tex_musgrave_f({co} * {scale} * 1.0)'
 | 
			
		||||
                res = f'tex_musgrave_f({co} * {scale} * 1.0, {detail}, {distortion})'
 | 
			
		||||
        else:
 | 
			
		||||
            if out_socket == node.outputs[1]:
 | 
			
		||||
                res = 'vec3(tex_noise({0} * {1},{2},{3}), tex_noise({0} * {1} + 120.0,{2},{3}), tex_noise({0} * {1} + 168.0,{2},{3}))'.format(co, scale, detail, distortion)
 | 
			
		||||
 | 
			
		||||
@ -86,7 +86,7 @@ def write(vert, particle_info=None, shadowmap=False):
 | 
			
		||||
        vert.write('p_fade = sin(min((p_age / 2) * 3.141592, 3.141592));')
 | 
			
		||||
 | 
			
		||||
    if out_index:
 | 
			
		||||
        vert.add_out('float p_index');
 | 
			
		||||
        vert.add_out('float p_index')
 | 
			
		||||
        vert.write('p_index = gl_InstanceID;')
 | 
			
		||||
 | 
			
		||||
def write_tilesheet(vert):
 | 
			
		||||
 | 
			
		||||
@ -209,7 +209,8 @@ def make_instancing_and_skinning(mat: Material, mat_users: Dict[Material, List[O
 | 
			
		||||
                    global_elems.append({'name': 'ipos', 'data': 'float3'})
 | 
			
		||||
                    if 'Rot' in inst:
 | 
			
		||||
                        global_elems.append({'name': 'irot', 'data': 'float3'})
 | 
			
		||||
                    if 'Scale' in inst:
 | 
			
		||||
                    #HACK: checking `mat.arm_particle_flag` to force appending 'iscl' to the particle's vertex shader
 | 
			
		||||
                    if 'Scale' in inst or mat.arm_particle_flag:
 | 
			
		||||
                        global_elems.append({'name': 'iscl', 'data': 'float3'})
 | 
			
		||||
 | 
			
		||||
            elif inst == 'Off':
 | 
			
		||||
 | 
			
		||||
@ -77,6 +77,25 @@ class LNX_MT_NodeAddOverride(bpy.types.Menu):
 | 
			
		||||
                layout.separator()
 | 
			
		||||
                layout.menu(f'LNX_MT_{INTERNAL_GROUPS_MENU_ID}_menu', text=internal_groups_menu_class.bl_label, icon='OUTLINER_OB_GROUP_INSTANCE')
 | 
			
		||||
 | 
			
		||||
        elif context.space_data.tree_type == 'ShaderNodeTree':
 | 
			
		||||
            # TO DO - Recursively gather nodes and draw them to menu
 | 
			
		||||
 | 
			
		||||
            LNX_MT_NodeAddOverride.overridden_draw(self, context)
 | 
			
		||||
            
 | 
			
		||||
            layout = self.layout
 | 
			
		||||
            layout.separator()
 | 
			
		||||
            layout.separator()
 | 
			
		||||
            col = layout.column()
 | 
			
		||||
            col.label(text="Custom")
 | 
			
		||||
            
 | 
			
		||||
            shader_data_op = col.operator("node.add_node", text="Shader Data")
 | 
			
		||||
            shader_data_op.type = "LnxShaderDataNode"
 | 
			
		||||
            shader_data_op.use_transform = True
 | 
			
		||||
            
 | 
			
		||||
            particle_op = col.operator("node.add_node", text="Custom Particle")
 | 
			
		||||
            particle_op.type = "LnxCustomParticleNode"
 | 
			
		||||
            particle_op.use_transform = True
 | 
			
		||||
            
 | 
			
		||||
        else:
 | 
			
		||||
            LNX_MT_NodeAddOverride.overridden_draw(self, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -197,38 +197,38 @@ def init_properties():
 | 
			
		||||
        items=[('Bullet', 'Bullet', 'Bullet'),
 | 
			
		||||
               ('Oimo', 'Oimo', 'Oimo')],
 | 
			
		||||
        name="Physics Engine", default='Bullet', update=assets.invalidate_compiler_cache)
 | 
			
		||||
    bpy.types.World.lnx_bullet_dbg_draw_wireframe = BoolProperty(
 | 
			
		||||
    bpy.types.World.lnx_physics_dbg_draw_wireframe = BoolProperty(
 | 
			
		||||
        name="Collider Wireframes", default=False,
 | 
			
		||||
        description="Draw wireframes of the physics collider meshes and suspensions of raycast vehicle simulations"
 | 
			
		||||
    )
 | 
			
		||||
    bpy.types.World.lnx_bullet_dbg_draw_raycast = BoolProperty(
 | 
			
		||||
        name="Trace Raycast", default=False,
 | 
			
		||||
    bpy.types.World.lnx_physics_dbg_draw_raycast = BoolProperty(
 | 
			
		||||
        name="Raycasts", default=False,
 | 
			
		||||
        description="Draw raycasts to trace the results"
 | 
			
		||||
    )
 | 
			
		||||
    bpy.types.World.lnx_bullet_dbg_draw_aabb = BoolProperty(
 | 
			
		||||
    bpy.types.World.lnx_physics_dbg_draw_aabb = BoolProperty(
 | 
			
		||||
        name="Axis-aligned Minimum Bounding Boxes", default=False,
 | 
			
		||||
        description="Draw axis-aligned minimum bounding boxes (AABBs) of the physics collider meshes"
 | 
			
		||||
    )
 | 
			
		||||
    bpy.types.World.lnx_bullet_dbg_draw_contact_points = BoolProperty(
 | 
			
		||||
    bpy.types.World.lnx_physics_dbg_draw_contact_points = BoolProperty(
 | 
			
		||||
        name="Contact Points", default=False,
 | 
			
		||||
        description="Visualize contact points of multiple colliders"
 | 
			
		||||
    )
 | 
			
		||||
    bpy.types.World.lnx_bullet_dbg_draw_constraints = BoolProperty(
 | 
			
		||||
    bpy.types.World.lnx_physics_dbg_draw_constraints = BoolProperty(
 | 
			
		||||
        name="Constraints", default=False,
 | 
			
		||||
        description="Draw axis gizmos for important constraint points"
 | 
			
		||||
    )
 | 
			
		||||
    bpy.types.World.lnx_bullet_dbg_draw_constraint_limits = BoolProperty(
 | 
			
		||||
    bpy.types.World.lnx_physics_dbg_draw_constraint_limits = BoolProperty(
 | 
			
		||||
        name="Constraint Limits", default=False,
 | 
			
		||||
        description="Draw additional constraint information such as distance or angle limits"
 | 
			
		||||
    )
 | 
			
		||||
    bpy.types.World.lnx_bullet_dbg_draw_normals = BoolProperty(
 | 
			
		||||
    bpy.types.World.lnx_physics_dbg_draw_normals = BoolProperty(
 | 
			
		||||
        name="Face Normals", default=False,
 | 
			
		||||
        description=(
 | 
			
		||||
            "Draw the normal vectors of the triangles of the physics collider meshes."
 | 
			
		||||
            " This only works for mesh collision shapes"
 | 
			
		||||
            " This only works with Bullet physics, for mesh collision shapes"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    bpy.types.World.lnx_bullet_dbg_draw_axis_gizmo = BoolProperty(
 | 
			
		||||
    bpy.types.World.lnx_physics_dbg_draw_axis_gizmo = BoolProperty(
 | 
			
		||||
        name="Axis Gizmos", default=False,
 | 
			
		||||
        description=(
 | 
			
		||||
            "Draw a small axis gizmo at the origin of the collision shape."
 | 
			
		||||
 | 
			
		||||
@ -1907,7 +1907,7 @@ class LNX_PT_RenderPathPostProcessPanel(bpy.types.Panel):
 | 
			
		||||
        col.prop(rpdat, "rp_bloom")
 | 
			
		||||
        _col = col.column()
 | 
			
		||||
        _col.enabled = rpdat.rp_bloom
 | 
			
		||||
        if bpy.app.version <= (4, 2, 4):
 | 
			
		||||
        if bpy.app.version < (4, 3, 0):
 | 
			
		||||
            _col.prop(rpdat, 'lnx_bloom_follow_blender')
 | 
			
		||||
            if not rpdat.lnx_bloom_follow_blender:
 | 
			
		||||
                _col.prop(rpdat, 'lnx_bloom_threshold')
 | 
			
		||||
@ -2749,20 +2749,20 @@ class LNX_PT_BulletDebugDrawingPanel(bpy.types.Panel):
 | 
			
		||||
        layout.use_property_decorate = False
 | 
			
		||||
        wrd = bpy.data.worlds['Lnx']
 | 
			
		||||
 | 
			
		||||
        if wrd.lnx_physics_engine != 'Bullet':
 | 
			
		||||
        if wrd.lnx_physics_engine != 'Bullet' and wrd.lnx_physics_engine != 'Oimo':
 | 
			
		||||
            row = layout.row()
 | 
			
		||||
            row.alert = True
 | 
			
		||||
            row.label(text="Physics debug drawing is only supported for the Bullet physics engine")
 | 
			
		||||
            row.label(text="Physics debug drawing is only supported for the Bullet and Oimo physics engines")
 | 
			
		||||
 | 
			
		||||
        col = layout.column(align=False)
 | 
			
		||||
        col.prop(wrd, "lnx_bullet_dbg_draw_wireframe")
 | 
			
		||||
        col.prop(wrd, "lnx_bullet_dbg_draw_raycast")
 | 
			
		||||
        col.prop(wrd, "lnx_bullet_dbg_draw_aabb")
 | 
			
		||||
        col.prop(wrd, "lnx_bullet_dbg_draw_contact_points")
 | 
			
		||||
        col.prop(wrd, "lnx_bullet_dbg_draw_constraints")
 | 
			
		||||
        col.prop(wrd, "lnx_bullet_dbg_draw_constraint_limits")
 | 
			
		||||
        col.prop(wrd, "lnx_bullet_dbg_draw_normals")
 | 
			
		||||
        col.prop(wrd, "lnx_bullet_dbg_draw_axis_gizmo")
 | 
			
		||||
        col.prop(wrd, "lnx_physics_dbg_draw_wireframe")
 | 
			
		||||
        col.prop(wrd, "lnx_physics_dbg_draw_raycast")
 | 
			
		||||
        col.prop(wrd, "lnx_physics_dbg_draw_aabb")
 | 
			
		||||
        col.prop(wrd, "lnx_physics_dbg_draw_contact_points")
 | 
			
		||||
        col.prop(wrd, "lnx_physics_dbg_draw_constraints")
 | 
			
		||||
        col.prop(wrd, "lnx_physics_dbg_draw_constraint_limits")
 | 
			
		||||
        col.prop(wrd, "lnx_physics_dbg_draw_normals")
 | 
			
		||||
        col.prop(wrd, "lnx_physics_dbg_draw_axis_gizmo")
 | 
			
		||||
 | 
			
		||||
def draw_custom_node_menu(self, context):
 | 
			
		||||
    """Extension of the node context menu.
 | 
			
		||||
 | 
			
		||||
@ -828,8 +828,8 @@ def check_blender_version(op: bpy.types.Operator):
 | 
			
		||||
    """Check whether the Blender version is supported by Leenkx,
 | 
			
		||||
    if not, report in UI.
 | 
			
		||||
    """
 | 
			
		||||
    if bpy.app.version[0] != 4 or bpy.app.version[1] != 2:
 | 
			
		||||
        op.report({'INFO'}, 'INFO: For Leenkx to work correctly, use a Blender LTS version such as 4.2 | 3.6 | 3.3')
 | 
			
		||||
    if bpy.app.version[:2] not in [(4, 4), (4, 2), (3, 6), (3, 3)]:
 | 
			
		||||
        op.report({'INFO'}, 'INFO: For Leenkx to work correctly, use a Blender LTS version')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_saved(self):
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user