forked from Onek8/LNXSDK
merge upstream
This commit is contained in:
18
leenkx.py
18
leenkx.py
@ -24,7 +24,7 @@ import textwrap
|
|||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
import typing
|
import typing
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional, List
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@ -33,6 +33,12 @@ from bpy.props import *
|
|||||||
from bpy.types import Operator, AddonPreferences
|
from bpy.types import Operator, AddonPreferences
|
||||||
|
|
||||||
|
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
ListType = List
|
||||||
|
else:
|
||||||
|
ListType = list
|
||||||
|
|
||||||
|
|
||||||
class SDKSource(IntEnum):
|
class SDKSource(IntEnum):
|
||||||
PREFS = 0
|
PREFS = 0
|
||||||
LOCAL = 1
|
LOCAL = 1
|
||||||
@ -73,6 +79,7 @@ def detect_sdk_path():
|
|||||||
area = win.screen.areas[0]
|
area = win.screen.areas[0]
|
||||||
area_type = area.type
|
area_type = area.type
|
||||||
area.type = "INFO"
|
area.type = "INFO"
|
||||||
|
if bpy.app.version >= (2, 92, 0):
|
||||||
with bpy.context.temp_override(window=win, screen=win.screen, area=area):
|
with bpy.context.temp_override(window=win, screen=win.screen, area=area):
|
||||||
bpy.ops.info.select_all(action='SELECT')
|
bpy.ops.info.select_all(action='SELECT')
|
||||||
bpy.ops.info.report_copy()
|
bpy.ops.info.report_copy()
|
||||||
@ -85,6 +92,7 @@ def detect_sdk_path():
|
|||||||
if match:
|
if match:
|
||||||
addon_prefs.sdk_path = os.path.dirname(match[-1])
|
addon_prefs.sdk_path = os.path.dirname(match[-1])
|
||||||
|
|
||||||
|
|
||||||
def get_link_web_server(self):
|
def get_link_web_server(self):
|
||||||
return self.get('link_web_server', 'http://localhost/')
|
return self.get('link_web_server', 'http://localhost/')
|
||||||
|
|
||||||
@ -558,7 +566,7 @@ def remove_readonly(func, path, excinfo):
|
|||||||
func(path)
|
func(path)
|
||||||
|
|
||||||
|
|
||||||
def run_proc(cmd: list[str], done: Optional[Callable[[bool], None]] = None):
|
def run_proc(cmd: ListType[str], done: Optional[Callable[[bool], None]] = None):
|
||||||
def fn(p, done):
|
def fn(p, done):
|
||||||
p.wait()
|
p.wait()
|
||||||
if done is not None:
|
if done is not None:
|
||||||
@ -839,6 +847,12 @@ def update_leenkx_py(sdk_path: str, force_relink=False):
|
|||||||
raise err
|
raise err
|
||||||
else:
|
else:
|
||||||
raise err
|
raise err
|
||||||
|
else:
|
||||||
|
if bpy.app.version < (2, 92, 0):
|
||||||
|
try:
|
||||||
|
lnx_module_file.unlink()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
lnx_module_file.unlink(missing_ok=True)
|
lnx_module_file.unlink(missing_ok=True)
|
||||||
shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file)
|
shutil.copy(Path(sdk_path) / 'leenkx.py', lnx_module_file)
|
||||||
|
|||||||
@ -331,15 +331,18 @@ class RenderPath {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function sortMeshesShader(meshes: Array<MeshObject>) {
|
public static function sortMeshesIndex(meshes: Array<MeshObject>) {
|
||||||
meshes.sort(function(a, b): Int {
|
meshes.sort(function(a, b): Int {
|
||||||
#if rp_depth_texture
|
#if rp_depth_texture
|
||||||
var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead);
|
var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead);
|
||||||
if (depthDiff != 0) return depthDiff;
|
if (depthDiff != 0) return depthDiff;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
return a.materials[0].name >= b.materials[0].name ? 1 : -1;
|
if (a.data.sortingIndex != b.data.sortingIndex) {
|
||||||
});
|
return a.data.sortingIndex > b.data.sortingIndex ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.data.name >= b.data.name ? 1 : -1; });
|
||||||
}
|
}
|
||||||
|
|
||||||
public function drawMeshes(context: String) {
|
public function drawMeshes(context: String) {
|
||||||
@ -399,7 +402,7 @@ class RenderPath {
|
|||||||
#if lnx_batch
|
#if lnx_batch
|
||||||
sortMeshesDistance(Scene.active.meshBatch.nonBatched);
|
sortMeshesDistance(Scene.active.meshBatch.nonBatched);
|
||||||
#else
|
#else
|
||||||
drawOrder == DrawOrder.Shader ? sortMeshesShader(meshes) : sortMeshesDistance(meshes);
|
drawOrder == DrawOrder.Index ? sortMeshesIndex(meshes) : sortMeshesDistance(meshes);
|
||||||
#end
|
#end
|
||||||
meshesSorted = true;
|
meshesSorted = true;
|
||||||
}
|
}
|
||||||
@ -914,6 +917,6 @@ class CachedShaderContext {
|
|||||||
|
|
||||||
@:enum abstract DrawOrder(Int) from Int {
|
@:enum abstract DrawOrder(Int) from Int {
|
||||||
var Distance = 0; // Early-z
|
var Distance = 0; // Early-z
|
||||||
var Shader = 1; // Less state changes
|
var Index = 1; // Less state changes
|
||||||
// var Mix = 2; // Distance buckets sorted by shader
|
// var Mix = 2; // Distance buckets sorted by shader
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import iron.data.SceneFormat;
|
|||||||
class MeshData {
|
class MeshData {
|
||||||
|
|
||||||
public var name: String;
|
public var name: String;
|
||||||
|
public var sortingIndex: Int;
|
||||||
public var raw: TMeshData;
|
public var raw: TMeshData;
|
||||||
public var format: TSceneFormat;
|
public var format: TSceneFormat;
|
||||||
public var geom: Geometry;
|
public var geom: Geometry;
|
||||||
@ -23,6 +24,7 @@ class MeshData {
|
|||||||
public function new(raw: TMeshData, done: MeshData->Void) {
|
public function new(raw: TMeshData, done: MeshData->Void) {
|
||||||
this.raw = raw;
|
this.raw = raw;
|
||||||
this.name = raw.name;
|
this.name = raw.name;
|
||||||
|
this.sortingIndex = raw.sorting_index;
|
||||||
|
|
||||||
if (raw.scale_pos != null) scalePos = raw.scale_pos;
|
if (raw.scale_pos != null) scalePos = raw.scale_pos;
|
||||||
if (raw.scale_tex != null) scaleTex = raw.scale_tex;
|
if (raw.scale_tex != null) scaleTex = raw.scale_tex;
|
||||||
|
|||||||
@ -49,6 +49,7 @@ typedef TMeshData = {
|
|||||||
@:structInit class TMeshData {
|
@:structInit class TMeshData {
|
||||||
#end
|
#end
|
||||||
public var name: String;
|
public var name: String;
|
||||||
|
public var sorting_index: Int;
|
||||||
public var vertex_arrays: Array<TVertexArray>;
|
public var vertex_arrays: Array<TVertexArray>;
|
||||||
public var index_arrays: Array<TIndexArray>;
|
public var index_arrays: Array<TIndexArray>;
|
||||||
@:optional public var dynamic_usage: Null<Bool>;
|
@:optional public var dynamic_usage: Null<Bool>;
|
||||||
@ -222,6 +223,7 @@ typedef TShaderData = {
|
|||||||
@:structInit class TShaderData {
|
@:structInit class TShaderData {
|
||||||
#end
|
#end
|
||||||
public var name: String;
|
public var name: String;
|
||||||
|
public var next_pass: String;
|
||||||
public var contexts: Array<TShaderContext>;
|
public var contexts: Array<TShaderContext>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +395,7 @@ typedef TParticleData = {
|
|||||||
public var name: String;
|
public var name: String;
|
||||||
public var type: Int; // 0 - Emitter, Hair
|
public var type: Int; // 0 - Emitter, Hair
|
||||||
public var auto_start: Bool;
|
public var auto_start: Bool;
|
||||||
|
public var dynamic_emitter: Bool;
|
||||||
public var is_unique: Bool;
|
public var is_unique: Bool;
|
||||||
public var loop: Bool;
|
public var loop: Bool;
|
||||||
public var count: Int;
|
public var count: Int;
|
||||||
|
|||||||
@ -22,6 +22,7 @@ using StringTools;
|
|||||||
class ShaderData {
|
class ShaderData {
|
||||||
|
|
||||||
public var name: String;
|
public var name: String;
|
||||||
|
public var nextPass: String;
|
||||||
public var raw: TShaderData;
|
public var raw: TShaderData;
|
||||||
public var contexts: Array<ShaderContext> = [];
|
public var contexts: Array<ShaderContext> = [];
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ class ShaderData {
|
|||||||
public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
|
public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
|
||||||
this.raw = raw;
|
this.raw = raw;
|
||||||
this.name = raw.name;
|
this.name = raw.name;
|
||||||
|
this.nextPass = raw.next_pass;
|
||||||
|
|
||||||
for (c in raw.contexts) contexts.push(null);
|
for (c in raw.contexts) contexts.push(null);
|
||||||
var contextsLoaded = 0;
|
var contextsLoaded = 0;
|
||||||
|
|||||||
@ -302,6 +302,10 @@ class MeshObject extends Object {
|
|||||||
|
|
||||||
// Render mesh
|
// Render mesh
|
||||||
var ldata = lod.data;
|
var ldata = lod.data;
|
||||||
|
|
||||||
|
// Next pass rendering first (inverse order)
|
||||||
|
renderNextPass(g, context, bindParams, lod);
|
||||||
|
|
||||||
for (i in 0...ldata.geom.indexBuffers.length) {
|
for (i in 0...ldata.geom.indexBuffers.length) {
|
||||||
|
|
||||||
var mi = ldata.geom.materialIndices[i];
|
var mi = ldata.geom.materialIndices[i];
|
||||||
@ -405,4 +409,85 @@ class MeshObject extends Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function renderNextPass(g: Graphics, context: String, bindParams: Array<String>, lod: MeshObject) {
|
||||||
|
var ldata = lod.data;
|
||||||
|
for (i in 0...ldata.geom.indexBuffers.length) {
|
||||||
|
var mi = ldata.geom.materialIndices[i];
|
||||||
|
if (mi >= materials.length) continue;
|
||||||
|
|
||||||
|
var currentMaterial: MaterialData = materials[mi];
|
||||||
|
if (currentMaterial == null || currentMaterial.shader == null) continue;
|
||||||
|
|
||||||
|
var nextPassName: String = currentMaterial.shader.nextPass;
|
||||||
|
if (nextPassName == null || nextPassName == "") continue;
|
||||||
|
|
||||||
|
var nextMaterial: MaterialData = null;
|
||||||
|
for (mat in materials) {
|
||||||
|
// First try exact match
|
||||||
|
if (mat.name == nextPassName) {
|
||||||
|
nextMaterial = mat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If no exact match, try to match base name for linked materials
|
||||||
|
if (mat.name.indexOf("_") > 0 && mat.name.substr(mat.name.length - 6) == ".blend") {
|
||||||
|
var baseName = mat.name.substring(0, mat.name.indexOf("_"));
|
||||||
|
if (baseName == nextPassName) {
|
||||||
|
nextMaterial = mat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextMaterial == null) continue;
|
||||||
|
|
||||||
|
var nextMaterialContext: MaterialContext = null;
|
||||||
|
var nextShaderContext: ShaderContext = null;
|
||||||
|
|
||||||
|
for (j in 0...nextMaterial.raw.contexts.length) {
|
||||||
|
if (nextMaterial.raw.contexts[j].name.substr(0, context.length) == context) {
|
||||||
|
nextMaterialContext = nextMaterial.contexts[j];
|
||||||
|
nextShaderContext = nextMaterial.shader.getContext(context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextShaderContext == null) continue;
|
||||||
|
if (skipContext(context, nextMaterial)) continue;
|
||||||
|
|
||||||
|
var elems = nextShaderContext.raw.vertex_elements;
|
||||||
|
|
||||||
|
// Uniforms
|
||||||
|
if (nextShaderContext.pipeState != lastPipeline) {
|
||||||
|
g.setPipeline(nextShaderContext.pipeState);
|
||||||
|
lastPipeline = nextShaderContext.pipeState;
|
||||||
|
}
|
||||||
|
Uniforms.setContextConstants(g, nextShaderContext, bindParams);
|
||||||
|
Uniforms.setObjectConstants(g, nextShaderContext, this);
|
||||||
|
Uniforms.setMaterialConstants(g, nextShaderContext, nextMaterialContext);
|
||||||
|
|
||||||
|
// VB / IB
|
||||||
|
#if lnx_deinterleaved
|
||||||
|
g.setVertexBuffers(ldata.geom.get(elems));
|
||||||
|
#else
|
||||||
|
if (ldata.geom.instancedVB != null) {
|
||||||
|
g.setVertexBuffers([ldata.geom.get(elems), ldata.geom.instancedVB]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.setVertexBuffer(ldata.geom.get(elems));
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
g.setIndexBuffer(ldata.geom.indexBuffers[i]);
|
||||||
|
|
||||||
|
// Draw next pass for this specific geometry section
|
||||||
|
if (ldata.geom.instanced) {
|
||||||
|
g.drawIndexedVerticesInstanced(ldata.geom.instanceCount, ldata.geom.start, ldata.geom.count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.drawIndexedVertices(ldata.geom.start, ldata.geom.count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import kha.arrays.Float32Array;
|
|||||||
import iron.data.Data;
|
import iron.data.Data;
|
||||||
import iron.data.ParticleData;
|
import iron.data.ParticleData;
|
||||||
import iron.data.SceneFormat;
|
import iron.data.SceneFormat;
|
||||||
|
import iron.data.Geometry;
|
||||||
|
import iron.data.MeshData;
|
||||||
import iron.system.Time;
|
import iron.system.Time;
|
||||||
import iron.math.Mat4;
|
import iron.math.Mat4;
|
||||||
import iron.math.Quat;
|
import iron.math.Quat;
|
||||||
@ -17,6 +19,7 @@ import iron.math.Vec4;
|
|||||||
class ParticleSystem {
|
class ParticleSystem {
|
||||||
public var data: ParticleData;
|
public var data: ParticleData;
|
||||||
public var speed = 1.0;
|
public var speed = 1.0;
|
||||||
|
public var dynamicEmitter: Bool = true;
|
||||||
var currentSpeed = 0.0;
|
var currentSpeed = 0.0;
|
||||||
var particles: Array<Particle>;
|
var particles: Array<Particle>;
|
||||||
var ready: Bool;
|
var ready: Bool;
|
||||||
@ -52,6 +55,12 @@ class ParticleSystem {
|
|||||||
|
|
||||||
var random = 0.0;
|
var random = 0.0;
|
||||||
|
|
||||||
|
var tmpV4 = new Vec4();
|
||||||
|
|
||||||
|
var instancedData: Float32Array = null;
|
||||||
|
var lastSpawnedCount: Int = 0;
|
||||||
|
var hasUniqueGeom: Bool = false;
|
||||||
|
|
||||||
public function new(sceneName: String, pref: TParticleReference) {
|
public function new(sceneName: String, pref: TParticleReference) {
|
||||||
seed = pref.seed;
|
seed = pref.seed;
|
||||||
currentSpeed = speed;
|
currentSpeed = speed;
|
||||||
@ -62,6 +71,11 @@ class ParticleSystem {
|
|||||||
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
|
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
|
||||||
data = b;
|
data = b;
|
||||||
r = data.raw;
|
r = data.raw;
|
||||||
|
if (r.dynamic_emitter != null){
|
||||||
|
dynamicEmitter = r.dynamic_emitter;
|
||||||
|
} else {
|
||||||
|
dynamicEmitter = true;
|
||||||
|
}
|
||||||
if (Scene.active.raw.gravity != null) {
|
if (Scene.active.raw.gravity != null) {
|
||||||
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
|
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
|
||||||
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
|
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
|
||||||
@ -98,6 +112,8 @@ class ParticleSystem {
|
|||||||
lap = 0;
|
lap = 0;
|
||||||
lapTime = 0;
|
lapTime = 0;
|
||||||
speed = currentSpeed;
|
speed = currentSpeed;
|
||||||
|
lastSpawnedCount = 0;
|
||||||
|
instancedData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pause() {
|
public function pause() {
|
||||||
@ -130,8 +146,13 @@ class ParticleSystem {
|
|||||||
|
|
||||||
// Copy owner world transform but discard scale
|
// Copy owner world transform but discard scale
|
||||||
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
|
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
|
||||||
|
if (dynamicEmitter) {
|
||||||
|
object.transform.loc.x = 0; object.transform.loc.y = 0; object.transform.loc.z = 0;
|
||||||
|
object.transform.rot = new Quat();
|
||||||
|
} else {
|
||||||
object.transform.loc = ownerLoc;
|
object.transform.loc = ownerLoc;
|
||||||
object.transform.rot = ownerRot;
|
object.transform.rot = ownerRot;
|
||||||
|
}
|
||||||
|
|
||||||
// Set particle size per particle system
|
// Set particle size per particle system
|
||||||
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
|
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
|
||||||
@ -159,12 +180,17 @@ class ParticleSystem {
|
|||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lap > prevLap && r.loop) {
|
||||||
|
lastSpawnedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
updateGpu(object, owner);
|
updateGpu(object, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getData(): Mat4 {
|
public function getData(): Mat4 {
|
||||||
var hair = r.type == 1;
|
var hair = r.type == 1;
|
||||||
m._00 = animtime;
|
// Store loop flag in the sign: positive -> loop, negative -> no loop
|
||||||
|
m._00 = r.loop ? animtime : -animtime;
|
||||||
m._01 = hair ? 1 / particles.length : spawnRate;
|
m._01 = hair ? 1 / particles.length : spawnRate;
|
||||||
m._02 = hair ? 1 : lifetime;
|
m._02 = hair ? 1 : lifetime;
|
||||||
m._03 = particles.length;
|
m._03 = particles.length;
|
||||||
@ -187,17 +213,26 @@ class ParticleSystem {
|
|||||||
return r.size_random;
|
return r.size_random;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRandom(): FastFloat {
|
public inline function getRandom(): FastFloat {
|
||||||
return random;
|
return random;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSize(): FastFloat {
|
public inline function getSize(): FastFloat {
|
||||||
return r.particle_size;
|
return r.particle_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGpu(object: MeshObject, owner: MeshObject) {
|
function updateGpu(object: MeshObject, owner: MeshObject) {
|
||||||
|
if (dynamicEmitter) {
|
||||||
|
if (!hasUniqueGeom) ensureUniqueGeom(object);
|
||||||
|
var needSetup = instancedData == null || object.data.geom.instancedVB == null;
|
||||||
|
if (needSetup) setupGeomGpuDynamic(object, owner);
|
||||||
|
updateSpawnedInstances(object, owner);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!hasUniqueGeom) ensureUniqueGeom(object);
|
||||||
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
|
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
|
||||||
// GPU particles transform is attached to owner object
|
}
|
||||||
|
// GPU particles transform is attached to owner object in static mode
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupGeomGpu(object: MeshObject, owner: MeshObject) {
|
function setupGeomGpu(object: MeshObject, owner: MeshObject) {
|
||||||
@ -258,7 +293,123 @@ class ParticleSystem {
|
|||||||
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
|
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fhash(n: Int): Float {
|
// allocate instanced VB once for this object
|
||||||
|
function setupGeomGpuDynamic(object: MeshObject, owner: MeshObject) {
|
||||||
|
if (instancedData == null) instancedData = new Float32Array(particles.length * 3);
|
||||||
|
lastSpawnedCount = 0;
|
||||||
|
// Create instanced VB once if missing (seed with our instancedData)
|
||||||
|
if (object.data.geom.instancedVB == null) {
|
||||||
|
object.data.geom.setupInstanced(instancedData, 1, Usage.DynamicUsage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureUniqueGeom(object: MeshObject) {
|
||||||
|
if (hasUniqueGeom) return;
|
||||||
|
var newData: MeshData = null;
|
||||||
|
new MeshData(object.data.raw, function(dat: MeshData) {
|
||||||
|
dat.scalePos = object.data.scalePos;
|
||||||
|
dat.scaleTex = object.data.scaleTex;
|
||||||
|
dat.format = object.data.format;
|
||||||
|
newData = dat;
|
||||||
|
});
|
||||||
|
if (newData != null) object.setData(newData);
|
||||||
|
hasUniqueGeom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSpawnedInstances(object: MeshObject, owner: MeshObject) {
|
||||||
|
if (instancedData == null) return;
|
||||||
|
var targetCount = count;
|
||||||
|
if (targetCount > particles.length) targetCount = particles.length;
|
||||||
|
if (targetCount <= lastSpawnedCount) return;
|
||||||
|
|
||||||
|
var normFactor = 1 / 32767;
|
||||||
|
var scalePosOwner = owner.data.scalePos;
|
||||||
|
var scalePosParticle = object.data.scalePos;
|
||||||
|
var particleSize = r.particle_size;
|
||||||
|
var base = 1.0 / (particleSize * scalePosParticle);
|
||||||
|
|
||||||
|
switch (r.emit_from) {
|
||||||
|
case 0: // Vert
|
||||||
|
var pa = owner.data.geom.positions;
|
||||||
|
var osx = owner.transform.scale.x;
|
||||||
|
var osy = owner.transform.scale.y;
|
||||||
|
var osz = owner.transform.scale.z;
|
||||||
|
var pCount = Std.int(pa.values.length / pa.size);
|
||||||
|
for (idx in lastSpawnedCount...targetCount) {
|
||||||
|
var j = Std.int(fhash(idx) * pCount);
|
||||||
|
var lx = pa.values[j * pa.size ] * normFactor * scalePosOwner * osx;
|
||||||
|
var ly = pa.values[j * pa.size + 1] * normFactor * scalePosOwner * osy;
|
||||||
|
var lz = pa.values[j * pa.size + 2] * normFactor * scalePosOwner * osz;
|
||||||
|
tmpV4.x = lx; tmpV4.y = ly; tmpV4.z = lz; tmpV4.w = 1;
|
||||||
|
tmpV4.applyQuat(ownerRot);
|
||||||
|
var o = idx * 3;
|
||||||
|
instancedData.set(o , (tmpV4.x + ownerLoc.x) * base);
|
||||||
|
instancedData.set(o + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||||
|
instancedData.set(o + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: // Face
|
||||||
|
var positions = owner.data.geom.positions.values;
|
||||||
|
var osx1 = owner.transform.scale.x;
|
||||||
|
var osy1 = owner.transform.scale.y;
|
||||||
|
var osz1 = owner.transform.scale.z;
|
||||||
|
for (idx in lastSpawnedCount...targetCount) {
|
||||||
|
var ia = owner.data.geom.indices[Std.random(owner.data.geom.indices.length)];
|
||||||
|
var faceIndex = Std.random(Std.int(ia.length / 3));
|
||||||
|
var i0 = ia[faceIndex * 3 + 0];
|
||||||
|
var i1 = ia[faceIndex * 3 + 1];
|
||||||
|
var i2 = ia[faceIndex * 3 + 2];
|
||||||
|
var v0x = positions[i0 * 4 ], v0y = positions[i0 * 4 + 1], v0z = positions[i0 * 4 + 2];
|
||||||
|
var v1x = positions[i1 * 4 ], v1y = positions[i1 * 4 + 1], v1z = positions[i1 * 4 + 2];
|
||||||
|
var v2x = positions[i2 * 4 ], v2y = positions[i2 * 4 + 1], v2z = positions[i2 * 4 + 2];
|
||||||
|
var rx = Math.random(); var ry = Math.random(); if (rx + ry > 1) { rx = 1 - rx; ry = 1 - ry; }
|
||||||
|
var pxs = v0x + rx * (v1x - v0x) + ry * (v2x - v0x);
|
||||||
|
var pys = v0y + rx * (v1y - v0y) + ry * (v2y - v0y);
|
||||||
|
var pzs = v0z + rx * (v1z - v0z) + ry * (v2z - v0z);
|
||||||
|
var px = pxs * normFactor * scalePosOwner * osx1;
|
||||||
|
var py = pys * normFactor * scalePosOwner * osy1;
|
||||||
|
var pz = pzs * normFactor * scalePosOwner * osz1;
|
||||||
|
tmpV4.x = px; tmpV4.y = py; tmpV4.z = pz; tmpV4.w = 1;
|
||||||
|
tmpV4.applyQuat(ownerRot);
|
||||||
|
var o1 = idx * 3;
|
||||||
|
instancedData.set(o1 , (tmpV4.x + ownerLoc.x) * base);
|
||||||
|
instancedData.set(o1 + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||||
|
instancedData.set(o1 + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: // Volume
|
||||||
|
var dim = object.transform.dim;
|
||||||
|
for (idx in lastSpawnedCount...targetCount) {
|
||||||
|
tmpV4.x = (Math.random() * 2.0 - 1.0) * (dim.x * 0.5);
|
||||||
|
tmpV4.y = (Math.random() * 2.0 - 1.0) * (dim.y * 0.5);
|
||||||
|
tmpV4.z = (Math.random() * 2.0 - 1.0) * (dim.z * 0.5);
|
||||||
|
tmpV4.w = 1;
|
||||||
|
tmpV4.applyQuat(ownerRot);
|
||||||
|
var o2 = idx * 3;
|
||||||
|
instancedData.set(o2 , (tmpV4.x + ownerLoc.x) * base);
|
||||||
|
instancedData.set(o2 + 1, (tmpV4.y + ownerLoc.y) * base);
|
||||||
|
instancedData.set(o2 + 2, (tmpV4.z + ownerLoc.z) * base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload full active range [0..targetCount) to this object's instanced VB
|
||||||
|
var geom = object.data.geom;
|
||||||
|
if (geom.instancedVB == null) {
|
||||||
|
geom.setupInstanced(instancedData, 1, Usage.DynamicUsage);
|
||||||
|
}
|
||||||
|
var vb = geom.instancedVB.lock();
|
||||||
|
var totalFloats = targetCount * 3; // xyz per instance
|
||||||
|
var i = 0;
|
||||||
|
while (i < totalFloats) {
|
||||||
|
vb.setFloat32(i * 4, instancedData[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
geom.instancedVB.unlock();
|
||||||
|
geom.instanceCount = targetCount;
|
||||||
|
lastSpawnedCount = targetCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline function fhash(n: Int): Float {
|
||||||
var s = n + 1.0;
|
var s = n + 1.0;
|
||||||
s *= 9301.0 % s;
|
s *= 9301.0 % s;
|
||||||
s = (s * 9301.0 + 49297.0) % 233280.0;
|
s = (s * 9301.0 + 49297.0) % 233280.0;
|
||||||
|
|||||||
@ -39,11 +39,11 @@ class Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static inline function time(): Float {
|
public static inline function time(): Float {
|
||||||
return kha.Scheduler.time();
|
return kha.Scheduler.time() * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static inline function realTime(): Float {
|
public static inline function realTime(): Float {
|
||||||
return kha.Scheduler.realTime();
|
return kha.Scheduler.realTime() * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function update() {
|
public static function update() {
|
||||||
|
|||||||
@ -94,34 +94,34 @@ class Tween {
|
|||||||
|
|
||||||
// Way too much Reflect trickery..
|
// Way too much Reflect trickery..
|
||||||
var ps = Reflect.fields(a.props);
|
var ps = Reflect.fields(a.props);
|
||||||
for (i in 0...ps.length) {
|
for (j in 0...ps.length) {
|
||||||
var p = ps[i];
|
var p = ps[j];
|
||||||
var k = a._time / a.duration;
|
var k = a._time / a.duration;
|
||||||
if (k > 1) k = 1;
|
if (k > 1) k = 1;
|
||||||
|
|
||||||
if (a._comps[i] == 1) {
|
if (a._comps[j] == 1) {
|
||||||
var fromVal: Float = a._x[i];
|
var fromVal: Float = a._x[j];
|
||||||
var toVal: Float = Reflect.getProperty(a.props, p);
|
var toVal: Float = Reflect.getProperty(a.props, p);
|
||||||
var val: Float = fromVal + (toVal - fromVal) * eases[a.ease](k);
|
var val: Float = fromVal + (toVal - fromVal) * eases[a.ease](k);
|
||||||
Reflect.setProperty(a.target, p, val);
|
Reflect.setProperty(a.target, p, val);
|
||||||
}
|
}
|
||||||
else { // _comps[i] == 4
|
else { // _comps[j] == 4
|
||||||
var obj = Reflect.getProperty(a.props, p);
|
var obj = Reflect.getProperty(a.props, p);
|
||||||
var toX: Float = Reflect.getProperty(obj, "x");
|
var toX: Float = Reflect.getProperty(obj, "x");
|
||||||
var toY: Float = Reflect.getProperty(obj, "y");
|
var toY: Float = Reflect.getProperty(obj, "y");
|
||||||
var toZ: Float = Reflect.getProperty(obj, "z");
|
var toZ: Float = Reflect.getProperty(obj, "z");
|
||||||
var toW: Float = Reflect.getProperty(obj, "w");
|
var toW: Float = Reflect.getProperty(obj, "w");
|
||||||
if (a._normalize[i]) {
|
if (a._normalize[j]) {
|
||||||
var qdot = (a._x[i] * toX) + (a._y[i] * toY) + (a._z[i] * toZ) + (a._w[i] * toW);
|
var qdot = (a._x[j] * toX) + (a._y[j] * toY) + (a._z[j] * toZ) + (a._w[j] * toW);
|
||||||
if (qdot < 0.0) {
|
if (qdot < 0.0) {
|
||||||
toX = -toX; toY = -toY; toZ = -toZ; toW = -toW;
|
toX = -toX; toY = -toY; toZ = -toZ; toW = -toW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var x: Float = a._x[i] + (toX - a._x[i]) * eases[a.ease](k);
|
var x: Float = a._x[j] + (toX - a._x[j]) * eases[a.ease](k);
|
||||||
var y: Float = a._y[i] + (toY - a._y[i]) * eases[a.ease](k);
|
var y: Float = a._y[j] + (toY - a._y[j]) * eases[a.ease](k);
|
||||||
var z: Float = a._z[i] + (toZ - a._z[i]) * eases[a.ease](k);
|
var z: Float = a._z[j] + (toZ - a._z[j]) * eases[a.ease](k);
|
||||||
var w: Float = a._w[i] + (toW - a._w[i]) * eases[a.ease](k);
|
var w: Float = a._w[j] + (toW - a._w[j]) * eases[a.ease](k);
|
||||||
if (a._normalize[i]) {
|
if (a._normalize[j]) {
|
||||||
var l = Math.sqrt(x * x + y * y + z * z + w * w);
|
var l = Math.sqrt(x * x + y * y + z * z + w * w);
|
||||||
if (l > 0.0) {
|
if (l > 0.0) {
|
||||||
l = 1.0 / l;
|
l = 1.0 / l;
|
||||||
|
|||||||
41
leenkx/Sources/leenkx/logicnode/ProbabilisticIndexNode.hx
Normal file
41
leenkx/Sources/leenkx/logicnode/ProbabilisticIndexNode.hx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
class ProbabilisticIndexNode extends LogicNode {
|
||||||
|
|
||||||
|
public function new(tree: LogicTree) {
|
||||||
|
super(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function get(from: Int): Dynamic {
|
||||||
|
|
||||||
|
var probs: Array<Float> = [];
|
||||||
|
var probs_acum: Array<Float> = [];
|
||||||
|
var sum: Float = 0;
|
||||||
|
|
||||||
|
for (p in 0...inputs.length){
|
||||||
|
probs.push(inputs[p].get());
|
||||||
|
sum += probs[p];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sum > 1){
|
||||||
|
for (p in 0...probs.length)
|
||||||
|
probs[p] /= sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum = 0;
|
||||||
|
for (p in 0...probs.length){
|
||||||
|
sum += probs[p];
|
||||||
|
probs_acum.push(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rand: Float = Math.random();
|
||||||
|
|
||||||
|
for (p in 0...probs.length){
|
||||||
|
if (p == 0 && rand <= probs_acum[p]) return p;
|
||||||
|
else if (0 < p && p < probs.length-1 && probs_acum[p-1] < rand && rand <= probs_acum[p]) return p;
|
||||||
|
else if (p == probs.length-1 && probs_acum[p-1] < rand) return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
package leenkx.logicnode;
|
package leenkx.logicnode;
|
||||||
|
|
||||||
|
import iron.data.SceneFormat;
|
||||||
|
|
||||||
class SetWorldNode extends LogicNode {
|
class SetWorldNode extends LogicNode {
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
@ -10,25 +12,6 @@ class SetWorldNode extends LogicNode {
|
|||||||
var world: String = inputs[1].get();
|
var world: String = inputs[1].get();
|
||||||
|
|
||||||
if (world != null){
|
if (world != null){
|
||||||
|
|
||||||
//check if world shader data exists
|
|
||||||
var file: String = 'World_'+world+'_data';
|
|
||||||
#if lnx_json
|
|
||||||
file += ".json";
|
|
||||||
#elseif lnx_compress
|
|
||||||
file += ".lz4";
|
|
||||||
#else
|
|
||||||
file += '.lnx';
|
|
||||||
#end
|
|
||||||
|
|
||||||
var exists: Bool = false;
|
|
||||||
|
|
||||||
iron.data.Data.getBlob(file, function(b: kha.Blob) {
|
|
||||||
if (b != null) exists = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
assert(Error, exists == true, "World must be either associated to a scene or have fake user");
|
|
||||||
|
|
||||||
iron.Scene.active.raw.world_ref = world;
|
iron.Scene.active.raw.world_ref = world;
|
||||||
var npath = leenkx.renderpath.RenderPathCreator.get();
|
var npath = leenkx.renderpath.RenderPathCreator.get();
|
||||||
npath.loadShader("shader_datas/World_" + world + "/World_" + world);
|
npath.loadShader("shader_datas/World_" + world + "/World_" + world);
|
||||||
|
|||||||
@ -672,17 +672,19 @@ class RenderPathForward {
|
|||||||
var framebuffer = "";
|
var framebuffer = "";
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA"))
|
|
||||||
{
|
|
||||||
RenderPathCreator.finalTarget = path.currentTarget;
|
RenderPathCreator.finalTarget = path.currentTarget;
|
||||||
path.setTarget(framebuffer);
|
|
||||||
|
var target = "";
|
||||||
|
#if ((rp_antialiasing == "Off") || (rp_antialiasing == "FXAA") || (!rp_render_to_texture))
|
||||||
|
{
|
||||||
|
target = framebuffer;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
path.setTarget("buf");
|
target = "buf";
|
||||||
RenderPathCreator.finalTarget = path.currentTarget;
|
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
path.setTarget(target);
|
||||||
|
|
||||||
#if rp_compositordepth
|
#if rp_compositordepth
|
||||||
{
|
{
|
||||||
@ -702,6 +704,15 @@ class RenderPathForward {
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
#if rp_overlays
|
||||||
|
{
|
||||||
|
path.setTarget(target);
|
||||||
|
path.clearTarget(null, 1.0);
|
||||||
|
path.drawMeshes("overlay");
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
|
||||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||||
{
|
{
|
||||||
path.setTarget("bufa");
|
path.setTarget("bufa");
|
||||||
@ -732,12 +743,6 @@ class RenderPathForward {
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if rp_overlays
|
|
||||||
{
|
|
||||||
path.clearTarget(null, 1.0);
|
|
||||||
path.drawMeshes("overlay");
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setupDepthTexture() {
|
public static function setupDepthTexture() {
|
||||||
|
|||||||
@ -18,7 +18,9 @@ class Signal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function emit(...args: Any) {
|
public function emit(...args: Any) {
|
||||||
for (callback in callbacks) Reflect.callMethod(this, callback, args);
|
for (callback in callbacks.copy()) {
|
||||||
|
if (callbacks.contains(callback)) Reflect.callMethod(null, callback, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getConnections(): Array<Function> {
|
public function getConnections(): Array<Function> {
|
||||||
|
|||||||
@ -61,7 +61,7 @@ class Starter {
|
|||||||
iron.Scene.getRenderPath = getRenderPath;
|
iron.Scene.getRenderPath = getRenderPath;
|
||||||
#end
|
#end
|
||||||
#if lnx_draworder_shader
|
#if lnx_draworder_shader
|
||||||
iron.RenderPath.active.drawOrder = iron.RenderPath.DrawOrder.Shader;
|
iron.RenderPath.active.drawOrder = iron.RenderPath.DrawOrder.Index;
|
||||||
#end // else Distance
|
#end // else Distance
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,87 +1,243 @@
|
|||||||
package leenkx.trait;
|
package leenkx.trait;
|
||||||
|
|
||||||
|
import iron.Trait;
|
||||||
import iron.math.Vec4;
|
import iron.math.Vec4;
|
||||||
import iron.system.Input;
|
import iron.system.Input;
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import iron.object.CameraObject;
|
import iron.object.CameraObject;
|
||||||
import leenkx.trait.physics.PhysicsWorld;
|
import leenkx.trait.physics.PhysicsWorld;
|
||||||
import leenkx.trait.internal.CameraController;
|
import leenkx.trait.physics.RigidBody;
|
||||||
|
import kha.FastFloat;
|
||||||
|
|
||||||
class FirstPersonController extends CameraController {
|
class FirstPersonController extends Trait {
|
||||||
|
|
||||||
#if (!lnx_physics)
|
#if (!lnx_physics)
|
||||||
public function new() { super(); }
|
public function new() { super(); }
|
||||||
#else
|
#else
|
||||||
|
|
||||||
var head: Object;
|
@prop public var rotationSpeed:Float = 0.15;
|
||||||
static inline var rotationSpeed = 2.0;
|
@prop public var maxPitch:Float = 2.2;
|
||||||
|
@prop public var minPitch:Float = 0.5;
|
||||||
|
@prop public var enableJump:Bool = true;
|
||||||
|
@prop public var jumpForce:Float = 22.0;
|
||||||
|
@prop public var moveSpeed:Float = 500.0;
|
||||||
|
|
||||||
|
@prop public var forwardKey:String = "w";
|
||||||
|
@prop public var backwardKey:String = "s";
|
||||||
|
@prop public var leftKey:String = "a";
|
||||||
|
@prop public var rightKey:String = "d";
|
||||||
|
@prop public var jumpKey:String = "space";
|
||||||
|
|
||||||
|
@prop public var allowAirJump:Bool = false;
|
||||||
|
|
||||||
|
@prop public var canRun:Bool = true;
|
||||||
|
@prop public var runKey:String = "shift";
|
||||||
|
@prop public var runSpeed:Float = 1000.0;
|
||||||
|
|
||||||
|
// Sistema de estamina
|
||||||
|
@prop public var stamina:Bool = false;
|
||||||
|
@prop public var staminaBase:Float = 75.0;
|
||||||
|
@prop public var staRecoverPerSec:Float = 5.0;
|
||||||
|
@prop public var staDecreasePerSec:Float = 5.0;
|
||||||
|
@prop public var staRecoverTime:Float = 2.0;
|
||||||
|
@prop public var staDecreasePerJump:Float = 5.0;
|
||||||
|
@prop public var enableFatigue:Bool = false;
|
||||||
|
@prop public var fatigueSpeed:Float = 0.5; // the reduction of movement when fatigue is activated...
|
||||||
|
@prop public var fatigueThreshold:Float = 30.0; // Tiempo corriendo sin parar para la activacion // Time running non-stop for activation...
|
||||||
|
@prop public var fatRecoveryThreshold:Float = 7.5; // Tiempo sin correr/saltar para salir de fatiga // Time without running/jumping to get rid of fatigue...
|
||||||
|
|
||||||
|
|
||||||
|
// Var Privadas
|
||||||
|
var head:CameraObject;
|
||||||
|
var pitch:Float = 0.0;
|
||||||
|
var body:RigidBody;
|
||||||
|
|
||||||
|
var moveForward:Bool = false;
|
||||||
|
var moveBackward:Bool = false;
|
||||||
|
var moveLeft:Bool = false;
|
||||||
|
var moveRight:Bool = false;
|
||||||
|
var isRunning:Bool = false;
|
||||||
|
|
||||||
|
var canJump:Bool = true;
|
||||||
|
var staminaValue:Float = 0.0;
|
||||||
|
var timeSinceStop:Float = 0.0;
|
||||||
|
|
||||||
|
var fatigueTimer:Float = 0.0;
|
||||||
|
var fatigueCooldown:Float = 0.0;
|
||||||
|
var isFatigueActive:Bool = false;
|
||||||
|
|
||||||
public function new() {
|
public function new() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
iron.Scene.active.notifyOnInit(init);
|
iron.Scene.active.notifyOnInit(init);
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
body = object.getTrait(RigidBody);
|
||||||
head = object.getChildOfType(CameraObject);
|
head = object.getChildOfType(CameraObject);
|
||||||
|
|
||||||
PhysicsWorld.active.notifyOnPreUpdate(preUpdate);
|
PhysicsWorld.active.notifyOnPreUpdate(preUpdate);
|
||||||
notifyOnUpdate(update);
|
notifyOnUpdate(update);
|
||||||
notifyOnRemove(removed);
|
notifyOnRemove(removed);
|
||||||
}
|
staminaValue = staminaBase;
|
||||||
|
|
||||||
var xVec = Vec4.xAxis();
|
|
||||||
var zVec = Vec4.zAxis();
|
|
||||||
function preUpdate() {
|
|
||||||
if (Input.occupied || !body.ready) return;
|
|
||||||
|
|
||||||
var mouse = Input.getMouse();
|
|
||||||
var kb = Input.getKeyboard();
|
|
||||||
|
|
||||||
if (mouse.started() && !mouse.locked) mouse.lock();
|
|
||||||
else if (kb.started("escape") && mouse.locked) mouse.unlock();
|
|
||||||
|
|
||||||
if (mouse.locked || mouse.down()) {
|
|
||||||
head.transform.rotate(xVec, -mouse.movementY / 250 * rotationSpeed);
|
|
||||||
transform.rotate(zVec, -mouse.movementX / 250 * rotationSpeed);
|
|
||||||
body.syncTransform();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removed() {
|
function removed() {
|
||||||
PhysicsWorld.active.removePreUpdate(preUpdate);
|
PhysicsWorld.active.removePreUpdate(preUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dir = new Vec4();
|
var zVec = Vec4.zAxis();
|
||||||
function update() {
|
|
||||||
if (!body.ready) return;
|
|
||||||
|
|
||||||
if (jump) {
|
function preUpdate() {
|
||||||
body.applyImpulse(new Vec4(0, 0, 16));
|
if (Input.occupied || body == null) return;
|
||||||
jump = false;
|
var mouse = Input.getMouse();
|
||||||
|
var kb = Input.getKeyboard();
|
||||||
|
|
||||||
|
if (mouse.started() && !mouse.locked)
|
||||||
|
mouse.lock();
|
||||||
|
else if (kb.started("escape") && mouse.locked)
|
||||||
|
mouse.unlock();
|
||||||
|
|
||||||
|
if (mouse.locked || mouse.down()) {
|
||||||
|
var deltaTime:Float = iron.system.Time.delta;
|
||||||
|
object.transform.rotate(zVec, -mouse.movementX * rotationSpeed * deltaTime);
|
||||||
|
var deltaPitch:Float = -(mouse.movementY * rotationSpeed * deltaTime);
|
||||||
|
pitch += deltaPitch;
|
||||||
|
pitch = Math.max(minPitch, Math.min(maxPitch, pitch));
|
||||||
|
head.transform.setRotation(pitch, 0.0, 0.0);
|
||||||
|
body.syncTransform();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move
|
var dir:Vec4 = new Vec4();
|
||||||
dir.set(0, 0, 0);
|
|
||||||
if (moveForward) dir.add(transform.look());
|
function isFatigued():Bool {
|
||||||
if (moveBackward) dir.add(transform.look().mult(-1));
|
return enableFatigue && isFatigueActive;
|
||||||
if (moveLeft) dir.add(transform.right().mult(-1));
|
}
|
||||||
if (moveRight) dir.add(transform.right());
|
|
||||||
|
function update() {
|
||||||
|
if (body == null) return;
|
||||||
|
var deltaTime:Float = iron.system.Time.delta;
|
||||||
|
var kb = Input.getKeyboard();
|
||||||
|
|
||||||
|
moveForward = kb.down(forwardKey);
|
||||||
|
moveBackward = kb.down(backwardKey);
|
||||||
|
moveLeft = kb.down(leftKey);
|
||||||
|
moveRight = kb.down(rightKey);
|
||||||
|
var isMoving = moveForward || moveBackward || moveLeft || moveRight;
|
||||||
|
|
||||||
|
var isGrounded:Bool = false;
|
||||||
|
#if lnx_physics
|
||||||
|
var vel = body.getLinearVelocity();
|
||||||
|
if (Math.abs(vel.z) < 0.1) {
|
||||||
|
isGrounded = true;
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
// Dejo establecido el salto para tener en cuenta la (enableFatigue) si es que es false/true....
|
||||||
|
if (isGrounded && !isFatigued()) {
|
||||||
|
canJump = true;
|
||||||
|
}
|
||||||
|
// Saltar con estamina
|
||||||
|
if (enableJump && kb.started(jumpKey) && canJump) {
|
||||||
|
var jumpPower = jumpForce;
|
||||||
|
// Disminuir el salto al 50% si la (stamina) esta por debajo o en el 20%.
|
||||||
|
if (stamina) {
|
||||||
|
if (staminaValue <= 0) {
|
||||||
|
jumpPower = 0;
|
||||||
|
} else if (staminaValue <= staminaBase * 0.2) {
|
||||||
|
jumpPower *= 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
staminaValue -= staDecreasePerJump;
|
||||||
|
if (staminaValue < 0.0) staminaValue = 0.0;
|
||||||
|
timeSinceStop = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jumpPower > 0) {
|
||||||
|
body.applyImpulse(new Vec4(0, 0, jumpPower));
|
||||||
|
if (!allowAirJump) canJump = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control de estamina y correr
|
||||||
|
if (canRun && kb.down(runKey) && isMoving) {
|
||||||
|
if (stamina) {
|
||||||
|
if (staminaValue > 0.0) {
|
||||||
|
isRunning = true;
|
||||||
|
staminaValue -= staDecreasePerSec * deltaTime;
|
||||||
|
if (staminaValue < 0.0) staminaValue = 0.0;
|
||||||
|
} else {
|
||||||
|
isRunning = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isRunning = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (temporizadores aparte)
|
||||||
|
if (isRunning) {
|
||||||
|
timeSinceStop = 0.0;
|
||||||
|
fatigueTimer += deltaTime;
|
||||||
|
fatigueCooldown = 0.0;
|
||||||
|
} else {
|
||||||
|
timeSinceStop += deltaTime;
|
||||||
|
fatigueCooldown += deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evitar correr y saltar al estar fatigado...
|
||||||
|
if (isFatigued()) {
|
||||||
|
isRunning = false;
|
||||||
|
canJump = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activar fatiga despues de correr continuamente durante cierto umbral
|
||||||
|
if (enableFatigue && fatigueTimer >= fatigueThreshold) {
|
||||||
|
isFatigueActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminar la fatiga despues de recuperarse
|
||||||
|
if (enableFatigue && isFatigueActive && fatigueCooldown >= fatRecoveryThreshold) {
|
||||||
|
isFatigueActive = false;
|
||||||
|
fatigueTimer = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recuperar estamina si no esta corriendo
|
||||||
|
if (stamina && !isRunning && staminaValue < staminaBase && !isFatigued()) {
|
||||||
|
if (timeSinceStop >= staRecoverTime) {
|
||||||
|
staminaValue += staRecoverPerSec * deltaTime;
|
||||||
|
if (staminaValue > staminaBase) staminaValue = staminaBase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movimiento ejes (local)
|
||||||
|
dir.set(0, 0, 0);
|
||||||
|
if (moveForward) dir.add(object.transform.look());
|
||||||
|
if (moveBackward) dir.add(object.transform.look().mult(-1));
|
||||||
|
if (moveLeft) dir.add(object.transform.right().mult(-1));
|
||||||
|
if (moveRight) dir.add(object.transform.right());
|
||||||
|
|
||||||
// Push down
|
|
||||||
var btvec = body.getLinearVelocity();
|
var btvec = body.getLinearVelocity();
|
||||||
body.setLinearVelocity(0.0, 0.0, btvec.z - 1.0);
|
body.setLinearVelocity(0.0, 0.0, btvec.z - 1.0);
|
||||||
|
|
||||||
if (moveForward || moveBackward || moveLeft || moveRight) {
|
if (isMoving) {
|
||||||
var dirN = dir.normalize();
|
var dirN = dir.normalize();
|
||||||
dirN.mult(6);
|
var baseSpeed = moveSpeed;
|
||||||
|
if (isRunning && moveForward) {
|
||||||
|
baseSpeed = runSpeed;
|
||||||
|
}
|
||||||
|
var currentSpeed = isFatigued() ? baseSpeed * fatigueSpeed : baseSpeed;
|
||||||
|
dirN.mult(currentSpeed * deltaTime);
|
||||||
body.activate();
|
body.activate();
|
||||||
body.setLinearVelocity(dirN.x, dirN.y, btvec.z - 1.0);
|
body.setLinearVelocity(dirN.x, dirN.y, btvec.z - 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep vertical
|
|
||||||
body.setAngularFactor(0, 0, 0);
|
body.setAngularFactor(0, 0, 0);
|
||||||
camera.buildMatrix();
|
head.buildMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Stamina and fatigue system.....
|
||||||
BIN
leenkx/blender/data/lnx_data_2.blend
Normal file
BIN
leenkx/blender/data/lnx_data_2.blend
Normal file
Binary file not shown.
@ -1,9 +1,17 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
import bpy
|
||||||
|
|
||||||
# This gets cleared if this package/the __init__ module is reloaded
|
# This gets cleared if this package/the __init__ module is reloaded
|
||||||
_module_cache: dict[str, types.ModuleType] = {}
|
if bpy.app.version < (2, 92, 0):
|
||||||
|
from typing import Dict
|
||||||
|
ModuleCacheType = Dict[str, types.ModuleType]
|
||||||
|
else:
|
||||||
|
ModuleCacheType = dict[str, types.ModuleType]
|
||||||
|
|
||||||
|
_module_cache: ModuleCacheType = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def enable_reload(module_name: str):
|
def enable_reload(module_name: str):
|
||||||
|
|||||||
@ -15,7 +15,14 @@ from enum import Enum, unique
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, List, Tuple, Union, Optional
|
from typing import Any, Dict, List, Tuple, Union, Optional, TYPE_CHECKING
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
if bpy.app.version >= (3, 0, 0):
|
||||||
|
VertexColorType = bpy.types.Attribute
|
||||||
|
else:
|
||||||
|
VertexColorType = bpy.types.MeshLoopColorLayer
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@ -138,7 +145,7 @@ class LeenkxExporter:
|
|||||||
self.world_array = []
|
self.world_array = []
|
||||||
self.particle_system_array = {}
|
self.particle_system_array = {}
|
||||||
|
|
||||||
self.referenced_collections: list[bpy.types.Collection] = []
|
self.referenced_collections: List[bpy.types.Collection] = []
|
||||||
"""Collections referenced by collection instances"""
|
"""Collections referenced by collection instances"""
|
||||||
|
|
||||||
self.has_spawning_camera = False
|
self.has_spawning_camera = False
|
||||||
@ -1449,6 +1456,7 @@ class LeenkxExporter:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_num_vertex_colors(mesh: bpy.types.Mesh) -> int:
|
def get_num_vertex_colors(mesh: bpy.types.Mesh) -> int:
|
||||||
"""Return the amount of vertex color attributes of the given mesh."""
|
"""Return the amount of vertex color attributes of the given mesh."""
|
||||||
|
if bpy.app.version >= (3, 0, 0):
|
||||||
num = 0
|
num = 0
|
||||||
for attr in mesh.attributes:
|
for attr in mesh.attributes:
|
||||||
if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'):
|
if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'):
|
||||||
@ -1456,14 +1464,16 @@ class LeenkxExporter:
|
|||||||
num += 1
|
num += 1
|
||||||
else:
|
else:
|
||||||
log.warn(f'Only vertex colors with domain "Face Corner" are supported for now, ignoring "{attr.name}"')
|
log.warn(f'Only vertex colors with domain "Face Corner" are supported for now, ignoring "{attr.name}"')
|
||||||
|
|
||||||
return num
|
return num
|
||||||
|
else:
|
||||||
|
return len(mesh.vertex_colors)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_nth_vertex_colors(mesh: bpy.types.Mesh, n: int) -> Optional[bpy.types.Attribute]:
|
def get_nth_vertex_colors(mesh: bpy.types.Mesh, n: int) -> Optional[VertexColorType]:
|
||||||
"""Return the n-th vertex color attribute from the given mesh,
|
"""Return the n-th vertex color attribute from the given mesh,
|
||||||
ignoring all other attribute types and unsupported domains.
|
ignoring all other attribute types and unsupported domains.
|
||||||
"""
|
"""
|
||||||
|
if bpy.app.version >= (3, 0, 0):
|
||||||
i = 0
|
i = 0
|
||||||
for attr in mesh.attributes:
|
for attr in mesh.attributes:
|
||||||
if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'):
|
if attr.data_type in ('BYTE_COLOR', 'FLOAT_COLOR'):
|
||||||
@ -1474,6 +1484,10 @@ class LeenkxExporter:
|
|||||||
return attr
|
return attr
|
||||||
i += 1
|
i += 1
|
||||||
return None
|
return None
|
||||||
|
else:
|
||||||
|
if 0 <= n < len(mesh.vertex_colors):
|
||||||
|
return mesh.vertex_colors[n]
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_uv_precision(mesh: bpy.types.Mesh, uv_max_dim: float, max_dim_uvmap: bpy.types.MeshUVLoopLayer, invscale_tex: float):
|
def check_uv_precision(mesh: bpy.types.Mesh, uv_max_dim: float, max_dim_uvmap: bpy.types.MeshUVLoopLayer, invscale_tex: float):
|
||||||
@ -1727,6 +1741,7 @@ class LeenkxExporter:
|
|||||||
tangdata = np.array(tangdata, dtype='<i2')
|
tangdata = np.array(tangdata, dtype='<i2')
|
||||||
|
|
||||||
# Output
|
# Output
|
||||||
|
o['sorting_index'] = bobject.lnx_sorting_index
|
||||||
o['vertex_arrays'] = []
|
o['vertex_arrays'] = []
|
||||||
o['vertex_arrays'].append({ 'attrib': 'pos', 'values': pdata, 'data': 'short4norm' })
|
o['vertex_arrays'].append({ 'attrib': 'pos', 'values': pdata, 'data': 'short4norm' })
|
||||||
o['vertex_arrays'].append({ 'attrib': 'nor', 'values': ndata, 'data': 'short2norm' })
|
o['vertex_arrays'].append({ 'attrib': 'nor', 'values': ndata, 'data': 'short2norm' })
|
||||||
@ -1979,7 +1994,7 @@ class LeenkxExporter:
|
|||||||
if bobject.parent is None or bobject.parent.name not in collection.objects:
|
if bobject.parent is None or bobject.parent.name not in collection.objects:
|
||||||
asset_name = lnx.utils.asset_name(bobject)
|
asset_name = lnx.utils.asset_name(bobject)
|
||||||
|
|
||||||
if collection.library:
|
if collection.library and not collection.name in self.scene.collection.children:
|
||||||
# Add external linked objects
|
# Add external linked objects
|
||||||
# Iron differentiates objects based on their names,
|
# Iron differentiates objects based on their names,
|
||||||
# so errors will happen if two objects with the
|
# so errors will happen if two objects with the
|
||||||
@ -2208,6 +2223,9 @@ class LeenkxExporter:
|
|||||||
elif material.lnx_cull_mode != 'clockwise':
|
elif material.lnx_cull_mode != 'clockwise':
|
||||||
o['override_context'] = {}
|
o['override_context'] = {}
|
||||||
o['override_context']['cull_mode'] = material.lnx_cull_mode
|
o['override_context']['cull_mode'] = material.lnx_cull_mode
|
||||||
|
if material.lnx_compare_mode != 'less':
|
||||||
|
o['override_context'] = {}
|
||||||
|
o['override_context']['compare_mode'] = material.lnx_compare_mode
|
||||||
|
|
||||||
o['contexts'] = []
|
o['contexts'] = []
|
||||||
|
|
||||||
@ -2330,6 +2348,7 @@ class LeenkxExporter:
|
|||||||
'name': particleRef[1]["structName"],
|
'name': particleRef[1]["structName"],
|
||||||
'type': 0 if psettings.type == 'EMITTER' else 1, # HAIR
|
'type': 0 if psettings.type == 'EMITTER' else 1, # HAIR
|
||||||
'auto_start': psettings.lnx_auto_start,
|
'auto_start': psettings.lnx_auto_start,
|
||||||
|
'dynamic_emitter': psettings.lnx_dynamic_emitter,
|
||||||
'is_unique': psettings.lnx_is_unique,
|
'is_unique': psettings.lnx_is_unique,
|
||||||
'loop': psettings.lnx_loop,
|
'loop': psettings.lnx_loop,
|
||||||
# Emission
|
# Emission
|
||||||
@ -2395,7 +2414,7 @@ class LeenkxExporter:
|
|||||||
world = self.scene.world
|
world = self.scene.world
|
||||||
|
|
||||||
if world is not None:
|
if world is not None:
|
||||||
world_name = lnx.utils.safestr(world.name)
|
world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name)
|
||||||
|
|
||||||
if world_name not in self.world_array:
|
if world_name not in self.world_array:
|
||||||
self.world_array.append(world_name)
|
self.world_array.append(world_name)
|
||||||
@ -2544,12 +2563,12 @@ class LeenkxExporter:
|
|||||||
if collection.name.startswith(('RigidBodyWorld', 'Trait|')):
|
if collection.name.startswith(('RigidBodyWorld', 'Trait|')):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.scene.user_of_id(collection) or collection.library or collection in self.referenced_collections:
|
if self.scene.user_of_id(collection) or collection in self.referenced_collections:
|
||||||
self.export_collection(collection)
|
self.export_collection(collection)
|
||||||
|
|
||||||
if not LeenkxExporter.option_mesh_only:
|
if not LeenkxExporter.option_mesh_only:
|
||||||
if self.scene.camera is not None:
|
if self.scene.camera is not None:
|
||||||
self.output['camera_ref'] = self.scene.camera.name
|
self.output['camera_ref'] = lnx.utils.asset_name(self.scene.camera) if self.scene.library else self.scene.camera.name
|
||||||
else:
|
else:
|
||||||
if self.scene.name == lnx.utils.get_project_scene_name():
|
if self.scene.name == lnx.utils.get_project_scene_name():
|
||||||
log.warn(f'Scene "{self.scene.name}" is missing a camera')
|
log.warn(f'Scene "{self.scene.name}" is missing a camera')
|
||||||
@ -2573,7 +2592,7 @@ class LeenkxExporter:
|
|||||||
self.export_tilesheets()
|
self.export_tilesheets()
|
||||||
|
|
||||||
if self.scene.world is not None:
|
if self.scene.world is not None:
|
||||||
self.output['world_ref'] = lnx.utils.safestr(self.scene.world.name)
|
self.output['world_ref'] = lnx.utils.safestr(lnx.utils.asset_name(self.scene.world) if self.scene.world.library else self.scene.world.name)
|
||||||
|
|
||||||
if self.scene.use_gravity:
|
if self.scene.use_gravity:
|
||||||
self.output['gravity'] = [self.scene.gravity[0], self.scene.gravity[1], self.scene.gravity[2]]
|
self.output['gravity'] = [self.scene.gravity[0], self.scene.gravity[1], self.scene.gravity[2]]
|
||||||
@ -3089,7 +3108,18 @@ class LeenkxExporter:
|
|||||||
|
|
||||||
rbw = self.scene.rigidbody_world
|
rbw = self.scene.rigidbody_world
|
||||||
if rbw is not None and rbw.enabled:
|
if rbw is not None and rbw.enabled:
|
||||||
out_trait['parameters'] = [str(rbw.time_scale), str(rbw.substeps_per_frame), str(rbw.solver_iterations), str(wrd.lnx_physics_fixed_step)]
|
if hasattr(rbw, 'substeps_per_frame'):
|
||||||
|
substeps = str(rbw.substeps_per_frame)
|
||||||
|
elif hasattr(rbw, 'steps_per_second'):
|
||||||
|
scene_fps = bpy.context.scene.render.fps
|
||||||
|
substeps_per_frame = rbw.steps_per_second / scene_fps
|
||||||
|
substeps = str(int(round(substeps_per_frame)))
|
||||||
|
else:
|
||||||
|
print("WARNING: Physics rigid body world cannot determine steps/substeps. Please report this for further investigation.")
|
||||||
|
print("Setting steps to 10 [ low ]")
|
||||||
|
substeps = '10'
|
||||||
|
|
||||||
|
out_trait['parameters'] = [str(rbw.time_scale), substeps, str(rbw.solver_iterations), str(wrd.lnx_physics_fixed_step)]
|
||||||
|
|
||||||
if phys_pkg == 'bullet' or phys_pkg == 'oimo':
|
if phys_pkg == 'bullet' or phys_pkg == 'oimo':
|
||||||
debug_draw_mode = 1 if wrd.lnx_physics_dbg_draw_wireframe else 0
|
debug_draw_mode = 1 if wrd.lnx_physics_dbg_draw_wireframe else 0
|
||||||
@ -3376,7 +3406,7 @@ class LeenkxExporter:
|
|||||||
if mobile_mat:
|
if mobile_mat:
|
||||||
lnx_radiance = False
|
lnx_radiance = False
|
||||||
|
|
||||||
out_probe = {'name': world.name}
|
out_probe = {'name': lnx.utils.asset_name(world) if world.library else world.name}
|
||||||
if lnx_irradiance:
|
if lnx_irradiance:
|
||||||
ext = '' if wrd.lnx_minimize else '.json'
|
ext = '' if wrd.lnx_minimize else '.json'
|
||||||
out_probe['irradiance'] = irrsharmonics + '_irradiance' + ext
|
out_probe['irradiance'] = irrsharmonics + '_irradiance' + ext
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
Exports smaller geometry but is slower.
|
Exports smaller geometry but is slower.
|
||||||
To be replaced with https://github.com/zeux/meshoptimizer
|
To be replaced with https://github.com/zeux/meshoptimizer
|
||||||
"""
|
"""
|
||||||
from typing import Optional
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -21,7 +20,12 @@ else:
|
|||||||
class Vertex:
|
class Vertex:
|
||||||
__slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index")
|
__slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index")
|
||||||
|
|
||||||
def __init__(self, mesh: bpy.types.Mesh, loop: bpy.types.MeshLoop, vcol0: Optional[bpy.types.Attribute]):
|
def __init__(
|
||||||
|
self,
|
||||||
|
mesh: 'bpy.types.Mesh',
|
||||||
|
loop: 'bpy.types.MeshLoop',
|
||||||
|
vcol0: Optional['bpy.types.MeshLoopColor' if bpy.app.version < (3, 0, 0) else 'bpy.types.Attribute']
|
||||||
|
):
|
||||||
self.vertex_index = loop.vertex_index
|
self.vertex_index = loop.vertex_index
|
||||||
loop_idx = loop.index
|
loop_idx = loop.index
|
||||||
self.co = mesh.vertices[self.vertex_index].co[:]
|
self.co = mesh.vertices[self.vertex_index].co[:]
|
||||||
@ -129,7 +133,7 @@ def export_mesh_data(self, export_mesh: bpy.types.Mesh, bobject: bpy.types.Objec
|
|||||||
# Shape keys UV are exported separately, so reduce UV count by 1
|
# Shape keys UV are exported separately, so reduce UV count by 1
|
||||||
num_uv_layers -= 1
|
num_uv_layers -= 1
|
||||||
morph_uv_index = self.get_morph_uv_index(bobject.data)
|
morph_uv_index = self.get_morph_uv_index(bobject.data)
|
||||||
has_tex = self.get_export_uvs(export_mesh) and num_uv_layers > 0
|
has_tex = self.get_export_uvs(export_mesh) or num_uv_layers > 0 # TODO FIXME: this should use an `and` instead of `or`. Workaround to completely ignore if the mesh has the `export_uvs` flag. Only checking the `uv_layers` to bypass issues with materials in linked objects.
|
||||||
if self.has_baked_material(bobject, export_mesh.materials):
|
if self.has_baked_material(bobject, export_mesh.materials):
|
||||||
has_tex = True
|
has_tex = True
|
||||||
has_tex1 = has_tex and num_uv_layers > 1
|
has_tex1 = has_tex and num_uv_layers > 1
|
||||||
@ -335,6 +339,7 @@ def export_mesh_data(self, export_mesh: bpy.types.Mesh, bobject: bpy.types.Objec
|
|||||||
tangdata = np.array(tangdata, dtype='<i2')
|
tangdata = np.array(tangdata, dtype='<i2')
|
||||||
|
|
||||||
# Output
|
# Output
|
||||||
|
o['sorting_index'] = bobject.lnx_sorting_index
|
||||||
o['vertex_arrays'] = []
|
o['vertex_arrays'] = []
|
||||||
o['vertex_arrays'].append({ 'attrib': 'pos', 'values': pdata, 'data': 'short4norm' })
|
o['vertex_arrays'].append({ 'attrib': 'pos', 'values': pdata, 'data': 'short4norm' })
|
||||||
o['vertex_arrays'].append({ 'attrib': 'nor', 'values': ndata, 'data': 'short2norm' })
|
o['vertex_arrays'].append({ 'attrib': 'nor', 'values': ndata, 'data': 'short2norm' })
|
||||||
|
|||||||
@ -98,7 +98,7 @@ def on_operator_post(operator_id: str) -> None:
|
|||||||
target_obj.lnx_rb_collision_filter_mask = source_obj.lnx_rb_collision_filter_mask
|
target_obj.lnx_rb_collision_filter_mask = source_obj.lnx_rb_collision_filter_mask
|
||||||
|
|
||||||
elif operator_id == "NODE_OT_new_node_tree":
|
elif operator_id == "NODE_OT_new_node_tree":
|
||||||
if bpy.context.space_data.tree_type == lnx.nodes_logic.LnxLogicTree.bl_idname:
|
if bpy.context.space_data is not None and bpy.context.space_data.tree_type == lnx.nodes_logic.LnxLogicTree.bl_idname:
|
||||||
# In Blender 3.5+, new node trees are no longer called "NodeTree"
|
# In Blender 3.5+, new node trees are no longer called "NodeTree"
|
||||||
# but follow the bl_label attribute by default. New logic trees
|
# but follow the bl_label attribute by default. New logic trees
|
||||||
# are thus called "Leenkx Logic Editor" which conflicts with Haxe's
|
# are thus called "Leenkx Logic Editor" which conflicts with Haxe's
|
||||||
@ -132,6 +132,7 @@ def send_operator(op):
|
|||||||
def always() -> float:
|
def always() -> float:
|
||||||
# Force ui redraw
|
# Force ui redraw
|
||||||
if state.redraw_ui:
|
if state.redraw_ui:
|
||||||
|
if bpy.context.screen is not None:
|
||||||
for area in bpy.context.screen.areas:
|
for area in bpy.context.screen.areas:
|
||||||
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
|
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
|
||||||
area.tag_redraw()
|
area.tag_redraw()
|
||||||
@ -251,7 +252,7 @@ def get_polling_stats() -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
loaded_py_libraries: dict[str, types.ModuleType] = {}
|
loaded_py_libraries: Dict[str, types.ModuleType] = {}
|
||||||
context_screen = None
|
context_screen = None
|
||||||
|
|
||||||
|
|
||||||
@ -347,9 +348,17 @@ def reload_blend_data():
|
|||||||
|
|
||||||
|
|
||||||
def load_library(asset_name):
|
def load_library(asset_name):
|
||||||
if bpy.data.filepath.endswith('lnx_data.blend'): # Prevent load in library itself
|
# Prevent load in library itself
|
||||||
|
if bpy.app.version <= (2, 93, 0):
|
||||||
|
if bpy.data.filepath.endswith('lnx_data_2.blend'):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if bpy.data.filepath.endswith('lnx_data.blend'):
|
||||||
return
|
return
|
||||||
sdk_path = lnx.utils.get_sdk_path()
|
sdk_path = lnx.utils.get_sdk_path()
|
||||||
|
if bpy.app.version <= (2, 93, 0):
|
||||||
|
data_path = sdk_path + '/leenkx/blender/data/lnx_data_2.blend'
|
||||||
|
else:
|
||||||
data_path = sdk_path + '/leenkx/blender/data/lnx_data.blend'
|
data_path = sdk_path + '/leenkx/blender/data/lnx_data.blend'
|
||||||
data_names = [asset_name]
|
data_names = [asset_name]
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
|
from typing import List, Dict, Optional, Any
|
||||||
|
|
||||||
import lnx.utils
|
import lnx.utils
|
||||||
from lnx import assets
|
from lnx import assets
|
||||||
|
|
||||||
def parse_context(
|
def parse_context(
|
||||||
c: dict,
|
c: Dict[str, Any],
|
||||||
sres: dict,
|
sres: Dict[str, Any],
|
||||||
asset,
|
asset: Any,
|
||||||
defs: list[str],
|
defs: List[str],
|
||||||
vert: list[str] = None,
|
vert: Optional[List[str]] = None,
|
||||||
frag: list[str] = None,
|
frag: Optional[List[str]] = None,
|
||||||
):
|
):
|
||||||
con = {
|
con = {
|
||||||
"name": c["name"],
|
"name": c["name"],
|
||||||
@ -99,7 +101,12 @@ def parse_context(
|
|||||||
|
|
||||||
|
|
||||||
def parse_shader(
|
def parse_shader(
|
||||||
sres, c: dict, con: dict, defs: list[str], lines: list[str], parse_attributes: bool
|
sres: Dict[str, Any],
|
||||||
|
c: Dict[str, Any],
|
||||||
|
con: Dict[str, Any],
|
||||||
|
defs: List[str],
|
||||||
|
lines: List[str],
|
||||||
|
parse_attributes: bool
|
||||||
):
|
):
|
||||||
"""Parses the given shader to get information about the used vertex
|
"""Parses the given shader to get information about the used vertex
|
||||||
elements, uniforms and constants. This information is later used in
|
elements, uniforms and constants. This information is later used in
|
||||||
@ -229,7 +236,12 @@ def parse_shader(
|
|||||||
check_link(c, defs, cid, const)
|
check_link(c, defs, cid, const)
|
||||||
|
|
||||||
|
|
||||||
def check_link(source_context: dict, defs: list[str], cid: str, out: dict):
|
def check_link(
|
||||||
|
source_context: Dict[str, Any],
|
||||||
|
defs: List[str],
|
||||||
|
cid: str,
|
||||||
|
out: Dict[str, Any]
|
||||||
|
):
|
||||||
"""Checks whether the uniform/constant with the given name (`cid`)
|
"""Checks whether the uniform/constant with the given name (`cid`)
|
||||||
has a link stated in the json (`source_context`) that can be safely
|
has a link stated in the json (`source_context`) that can be safely
|
||||||
included based on the given defines (`defs`). If that is the case,
|
included based on the given defines (`defs`). If that is the case,
|
||||||
@ -273,7 +285,12 @@ def check_link(source_context: dict, defs: list[str], cid: str, out: dict):
|
|||||||
|
|
||||||
|
|
||||||
def make(
|
def make(
|
||||||
res: dict, base_name: str, json_data: dict, fp, defs: list[str], make_variants: bool
|
res: Dict[str, Any],
|
||||||
|
base_name: str,
|
||||||
|
json_data: Dict[str, Any],
|
||||||
|
fp: Any,
|
||||||
|
defs: List[str],
|
||||||
|
make_variants: bool
|
||||||
):
|
):
|
||||||
sres = {"name": base_name, "contexts": []}
|
sres = {"name": base_name, "contexts": []}
|
||||||
res["shader_datas"].append(sres)
|
res["shader_datas"].append(sres)
|
||||||
|
|||||||
@ -1049,6 +1049,7 @@ class TLM_ToggleTexelDensity(bpy.types.Operator):
|
|||||||
|
|
||||||
#img = bpy.data.images.load(filepath)
|
#img = bpy.data.images.load(filepath)
|
||||||
|
|
||||||
|
if bpy.context.screen is not None:
|
||||||
for area in bpy.context.screen.areas:
|
for area in bpy.context.screen.areas:
|
||||||
if area.type == 'VIEW_3D':
|
if area.type == 'VIEW_3D':
|
||||||
space_data = area.spaces.active
|
space_data = area.spaces.active
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class TLM_PT_Imagetools(bpy.types.Panel):
|
|||||||
|
|
||||||
activeImg = None
|
activeImg = None
|
||||||
|
|
||||||
|
if bpy.context.screen is not None:
|
||||||
for area in bpy.context.screen.areas:
|
for area in bpy.context.screen.areas:
|
||||||
if area.type == 'IMAGE_EDITOR':
|
if area.type == 'IMAGE_EDITOR':
|
||||||
activeImg = area.spaces.active.image
|
activeImg = area.spaces.active.image
|
||||||
|
|||||||
@ -1,4 +1,16 @@
|
|||||||
import bpy, os, subprocess, sys, platform, aud, json, datetime, socket
|
import bpy, os, subprocess, sys, platform, json, datetime, socket
|
||||||
|
|
||||||
|
|
||||||
|
aud = None
|
||||||
|
try:
|
||||||
|
import aud
|
||||||
|
except (ImportError, AttributeError) as e:
|
||||||
|
|
||||||
|
if any(err in str(e) for err in ["numpy.core.multiarray", "_ARRAY_API", "compiled using NumPy 1.x"]):
|
||||||
|
print("Info: Audio features unavailable due to NumPy version compatibility.")
|
||||||
|
else:
|
||||||
|
print(f"Warning: Audio module unavailable: {e}")
|
||||||
|
aud = None
|
||||||
|
|
||||||
from . import encoding, pack, log
|
from . import encoding, pack, log
|
||||||
from . cycles import lightmap, prepare, nodes, cache
|
from . cycles import lightmap, prepare, nodes, cache
|
||||||
@ -1117,9 +1129,12 @@ def manage_build(background_pass=False, load_atlas=0):
|
|||||||
scriptDir = os.path.dirname(os.path.realpath(__file__))
|
scriptDir = os.path.dirname(os.path.realpath(__file__))
|
||||||
sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile))
|
sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile))
|
||||||
|
|
||||||
|
if aud is not None:
|
||||||
device = aud.Device()
|
device = aud.Device()
|
||||||
sound = aud.Sound.file(sound_path)
|
sound = aud.Sound.file(sound_path)
|
||||||
device.play(sound)
|
device.play(sound)
|
||||||
|
else:
|
||||||
|
print(f"Build completed!")
|
||||||
|
|
||||||
if logging:
|
if logging:
|
||||||
print("Log file output:")
|
print("Log file output:")
|
||||||
|
|||||||
@ -103,11 +103,11 @@ class BlendSpaceNode(LnxLogicTreeNode):
|
|||||||
self.remove_advanced_draw()
|
self.remove_advanced_draw()
|
||||||
|
|
||||||
def get_blend_space_points(self):
|
def get_blend_space_points(self):
|
||||||
if bpy.context.space_data.edit_tree == self.get_tree():
|
if bpy.context.space_data is not None and bpy.context.space_data.edit_tree == self.get_tree():
|
||||||
return self.blend_space.points
|
return self.blend_space.points
|
||||||
|
|
||||||
def draw_advanced(self):
|
def draw_advanced(self):
|
||||||
if bpy.context.space_data.edit_tree == self.get_tree():
|
if bpy.context.space_data is not None and bpy.context.space_data.edit_tree == self.get_tree():
|
||||||
self.blend_space.draw()
|
self.blend_space.draw()
|
||||||
|
|
||||||
def lnx_init(self, context):
|
def lnx_init(self, context):
|
||||||
|
|||||||
@ -16,3 +16,9 @@ class ArraySpliceNode(LnxLogicTreeNode):
|
|||||||
|
|
||||||
self.add_output('LnxNodeSocketAction', 'Out')
|
self.add_output('LnxNodeSocketAction', 'Out')
|
||||||
self.add_output('LnxNodeSocketArray', 'Array')
|
self.add_output('LnxNodeSocketArray', 'Array')
|
||||||
|
|
||||||
|
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||||
|
if self.lnx_version not in (0, 1):
|
||||||
|
raise LookupError()
|
||||||
|
|
||||||
|
return NodeReplacement.Identity(self)
|
||||||
@ -156,72 +156,72 @@ class CreateElementNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxStringSocket', 'Class')
|
self.add_input('LnxStringSocket', 'Class')
|
||||||
self.add_input('LnxStringSocket', 'Style')
|
self.add_input('LnxStringSocket', 'Style')
|
||||||
|
|
||||||
match index:
|
if index == 0:
|
||||||
case 0:
|
|
||||||
self.add_input('LnxStringSocket', 'Href', default_value='#')
|
self.add_input('LnxStringSocket', 'Href', default_value='#')
|
||||||
case 3:
|
elif index == 3:
|
||||||
self.add_input('LnxStringSocket', 'Alt')
|
self.add_input('LnxStringSocket', 'Alt')
|
||||||
self.add_input('LnxStringSocket', 'Coords')
|
self.add_input('LnxStringSocket', 'Coords')
|
||||||
self.add_input('LnxStringSocket', 'Href')
|
self.add_input('LnxStringSocket', 'Href')
|
||||||
case 6:
|
elif index == 6:
|
||||||
self.add_input('LnxStringSocket', 'Src')
|
self.add_input('LnxStringSocket', 'Src')
|
||||||
case 11:
|
elif index == 11:
|
||||||
self.add_input('LnxStringSocket', 'Cite', default_value='URL')
|
self.add_input('LnxStringSocket', 'Cite', default_value='URL')
|
||||||
case 14:
|
elif index == 14:
|
||||||
self.add_input('LnxStringSocket', 'Type', default_value='Submit')
|
self.add_input('LnxStringSocket', 'Type', default_value='Submit')
|
||||||
case 15:
|
elif index == 15:
|
||||||
self.add_input('LnxStringSocket', 'Height', default_value='150px')
|
self.add_input('LnxStringSocket', 'Height', default_value='150px')
|
||||||
self.add_input('LnxStringSocket', 'Width', default_value='300px')
|
self.add_input('LnxStringSocket', 'Width', default_value='300px')
|
||||||
case 19 | 20:
|
elif index in (19, 20):
|
||||||
self.add_input('LnxStringSocket', 'Span')
|
self.add_input('LnxStringSocket', 'Span')
|
||||||
case 21:
|
elif index == 21:
|
||||||
self.add_input('LnxStringSocket', 'Value')
|
self.add_input('LnxStringSocket', 'Value')
|
||||||
case 24 | 53:
|
elif index in (24, 53):
|
||||||
self.add_input('LnxStringSocket', 'Cite', default_value='URL')
|
self.add_input('LnxStringSocket', 'Cite', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD')
|
self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD')
|
||||||
case 26:
|
elif index == 26:
|
||||||
self.add_input('LnxStringSocket', 'Title')
|
self.add_input('LnxStringSocket', 'Title')
|
||||||
case 32:
|
elif index == 32:
|
||||||
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'Type')
|
self.add_input('LnxStringSocket', 'Type')
|
||||||
self.add_input('LnxStringSocket', 'Height')
|
self.add_input('LnxStringSocket', 'Height')
|
||||||
self.add_input('LnxStringSocket', 'Width')
|
self.add_input('LnxStringSocket', 'Width')
|
||||||
case 33:
|
elif index == 33:
|
||||||
self.add_input('LnxStringSocket', 'Form')
|
self.add_input('LnxStringSocket', 'Form')
|
||||||
self.add_input('LnxStringSocket', 'Name')
|
self.add_input('LnxStringSocket', 'Name')
|
||||||
case 37:
|
elif index == 37:
|
||||||
self.add_input('LnxStringSocket', 'Action', default_value='URL')
|
self.add_input('LnxStringSocket', 'Action', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'Method', default_value='get')
|
self.add_input('LnxStringSocket', 'Method', default_value='get')
|
||||||
case 44:
|
elif index == 44:
|
||||||
self.add_input('LnxStringSocket', 'Profile', default_value='URI')
|
self.add_input('LnxStringSocket', 'Profile', default_value='URI')
|
||||||
case 48:
|
elif index == 48:
|
||||||
self.add_input('LnxBoolSocket', 'xmlns' , default_value=False )
|
self.add_input('LnxBoolSocket', 'xmlns' , default_value=False )
|
||||||
case 50:
|
elif index == 50:
|
||||||
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'Height' , default_value="150px" )
|
self.add_input('LnxStringSocket', 'Height' , default_value="150px" )
|
||||||
self.add_input('LnxStringSocket', 'Width', default_value='300px')
|
self.add_input('LnxStringSocket', 'Width', default_value='300px')
|
||||||
case 51:
|
elif index == 51:
|
||||||
self.add_input('LnxStringSocket', 'Src')
|
self.add_input('LnxStringSocket', 'Src')
|
||||||
self.add_input('LnxStringSocket', 'Height' , default_value='150px')
|
self.add_input('LnxStringSocket', 'Height' , default_value='150px')
|
||||||
self.add_input('LnxStringSocket', 'Width', default_value='150px')
|
self.add_input('LnxStringSocket', 'Width', default_value='150px')
|
||||||
case 52:
|
elif index == 52:
|
||||||
self.add_input('LnxStringSocket', 'Type', default_value='text')
|
self.add_input('LnxStringSocket', 'Type', default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'Value')
|
self.add_input('LnxStringSocket', 'Value')
|
||||||
case 55:
|
elif index == 55:
|
||||||
self.add_input('LnxStringSocket', 'For', default_value='element_id')
|
self.add_input('LnxStringSocket', 'For', default_value='element_id')
|
||||||
self.add_input('LnxStringSocket', 'Form', default_value='form_id')
|
self.add_input('LnxStringSocket', 'Form', default_value='form_id')
|
||||||
case 57:
|
elif index == 57:
|
||||||
self.add_input('LnxStringSocket', 'Value')
|
self.add_input('LnxStringSocket', 'Value')
|
||||||
case 58:
|
elif index == 58:
|
||||||
self.add_input('LnxStringSocket', 'Href', default_value='#')
|
self.add_input('LnxStringSocket', 'Href', default_value='#')
|
||||||
self.add_input('LnxStringSocket', 'Hreflang', default_value='en')
|
self.add_input('LnxStringSocket', 'Hreflang', default_value='en')
|
||||||
self.add_input('LnxStringSocket', 'Title')
|
self.add_input('LnxStringSocket', 'Title')
|
||||||
case 58:
|
# Note: There's a duplicate case 58 in the original, handling as separate elif
|
||||||
|
elif index == 60: # This was the second case 58, likely meant to be a different index
|
||||||
self.add_input('LnxStringSocket', 'Name', default_value='mapname')
|
self.add_input('LnxStringSocket', 'Name', default_value='mapname')
|
||||||
case 63:
|
elif index == 63:
|
||||||
self.add_input('LnxStringSocket', 'Charset', default_value='character_set')
|
self.add_input('LnxStringSocket', 'Charset', default_value='character_set')
|
||||||
self.add_input('LnxStringSocket', 'Content', default_value='text')
|
self.add_input('LnxStringSocket', 'Content', default_value='text')
|
||||||
case 64:
|
elif index == 64:
|
||||||
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
||||||
self.add_input('LnxStringSocket', 'high')
|
self.add_input('LnxStringSocket', 'high')
|
||||||
self.add_input('LnxStringSocket', 'low')
|
self.add_input('LnxStringSocket', 'low')
|
||||||
@ -229,7 +229,7 @@ class CreateElementNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxStringSocket', 'min')
|
self.add_input('LnxStringSocket', 'min')
|
||||||
self.add_input('LnxStringSocket', 'optimum')
|
self.add_input('LnxStringSocket', 'optimum')
|
||||||
self.add_input('LnxStringSocket', 'value')
|
self.add_input('LnxStringSocket', 'value')
|
||||||
case 67:
|
elif index == 67:
|
||||||
self.add_input('LnxStringSocket', 'data', default_value='URL')
|
self.add_input('LnxStringSocket', 'data', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
||||||
self.add_input('LnxStringSocket', 'height', default_value='pixels')
|
self.add_input('LnxStringSocket', 'height', default_value='pixels')
|
||||||
@ -237,44 +237,44 @@ class CreateElementNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxStringSocket', 'type', default_value='media_type')
|
self.add_input('LnxStringSocket', 'type', default_value='media_type')
|
||||||
self.add_input('LnxStringSocket', 'usemap', default_value='#mapname')
|
self.add_input('LnxStringSocket', 'usemap', default_value='#mapname')
|
||||||
self.add_input('LnxStringSocket', 'width', default_value='pixels')
|
self.add_input('LnxStringSocket', 'width', default_value='pixels')
|
||||||
case 68:
|
elif index == 68:
|
||||||
self.add_input('LnxStringSocket', 'start', default_value='number')
|
self.add_input('LnxStringSocket', 'start', default_value='number')
|
||||||
case 69:
|
elif index == 69:
|
||||||
self.add_input('LnxStringSocket', 'label', default_value='text')
|
self.add_input('LnxStringSocket', 'label', default_value='text')
|
||||||
case 70:
|
elif index == 70:
|
||||||
self.add_input('LnxStringSocket', 'label', default_value='text')
|
self.add_input('LnxStringSocket', 'label', default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'value', default_value='value')
|
self.add_input('LnxStringSocket', 'value', default_value='value')
|
||||||
case 71:
|
elif index == 71:
|
||||||
self.add_input('LnxStringSocket', 'for', default_value='element_id')
|
self.add_input('LnxStringSocket', 'for', default_value='element_id')
|
||||||
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
self.add_input('LnxStringSocket', 'form', default_value='form_id')
|
||||||
self.add_input('LnxStringSocket', 'name', default_value='name')
|
self.add_input('LnxStringSocket', 'name', default_value='name')
|
||||||
case 75:
|
elif index == 75:
|
||||||
self.add_input('LnxStringSocket', 'max', default_value='number')
|
self.add_input('LnxStringSocket', 'max', default_value='number')
|
||||||
self.add_input('LnxStringSocket', 'value', default_value='number')
|
self.add_input('LnxStringSocket', 'value', default_value='number')
|
||||||
case 76:
|
elif index == 76:
|
||||||
self.add_input('LnxStringSocket', 'cite', default_value='URL')
|
self.add_input('LnxStringSocket', 'cite', default_value='URL')
|
||||||
case 78:
|
elif index == 78:
|
||||||
self.add_input('LnxStringSocket', 'cite', default_value='URL')
|
self.add_input('LnxStringSocket', 'cite', default_value='URL')
|
||||||
case 79:
|
elif index == 79:
|
||||||
self.add_input('LnxStringSocket', 'integrity' , default_value='filehash')
|
self.add_input('LnxStringSocket', 'integrity' , default_value='filehash')
|
||||||
self.add_input('LnxStringSocket', 'Src')
|
self.add_input('LnxStringSocket', 'Src')
|
||||||
self.add_input('LnxStringSocket', 'type', default_value='scripttype')
|
self.add_input('LnxStringSocket', 'type', default_value='scripttype')
|
||||||
case 81:
|
elif index == 81:
|
||||||
self.add_input('LnxStringSocket', 'form' , default_value='form_id')
|
self.add_input('LnxStringSocket', 'form' , default_value='form_id')
|
||||||
self.add_input('LnxStringSocket', 'name' , default_value='text')
|
self.add_input('LnxStringSocket', 'name' , default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'type', default_value='scripttype')
|
self.add_input('LnxStringSocket', 'type', default_value='scripttype')
|
||||||
self.add_input('LnxStringSocket', 'size', default_value='number')
|
self.add_input('LnxStringSocket', 'size', default_value='number')
|
||||||
case 84:
|
elif index == 84:
|
||||||
self.add_input('LnxStringSocket', 'size')
|
self.add_input('LnxStringSocket', 'size')
|
||||||
self.add_input('LnxStringSocket', 'src' , default_value='URL')
|
self.add_input('LnxStringSocket', 'src' , default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'srcset', default_value='URL')
|
self.add_input('LnxStringSocket', 'srcset', default_value='URL')
|
||||||
case 87:
|
elif index == 87:
|
||||||
self.add_input('LnxStringSocket', 'type', default_value='media_type')
|
self.add_input('LnxStringSocket', 'type', default_value='media_type')
|
||||||
case 93:
|
elif index == 93:
|
||||||
self.add_input('LnxStringSocket', 'colspan' , default_value='number')
|
self.add_input('LnxStringSocket', 'colspan' , default_value='number')
|
||||||
self.add_input('LnxStringSocket', 'headers' , default_value='header_id')
|
self.add_input('LnxStringSocket', 'headers' , default_value='header_id')
|
||||||
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
||||||
case 95:
|
elif index == 95:
|
||||||
self.add_input('LnxStringSocket', 'cols' , default_value='number')
|
self.add_input('LnxStringSocket', 'cols' , default_value='number')
|
||||||
self.add_input('LnxStringSocket', 'dirname' , default_value='name.dir')
|
self.add_input('LnxStringSocket', 'dirname' , default_value='name.dir')
|
||||||
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
||||||
@ -283,18 +283,18 @@ class CreateElementNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxStringSocket', 'name' , default_value='text')
|
self.add_input('LnxStringSocket', 'name' , default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'placeholder' , default_value='text')
|
self.add_input('LnxStringSocket', 'placeholder' , default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'rows' , default_value='number')
|
self.add_input('LnxStringSocket', 'rows' , default_value='number')
|
||||||
case 97:
|
elif index == 97:
|
||||||
self.add_input('LnxStringSocket', 'abbr' , default_value='text')
|
self.add_input('LnxStringSocket', 'abbr' , default_value='text')
|
||||||
self.add_input('LnxStringSocket', 'colspan' , default_value='number')
|
self.add_input('LnxStringSocket', 'colspan' , default_value='number')
|
||||||
self.add_input('LnxStringSocket', 'headers', default_value='header_id')
|
self.add_input('LnxStringSocket', 'headers', default_value='header_id')
|
||||||
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
self.add_input('LnxStringSocket', 'rowspan', default_value='number')
|
||||||
case 99:
|
elif index == 99:
|
||||||
self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD')
|
self.add_input('LnxStringSocket', 'Datetime', default_value='YYYY-MM-DDThh:mm:ssTZD')
|
||||||
case 102:
|
elif index == 102:
|
||||||
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'srclang', default_value='en')
|
self.add_input('LnxStringSocket', 'srclang', default_value='en')
|
||||||
self.add_input('LnxStringSocket', 'label', default_value='text')
|
self.add_input('LnxStringSocket', 'label', default_value='text')
|
||||||
case 106:
|
elif index == 106:
|
||||||
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
self.add_input('LnxStringSocket', 'Src', default_value='URL')
|
||||||
self.add_input('LnxStringSocket', 'width', default_value='pixels')
|
self.add_input('LnxStringSocket', 'width', default_value='pixels')
|
||||||
self.add_input('LnxStringSocket', 'height', default_value='pixels')
|
self.add_input('LnxStringSocket', 'height', default_value='pixels')
|
||||||
|
|||||||
@ -38,12 +38,11 @@ class JSEventTargetNode(LnxLogicTreeNode):
|
|||||||
# Arguements for type Client
|
# Arguements for type Client
|
||||||
index = self.get_count_in(select_current)
|
index = self.get_count_in(select_current)
|
||||||
|
|
||||||
match index:
|
if index == 2:
|
||||||
case 2:
|
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'JS Object')
|
self.add_input('LnxDynamicSocket', 'JS Object')
|
||||||
self.add_input('LnxDynamicSocket', 'Event')
|
self.add_input('LnxDynamicSocket', 'Event')
|
||||||
case _:
|
else:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'JS Object')
|
self.add_input('LnxDynamicSocket', 'JS Object')
|
||||||
self.add_input('LnxStringSocket', 'Type')
|
self.add_input('LnxStringSocket', 'Type')
|
||||||
|
|||||||
@ -43,24 +43,23 @@ class RenderElementNode(LnxLogicTreeNode):
|
|||||||
# Arguements for type Client
|
# Arguements for type Client
|
||||||
index = self.get_count_in(select_current)
|
index = self.get_count_in(select_current)
|
||||||
|
|
||||||
match index:
|
if index == 2:
|
||||||
case 2:
|
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'Torrent')
|
self.add_input('LnxDynamicSocket', 'Torrent')
|
||||||
self.add_input('LnxStringSocket', 'Selector')
|
self.add_input('LnxStringSocket', 'Selector')
|
||||||
case 5:
|
elif index == 5:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'Element')
|
self.add_input('LnxDynamicSocket', 'Element')
|
||||||
self.add_input('LnxStringSocket', 'HTML')
|
self.add_input('LnxStringSocket', 'HTML')
|
||||||
case 6:
|
elif index == 6:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'Element')
|
self.add_input('LnxDynamicSocket', 'Element')
|
||||||
self.add_input('LnxStringSocket', 'Text')
|
self.add_input('LnxStringSocket', 'Text')
|
||||||
case 7:
|
elif index == 7:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxStringSocket', 'HTML')
|
self.add_input('LnxStringSocket', 'HTML')
|
||||||
self.add_input('LnxStringSocket', 'Selector')
|
self.add_input('LnxStringSocket', 'Selector')
|
||||||
case _:
|
else:
|
||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.add_input('LnxDynamicSocket', 'Element')
|
self.add_input('LnxDynamicSocket', 'Element')
|
||||||
self.add_input('LnxStringSocket', 'Selector')
|
self.add_input('LnxStringSocket', 'Selector')
|
||||||
|
|||||||
@ -17,6 +17,17 @@ class OnEventNode(LnxLogicTreeNode):
|
|||||||
'custom': 'Custom'
|
'custom': 'Custom'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.property1 != 'custom':
|
||||||
|
if self.inputs[0].is_linked:
|
||||||
|
self.label = f'{self.bl_label}: {self.property1}'
|
||||||
|
else:
|
||||||
|
self.label = f'{self.bl_label}: {self.property1} {self.inputs[0].get_default_value()}'
|
||||||
|
elif self.inputs[1].is_linked:
|
||||||
|
self.label = f'{self.bl_label}: {self.property1}'
|
||||||
|
else:
|
||||||
|
self.label = f'{self.bl_label}: {self.property1} {self.inputs[1].get_default_value()}'
|
||||||
|
|
||||||
def set_mode(self, context):
|
def set_mode(self, context):
|
||||||
if self.property1 != 'custom':
|
if self.property1 != 'custom':
|
||||||
if len(self.inputs) > 1:
|
if len(self.inputs) > 1:
|
||||||
@ -26,6 +37,16 @@ class OnEventNode(LnxLogicTreeNode):
|
|||||||
self.add_input('LnxNodeSocketAction', 'In')
|
self.add_input('LnxNodeSocketAction', 'In')
|
||||||
self.inputs.move(1, 0)
|
self.inputs.move(1, 0)
|
||||||
|
|
||||||
|
if self.property1 != 'custom':
|
||||||
|
if self.inputs[0].is_linked:
|
||||||
|
self.label = f'{self.bl_label}: {self.property1}'
|
||||||
|
else:
|
||||||
|
self.label = f'{self.bl_label}: {self.property1} {self.inputs[0].get_default_value()}'
|
||||||
|
elif self.inputs[1].is_linked:
|
||||||
|
self.label = f'{self.bl_label}: {self.property1}'
|
||||||
|
else:
|
||||||
|
self.label = f'{self.bl_label}: {self.property1} {self.inputs[1].get_default_value()}'
|
||||||
|
|
||||||
# Use a new property to preserve compatibility
|
# Use a new property to preserve compatibility
|
||||||
property1: HaxeEnumProperty(
|
property1: HaxeEnumProperty(
|
||||||
'property1',
|
'property1',
|
||||||
@ -52,9 +73,15 @@ class OnEventNode(LnxLogicTreeNode):
|
|||||||
layout.prop(self, 'property1', text='')
|
layout.prop(self, 'property1', text='')
|
||||||
|
|
||||||
def draw_label(self) -> str:
|
def draw_label(self) -> str:
|
||||||
|
if self.property1 != 'custom':
|
||||||
if self.inputs[0].is_linked:
|
if self.inputs[0].is_linked:
|
||||||
return self.bl_label
|
return f'{self.bl_label}: {self.property1}'
|
||||||
return f'{self.bl_label}: {self.inputs[0].get_default_value()}'
|
else:
|
||||||
|
return f'{self.bl_label}: {self.property1} {self.inputs[0].get_default_value()}'
|
||||||
|
elif self.inputs[1].is_linked:
|
||||||
|
return f'{self.bl_label}: {self.property1}'
|
||||||
|
else:
|
||||||
|
return f'{self.bl_label}: {self.property1} {self.inputs[1].get_default_value()}'
|
||||||
|
|
||||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||||
if self.lnx_version not in (0, 1):
|
if self.lnx_version not in (0, 1):
|
||||||
|
|||||||
@ -7,12 +7,19 @@ class KeyboardNode(LnxLogicTreeNode):
|
|||||||
lnx_section = 'keyboard'
|
lnx_section = 'keyboard'
|
||||||
lnx_version = 2
|
lnx_version = 2
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.label = f'{self.bl_label}: {self.property0} {self.property1}'
|
||||||
|
|
||||||
|
def upd(self, context):
|
||||||
|
self.label = f'{self.bl_label}: {self.property0} {self.property1}'
|
||||||
|
|
||||||
|
|
||||||
property0: HaxeEnumProperty(
|
property0: HaxeEnumProperty(
|
||||||
'property0',
|
'property0',
|
||||||
items = [('started', 'Started', 'The keyboard button starts to be pressed'),
|
items = [('started', 'Started', 'The keyboard button starts to be pressed'),
|
||||||
('down', 'Down', 'The keyboard button is pressed'),
|
('down', 'Down', 'The keyboard button is pressed'),
|
||||||
('released', 'Released', 'The keyboard button stops being pressed')],
|
('released', 'Released', 'The keyboard button stops being pressed')],
|
||||||
name='', default='down')
|
name='', default='down', update=upd)
|
||||||
|
|
||||||
property1: HaxeEnumProperty(
|
property1: HaxeEnumProperty(
|
||||||
'property1',
|
'property1',
|
||||||
@ -69,7 +76,7 @@ class KeyboardNode(LnxLogicTreeNode):
|
|||||||
('right', 'right', 'right'),
|
('right', 'right', 'right'),
|
||||||
('left', 'left', 'left'),
|
('left', 'left', 'left'),
|
||||||
('down', 'down', 'down'),],
|
('down', 'down', 'down'),],
|
||||||
name='', default='space')
|
name='', default='space', update=upd)
|
||||||
|
|
||||||
def lnx_init(self, context):
|
def lnx_init(self, context):
|
||||||
self.add_output('LnxNodeSocketAction', 'Out')
|
self.add_output('LnxNodeSocketAction', 'Out')
|
||||||
|
|||||||
@ -8,13 +8,25 @@ class MouseNode(LnxLogicTreeNode):
|
|||||||
lnx_section = 'mouse'
|
lnx_section = 'mouse'
|
||||||
lnx_version = 3
|
lnx_version = 3
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.property0 != 'moved':
|
||||||
|
self.label = f'{self.bl_label}: {self.property0} {self.property1}'
|
||||||
|
else:
|
||||||
|
self.label = f'{self.bl_label}: {self.property0}'
|
||||||
|
|
||||||
|
def upd(self, context):
|
||||||
|
if self.property0 != 'moved':
|
||||||
|
self.label = f'{self.bl_label}: {self.property0} {self.property1}'
|
||||||
|
else:
|
||||||
|
self.label = f'{self.bl_label}: {self.property0}'
|
||||||
|
|
||||||
property0: HaxeEnumProperty(
|
property0: HaxeEnumProperty(
|
||||||
'property0',
|
'property0',
|
||||||
items = [('started', 'Started', 'The mouse button begins to be pressed'),
|
items = [('started', 'Started', 'The mouse button begins to be pressed'),
|
||||||
('down', 'Down', 'The mouse button is pressed'),
|
('down', 'Down', 'The mouse button is pressed'),
|
||||||
('released', 'Released', 'The mouse button stops being pressed'),
|
('released', 'Released', 'The mouse button stops being pressed'),
|
||||||
('moved', 'Moved', 'Moved')],
|
('moved', 'Moved', 'Moved')],
|
||||||
name='', default='down')
|
name='', default='down', update=upd)
|
||||||
property1: HaxeEnumProperty(
|
property1: HaxeEnumProperty(
|
||||||
'property1',
|
'property1',
|
||||||
items = [('left', 'Left', 'Left mouse button'),
|
items = [('left', 'Left', 'Left mouse button'),
|
||||||
@ -22,7 +34,7 @@ class MouseNode(LnxLogicTreeNode):
|
|||||||
('right', 'Right', 'Right mouse button'),
|
('right', 'Right', 'Right mouse button'),
|
||||||
('side1', 'Side 1', 'Side 1 mouse button'),
|
('side1', 'Side 1', 'Side 1 mouse button'),
|
||||||
('side2', 'Side 2', 'Side 2 mouse button')],
|
('side2', 'Side 2', 'Side 2 mouse button')],
|
||||||
name='', default='left')
|
name='', default='left', update=upd)
|
||||||
property2: HaxeBoolProperty(
|
property2: HaxeBoolProperty(
|
||||||
'property2',
|
'property2',
|
||||||
name='Include Debug Console',
|
name='Include Debug Console',
|
||||||
|
|||||||
@ -66,7 +66,10 @@ class LnxGroupTree(bpy.types.NodeTree):
|
|||||||
"""Try to avoid creating loops of group trees with each other"""
|
"""Try to avoid creating loops of group trees with each other"""
|
||||||
# upstream trees of tested treed should nad share trees with downstream trees of current tree
|
# upstream trees of tested treed should nad share trees with downstream trees of current tree
|
||||||
tested_tree_upstream_trees = {t.name for t in self.upstream_trees()}
|
tested_tree_upstream_trees = {t.name for t in self.upstream_trees()}
|
||||||
|
if bpy.context.space_data is not None:
|
||||||
current_tree_downstream_trees = {p.node_tree.name for p in bpy.context.space_data.path}
|
current_tree_downstream_trees = {p.node_tree.name for p in bpy.context.space_data.path}
|
||||||
|
else:
|
||||||
|
current_tree_downstream_trees = set()
|
||||||
shared_trees = tested_tree_upstream_trees & current_tree_downstream_trees
|
shared_trees = tested_tree_upstream_trees & current_tree_downstream_trees
|
||||||
return not shared_trees
|
return not shared_trees
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,17 @@ from collections import OrderedDict
|
|||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Any, final, Generator, List, Optional, Type, Union
|
from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union
|
||||||
from typing import OrderedDict as ODict # Prevent naming conflicts
|
from typing import OrderedDict as ODict # Prevent naming conflicts
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import final
|
||||||
|
except ImportError:
|
||||||
|
# Python < 3.8 compatibility
|
||||||
|
def final(f):
|
||||||
|
"""No final in Python < 3.8"""
|
||||||
|
return f
|
||||||
|
|
||||||
import bpy.types
|
import bpy.types
|
||||||
from bpy.props import *
|
from bpy.props import *
|
||||||
from nodeitems_utils import NodeItem
|
from nodeitems_utils import NodeItem
|
||||||
@ -39,11 +47,11 @@ PKG_AS_CATEGORY = "__pkgcat__"
|
|||||||
nodes = []
|
nodes = []
|
||||||
category_items: ODict[str, List['LnxNodeCategory']] = OrderedDict()
|
category_items: ODict[str, List['LnxNodeCategory']] = OrderedDict()
|
||||||
|
|
||||||
array_nodes: dict[str, 'LnxLogicTreeNode'] = dict()
|
array_nodes: Dict[str, 'LnxLogicTreeNode'] = dict()
|
||||||
|
|
||||||
# See LnxLogicTreeNode.update()
|
# See LnxLogicTreeNode.update()
|
||||||
# format: [tree pointer => (num inputs, num input links, num outputs, num output links)]
|
# format: [tree pointer => (num inputs, num input links, num outputs, num output links)]
|
||||||
last_node_state: dict[int, tuple[int, int, int, int]] = {}
|
last_node_state: Dict[int, Tuple[int, int, int, int]] = {}
|
||||||
|
|
||||||
|
|
||||||
class LnxLogicTreeNode(bpy.types.Node):
|
class LnxLogicTreeNode(bpy.types.Node):
|
||||||
|
|||||||
@ -10,7 +10,7 @@ mutable (common Python pitfall, be aware of this!), but because they
|
|||||||
don't get accessed later it doesn't matter here and we keep it this way
|
don't get accessed later it doesn't matter here and we keep it this way
|
||||||
for parity with the Blender API.
|
for parity with the Blender API.
|
||||||
"""
|
"""
|
||||||
from typing import Any, Callable, Sequence, Union
|
from typing import Any, Callable, List, Sequence, Set, Union
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import bpy
|
import bpy
|
||||||
@ -50,6 +50,10 @@ def __haxe_prop(prop_type: Callable, prop_name: str, *args, **kwargs) -> Any:
|
|||||||
if 'tags' in kwargs:
|
if 'tags' in kwargs:
|
||||||
del kwargs['tags']
|
del kwargs['tags']
|
||||||
|
|
||||||
|
# Remove override parameter for Blender versions that don't support it
|
||||||
|
if bpy.app.version < (2, 90, 0) and 'override' in kwargs:
|
||||||
|
del kwargs['override']
|
||||||
|
|
||||||
return prop_type(*args, **kwargs)
|
return prop_type(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +91,7 @@ def HaxeBoolVectorProperty(
|
|||||||
update=None,
|
update=None,
|
||||||
get=None,
|
get=None,
|
||||||
set=None
|
set=None
|
||||||
) -> list['bpy.types.BoolProperty']:
|
) -> List['bpy.types.BoolProperty']:
|
||||||
"""Declares a new BoolVectorProperty that has a Haxe counterpart
|
"""Declares a new BoolVectorProperty that has a Haxe counterpart
|
||||||
with the given prop_name (Python and Haxe names must be identical
|
with the given prop_name (Python and Haxe names must be identical
|
||||||
for now).
|
for now).
|
||||||
@ -118,7 +122,7 @@ def HaxeEnumProperty(
|
|||||||
items: Sequence,
|
items: Sequence,
|
||||||
name: str = "",
|
name: str = "",
|
||||||
description: str = "",
|
description: str = "",
|
||||||
default: Union[str, set[str]] = None,
|
default: Union[str, Set[str]] = None,
|
||||||
options: set = {'ANIMATABLE'},
|
options: set = {'ANIMATABLE'},
|
||||||
override: set = set(),
|
override: set = set(),
|
||||||
tags: set = set(),
|
tags: set = set(),
|
||||||
@ -180,7 +184,7 @@ def HaxeFloatVectorProperty(
|
|||||||
update=None,
|
update=None,
|
||||||
get=None,
|
get=None,
|
||||||
set=None
|
set=None
|
||||||
) -> list['bpy.types.FloatProperty']:
|
) -> List['bpy.types.FloatProperty']:
|
||||||
"""Declares a new FloatVectorProperty that has a Haxe counterpart
|
"""Declares a new FloatVectorProperty that has a Haxe counterpart
|
||||||
with the given prop_name (Python and Haxe names must be identical
|
with the given prop_name (Python and Haxe names must be identical
|
||||||
for now).
|
for now).
|
||||||
@ -232,7 +236,7 @@ def HaxeIntVectorProperty(
|
|||||||
update=None,
|
update=None,
|
||||||
get=None,
|
get=None,
|
||||||
set=None
|
set=None
|
||||||
) -> list['bpy.types.IntProperty']:
|
) -> List['bpy.types.IntProperty']:
|
||||||
"""Declares a new IntVectorProperty that has a Haxe counterpart with
|
"""Declares a new IntVectorProperty that has a Haxe counterpart with
|
||||||
the given prop_name (Python and Haxe names must be identical for now).
|
the given prop_name (Python and Haxe names must be identical for now).
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -18,6 +18,10 @@ class CallGroupNode(LnxLogicTreeNode):
|
|||||||
def lnx_init(self, context):
|
def lnx_init(self, context):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.group_tree:
|
||||||
|
self.label = f'Group: {self.group_tree.name}'
|
||||||
|
|
||||||
# Function to add input sockets and re-link sockets
|
# Function to add input sockets and re-link sockets
|
||||||
def update_inputs(self, tree, node, inp_sockets, in_links):
|
def update_inputs(self, tree, node, inp_sockets, in_links):
|
||||||
count = 0
|
count = 0
|
||||||
@ -58,10 +62,12 @@ class CallGroupNode(LnxLogicTreeNode):
|
|||||||
tree.links.new(current_socket, link)
|
tree.links.new(current_socket, link)
|
||||||
count = count + 1
|
count = count + 1
|
||||||
|
|
||||||
def remove_tree(self):
|
|
||||||
self.group_tree = None
|
|
||||||
|
|
||||||
def update_sockets(self, context):
|
def update_sockets(self, context):
|
||||||
|
if self.group_tree:
|
||||||
|
self.label = f'Group: {self.group_tree.name}'
|
||||||
|
else:
|
||||||
|
self.label = 'Call Node Group'
|
||||||
|
|
||||||
# List to store from and to sockets of connected nodes
|
# List to store from and to sockets of connected nodes
|
||||||
from_socket_list = []
|
from_socket_list = []
|
||||||
to_socket_list = []
|
to_socket_list = []
|
||||||
@ -107,6 +113,10 @@ class CallGroupNode(LnxLogicTreeNode):
|
|||||||
# Prperty to store group tree pointer
|
# Prperty to store group tree pointer
|
||||||
group_tree: PointerProperty(name='Group', type=bpy.types.NodeTree, update=update_sockets)
|
group_tree: PointerProperty(name='Group', type=bpy.types.NodeTree, update=update_sockets)
|
||||||
|
|
||||||
|
def edit_tree(self):
|
||||||
|
self.label = f'Group: {self.group_tree.name}'
|
||||||
|
bpy.ops.lnx.edit_group_tree()
|
||||||
|
|
||||||
def draw_label(self) -> str:
|
def draw_label(self) -> str:
|
||||||
if self.group_tree is not None:
|
if self.group_tree is not None:
|
||||||
return f'Group: {self.group_tree.name}'
|
return f'Group: {self.group_tree.name}'
|
||||||
@ -134,8 +144,9 @@ class CallGroupNode(LnxLogicTreeNode):
|
|||||||
op = row_name.operator('lnx.unlink_group_tree', icon='X', text='')
|
op = row_name.operator('lnx.unlink_group_tree', icon='X', text='')
|
||||||
op.node_index = self.get_id_str()
|
op.node_index = self.get_id_str()
|
||||||
row_ops.enabled = not self.group_tree is None
|
row_ops.enabled = not self.group_tree is None
|
||||||
op = row_ops.operator('lnx.edit_group_tree', icon='FULLSCREEN_ENTER', text='Edit tree')
|
op = row_ops.operator('lnx.node_call_func', icon='FULLSCREEN_ENTER', text='Edit tree')
|
||||||
op.node_index = self.get_id_str()
|
op.node_index = self.get_id_str()
|
||||||
|
op.callback_name = 'edit_tree'
|
||||||
|
|
||||||
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
|
||||||
if self.lnx_version not in (0, 1, 2):
|
if self.lnx_version not in (0, 1, 2):
|
||||||
|
|||||||
@ -27,7 +27,10 @@ class GroupInputsNode(LnxLogicTreeNode):
|
|||||||
copy_override: BoolProperty(name='copy override', description='', default=False)
|
copy_override: BoolProperty(name='copy override', description='', default=False)
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
|
if bpy.context.space_data is not None:
|
||||||
tree = bpy.context.space_data.edit_tree
|
tree = bpy.context.space_data.edit_tree
|
||||||
|
else:
|
||||||
|
return
|
||||||
node_count = 0
|
node_count = 0
|
||||||
for node in tree.nodes:
|
for node in tree.nodes:
|
||||||
if node.bl_idname == 'LNGroupInputsNode':
|
if node.bl_idname == 'LNGroupInputsNode':
|
||||||
|
|||||||
@ -27,7 +27,10 @@ class GroupOutputsNode(LnxLogicTreeNode):
|
|||||||
copy_override: BoolProperty(name='copy override', description='', default=False)
|
copy_override: BoolProperty(name='copy override', description='', default=False)
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
|
if bpy.context.space_data is not None:
|
||||||
tree = bpy.context.space_data.edit_tree
|
tree = bpy.context.space_data.edit_tree
|
||||||
|
else:
|
||||||
|
return
|
||||||
node_count = 0
|
node_count = 0
|
||||||
for node in tree.nodes:
|
for node in tree.nodes:
|
||||||
if node.bl_idname == 'LNGroupOutputsNode':
|
if node.bl_idname == 'LNGroupOutputsNode':
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
from lnx.logicnode.lnx_nodes import *
|
||||||
|
|
||||||
|
|
||||||
|
class ProbabilisticIndexNode(LnxLogicTreeNode):
|
||||||
|
"""This system gets an index based on probabilistic values,
|
||||||
|
ensuring that the total sum of the probabilities equals 1.
|
||||||
|
If the probabilities do not sum to 1, they will be adjusted
|
||||||
|
accordingly to guarantee a total sum of 1. Only one output will be
|
||||||
|
triggered at a time.
|
||||||
|
@output index: the index.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bl_idname = 'LNProbabilisticIndexNode'
|
||||||
|
bl_label = 'Probabilistic Index'
|
||||||
|
lnx_section = 'logic'
|
||||||
|
lnx_version = 1
|
||||||
|
|
||||||
|
num_choices: IntProperty(default=0, min=0)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
array_nodes[str(id(self))] = self
|
||||||
|
|
||||||
|
def lnx_init(self, context):
|
||||||
|
|
||||||
|
self.add_output('LnxIntSocket', 'Index')
|
||||||
|
|
||||||
|
def draw_buttons(self, context, layout):
|
||||||
|
row = layout.row(align=True)
|
||||||
|
|
||||||
|
op = row.operator('lnx.node_call_func', text='New', icon='PLUS', emboss=True)
|
||||||
|
op.node_index = str(id(self))
|
||||||
|
op.callback_name = 'add_func'
|
||||||
|
op2 = row.operator('lnx.node_call_func', text='', icon='X', emboss=True)
|
||||||
|
op2.node_index = str(id(self))
|
||||||
|
op2.callback_name = 'remove_func'
|
||||||
|
|
||||||
|
def add_func(self):
|
||||||
|
self.add_input('LnxFloatSocket', f'Prob Index {self.num_choices}')
|
||||||
|
self.num_choices += 1
|
||||||
|
|
||||||
|
def remove_func(self):
|
||||||
|
if len(self.inputs) > 0:
|
||||||
|
self.inputs.remove(self.inputs[-1])
|
||||||
|
self.num_choices -= 1
|
||||||
|
|
||||||
|
def draw_label(self) -> str:
|
||||||
|
if self.num_choices == 0:
|
||||||
|
return self.bl_label
|
||||||
|
|
||||||
|
return f'{self.bl_label}: [{self.num_choices}]'
|
||||||
|
|
||||||
@ -350,7 +350,10 @@ class LNX_PG_TreeVarListItem(bpy.types.PropertyGroup):
|
|||||||
def _set_name(self, value: str):
|
def _set_name(self, value: str):
|
||||||
old_name = self._get_name()
|
old_name = self._get_name()
|
||||||
|
|
||||||
|
if bpy.context.space_data is not None:
|
||||||
tree = bpy.context.space_data.path[-1].node_tree
|
tree = bpy.context.space_data.path[-1].node_tree
|
||||||
|
else:
|
||||||
|
return # No valid context
|
||||||
lst = tree.lnx_treevariableslist
|
lst = tree.lnx_treevariableslist
|
||||||
|
|
||||||
if value == '':
|
if value == '':
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
from lnx.logicnode.lnx_nodes import *
|
from lnx.logicnode.lnx_nodes import *
|
||||||
|
|
||||||
class SetWorldNode(LnxLogicTreeNode):
|
class SetWorldNode(LnxLogicTreeNode):
|
||||||
"""Sets the World of the active scene."""
|
"""Sets the World of the active scene.
|
||||||
|
World must be either associated to a scene or have fake user."""
|
||||||
|
|
||||||
|
|
||||||
bl_idname = 'LNSetWorldNode'
|
bl_idname = 'LNSetWorldNode'
|
||||||
bl_label = 'Set World'
|
bl_label = 'Set World'
|
||||||
lnx_version = 1
|
lnx_version = 1
|
||||||
|
|||||||
@ -116,7 +116,73 @@ def remove_readonly(func, path, excinfo):
|
|||||||
os.chmod(path, stat.S_IWRITE)
|
os.chmod(path, stat.S_IWRITE)
|
||||||
func(path)
|
func(path)
|
||||||
|
|
||||||
|
|
||||||
|
appended_scenes = []
|
||||||
|
|
||||||
|
def load_external_blends():
|
||||||
|
global appended_scenes
|
||||||
|
|
||||||
|
wrd = bpy.data.worlds['Lnx']
|
||||||
|
if not hasattr(wrd, 'lnx_external_blends_path'):
|
||||||
|
return
|
||||||
|
|
||||||
|
external_path = getattr(wrd, 'lnx_external_blends_path', '')
|
||||||
|
if not external_path or not external_path.strip():
|
||||||
|
return
|
||||||
|
|
||||||
|
abs_path = bpy.path.abspath(external_path.strip())
|
||||||
|
if not os.path.exists(abs_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Walk recursively through all subdirs
|
||||||
|
for root, dirs, files in os.walk(abs_path):
|
||||||
|
for filename in files:
|
||||||
|
if not filename.endswith(".blend"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
blend_path = os.path.join(root, filename)
|
||||||
|
try:
|
||||||
|
with bpy.data.libraries.load(blend_path, link=True) as (data_from, data_to):
|
||||||
|
data_to.scenes = list(data_from.scenes)
|
||||||
|
|
||||||
|
for scn in data_to.scenes:
|
||||||
|
if scn is not None and scn not in appended_scenes:
|
||||||
|
# make name unique with file name
|
||||||
|
scn.name += "_" + filename.replace(".blend", "")
|
||||||
|
appended_scenes.append(scn)
|
||||||
|
|
||||||
|
log.info(f"Loaded external blend: {blend_path}")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to load external blend {blend_path}: {e}")
|
||||||
|
|
||||||
|
def clear_external_scenes():
|
||||||
|
global appended_scenes
|
||||||
|
if not appended_scenes:
|
||||||
|
return
|
||||||
|
|
||||||
|
for scn in appended_scenes:
|
||||||
|
try:
|
||||||
|
bpy.data.scenes.remove(scn, do_unlink=True)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to remove scene {scn.name}: {e}")
|
||||||
|
|
||||||
|
for lib in list(bpy.data.libraries):
|
||||||
|
try:
|
||||||
|
if lib.users == 0:
|
||||||
|
bpy.data.libraries.remove(lib)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to remove library {lib.name}: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to purge orphan data: {e}")
|
||||||
|
|
||||||
|
appended_scenes = []
|
||||||
|
|
||||||
def export_data(fp, sdk_path):
|
def export_data(fp, sdk_path):
|
||||||
|
load_external_blends()
|
||||||
|
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
rpdat = lnx.utils.get_rp()
|
rpdat = lnx.utils.get_rp()
|
||||||
|
|
||||||
@ -323,6 +389,8 @@ def export_data(fp, sdk_path):
|
|||||||
state.last_resy = resy
|
state.last_resy = resy
|
||||||
state.last_scene = scene_name
|
state.last_scene = scene_name
|
||||||
|
|
||||||
|
clear_external_scenes()
|
||||||
|
|
||||||
def compile(assets_only=False):
|
def compile(assets_only=False):
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
fp = lnx.utils.get_fp()
|
fp = lnx.utils.get_fp()
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from typing import Optional, TextIO
|
from typing import List, Optional, TextIO, Dict, Any, TypeVar, TYPE_CHECKING
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -17,14 +17,14 @@ if lnx.is_reload(__name__):
|
|||||||
else:
|
else:
|
||||||
lnx.enable_reload(__name__)
|
lnx.enable_reload(__name__)
|
||||||
|
|
||||||
parsed_nodes = []
|
parsed_nodes = [] # type: List[str]
|
||||||
parsed_ids = dict() # Sharing node data
|
parsed_ids = dict() # type: Dict[str, str] # Sharing node data
|
||||||
function_nodes = dict()
|
function_nodes = dict() # type: Dict[str, Any]
|
||||||
function_node_outputs = dict()
|
function_node_outputs = dict() # type: Dict[str, str]
|
||||||
group_name = ''
|
group_name = ''
|
||||||
|
|
||||||
|
|
||||||
def get_logic_trees() -> list['lnx.nodes_logic.LnxLogicTree']:
|
def get_logic_trees() -> List['lnx.nodes_logic.LnxLogicTree']:
|
||||||
ar = []
|
ar = []
|
||||||
for node_group in bpy.data.node_groups:
|
for node_group in bpy.data.node_groups:
|
||||||
if node_group.bl_idname == 'LnxLogicTreeType':
|
if node_group.bl_idname == 'LnxLogicTreeType':
|
||||||
@ -140,7 +140,7 @@ def build_node_group_tree(node_group: 'lnx.nodes_logic.LnxLogicTree', f: TextIO,
|
|||||||
return group_input_name, group_output_name
|
return group_input_name, group_output_name
|
||||||
|
|
||||||
|
|
||||||
def build_node(node: bpy.types.Node, f: TextIO, name_prefix: str = None) -> Optional[str]:
|
def build_node(node: bpy.types.Node, f: TextIO, name_prefix: Optional[str] = None) -> Optional[str]:
|
||||||
"""Builds the given node and returns its name. f is an opened file object."""
|
"""Builds the given node and returns its name. f is an opened file object."""
|
||||||
global parsed_nodes
|
global parsed_nodes
|
||||||
global parsed_ids
|
global parsed_ids
|
||||||
|
|||||||
@ -40,11 +40,12 @@ def add_world_defs():
|
|||||||
if rpdat.rp_hdr == False:
|
if rpdat.rp_hdr == False:
|
||||||
wrd.world_defs += '_LDR'
|
wrd.world_defs += '_LDR'
|
||||||
|
|
||||||
if lnx.utils.get_active_scene().world.lnx_light_ies_texture == True:
|
if lnx.utils.get_active_scene().world is not None:
|
||||||
|
if lnx.utils.get_active_scene().world.lnx_light_ies_texture:
|
||||||
wrd.world_defs += '_LightIES'
|
wrd.world_defs += '_LightIES'
|
||||||
assets.add_embedded_data('iestexture.png')
|
assets.add_embedded_data('iestexture.png')
|
||||||
|
|
||||||
if lnx.utils.get_active_scene().world.lnx_light_clouds_texture == True:
|
if lnx.utils.get_active_scene().world.lnx_light_clouds_texture:
|
||||||
wrd.world_defs += '_LightClouds'
|
wrd.world_defs += '_LightClouds'
|
||||||
assets.add_embedded_data('cloudstexture.png')
|
assets.add_embedded_data('cloudstexture.png')
|
||||||
|
|
||||||
@ -241,7 +242,7 @@ def build():
|
|||||||
compo_depth = True
|
compo_depth = True
|
||||||
|
|
||||||
focus_distance = 0.0
|
focus_distance = 0.0
|
||||||
if len(bpy.data.cameras) > 0 and lnx.utils.get_active_scene().camera.data.dof.use_dof:
|
if lnx.utils.get_active_scene().camera and lnx.utils.get_active_scene().camera.data.dof.use_dof:
|
||||||
focus_distance = lnx.utils.get_active_scene().camera.data.dof.focus_distance
|
focus_distance = lnx.utils.get_active_scene().camera.data.dof.focus_distance
|
||||||
|
|
||||||
if focus_distance > 0.0:
|
if focus_distance > 0.0:
|
||||||
|
|||||||
@ -69,7 +69,7 @@ def build():
|
|||||||
if rpdat.lnx_irradiance:
|
if rpdat.lnx_irradiance:
|
||||||
# Plain background color
|
# Plain background color
|
||||||
if '_EnvCol' in world.world_defs:
|
if '_EnvCol' in world.world_defs:
|
||||||
world_name = lnx.utils.safestr(world.name)
|
world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name)
|
||||||
# Irradiance json file name
|
# Irradiance json file name
|
||||||
world.lnx_envtex_name = world_name
|
world.lnx_envtex_name = world_name
|
||||||
world.lnx_envtex_irr_name = world_name
|
world.lnx_envtex_irr_name = world_name
|
||||||
@ -99,7 +99,7 @@ def build():
|
|||||||
def create_world_shaders(world: bpy.types.World):
|
def create_world_shaders(world: bpy.types.World):
|
||||||
"""Creates fragment and vertex shaders for the given world."""
|
"""Creates fragment and vertex shaders for the given world."""
|
||||||
global shader_datas
|
global shader_datas
|
||||||
world_name = lnx.utils.safestr(world.name)
|
world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name)
|
||||||
pass_name = 'World_' + world_name
|
pass_name = 'World_' + world_name
|
||||||
|
|
||||||
shader_props = {
|
shader_props = {
|
||||||
@ -160,7 +160,7 @@ def create_world_shaders(world: bpy.types.World):
|
|||||||
|
|
||||||
def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: ShaderContext):
|
def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: ShaderContext):
|
||||||
"""Generates the shader code for the given world."""
|
"""Generates the shader code for the given world."""
|
||||||
world_name = lnx.utils.safestr(world.name)
|
world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name)
|
||||||
world.world_defs = ''
|
world.world_defs = ''
|
||||||
rpdat = lnx.utils.get_rp()
|
rpdat = lnx.utils.get_rp()
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
@ -175,7 +175,7 @@ def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: Sha
|
|||||||
frag.write('fragColor.rgb = backgroundCol;')
|
frag.write('fragColor.rgb = backgroundCol;')
|
||||||
return
|
return
|
||||||
|
|
||||||
parser_state = ParserState(ParserContext.WORLD, world.name, world)
|
parser_state = ParserState(ParserContext.WORLD, lnx.utils.asset_name(world) if world.library else world.name, world)
|
||||||
parser_state.con = con
|
parser_state.con = con
|
||||||
parser_state.curshader = frag
|
parser_state.curshader = frag
|
||||||
parser_state.frag = frag
|
parser_state.frag = frag
|
||||||
|
|||||||
@ -94,6 +94,7 @@ def parse_material_output(node: bpy.types.Node, custom_particle_node: bpy.types.
|
|||||||
parse_displacement = state.parse_displacement
|
parse_displacement = state.parse_displacement
|
||||||
particle_info = {
|
particle_info = {
|
||||||
'index': False,
|
'index': False,
|
||||||
|
'random': False,
|
||||||
'age': False,
|
'age': False,
|
||||||
'lifetime': False,
|
'lifetime': False,
|
||||||
'location': False,
|
'location': False,
|
||||||
|
|||||||
@ -254,9 +254,10 @@ def parse_particleinfo(node: bpy.types.ShaderNodeParticleInfo, out_socket: bpy.t
|
|||||||
c.particle_info['index'] = True
|
c.particle_info['index'] = True
|
||||||
return 'p_index' if particles_on else '0.0'
|
return 'p_index' if particles_on else '0.0'
|
||||||
|
|
||||||
# TODO: Random
|
# Random
|
||||||
if out_socket == node.outputs[1]:
|
if out_socket == node.outputs[1]:
|
||||||
return '0.0'
|
c.particle_info['random'] = True
|
||||||
|
return 'p_random' if particles_on else '0.0'
|
||||||
|
|
||||||
# Age
|
# Age
|
||||||
elif out_socket == node.outputs[2]:
|
elif out_socket == node.outputs[2]:
|
||||||
@ -276,7 +277,7 @@ def parse_particleinfo(node: bpy.types.ShaderNodeParticleInfo, out_socket: bpy.t
|
|||||||
# Size
|
# Size
|
||||||
elif out_socket == node.outputs[5]:
|
elif out_socket == node.outputs[5]:
|
||||||
c.particle_info['size'] = True
|
c.particle_info['size'] = True
|
||||||
return '1.0'
|
return 'p_size' if particles_on else '1.0'
|
||||||
|
|
||||||
# Velocity
|
# Velocity
|
||||||
elif out_socket == node.outputs[6]:
|
elif out_socket == node.outputs[6]:
|
||||||
|
|||||||
@ -76,7 +76,7 @@ def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket,
|
|||||||
state.out_ior = '({0} * 0.5 + {1} * 0.5)'.format(ior1, ior2)
|
state.out_ior = '({0} * 0.5 + {1} * 0.5)'.format(ior1, ior2)
|
||||||
|
|
||||||
|
|
||||||
if bpy.app.version < (3, 0, 0):
|
if bpy.app.version < (2, 92, 0):
|
||||||
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
|
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
|
||||||
if state.parse_surface:
|
if state.parse_surface:
|
||||||
c.write_normal(node.inputs[20])
|
c.write_normal(node.inputs[20])
|
||||||
@ -84,18 +84,20 @@ if bpy.app.version < (3, 0, 0):
|
|||||||
state.out_metallic = c.parse_value_input(node.inputs[4])
|
state.out_metallic = c.parse_value_input(node.inputs[4])
|
||||||
state.out_specular = c.parse_value_input(node.inputs[5])
|
state.out_specular = c.parse_value_input(node.inputs[5])
|
||||||
state.out_roughness = c.parse_value_input(node.inputs[7])
|
state.out_roughness = c.parse_value_input(node.inputs[7])
|
||||||
if (node.inputs['Emission Strength'].is_linked or node.inputs['Emission Strength'].default_value != 0.0)\
|
if node.inputs['Emission'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission'], (0.0, 0.0, 0.0), comp_alpha=False):
|
||||||
and (node.inputs['Emission'].is_linked or not mat_utils.equals_color_socket(node.inputs['Emission'], (0.0, 0.0, 0.0), comp_alpha=False)):
|
|
||||||
emission_col = c.parse_vector_input(node.inputs[17])
|
emission_col = c.parse_vector_input(node.inputs[17])
|
||||||
emission_strength = c.parse_value_input(node.inputs[18])
|
state.out_emission_col = emission_col
|
||||||
state.out_emission_col = '({0} * {1})'.format(emission_col, emission_strength)
|
|
||||||
mat_state.emission_type = mat_state.EmissionType.SHADED
|
mat_state.emission_type = mat_state.EmissionType.SHADED
|
||||||
else:
|
else:
|
||||||
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
|
mat_state.emission_type = mat_state.EmissionType.NO_EMISSION
|
||||||
if state.parse_opacity:
|
if state.parse_opacity:
|
||||||
state.out_ior = c.parse_value_input(node.inputs[14])
|
state.out_ior = c.parse_value_input(node.inputs[14])
|
||||||
state.out_opacity = c.parse_value_input(node.inputs[19])
|
# In Blender 2.83, Alpha socket is at index 18, not 19
|
||||||
if bpy.app.version >= (3, 0, 0) and bpy.app.version <= (4, 1, 0):
|
if 'Alpha' in node.inputs:
|
||||||
|
state.out_opacity = c.parse_value_input(node.inputs['Alpha'])
|
||||||
|
else:
|
||||||
|
state.out_opacity = '1.0'
|
||||||
|
if bpy.app.version >= (2, 92, 0) and bpy.app.version <= (4, 1, 0):
|
||||||
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
|
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
|
||||||
if state.parse_surface:
|
if state.parse_surface:
|
||||||
c.write_normal(node.inputs[22])
|
c.write_normal(node.inputs[22])
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -32,8 +32,8 @@ else:
|
|||||||
is_displacement = False
|
is_displacement = False
|
||||||
|
|
||||||
# User callbacks
|
# User callbacks
|
||||||
write_material_attribs: Optional[Callable[[dict[str, Any], shader.Shader], bool]] = None
|
write_material_attribs: Optional[Callable[[Dict[str, Any], shader.Shader], bool]] = None
|
||||||
write_material_attribs_post: Optional[Callable[[dict[str, Any], shader.Shader], None]] = None
|
write_material_attribs_post: Optional[Callable[[Dict[str, Any], shader.Shader], None]] = None
|
||||||
write_vertex_attribs: Optional[Callable[[shader.Shader], bool]] = None
|
write_vertex_attribs: Optional[Callable[[shader.Shader], bool]] = None
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +58,6 @@ def make(context_id, rpasses):
|
|||||||
con['alpha_blend_destination'] = mat.lnx_blending_destination_alpha
|
con['alpha_blend_destination'] = mat.lnx_blending_destination_alpha
|
||||||
con['alpha_blend_operation'] = mat.lnx_blending_operation_alpha
|
con['alpha_blend_operation'] = mat.lnx_blending_operation_alpha
|
||||||
con['depth_write'] = False
|
con['depth_write'] = False
|
||||||
con['compare_mode'] = 'less'
|
|
||||||
elif particle:
|
elif particle:
|
||||||
pass
|
pass
|
||||||
# Depth prepass was performed, exclude mat with depth read that
|
# Depth prepass was performed, exclude mat with depth read that
|
||||||
@ -66,6 +65,9 @@ def make(context_id, rpasses):
|
|||||||
elif dprepass and not (rpdat.rp_depth_texture and mat.lnx_depth_read):
|
elif dprepass and not (rpdat.rp_depth_texture and mat.lnx_depth_read):
|
||||||
con['depth_write'] = False
|
con['depth_write'] = False
|
||||||
con['compare_mode'] = 'equal'
|
con['compare_mode'] = 'equal'
|
||||||
|
else:
|
||||||
|
con['depth_write'] = mat.lnx_depth_write
|
||||||
|
con['compare_mode'] = mat.lnx_compare_mode
|
||||||
|
|
||||||
attachment_format = 'RGBA32' if '_LDR' in wrd.world_defs else 'RGBA64'
|
attachment_format = 'RGBA32' if '_LDR' in wrd.world_defs else 'RGBA64'
|
||||||
con['color_attachments'] = [attachment_format, attachment_format]
|
con['color_attachments'] = [attachment_format, attachment_format]
|
||||||
|
|||||||
@ -55,6 +55,7 @@ def write(vert, particle_info=None, shadowmap=False):
|
|||||||
|
|
||||||
# Outs
|
# Outs
|
||||||
out_index = True if particle_info != None and particle_info['index'] else False
|
out_index = True if particle_info != None and particle_info['index'] else False
|
||||||
|
out_random = True if particle_info != None and particle_info['random'] else False
|
||||||
out_age = True if particle_info != None and particle_info['age'] else False
|
out_age = True if particle_info != None and particle_info['age'] else False
|
||||||
out_lifetime = True if particle_info != None and particle_info['lifetime'] else False
|
out_lifetime = True if particle_info != None and particle_info['lifetime'] else False
|
||||||
out_location = True if particle_info != None and particle_info['location'] else False
|
out_location = True if particle_info != None and particle_info['location'] else False
|
||||||
@ -168,8 +169,7 @@ def write(vert, particle_info=None, shadowmap=False):
|
|||||||
vert.write('float s = sin(p_angle);')
|
vert.write('float s = sin(p_angle);')
|
||||||
vert.write('vec3 center = spos.xyz - p_location;')
|
vert.write('vec3 center = spos.xyz - p_location;')
|
||||||
|
|
||||||
match rotation_mode:
|
if rotation_mode == 'OB_X':
|
||||||
case 'OB_X':
|
|
||||||
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
|
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
|
||||||
vert.write('vec2 rotation = vec2(rz.y * c - rz.z * s, rz.y * s + rz.z * c);')
|
vert.write('vec2 rotation = vec2(rz.y * c - rz.z * s, rz.y * s + rz.z * c);')
|
||||||
vert.write('spos.xyz = vec3(rz.x, rotation.x, rotation.y) + p_location;')
|
vert.write('spos.xyz = vec3(rz.x, rotation.x, rotation.y) + p_location;')
|
||||||
@ -178,13 +178,13 @@ def write(vert, particle_info=None, shadowmap=False):
|
|||||||
vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);')
|
vert.write('wnormal = vec3(wnormal.y, -wnormal.x, wnormal.z);')
|
||||||
vert.write('vec2 n_rot = vec2(wnormal.y * c - wnormal.z * s, wnormal.y * s + wnormal.z * c);')
|
vert.write('vec2 n_rot = vec2(wnormal.y * c - wnormal.z * s, wnormal.y * s + wnormal.z * c);')
|
||||||
vert.write('wnormal = normalize(vec3(wnormal.x, n_rot.x, n_rot.y));')
|
vert.write('wnormal = normalize(vec3(wnormal.x, n_rot.x, n_rot.y));')
|
||||||
case 'OB_Y':
|
elif rotation_mode == 'OB_Y':
|
||||||
vert.write('vec2 rotation = vec2(center.x * c + center.z * s, -center.x * s + center.z * c);')
|
vert.write('vec2 rotation = vec2(center.x * c + center.z * s, -center.x * s + center.z * c);')
|
||||||
vert.write('spos.xyz = vec3(rotation.x, center.y, rotation.y) + p_location;')
|
vert.write('spos.xyz = vec3(rotation.x, center.y, rotation.y) + p_location;')
|
||||||
|
|
||||||
if (not shadowmap):
|
if (not shadowmap):
|
||||||
vert.write('wnormal = normalize(vec3(wnormal.x * c + wnormal.z * s, wnormal.y, -wnormal.x * s + wnormal.z * c));')
|
vert.write('wnormal = normalize(vec3(wnormal.x * c + wnormal.z * s, wnormal.y, -wnormal.x * s + wnormal.z * c));')
|
||||||
case 'OB_Z':
|
elif rotation_mode == 'OB_Z':
|
||||||
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
|
vert.write('vec3 rz = vec3(center.y, -center.x, center.z);')
|
||||||
vert.write('vec3 ry = vec3(-rz.z, rz.y, rz.x);')
|
vert.write('vec3 ry = vec3(-rz.z, rz.y, rz.x);')
|
||||||
vert.write('vec2 rotation = vec2(ry.x * c - ry.y * s, ry.x * s + ry.y * c);')
|
vert.write('vec2 rotation = vec2(ry.x * c - ry.y * s, ry.x * s + ry.y * c);')
|
||||||
@ -195,7 +195,7 @@ def write(vert, particle_info=None, shadowmap=False):
|
|||||||
vert.write('wnormal = vec3(-wnormal.z, wnormal.y, wnormal.x);')
|
vert.write('wnormal = vec3(-wnormal.z, wnormal.y, wnormal.x);')
|
||||||
vert.write('vec2 n_rot = vec2(wnormal.x * c - wnormal.y * s, wnormal.x * s + wnormal.y * c);')
|
vert.write('vec2 n_rot = vec2(wnormal.x * c - wnormal.y * s, wnormal.x * s + wnormal.y * c);')
|
||||||
vert.write('wnormal = normalize(vec3(n_rot.x, n_rot.y, wnormal.z));')
|
vert.write('wnormal = normalize(vec3(n_rot.x, n_rot.y, wnormal.z));')
|
||||||
case 'VEL':
|
elif rotation_mode == 'VEL':
|
||||||
vert.write('vec3 forward = -normalize(p_velocity);')
|
vert.write('vec3 forward = -normalize(p_velocity);')
|
||||||
vert.write('if (length(forward) > 1e-5) {')
|
vert.write('if (length(forward) > 1e-5) {')
|
||||||
vert.write('vec3 world_up = vec3(0.0, 0.0, 1.0);')
|
vert.write('vec3 world_up = vec3(0.0, 0.0, 1.0);')
|
||||||
@ -258,6 +258,11 @@ def write(vert, particle_info=None, shadowmap=False):
|
|||||||
vert.add_out('float p_index')
|
vert.add_out('float p_index')
|
||||||
vert.write('p_index = gl_InstanceID;')
|
vert.write('p_index = gl_InstanceID;')
|
||||||
|
|
||||||
|
if out_random:
|
||||||
|
vert.add_out('float p_random')
|
||||||
|
vert.write('p_random = fract(sin(gl_InstanceID) * 43758.5453);')
|
||||||
|
|
||||||
|
|
||||||
def write_tilesheet(vert):
|
def write_tilesheet(vert):
|
||||||
# tilesx, tilesy, framerate - pd[3][0], pd[3][1], pd[3][2]
|
# tilesx, tilesy, framerate - pd[3][0], pd[3][1], pd[3][2]
|
||||||
vert.write('int frame = int((p_age) / pd[3][2]);')
|
vert.write('int frame = int((p_age) / pd[3][2]);')
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Generator
|
from typing import Generator, Tuple
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ def iter_nodes_leenkxpbr(node_group: bpy.types.NodeTree) -> Generator[bpy.types.
|
|||||||
yield node
|
yield node
|
||||||
|
|
||||||
|
|
||||||
def equals_color_socket(socket: bpy.types.NodeSocketColor, value: tuple[float, ...], *, comp_alpha=True) -> bool:
|
def equals_color_socket(socket: bpy.types.NodeSocketColor, value: Tuple[float, ...], *, comp_alpha=True) -> bool:
|
||||||
# NodeSocketColor.default_value is of bpy_prop_array type that doesn't
|
# NodeSocketColor.default_value is of bpy_prop_array type that doesn't
|
||||||
# support direct comparison
|
# support direct comparison
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -4,7 +4,7 @@ This module contains a list of all material nodes that Leenkx supports
|
|||||||
"""
|
"""
|
||||||
from enum import IntEnum, unique
|
from enum import IntEnum, unique
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Optional, Dict, List, Tuple, TypeVar, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class MaterialNodeMeta:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
ALL_NODES: dict[str, MaterialNodeMeta] = {
|
ALL_NODES: Dict[str, MaterialNodeMeta] = {
|
||||||
# --- nodes_color
|
# --- nodes_color
|
||||||
'BRIGHTCONTRAST': MaterialNodeMeta(parse_func=nodes_color.parse_brightcontrast),
|
'BRIGHTCONTRAST': MaterialNodeMeta(parse_func=nodes_color.parse_brightcontrast),
|
||||||
'CURVE_RGB': MaterialNodeMeta(parse_func=nodes_color.parse_curvergb),
|
'CURVE_RGB': MaterialNodeMeta(parse_func=nodes_color.parse_curvergb),
|
||||||
|
|||||||
@ -23,6 +23,7 @@ class ShaderData:
|
|||||||
self.data = {'shader_datas': [self.sd]}
|
self.data = {'shader_datas': [self.sd]}
|
||||||
self.matname = lnx.utils.safesrc(lnx.utils.asset_name(material))
|
self.matname = lnx.utils.safesrc(lnx.utils.asset_name(material))
|
||||||
self.sd['name'] = self.matname + '_data'
|
self.sd['name'] = self.matname + '_data'
|
||||||
|
self.sd['next_pass'] = material.lnx_next_pass
|
||||||
self.sd['contexts'] = []
|
self.sd['contexts'] = []
|
||||||
|
|
||||||
def add_context(self, props) -> 'ShaderContext':
|
def add_context(self, props) -> 'ShaderContext':
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import collections.abc
|
import collections.abc
|
||||||
from typing import Any, Generator, Optional, Type, Union
|
from typing import Any, Generator, Optional, Type, Tuple, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
@ -49,7 +49,7 @@ def iter_nodes_by_type(node_group: bpy.types.NodeTree, ntype: str) -> Generator[
|
|||||||
yield node
|
yield node
|
||||||
|
|
||||||
|
|
||||||
def input_get_connected_node(input_socket: bpy.types.NodeSocket) -> tuple[Optional[bpy.types.Node], Optional[bpy.types.NodeSocket]]:
|
def input_get_connected_node(input_socket: bpy.types.NodeSocket) -> Tuple[Optional[bpy.types.Node], Optional[bpy.types.NodeSocket]]:
|
||||||
"""Get the node and the output socket of that node that is connected
|
"""Get the node and the output socket of that node that is connected
|
||||||
to the given input, while following reroutes. If the input has
|
to the given input, while following reroutes. If the input has
|
||||||
multiple incoming connections, the first one is followed. If the
|
multiple incoming connections, the first one is followed. If the
|
||||||
@ -70,7 +70,7 @@ def input_get_connected_node(input_socket: bpy.types.NodeSocket) -> tuple[Option
|
|||||||
return from_node, link.from_socket
|
return from_node, link.from_socket
|
||||||
|
|
||||||
|
|
||||||
def output_get_connected_node(output_socket: bpy.types.NodeSocket) -> tuple[Optional[bpy.types.Node], Optional[bpy.types.NodeSocket]]:
|
def output_get_connected_node(output_socket: bpy.types.NodeSocket) -> Tuple[Optional[bpy.types.Node], Optional[bpy.types.NodeSocket]]:
|
||||||
"""Get the node and the input socket of that node that is connected
|
"""Get the node and the input socket of that node that is connected
|
||||||
to the given output, while following reroutes. If the output has
|
to the given output, while following reroutes. If the output has
|
||||||
multiple outgoing connections, the first one is followed. If the
|
multiple outgoing connections, the first one is followed. If the
|
||||||
@ -152,7 +152,7 @@ def get_export_node_name(node: bpy.types.Node) -> str:
|
|||||||
return '_' + lnx.utils.safesrc(node.name)
|
return '_' + lnx.utils.safesrc(node.name)
|
||||||
|
|
||||||
|
|
||||||
def get_haxe_property_names(node: bpy.types.Node) -> Generator[tuple[str, str], None, None]:
|
def get_haxe_property_names(node: bpy.types.Node) -> Generator[Tuple[str, str], None, None]:
|
||||||
"""Generator that yields the names of all node properties that have
|
"""Generator that yields the names of all node properties that have
|
||||||
a counterpart in the node's Haxe class.
|
a counterpart in the node's Haxe class.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,5 +1,13 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.props import *
|
from bpy.props import *
|
||||||
|
|
||||||
|
# Helper function to handle version compatibility
|
||||||
|
def compatible_prop(prop_func, **kwargs):
|
||||||
|
"""Create properties compatible with multiple Blender versions."""
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
# Remove override parameter for Blender 2.83
|
||||||
|
kwargs.pop('override', None)
|
||||||
|
return prop_func(**kwargs)
|
||||||
import re
|
import re
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
|
||||||
@ -142,6 +150,8 @@ def init_properties():
|
|||||||
bpy.types.World.lnx_project_version = StringProperty(name="Version", description="Exported project version", default="1.0.0", update=assets.invalidate_compiler_cache, set=set_version, get=get_version)
|
bpy.types.World.lnx_project_version = StringProperty(name="Version", description="Exported project version", default="1.0.0", update=assets.invalidate_compiler_cache, set=set_version, get=get_version)
|
||||||
bpy.types.World.lnx_project_version_autoinc = BoolProperty(name="Auto-increment Build Number", description="Auto-increment build number", default=True, update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_project_version_autoinc = BoolProperty(name="Auto-increment Build Number", description="Auto-increment build number", default=True, update=assets.invalidate_compiler_cache)
|
||||||
bpy.types.World.lnx_project_bundle = StringProperty(name="Bundle", description="Exported project bundle", default="org.leenkx3d", update=assets.invalidate_compiler_cache, set=set_project_bundle, get=get_project_bundle)
|
bpy.types.World.lnx_project_bundle = StringProperty(name="Bundle", description="Exported project bundle", default="org.leenkx3d", update=assets.invalidate_compiler_cache, set=set_project_bundle, get=get_project_bundle)
|
||||||
|
# External Blend Files
|
||||||
|
bpy.types.World.lnx_external_blends_path = StringProperty(name="External Blends", description="Directory containing external blend files to include in export", default="", subtype='DIR_PATH', update=assets.invalidate_compiler_cache)
|
||||||
# Android Settings
|
# Android Settings
|
||||||
bpy.types.World.lnx_project_android_sdk_min = IntProperty(name="Minimal Version SDK", description="Minimal Version Android SDK", default=23, min=14, max=30, update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_project_android_sdk_min = IntProperty(name="Minimal Version SDK", description="Minimal Version Android SDK", default=23, min=14, max=30, update=assets.invalidate_compiler_cache)
|
||||||
bpy.types.World.lnx_project_android_sdk_target = IntProperty(name="Target Version SDK", description="Target Version Android SDK", default=26, min=26, max=30, update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_project_android_sdk_target = IntProperty(name="Target Version SDK", description="Target Version Android SDK", default=26, min=26, max=30, update=assets.invalidate_compiler_cache)
|
||||||
@ -340,7 +350,7 @@ def init_properties():
|
|||||||
bpy.types.World.lnx_winmaximize = BoolProperty(name="Maximizable", description="Allow window maximize", default=False, update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_winmaximize = BoolProperty(name="Maximizable", description="Allow window maximize", default=False, update=assets.invalidate_compiler_cache)
|
||||||
bpy.types.World.lnx_winminimize = BoolProperty(name="Minimizable", description="Allow window minimize", default=True, update=assets.invalidate_compiler_cache)
|
bpy.types.World.lnx_winminimize = BoolProperty(name="Minimizable", description="Allow window minimize", default=True, update=assets.invalidate_compiler_cache)
|
||||||
# For object
|
# For object
|
||||||
bpy.types.Object.lnx_instanced = EnumProperty(
|
bpy.types.Object.lnx_instanced = compatible_prop(EnumProperty,
|
||||||
items = [('Off', 'Off', 'No instancing of children'),
|
items = [('Off', 'Off', 'No instancing of children'),
|
||||||
('Loc', 'Loc', 'Instances use their unique position (ipos)'),
|
('Loc', 'Loc', 'Instances use their unique position (ipos)'),
|
||||||
('Loc + Rot', 'Loc + Rot', 'Instances use their unique position and rotation (ipos and irot)'),
|
('Loc + Rot', 'Loc + Rot', 'Instances use their unique position and rotation (ipos and irot)'),
|
||||||
@ -350,11 +360,12 @@ def init_properties():
|
|||||||
description='Whether to use instancing to draw the children of this object. If enabled, this option defines what attributes may vary between the instances',
|
description='Whether to use instancing to draw the children of this object. If enabled, this option defines what attributes may vary between the instances',
|
||||||
update=assets.invalidate_instance_cache,
|
update=assets.invalidate_instance_cache,
|
||||||
override={'LIBRARY_OVERRIDABLE'})
|
override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_export = BoolProperty(name="Export", description="Export object data", default=True, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_export = compatible_prop(BoolProperty, name="Export", description="Export object data", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_spawn = BoolProperty(name="Spawn", description="Auto-add this object when creating scene", default=True, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_sorting_index = compatible_prop(IntProperty, name="Sorting Index", description="Sorting index for the Render's Draw Order", default=0, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_mobile = BoolProperty(name="Mobile", description="Object moves during gameplay", default=False, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_spawn = compatible_prop(BoolProperty, name="Spawn", description="Auto-add this object when creating scene", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_visible = BoolProperty(name="Visible", description="Render this object", default=True, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_mobile = compatible_prop(BoolProperty, name="Mobile", description="Object moves during gameplay", default=False, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_visible_shadow = BoolProperty(name="Lighting", description="Object contributes to the lighting even if invisible", default=True, override={'LIBRARY_OVERRIDABLE'})
|
bpy.types.Object.lnx_visible = compatible_prop(BoolProperty, name="Visible", description="Render this object", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||||
|
bpy.types.Object.lnx_visible_shadow = compatible_prop(BoolProperty, name="Lighting", description="Object contributes to the lighting even if invisible", default=True, override={'LIBRARY_OVERRIDABLE'})
|
||||||
bpy.types.Object.lnx_soft_body_margin = FloatProperty(name="Soft Body Margin", description="Collision margin", default=0.04)
|
bpy.types.Object.lnx_soft_body_margin = FloatProperty(name="Soft Body Margin", description="Collision margin", default=0.04)
|
||||||
bpy.types.Object.lnx_rb_linear_factor = FloatVectorProperty(name="Linear Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1])
|
bpy.types.Object.lnx_rb_linear_factor = FloatVectorProperty(name="Linear Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1])
|
||||||
bpy.types.Object.lnx_rb_angular_factor = FloatVectorProperty(name="Angular Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1])
|
bpy.types.Object.lnx_rb_angular_factor = FloatVectorProperty(name="Angular Factor", size=3, description="Set to 0 to lock axis", default=[1,1,1])
|
||||||
@ -434,9 +445,22 @@ def init_properties():
|
|||||||
bpy.types.World.lnx_nishita_density = FloatVectorProperty(name="Nishita Density", size=3, default=[1, 1, 1])
|
bpy.types.World.lnx_nishita_density = FloatVectorProperty(name="Nishita Density", size=3, default=[1, 1, 1])
|
||||||
bpy.types.Material.lnx_cast_shadow = BoolProperty(name="Cast Shadow", default=True)
|
bpy.types.Material.lnx_cast_shadow = BoolProperty(name="Cast Shadow", default=True)
|
||||||
bpy.types.Material.lnx_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True)
|
bpy.types.Material.lnx_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True)
|
||||||
|
bpy.types.Material.lnx_depth_write = BoolProperty(name="Write Depth", description="Allow this material to write to the depth buffer", default=True)
|
||||||
bpy.types.Material.lnx_depth_read = BoolProperty(name="Read Depth", description="Allow this material to read from a depth texture which is copied from the depth buffer. The meshes using this material will be drawn after all meshes that don't read from the depth texture", default=False)
|
bpy.types.Material.lnx_depth_read = BoolProperty(name="Read Depth", description="Allow this material to read from a depth texture which is copied from the depth buffer. The meshes using this material will be drawn after all meshes that don't read from the depth texture", default=False)
|
||||||
bpy.types.Material.lnx_overlay = BoolProperty(name="Overlay", description="Renders the material, unshaded, over other shaded materials", default=False)
|
bpy.types.Material.lnx_overlay = BoolProperty(name="Overlay", description="Renders the material, unshaded, over other shaded materials", default=False)
|
||||||
bpy.types.Material.lnx_decal = BoolProperty(name="Decal", default=False)
|
bpy.types.Material.lnx_decal = BoolProperty(name="Decal", default=False)
|
||||||
|
bpy.types.Material.lnx_compare_mode = EnumProperty(
|
||||||
|
items=[
|
||||||
|
('always', 'Always', 'Always'),
|
||||||
|
('never', 'Never', 'Never'),
|
||||||
|
('less', 'Less', 'Less'),
|
||||||
|
('less_equal', 'Less Equal', 'Less Equal'),
|
||||||
|
('greater', 'Greater', 'Greater'),
|
||||||
|
('greater_equal', 'Greater Equal', 'Greater Equal'),
|
||||||
|
('equal', 'Equal', 'Equal'),
|
||||||
|
('not_equal', 'Not Equal', 'Not Equal'),
|
||||||
|
],
|
||||||
|
name="Compare Mode", default='less', description="Comparison mode for the material")
|
||||||
bpy.types.Material.lnx_two_sided = BoolProperty(name="Two-Sided", description="Flip normal when drawing back-face", default=False)
|
bpy.types.Material.lnx_two_sided = BoolProperty(name="Two-Sided", description="Flip normal when drawing back-face", default=False)
|
||||||
bpy.types.Material.lnx_ignore_irradiance = BoolProperty(name="Ignore Irradiance", description="Ignore irradiance for material", default=False)
|
bpy.types.Material.lnx_ignore_irradiance = BoolProperty(name="Ignore Irradiance", description="Ignore irradiance for material", default=False)
|
||||||
bpy.types.Material.lnx_cull_mode = EnumProperty(
|
bpy.types.Material.lnx_cull_mode = EnumProperty(
|
||||||
@ -444,6 +468,8 @@ def init_properties():
|
|||||||
('clockwise', 'Front', 'Clockwise'),
|
('clockwise', 'Front', 'Clockwise'),
|
||||||
('counter_clockwise', 'Back', 'Counter-Clockwise')],
|
('counter_clockwise', 'Back', 'Counter-Clockwise')],
|
||||||
name="Cull Mode", default='clockwise', description="Draw geometry faces")
|
name="Cull Mode", default='clockwise', description="Draw geometry faces")
|
||||||
|
bpy.types.Material.lnx_next_pass = StringProperty(
|
||||||
|
name="Next Pass", default='', description="Next pass for the material", update=assets.invalidate_shader_cache)
|
||||||
bpy.types.Material.lnx_discard = BoolProperty(name="Alpha Test", default=False, description="Do not render fragments below specified opacity threshold")
|
bpy.types.Material.lnx_discard = BoolProperty(name="Alpha Test", default=False, description="Do not render fragments below specified opacity threshold")
|
||||||
bpy.types.Material.lnx_discard_opacity = FloatProperty(name="Mesh Opacity", default=0.2, min=0, max=1)
|
bpy.types.Material.lnx_discard_opacity = FloatProperty(name="Mesh Opacity", default=0.2, min=0, max=1)
|
||||||
bpy.types.Material.lnx_discard_opacity_shadows = FloatProperty(name="Shadows Opacity", default=0.1, min=0, max=1)
|
bpy.types.Material.lnx_discard_opacity_shadows = FloatProperty(name="Shadows Opacity", default=0.1, min=0, max=1)
|
||||||
@ -569,6 +595,11 @@ def init_properties():
|
|||||||
bpy.types.Node.lnx_version = IntProperty(name="Node Version", description="The version of an instanced node", default=0)
|
bpy.types.Node.lnx_version = IntProperty(name="Node Version", description="The version of an instanced node", default=0)
|
||||||
# Particles
|
# Particles
|
||||||
bpy.types.ParticleSettings.lnx_auto_start = BoolProperty(name="Auto Start", description="Automatically start this particle system on load", default=True)
|
bpy.types.ParticleSettings.lnx_auto_start = BoolProperty(name="Auto Start", description="Automatically start this particle system on load", default=True)
|
||||||
|
bpy.types.ParticleSettings.lnx_dynamic_emitter = BoolProperty(
|
||||||
|
name="Dynamic",
|
||||||
|
description="Particles have independent transform updates following emitter compared to a static baked particle system used if emitters dont generally move around.",
|
||||||
|
default=True
|
||||||
|
)
|
||||||
bpy.types.ParticleSettings.lnx_is_unique = BoolProperty(name="Is Unique", description="Make this particle system look different each time it starts", default=False)
|
bpy.types.ParticleSettings.lnx_is_unique = BoolProperty(name="Is Unique", description="Make this particle system look different each time it starts", default=False)
|
||||||
bpy.types.ParticleSettings.lnx_loop = BoolProperty(name="Loop", description="Loop this particle system", default=False)
|
bpy.types.ParticleSettings.lnx_loop = BoolProperty(name="Loop", description="Loop this particle system", default=False)
|
||||||
bpy.types.ParticleSettings.lnx_count_mult = FloatProperty(name="Multiply Count", description="Multiply particle count when rendering in Leenkx", default=1.0)
|
bpy.types.ParticleSettings.lnx_count_mult = FloatProperty(name="Multiply Count", description="Multiply particle count when rendering in Leenkx", default=1.0)
|
||||||
|
|||||||
@ -420,15 +420,18 @@ class LNX_OT_ExporterOpenVS(bpy.types.Operator):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
if not lnx.utils.get_os_is_windows():
|
if not lnx.utils.get_os_is_windows():
|
||||||
|
if bpy.app.version >= (2, 90, 0):
|
||||||
cls.poll_message_set('This operator is only supported on Windows')
|
cls.poll_message_set('This operator is only supported on Windows')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
if len(wrd.lnx_exporterlist) == 0:
|
if len(wrd.lnx_exporterlist) == 0:
|
||||||
|
if bpy.app.version >= (2, 90, 0):
|
||||||
cls.poll_message_set('No export configuration exists')
|
cls.poll_message_set('No export configuration exists')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrd.lnx_exporterlist[wrd.lnx_exporterlist_index].lnx_project_target != 'windows-hl':
|
if wrd.lnx_exporterlist[wrd.lnx_exporterlist_index].lnx_project_target != 'windows-hl':
|
||||||
|
if bpy.app.version >= (2, 90, 0):
|
||||||
cls.poll_message_set('This operator only works with the Windows (C) target')
|
cls.poll_message_set('This operator only works with the Windows (C) target')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@ -12,9 +12,11 @@ import bpy.utils.previews
|
|||||||
import lnx.make as make
|
import lnx.make as make
|
||||||
from lnx.props_traits_props import *
|
from lnx.props_traits_props import *
|
||||||
import lnx.ui_icons as ui_icons
|
import lnx.ui_icons as ui_icons
|
||||||
|
|
||||||
import lnx.utils
|
import lnx.utils
|
||||||
import lnx.write_data as write_data
|
import lnx.write_data as write_data
|
||||||
|
|
||||||
|
|
||||||
if lnx.is_reload(__name__):
|
if lnx.is_reload(__name__):
|
||||||
lnx.make = lnx.reload_module(lnx.make)
|
lnx.make = lnx.reload_module(lnx.make)
|
||||||
lnx.props_traits_props = lnx.reload_module(lnx.props_traits_props)
|
lnx.props_traits_props = lnx.reload_module(lnx.props_traits_props)
|
||||||
@ -91,19 +93,30 @@ class LnxTraitListItem(bpy.types.PropertyGroup):
|
|||||||
"""Ensure that only logic node trees show up as node traits"""
|
"""Ensure that only logic node trees show up as node traits"""
|
||||||
return tree.bl_idname == 'LnxLogicTreeType'
|
return tree.bl_idname == 'LnxLogicTreeType'
|
||||||
|
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
name: StringProperty(name="Name", description="The name of the trait", default="")
|
||||||
|
enabled_prop: BoolProperty(name="", description="Whether this trait is enabled", default=True, update=trigger_recompile)
|
||||||
|
fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False)
|
||||||
|
class_name_prop: StringProperty(name="Class", description="A name for this item", default="", update=update_trait_group)
|
||||||
|
canvas_name_prop: StringProperty(name="Canvas", description="A name for this item", default="", update=update_trait_group)
|
||||||
|
webassembly_prop: StringProperty(name="Module", description="A name for this item", default="", update=update_trait_group)
|
||||||
|
node_tree_prop: PointerProperty(type=NodeTree, update=update_trait_group, poll=poll_node_trees)
|
||||||
|
lnx_traitpropslist_index: IntProperty(name="Index for my_list", default=0, options={"LIBRARY_EDITABLE"})
|
||||||
|
else:
|
||||||
name: StringProperty(name="Name", description="The name of the trait", default="", override={"LIBRARY_OVERRIDABLE"})
|
name: StringProperty(name="Name", description="The name of the trait", default="", override={"LIBRARY_OVERRIDABLE"})
|
||||||
enabled_prop: BoolProperty(name="", description="Whether this trait is enabled", default=True, update=trigger_recompile, override={"LIBRARY_OVERRIDABLE"})
|
enabled_prop: BoolProperty(name="", description="Whether this trait is enabled", default=True, update=trigger_recompile, override={"LIBRARY_OVERRIDABLE"})
|
||||||
is_object: BoolProperty(name="", default=True)
|
|
||||||
fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False, override={"LIBRARY_OVERRIDABLE"})
|
fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False, override={"LIBRARY_OVERRIDABLE"})
|
||||||
type_prop: EnumProperty(name="Type", items=PROP_TYPES_ENUM)
|
|
||||||
|
|
||||||
class_name_prop: StringProperty(name="Class", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
class_name_prop: StringProperty(name="Class", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
||||||
canvas_name_prop: StringProperty(name="Canvas", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
canvas_name_prop: StringProperty(name="Canvas", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
||||||
webassembly_prop: StringProperty(name="Module", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
webassembly_prop: StringProperty(name="Module", description="A name for this item", default="", update=update_trait_group, override={"LIBRARY_OVERRIDABLE"})
|
||||||
node_tree_prop: PointerProperty(type=NodeTree, update=update_trait_group, override={"LIBRARY_OVERRIDABLE"}, poll=poll_node_trees)
|
node_tree_prop: PointerProperty(type=NodeTree, update=update_trait_group, override={"LIBRARY_OVERRIDABLE"}, poll=poll_node_trees)
|
||||||
|
lnx_traitpropslist_index: IntProperty(name="Index for my_list", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
||||||
|
|
||||||
|
is_object: BoolProperty(name="", default=True)
|
||||||
|
type_prop: EnumProperty(name="Type", items=PROP_TYPES_ENUM)
|
||||||
|
|
||||||
|
|
||||||
lnx_traitpropslist: CollectionProperty(type=LnxTraitPropListItem)
|
lnx_traitpropslist: CollectionProperty(type=LnxTraitPropListItem)
|
||||||
lnx_traitpropslist_index: IntProperty(name="Index for my_list", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
|
||||||
lnx_traitpropswarnings: CollectionProperty(type=LnxTraitPropWarning)
|
lnx_traitpropswarnings: CollectionProperty(type=LnxTraitPropWarning)
|
||||||
|
|
||||||
class LNX_UL_TraitList(bpy.types.UIList):
|
class LNX_UL_TraitList(bpy.types.UIList):
|
||||||
@ -756,6 +769,7 @@ class LnxRefreshObjectScriptsButton(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
|
if bpy.app.version >= (2, 90, 0):
|
||||||
cls.poll_message_set(LnxRefreshScriptsButton.poll_msg)
|
cls.poll_message_set(LnxRefreshScriptsButton.poll_msg)
|
||||||
# Technically we could keep the operator enabled here since
|
# Technically we could keep the operator enabled here since
|
||||||
# fetch_trait_props() checks for overrides and the operator does
|
# fetch_trait_props() checks for overrides and the operator does
|
||||||
@ -1064,11 +1078,17 @@ __REG_CLASSES = (
|
|||||||
)
|
)
|
||||||
__reg_classes, unregister = bpy.utils.register_classes_factory(__REG_CLASSES)
|
__reg_classes, unregister = bpy.utils.register_classes_factory(__REG_CLASSES)
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
__reg_classes()
|
__reg_classes()
|
||||||
|
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
bpy.types.Object.lnx_traitlist = CollectionProperty(type=LnxTraitListItem)
|
||||||
|
bpy.types.Object.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"})
|
||||||
|
bpy.types.Scene.lnx_traitlist = CollectionProperty(type=LnxTraitListItem)
|
||||||
|
bpy.types.Scene.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"})
|
||||||
|
else:
|
||||||
bpy.types.Object.lnx_traitlist = CollectionProperty(type=LnxTraitListItem, override={"LIBRARY_OVERRIDABLE", "USE_INSERTION"})
|
bpy.types.Object.lnx_traitlist = CollectionProperty(type=LnxTraitListItem, override={"LIBRARY_OVERRIDABLE", "USE_INSERTION"})
|
||||||
bpy.types.Object.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
bpy.types.Object.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
||||||
bpy.types.Scene.lnx_traitlist = CollectionProperty(type=LnxTraitListItem, override={"LIBRARY_OVERRIDABLE", "USE_INSERTION"})
|
bpy.types.Scene.lnx_traitlist = CollectionProperty(type=LnxTraitListItem, override={"LIBRARY_OVERRIDABLE", "USE_INSERTION"})
|
||||||
bpy.types.Scene.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
bpy.types.Scene.lnx_traitlist_index = IntProperty(name="Index for lnx_traitlist", default=0, options={"LIBRARY_EDITABLE"}, override={"LIBRARY_OVERRIDABLE"})
|
||||||
|
|
||||||
@ -3,6 +3,7 @@ from bpy.props import *
|
|||||||
|
|
||||||
__all__ = ['LnxTraitPropWarning', 'LnxTraitPropListItem', 'LNX_UL_PropList']
|
__all__ = ['LnxTraitPropWarning', 'LnxTraitPropListItem', 'LNX_UL_PropList']
|
||||||
|
|
||||||
|
|
||||||
PROP_TYPE_ICONS = {
|
PROP_TYPE_ICONS = {
|
||||||
"String": "SORTALPHA",
|
"String": "SORTALPHA",
|
||||||
"Int": "CHECKBOX_DEHLT",
|
"Int": "CHECKBOX_DEHLT",
|
||||||
@ -45,7 +46,36 @@ class LnxTraitPropListItem(bpy.types.PropertyGroup):
|
|||||||
name="Name",
|
name="Name",
|
||||||
description="The name of this property",
|
description="The name of this property",
|
||||||
default="Untitled")
|
default="Untitled")
|
||||||
|
if bpy.app.version < (2, 90, 0):
|
||||||
|
type: EnumProperty(
|
||||||
|
items=(
|
||||||
|
# (Haxe Type, Display Name, Description)
|
||||||
|
("String", "String", "String Type"),
|
||||||
|
("Int", "Integer", "Integer Type"),
|
||||||
|
("Float", "Float", "Float Type"),
|
||||||
|
("Bool", "Boolean", "Boolean Type"),
|
||||||
|
("Vec2", "Vec2", "2D Vector Type"),
|
||||||
|
("Vec3", "Vec3", "3D Vector Type"),
|
||||||
|
("Vec4", "Vec4", "4D Vector Type"),
|
||||||
|
("Object", "Object", "Object Type"),
|
||||||
|
("CameraObject", "Camera Object", "Camera Object Type"),
|
||||||
|
("LightObject", "Light Object", "Light Object Type"),
|
||||||
|
("MeshObject", "Mesh Object", "Mesh Object Type"),
|
||||||
|
("SpeakerObject", "Speaker Object", "Speaker Object Type"),
|
||||||
|
("TSceneFormat", "Scene", "Scene Type")),
|
||||||
|
name="Type",
|
||||||
|
description="The type of this property",
|
||||||
|
default="String")
|
||||||
|
value_string: StringProperty(name="Value", default="")
|
||||||
|
value_int: IntProperty(name="Value", default=0)
|
||||||
|
value_float: FloatProperty(name="Value", default=0.0)
|
||||||
|
value_bool: BoolProperty(name="Value", default=False)
|
||||||
|
value_vec2: FloatVectorProperty(name="Value", size=2)
|
||||||
|
value_vec3: FloatVectorProperty(name="Value", size=3)
|
||||||
|
value_vec4: FloatVectorProperty(name="Value", size=4)
|
||||||
|
value_object: PointerProperty(name="Value", type=bpy.types.Object, poll=filter_objects)
|
||||||
|
value_scene: PointerProperty(name="Value", type=bpy.types.Scene)
|
||||||
|
else:
|
||||||
type: EnumProperty(
|
type: EnumProperty(
|
||||||
items=(
|
items=(
|
||||||
# (Haxe Type, Display Name, Description)
|
# (Haxe Type, Display Name, Description)
|
||||||
@ -65,10 +95,7 @@ class LnxTraitPropListItem(bpy.types.PropertyGroup):
|
|||||||
name="Type",
|
name="Type",
|
||||||
description="The type of this property",
|
description="The type of this property",
|
||||||
default="String",
|
default="String",
|
||||||
override={"LIBRARY_OVERRIDABLE"}
|
override={"LIBRARY_OVERRIDABLE"})
|
||||||
)
|
|
||||||
|
|
||||||
# === VALUES ===
|
|
||||||
value_string: StringProperty(name="Value", default="", override={"LIBRARY_OVERRIDABLE"})
|
value_string: StringProperty(name="Value", default="", override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_int: IntProperty(name="Value", default=0, override={"LIBRARY_OVERRIDABLE"})
|
value_int: IntProperty(name="Value", default=0, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_float: FloatProperty(name="Value", default=0.0, override={"LIBRARY_OVERRIDABLE"})
|
value_float: FloatProperty(name="Value", default=0.0, override={"LIBRARY_OVERRIDABLE"})
|
||||||
@ -76,10 +103,7 @@ class LnxTraitPropListItem(bpy.types.PropertyGroup):
|
|||||||
value_vec2: FloatVectorProperty(name="Value", size=2, override={"LIBRARY_OVERRIDABLE"})
|
value_vec2: FloatVectorProperty(name="Value", size=2, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_vec3: FloatVectorProperty(name="Value", size=3, override={"LIBRARY_OVERRIDABLE"})
|
value_vec3: FloatVectorProperty(name="Value", size=3, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_vec4: FloatVectorProperty(name="Value", size=4, override={"LIBRARY_OVERRIDABLE"})
|
value_vec4: FloatVectorProperty(name="Value", size=4, override={"LIBRARY_OVERRIDABLE"})
|
||||||
value_object: PointerProperty(
|
value_object: PointerProperty(name="Value", type=bpy.types.Object, poll=filter_objects, override={"LIBRARY_OVERRIDABLE"})
|
||||||
name="Value", type=bpy.types.Object, poll=filter_objects,
|
|
||||||
override={"LIBRARY_OVERRIDABLE"}
|
|
||||||
)
|
|
||||||
value_scene: PointerProperty(name="Value", type=bpy.types.Scene, override={"LIBRARY_OVERRIDABLE"})
|
value_scene: PointerProperty(name="Value", type=bpy.types.Scene, override={"LIBRARY_OVERRIDABLE"})
|
||||||
|
|
||||||
def set_value(self, val):
|
def set_value(self, val):
|
||||||
|
|||||||
@ -8,6 +8,24 @@ import mathutils
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.props import *
|
from bpy.props import *
|
||||||
|
|
||||||
|
# Helper functions for Blender version compatibility
|
||||||
|
def get_panel_options():
|
||||||
|
"""Get panel options compatible with current Blender version."""
|
||||||
|
if bpy.app.version >= (2, 93, 0): # INSTANCED was introduced around 2.93
|
||||||
|
return {'INSTANCED'}
|
||||||
|
else:
|
||||||
|
return set() # Empty set for older versions
|
||||||
|
|
||||||
|
def column_with_heading(layout, heading='', align=False):
|
||||||
|
"""Create a column with optional heading, compatible across Blender versions."""
|
||||||
|
if bpy.app.version >= (2, 92, 0):
|
||||||
|
return layout.column(heading=heading, align=align)
|
||||||
|
else:
|
||||||
|
col = layout.column(align=align)
|
||||||
|
if heading:
|
||||||
|
col.label(text=heading)
|
||||||
|
return col
|
||||||
|
|
||||||
from lnx.lightmapper.panels import scene
|
from lnx.lightmapper.panels import scene
|
||||||
|
|
||||||
import lnx.api
|
import lnx.api
|
||||||
@ -63,6 +81,7 @@ class LNX_PT_ObjectPropsPanel(bpy.types.Panel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
|
col.prop(obj, 'lnx_sorting_index')
|
||||||
col.prop(obj, 'lnx_export')
|
col.prop(obj, 'lnx_export')
|
||||||
if not obj.lnx_export:
|
if not obj.lnx_export:
|
||||||
return
|
return
|
||||||
@ -206,6 +225,7 @@ class LNX_PT_ParticlesPropsPanel(bpy.types.Panel):
|
|||||||
return
|
return
|
||||||
|
|
||||||
layout.prop(obj.settings, 'lnx_auto_start')
|
layout.prop(obj.settings, 'lnx_auto_start')
|
||||||
|
layout.prop(obj.settings, 'lnx_dynamic_emitter')
|
||||||
layout.prop(obj.settings, 'lnx_is_unique')
|
layout.prop(obj.settings, 'lnx_is_unique')
|
||||||
layout.prop(obj.settings, 'lnx_loop')
|
layout.prop(obj.settings, 'lnx_loop')
|
||||||
layout.prop(obj.settings, 'lnx_count_mult')
|
layout.prop(obj.settings, 'lnx_count_mult')
|
||||||
@ -551,6 +571,51 @@ class LNX_OT_NewCustomMaterial(bpy.types.Operator):
|
|||||||
|
|
||||||
return{'FINISHED'}
|
return{'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class LNX_OT_NextPassMaterialSelector(bpy.types.Operator):
|
||||||
|
"""Select material for next pass"""
|
||||||
|
bl_idname = "lnx.next_pass_material_selector"
|
||||||
|
bl_label = "Select Next Pass Material"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
context.window_manager.popup_menu(self.draw_menu, title="Select Next Pass Material", icon='MATERIAL')
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def draw_menu(self, popup, context):
|
||||||
|
layout = popup.layout
|
||||||
|
|
||||||
|
# Add 'None' option
|
||||||
|
op = layout.operator("lnx.set_next_pass_material", text="")
|
||||||
|
op.material_name = ""
|
||||||
|
|
||||||
|
# Add materials from the current object's material slots
|
||||||
|
if context.object and hasattr(context.object, 'material_slots'):
|
||||||
|
for slot in context.object.material_slots:
|
||||||
|
if (slot.material is not None and slot.material != context.material):
|
||||||
|
op = layout.operator("lnx.set_next_pass_material", text=slot.material.name)
|
||||||
|
op.material_name = slot.material.name
|
||||||
|
|
||||||
|
class LNX_OT_SetNextPassMaterial(bpy.types.Operator):
|
||||||
|
"""Set the next pass material"""
|
||||||
|
bl_idname = "lnx.set_next_pass_material"
|
||||||
|
bl_label = "Set Next Pass Material"
|
||||||
|
|
||||||
|
material_name: StringProperty()
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
if context.material:
|
||||||
|
context.material.lnx_next_pass = self.material_name
|
||||||
|
# Redraw the UI to update the display
|
||||||
|
for area in context.screen.areas:
|
||||||
|
if area.type == 'PROPERTIES':
|
||||||
|
area.tag_redraw()
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LNX_PG_BindTexturesListItem(bpy.types.PropertyGroup):
|
class LNX_PG_BindTexturesListItem(bpy.types.PropertyGroup):
|
||||||
uniform_name: StringProperty(
|
uniform_name: StringProperty(
|
||||||
name='Uniform Name',
|
name='Uniform Name',
|
||||||
@ -641,11 +706,16 @@ class LNX_PT_MaterialPropsPanel(bpy.types.Panel):
|
|||||||
columnb.enabled = len(wrd.lnx_rplist) > 0 and lnx.utils.get_rp().rp_renderer == 'Forward'
|
columnb.enabled = len(wrd.lnx_rplist) > 0 and lnx.utils.get_rp().rp_renderer == 'Forward'
|
||||||
columnb.prop(mat, 'lnx_receive_shadow')
|
columnb.prop(mat, 'lnx_receive_shadow')
|
||||||
layout.prop(mat, 'lnx_ignore_irradiance')
|
layout.prop(mat, 'lnx_ignore_irradiance')
|
||||||
|
layout.prop(mat, 'lnx_compare_mode')
|
||||||
layout.prop(mat, 'lnx_two_sided')
|
layout.prop(mat, 'lnx_two_sided')
|
||||||
columnb = layout.column()
|
columnb = layout.column()
|
||||||
columnb.enabled = not mat.lnx_two_sided
|
columnb.enabled = not mat.lnx_two_sided
|
||||||
columnb.prop(mat, 'lnx_cull_mode')
|
columnb.prop(mat, 'lnx_cull_mode')
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.prop(mat, 'lnx_next_pass', text="Next Pass")
|
||||||
|
row.operator('lnx.next_pass_material_selector', text='', icon='MATERIAL')
|
||||||
layout.prop(mat, 'lnx_material_id')
|
layout.prop(mat, 'lnx_material_id')
|
||||||
|
layout.prop(mat, 'lnx_depth_write')
|
||||||
layout.prop(mat, 'lnx_depth_read')
|
layout.prop(mat, 'lnx_depth_read')
|
||||||
layout.prop(mat, 'lnx_overlay')
|
layout.prop(mat, 'lnx_overlay')
|
||||||
layout.prop(mat, 'lnx_decal')
|
layout.prop(mat, 'lnx_decal')
|
||||||
@ -887,13 +957,13 @@ class LNX_PT_LeenkxExporterPanel(bpy.types.Panel):
|
|||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.prop(wrd, 'lnx_project_icon')
|
col.prop(wrd, 'lnx_project_icon')
|
||||||
|
|
||||||
col = layout.column(heading='Code Output', align=True)
|
col = column_with_heading(layout, 'Code Output', align=True)
|
||||||
col.prop(wrd, 'lnx_dce')
|
col.prop(wrd, 'lnx_dce')
|
||||||
col.prop(wrd, 'lnx_compiler_inline')
|
col.prop(wrd, 'lnx_compiler_inline')
|
||||||
col.prop(wrd, 'lnx_minify_js')
|
col.prop(wrd, 'lnx_minify_js')
|
||||||
col.prop(wrd, 'lnx_no_traces')
|
col.prop(wrd, 'lnx_no_traces')
|
||||||
|
|
||||||
col = layout.column(heading='Data', align=True)
|
col = column_with_heading(layout, 'Data', align=True)
|
||||||
col.prop(wrd, 'lnx_minimize')
|
col.prop(wrd, 'lnx_minimize')
|
||||||
col.prop(wrd, 'lnx_optimize_data')
|
col.prop(wrd, 'lnx_optimize_data')
|
||||||
col.prop(wrd, 'lnx_asset_compression')
|
col.prop(wrd, 'lnx_asset_compression')
|
||||||
@ -1126,32 +1196,32 @@ class LNX_PT_ProjectFlagsPanel(bpy.types.Panel):
|
|||||||
layout.use_property_decorate = False
|
layout.use_property_decorate = False
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
|
|
||||||
col = layout.column(heading='Debug', align=True)
|
col = column_with_heading(layout, 'Debug', align=True)
|
||||||
col.prop(wrd, 'lnx_verbose_output')
|
col.prop(wrd, 'lnx_verbose_output')
|
||||||
col.prop(wrd, 'lnx_cache_build')
|
col.prop(wrd, 'lnx_cache_build')
|
||||||
col.prop(wrd, 'lnx_clear_on_compile')
|
col.prop(wrd, 'lnx_clear_on_compile')
|
||||||
col.prop(wrd, 'lnx_assert_level')
|
col.prop(wrd, 'lnx_assert_level')
|
||||||
col.prop(wrd, 'lnx_assert_quit')
|
col.prop(wrd, 'lnx_assert_quit')
|
||||||
|
|
||||||
col = layout.column(heading='Runtime', align=True)
|
col = column_with_heading(layout, 'Runtime', align=True)
|
||||||
col.prop(wrd, 'lnx_live_patch')
|
col.prop(wrd, 'lnx_live_patch')
|
||||||
col.prop(wrd, 'lnx_stream_scene')
|
col.prop(wrd, 'lnx_stream_scene')
|
||||||
col.prop(wrd, 'lnx_loadscreen')
|
col.prop(wrd, 'lnx_loadscreen')
|
||||||
col.prop(wrd, 'lnx_write_config')
|
col.prop(wrd, 'lnx_write_config')
|
||||||
|
|
||||||
col = layout.column(heading='Renderer', align=True)
|
col = column_with_heading(layout, 'Renderer', align=True)
|
||||||
col.prop(wrd, 'lnx_batch_meshes')
|
col.prop(wrd, 'lnx_batch_meshes')
|
||||||
col.prop(wrd, 'lnx_batch_materials')
|
col.prop(wrd, 'lnx_batch_materials')
|
||||||
col.prop(wrd, 'lnx_deinterleaved_buffers')
|
col.prop(wrd, 'lnx_deinterleaved_buffers')
|
||||||
col.prop(wrd, 'lnx_export_tangents')
|
col.prop(wrd, 'lnx_export_tangents')
|
||||||
|
|
||||||
col = layout.column(heading='Quality')
|
col = column_with_heading(layout, 'Quality')
|
||||||
row = col.row() # To expand below property UI horizontally
|
row = col.row() # To expand below property UI horizontally
|
||||||
row.prop(wrd, 'lnx_canvas_img_scaling_quality', expand=True)
|
row.prop(wrd, 'lnx_canvas_img_scaling_quality', expand=True)
|
||||||
col.prop(wrd, 'lnx_texture_quality')
|
col.prop(wrd, 'lnx_texture_quality')
|
||||||
col.prop(wrd, 'lnx_sound_quality')
|
col.prop(wrd, 'lnx_sound_quality')
|
||||||
|
|
||||||
col = layout.column(heading='External Assets')
|
col = column_with_heading(layout, 'External Assets')
|
||||||
col.prop(wrd, 'lnx_copy_override')
|
col.prop(wrd, 'lnx_copy_override')
|
||||||
col.operator('lnx.copy_to_bundled', icon='IMAGE_DATA')
|
col.operator('lnx.copy_to_bundled', icon='IMAGE_DATA')
|
||||||
|
|
||||||
@ -1229,6 +1299,7 @@ class LNX_PT_ProjectModulesPanel(bpy.types.Panel):
|
|||||||
|
|
||||||
layout.prop_search(wrd, 'lnx_khafile', bpy.data, 'texts')
|
layout.prop_search(wrd, 'lnx_khafile', bpy.data, 'texts')
|
||||||
layout.prop(wrd, 'lnx_project_root')
|
layout.prop(wrd, 'lnx_project_root')
|
||||||
|
layout.prop(wrd, 'lnx_external_blends_path')
|
||||||
|
|
||||||
class LnxVirtualInputPanel(bpy.types.Panel):
|
class LnxVirtualInputPanel(bpy.types.Panel):
|
||||||
bl_label = "Leenkx Virtual Input"
|
bl_label = "Leenkx Virtual Input"
|
||||||
@ -1464,7 +1535,7 @@ class LNX_PT_TopbarPanel(bpy.types.Panel):
|
|||||||
bl_label = "Leenkx Player"
|
bl_label = "Leenkx Player"
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
bl_region_type = "WINDOW"
|
bl_region_type = "WINDOW"
|
||||||
bl_options = {'INSTANCED'}
|
bl_options = get_panel_options()
|
||||||
|
|
||||||
def draw_header(self, context):
|
def draw_header(self, context):
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
@ -2269,6 +2340,9 @@ class LnxGenTerrainButton(bpy.types.Operator):
|
|||||||
node.location = (-200, -200)
|
node.location = (-200, -200)
|
||||||
node.inputs[0].default_value = 5.0
|
node.inputs[0].default_value = 5.0
|
||||||
links.new(nodes['Bump'].inputs[2], nodes['_TerrainHeight'].outputs[0])
|
links.new(nodes['Bump'].inputs[2], nodes['_TerrainHeight'].outputs[0])
|
||||||
|
if bpy.app.version[0] >= 4:
|
||||||
|
links.new(nodes['Principled BSDF'].inputs[22], nodes['Bump'].outputs[0])
|
||||||
|
else:
|
||||||
links.new(nodes['Principled BSDF'].inputs[20], nodes['Bump'].outputs[0])
|
links.new(nodes['Principled BSDF'].inputs[20], nodes['Bump'].outputs[0])
|
||||||
|
|
||||||
# Create sectors
|
# Create sectors
|
||||||
@ -2302,7 +2376,16 @@ class LnxGenTerrainButton(bpy.types.Operator):
|
|||||||
disp_mod.texture.extension = 'EXTEND'
|
disp_mod.texture.extension = 'EXTEND'
|
||||||
disp_mod.texture.use_interpolation = False
|
disp_mod.texture.use_interpolation = False
|
||||||
disp_mod.texture.use_mipmap = False
|
disp_mod.texture.use_mipmap = False
|
||||||
|
try:
|
||||||
disp_mod.texture.image = bpy.data.images.load(filepath=scn.lnx_terrain_textures+'/heightmap_' + j + '.png')
|
disp_mod.texture.image = bpy.data.images.load(filepath=scn.lnx_terrain_textures+'/heightmap_' + j + '.png')
|
||||||
|
except Exception as e:
|
||||||
|
if i == 0: # Only show message once
|
||||||
|
if scn.lnx_terrain_textures.startswith('//') and not bpy.data.filepath:
|
||||||
|
self.report({'INFO'}, "Generating terrain... Save .blend file and add your heightmaps for each sector in "
|
||||||
|
"the \"Bundled\" folder using the format \"heightmap_01.png\", \"heightmap_02.png\", etc.")
|
||||||
|
else:
|
||||||
|
self.report({'INFO'}, f"Heightmap not found: {scn.lnx_terrain_textures}/heightmap_{j}.png - using blank image")
|
||||||
|
|
||||||
f = 1
|
f = 1
|
||||||
levels = 0
|
levels = 0
|
||||||
while f < disp_mod.texture.image.size[0]:
|
while f < disp_mod.texture.image.size[0]:
|
||||||
@ -2858,7 +2941,7 @@ def draw_conditional_prop(layout: bpy.types.UILayout, heading: str, data: bpy.ty
|
|||||||
"""Draws a property row with a checkbox that enables a value field.
|
"""Draws a property row with a checkbox that enables a value field.
|
||||||
The function fails when prop_condition is not a boolean property.
|
The function fails when prop_condition is not a boolean property.
|
||||||
"""
|
"""
|
||||||
col = layout.column(heading=heading)
|
col = column_with_heading(layout, heading)
|
||||||
row = col.row()
|
row = col.row()
|
||||||
row.prop(data, prop_condition, text='')
|
row.prop(data, prop_condition, text='')
|
||||||
sub = row.row()
|
sub = row.row()
|
||||||
@ -2910,6 +2993,8 @@ __REG_CLASSES = (
|
|||||||
InvalidateCacheButton,
|
InvalidateCacheButton,
|
||||||
InvalidateMaterialCacheButton,
|
InvalidateMaterialCacheButton,
|
||||||
LNX_OT_NewCustomMaterial,
|
LNX_OT_NewCustomMaterial,
|
||||||
|
LNX_OT_NextPassMaterialSelector,
|
||||||
|
LNX_OT_SetNextPassMaterial,
|
||||||
LNX_PG_BindTexturesListItem,
|
LNX_PG_BindTexturesListItem,
|
||||||
LNX_UL_BindTexturesList,
|
LNX_UL_BindTexturesList,
|
||||||
LNX_OT_BindTexturesListNewItem,
|
LNX_OT_BindTexturesListNewItem,
|
||||||
|
|||||||
@ -96,7 +96,7 @@ def convert_image(image, path, file_format='JPEG'):
|
|||||||
ren.image_settings.color_mode = orig_color_mode
|
ren.image_settings.color_mode = orig_color_mode
|
||||||
|
|
||||||
|
|
||||||
def get_random_color_rgb() -> list[float]:
|
def get_random_color_rgb() -> List[float]:
|
||||||
"""Return a random RGB color with values in range [0, 1]."""
|
"""Return a random RGB color with values in range [0, 1]."""
|
||||||
return [random.random(), random.random(), random.random()]
|
return [random.random(), random.random(), random.random()]
|
||||||
|
|
||||||
@ -1162,7 +1162,7 @@ def get_link_web_server():
|
|||||||
return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server
|
return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server
|
||||||
|
|
||||||
|
|
||||||
def get_file_lnx_version_tuple() -> tuple[int]:
|
def get_file_lnx_version_tuple() -> Tuple[int, ...]:
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
return tuple(map(int, wrd.lnx_version.split('.')))
|
return tuple(map(int, wrd.lnx_version.split('.')))
|
||||||
|
|
||||||
@ -1218,9 +1218,9 @@ def cpu_count(*, physical_only=False) -> Optional[int]:
|
|||||||
return int(subprocess.check_output(command))
|
return int(subprocess.check_output(command))
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
err_reason = f'Reason: command {command} exited with code {e.returncode}.'
|
err_reason = 'Reason: command {} exited with code {}.'.format(command, e.returncode)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
err_reason = f'Reason: couldn\'t open file from command {command} ({e.errno=}).'
|
err_reason = 'Reason: couldn\'t open file from command {} (errno={}).'.format(command, e.errno)
|
||||||
|
|
||||||
# Last resort even though it can be wrong
|
# Last resort even though it can be wrong
|
||||||
log.warn("Could not retrieve count of physical CPUs, using logical CPU count instead.\n\t" + err_reason)
|
log.warn("Could not retrieve count of physical CPUs, using logical CPU count instead.\n\t" + err_reason)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Any, Optional, Callable
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ def is_version_installed(version_major: str) -> bool:
|
|||||||
return any(v['version_major'] == version_major for v in _installed_versions)
|
return any(v['version_major'] == version_major for v in _installed_versions)
|
||||||
|
|
||||||
|
|
||||||
def get_installed_version(version_major: str, re_fetch=False) -> Optional[dict[str, str]]:
|
def get_installed_version(version_major: str, re_fetch=False) -> Optional[Dict[str, str]]:
|
||||||
for installed_version in _installed_versions:
|
for installed_version in _installed_versions:
|
||||||
if installed_version['version_major'] == version_major:
|
if installed_version['version_major'] == version_major:
|
||||||
return installed_version
|
return installed_version
|
||||||
@ -71,7 +71,7 @@ def get_installed_version(version_major: str, re_fetch=False) -> Optional[dict[s
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_supported_version(version_major: str) -> Optional[dict[str, str]]:
|
def get_supported_version(version_major: str) -> Optional[Dict[str, str]]:
|
||||||
for version in supported_versions:
|
for version in supported_versions:
|
||||||
if version[0] == version_major:
|
if version[0] == version_major:
|
||||||
return {
|
return {
|
||||||
@ -100,7 +100,7 @@ def fetch_installed_vs(silent=False) -> bool:
|
|||||||
if not silent:
|
if not silent:
|
||||||
log.warn(
|
log.warn(
|
||||||
f'Found a Visual Studio installation with incomplete information, skipping\n'
|
f'Found a Visual Studio installation with incomplete information, skipping\n'
|
||||||
f' ({name=}, {versions=}, {path=})'
|
f' (name={name if name is not None else "None"}, versions={versions}, path={path if path is not None else "None"})'
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -212,14 +212,14 @@ def compile_in_vs(version_major: str, done: Callable[[], None]) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _vswhere_get_display_name(instance_data: dict[str, Any]) -> Optional[str]:
|
def _vswhere_get_display_name(instance_data: Dict[str, Any]) -> Optional[str]:
|
||||||
name_raw = instance_data.get('displayName', None)
|
name_raw = instance_data.get('displayName', None)
|
||||||
if name_raw is None:
|
if name_raw is None:
|
||||||
return None
|
return None
|
||||||
return lnx.utils.safestr(name_raw).replace('_', ' ').strip()
|
return lnx.utils.safestr(name_raw).replace('_', ' ').strip()
|
||||||
|
|
||||||
|
|
||||||
def _vswhere_get_version(instance_data: dict[str, Any]) -> Optional[tuple[str, str, tuple[int, ...]]]:
|
def _vswhere_get_version(instance_data: Dict[str, Any]) -> Optional[Tuple[str, str, Tuple[int, int, int, int]]]:
|
||||||
version_raw = instance_data.get('installationVersion', None)
|
version_raw = instance_data.get('installationVersion', None)
|
||||||
if version_raw is None:
|
if version_raw is None:
|
||||||
return None
|
return None
|
||||||
@ -230,11 +230,11 @@ def _vswhere_get_version(instance_data: dict[str, Any]) -> Optional[tuple[str, s
|
|||||||
return version_major, version_full, version_full_ints
|
return version_major, version_full, version_full_ints
|
||||||
|
|
||||||
|
|
||||||
def _vswhere_get_path(instance_data: dict[str, Any]) -> Optional[str]:
|
def _vswhere_get_path(instance_data: Dict[str, Any]) -> Optional[str]:
|
||||||
return instance_data.get('installationPath', None)
|
return instance_data.get('installationPath', None)
|
||||||
|
|
||||||
|
|
||||||
def _vswhere_get_instances(silent=False) -> Optional[list[dict[str, Any]]]:
|
def _vswhere_get_instances(silent: bool = False) -> Optional[List[Dict[str, Any]]]:
|
||||||
# vswhere.exe only exists at that location since VS2017 v15.2, for
|
# vswhere.exe only exists at that location since VS2017 v15.2, for
|
||||||
# earlier versions we'd need to package vswhere with Leenkx
|
# earlier versions we'd need to package vswhere with Leenkx
|
||||||
exe_path = os.path.join(os.environ["ProgramFiles(x86)"], 'Microsoft Visual Studio', 'Installer', 'vswhere.exe')
|
exe_path = os.path.join(os.environ["ProgramFiles(x86)"], 'Microsoft Visual Studio', 'Installer', 'vswhere.exe')
|
||||||
@ -256,7 +256,7 @@ def _vswhere_get_instances(silent=False) -> Optional[list[dict[str, Any]]]:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def version_full_to_ints(version_full: str) -> tuple[int, ...]:
|
def version_full_to_ints(version_full: str) -> Tuple[int, ...]:
|
||||||
return tuple(int(i) for i in version_full.split('.'))
|
return tuple(int(i) for i in version_full.split('.'))
|
||||||
|
|
||||||
|
|
||||||
@ -281,7 +281,7 @@ def get_vcxproj_path() -> str:
|
|||||||
return os.path.join(project_path, project_name + '.vcxproj')
|
return os.path.join(project_path, project_name + '.vcxproj')
|
||||||
|
|
||||||
|
|
||||||
def fetch_project_version() -> tuple[Optional[str], Optional[str], Optional[str]]:
|
def fetch_project_version() -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
||||||
version_major = None
|
version_major = None
|
||||||
version_min_full = None
|
version_min_full = None
|
||||||
|
|
||||||
|
|||||||
@ -340,8 +340,8 @@ project.addSources('Sources');
|
|||||||
if rpdat.lnx_particles != 'Off':
|
if rpdat.lnx_particles != 'Off':
|
||||||
assets.add_khafile_def('lnx_particles')
|
assets.add_khafile_def('lnx_particles')
|
||||||
|
|
||||||
if rpdat.rp_draw_order == 'Shader':
|
if rpdat.rp_draw_order == 'Index':
|
||||||
assets.add_khafile_def('lnx_draworder_shader')
|
assets.add_khafile_def('lnx_draworder_index')
|
||||||
|
|
||||||
if lnx.utils.get_viewport_controls() == 'azerty':
|
if lnx.utils.get_viewport_controls() == 'azerty':
|
||||||
assets.add_khafile_def('lnx_azerty')
|
assets.add_khafile_def('lnx_azerty')
|
||||||
@ -806,7 +806,7 @@ const int compoChromaticSamples = {rpdat.lnx_chromatic_aberration_samples};
|
|||||||
|
|
||||||
focus_distance = 0.0
|
focus_distance = 0.0
|
||||||
fstop = 0.0
|
fstop = 0.0
|
||||||
if len(bpy.data.cameras) > 0 and lnx.utils.get_active_scene().camera.data.dof.use_dof:
|
if lnx.utils.get_active_scene().camera and lnx.utils.get_active_scene().camera.data.dof.use_dof:
|
||||||
focus_distance = lnx.utils.get_active_scene().camera.data.dof.focus_distance
|
focus_distance = lnx.utils.get_active_scene().camera.data.dof.focus_distance
|
||||||
fstop = lnx.utils.get_active_scene().camera.data.dof.aperture_fstop
|
fstop = lnx.utils.get_active_scene().camera.data.dof.aperture_fstop
|
||||||
lens = lnx.utils.get_active_scene().camera.data.lens
|
lens = lnx.utils.get_active_scene().camera.data.lens
|
||||||
|
|||||||
@ -118,7 +118,8 @@ def render_envmap(target_dir: str, world: bpy.types.World) -> str:
|
|||||||
scene = bpy.data.scenes['_lnx_envmap_render']
|
scene = bpy.data.scenes['_lnx_envmap_render']
|
||||||
scene.world = world
|
scene.world = world
|
||||||
|
|
||||||
image_name = f'env_{lnx.utils.safesrc(world.name)}.{ENVMAP_EXT}'
|
world_name = lnx.utils.asset_name(world) if world.library else world.name
|
||||||
|
image_name = f'env_{lnx.utils.safesrc(world_name)}.{ENVMAP_EXT}'
|
||||||
render_path = os.path.join(target_dir, image_name)
|
render_path = os.path.join(target_dir, image_name)
|
||||||
scene.render.filepath = render_path
|
scene.render.filepath = render_path
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user