diff --git a/Shaders/deferred_light/deferred_light.frag.glsl b/Shaders/deferred_light/deferred_light.frag.glsl index e119349e..fbe01534 100644 --- a/Shaders/deferred_light/deferred_light.frag.glsl +++ b/Shaders/deferred_light/deferred_light.frag.glsl @@ -21,7 +21,9 @@ uniform sampler2D gbufferD; uniform sampler2D gbuffer0; uniform sampler2D gbuffer1; +#ifdef _gbuffer2 uniform sampler2D gbuffer2; +#endif #ifdef _VoxelAOvar uniform sampler3D voxels; @@ -94,7 +96,7 @@ uniform vec3 eye; uniform vec3 eyeLook; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif @@ -206,7 +208,9 @@ void main() { vec3 v = normalize(eye - p); float dotNV = max(dot(n, v), 0.0); +#ifdef _gbuffer2 vec4 g2 = textureLod(gbuffer2, texCoord, 0.0); +#endif #ifdef _MicroShadowing occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel @@ -221,14 +225,16 @@ void main() { vec3 envl = shIrradiance(n, shirr); - if (g2.b < 0.5) { - envl = envl; - } else { - envl = vec3(1.0); - } + #ifdef _gbuffer2 + if (g2.b < 0.5) { + envl = envl; + } else { + envl = vec3(1.0); + } + #endif #ifdef _EnvTex - envl /= PI; + envl /= PI; #endif #else vec3 envl = vec3(1.0); @@ -456,18 +462,19 @@ void main() { n, v, dotNV, - lightsArray[li * 2].xyz, // lp - lightsArray[li * 2 + 1].xyz, // lightCol + lightsArray[li * 3].xyz, // lp + lightsArray[li * 3 + 1].xyz, // lightCol albedo, roughness, occspec.y, f0 #ifdef _ShadowMap - , li, lightsArray[li * 2].w, true // bias + // light index, shadow bias, cast_shadows + , li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0 #endif #ifdef _Spot - , lightsArray[li * 2 + 1].w != 0.0 - , lightsArray[li * 2 + 1].w // cutoff + , lightsArray[li * 3 + 2].y != 0.0 + , lightsArray[li * 3 + 2].y // cutoff , lightsArraySpot[li].w // cutoff - exponent , lightsArraySpot[li].xyz // spotDir #endif diff --git a/Shaders/deferred_light/deferred_light.json b/Shaders/deferred_light/deferred_light.json index d0d02f1f..efc70925 100755 --- a/Shaders/deferred_light/deferred_light.json +++ b/Shaders/deferred_light/deferred_light.json @@ -99,7 +99,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, diff --git a/Shaders/deferred_light_mobile/deferred_light.frag.glsl b/Shaders/deferred_light_mobile/deferred_light.frag.glsl index cc8ce302..3d679c7e 100644 --- a/Shaders/deferred_light_mobile/deferred_light.frag.glsl +++ b/Shaders/deferred_light_mobile/deferred_light.frag.glsl @@ -29,12 +29,15 @@ uniform int envmapNumMipmaps; uniform vec3 backgroundCol; #endif +#ifdef _SMSizeUniform +//!uniform vec2 smSizeUniform; +#endif uniform vec2 cameraProj; uniform vec3 eye; uniform vec3 eyeLook; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif @@ -255,18 +258,19 @@ void main() { n, v, dotNV, - lightsArray[li * 2].xyz, // lp - lightsArray[li * 2 + 1].xyz, // lightCol + lightsArray[li * 3].xyz, // lp + lightsArray[li * 3 + 1].xyz, // lightCol albedo, roughness, occspec.y, f0 #ifdef _ShadowMap - , li, lightsArray[li * 2].w, true // bias + // light index, shadow bias, cast_shadows + , li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0 #endif #ifdef _Spot - , lightsArray[li * 2 + 1].w != 0.0 - , lightsArray[li * 2 + 1].w // cutoff + , lightsArray[li * 3 + 2].y != 0.0 + , lightsArray[li * 3 + 2].y // cutoff , lightsArraySpot[li].w // cutoff - exponent , lightsArraySpot[li].xyz // spotDir #endif diff --git a/Shaders/deferred_light_mobile/deferred_light_mobile.json b/Shaders/deferred_light_mobile/deferred_light_mobile.json index 620e27e1..4413dc02 100644 --- a/Shaders/deferred_light_mobile/deferred_light_mobile.json +++ b/Shaders/deferred_light_mobile/deferred_light_mobile.json @@ -88,7 +88,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, @@ -107,6 +107,11 @@ "link": "_viewProjectionMatrix", "ifdef": ["_SSRS"] }, + { + "name": "smSizeUniform", + "link": "_shadowMapSize", + "ifdef": ["_SMSizeUniform"] + }, { "name": "lightProj", "link": "_lightPlaneProj", diff --git a/Shaders/std/clusters.glsl b/Shaders/std/clusters.glsl index 04570ef6..9a8b66e6 100644 --- a/Shaders/std/clusters.glsl +++ b/Shaders/std/clusters.glsl @@ -8,6 +8,10 @@ int getClusterI(vec2 tc, float viewz, vec2 cameraPlane) { float z = log(viewz - cnear + 1.0) / log(cameraPlane.y - cnear + 1.0); sliceZ = int(z * (clusterSlices.z - 1)) + 1; } + // address gap between near plane and cluster near offset + else if (viewz >= cameraPlane.x) { + sliceZ = 1; + } return int(tc.x * clusterSlices.x) + int(int(tc.y * clusterSlices.y) * clusterSlices.x) + int(sliceZ * clusterSlices.x * clusterSlices.y); diff --git a/Shaders/std/light.glsl b/Shaders/std/light.glsl index aa3024ae..61c26771 100644 --- a/Shaders/std/light.glsl +++ b/Shaders/std/light.glsl @@ -185,17 +185,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas - vec3 uv = lPos.xyz / lPos.w; - #ifdef _InvY - uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system - #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , uv, bias + , lPos.xyz / lPos.w, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); diff --git a/Shaders/std/light_mobile.glsl b/Shaders/std/light_mobile.glsl index e547129c..9686f844 100644 --- a/Shaders/std/light_mobile.glsl +++ b/Shaders/std/light_mobile.glsl @@ -8,39 +8,39 @@ #endif #ifdef _ShadowMap -#ifdef _SinglePoint - #ifdef _Spot - uniform sampler2DShadow shadowMapSpot[1]; - uniform mat4 LWVPSpot[1]; - #else - uniform samplerCubeShadow shadowMapPoint[1]; - uniform vec2 lightProj; + #ifdef _SinglePoint + #ifdef _Spot + uniform sampler2DShadow shadowMapSpot[1]; + uniform mat4 LWVPSpot[1]; + #else + uniform samplerCubeShadow shadowMapPoint[1]; + uniform vec2 lightProj; + #endif #endif -#endif -#ifdef _Clusters - #ifdef _SingleAtlas - //!uniform sampler2DShadow shadowMapAtlas; - #endif - uniform vec2 lightProj; - #ifdef _ShadowMapAtlas - #ifndef _SingleAtlas - uniform sampler2DShadow shadowMapAtlasPoint; - #endif - #else - uniform samplerCubeShadow shadowMapPoint[4]; - #endif - #ifdef _Spot + #ifdef _Clusters + #ifdef _SingleAtlas + //!uniform sampler2DShadow shadowMapAtlas; + #endif + uniform vec2 lightProj; #ifdef _ShadowMapAtlas #ifndef _SingleAtlas - uniform sampler2DShadow shadowMapAtlasSpot; + uniform sampler2DShadow shadowMapAtlasPoint; #endif #else - uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + uniform samplerCubeShadow shadowMapPoint[4]; + #endif + #ifdef _Spot + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSpot; + #endif + #else + uniform sampler2DShadow shadowMapSpot[maxLightsCluster]; + #endif + uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif - uniform mat4 LWVPSpotArray[maxLightsCluster]; #endif #endif -#endif vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol, const vec3 albedo, const float rough, const float spec, const vec3 f0 @@ -80,17 +80,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #ifdef _Clusters vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0); #ifdef _ShadowMapAtlas - vec3 uv = lPos.xyz / lPos.w; - #ifdef _InvY - uv.y = 1.0 - uv.y; // invert Y coordinates for direct3d coordinate system - #endif direct *= shadowTest( #ifndef _SingleAtlas shadowMapAtlasSpot #else shadowMapAtlas #endif - , uv, bias + , lPos.xyz / lPos.w, bias ); #else if (index == 0) direct *= shadowTest(shadowMapSpot[0], lPos.xyz / lPos.w, bias); @@ -106,11 +102,13 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #endif #ifdef _ShadowMap - #ifndef _Spot + if (receiveShadow) { #ifdef _SinglePoint + #ifndef _Spot direct *= PCFCube(shadowMapPoint[0], ld, -l, bias, lightProj, n); #endif + #endif #ifdef _Clusters #ifdef _ShadowMapAtlas direct *= PCFFakeCube( @@ -130,7 +128,6 @@ vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, co #endif } #endif - #endif return direct; } diff --git a/Shaders/std/math.glsl b/Shaders/std/math.glsl index 93293401..a4a64138 100755 --- a/Shaders/std/math.glsl +++ b/Shaders/std/math.glsl @@ -23,7 +23,7 @@ vec2 rand2(const vec2 coord) { const float width = 1100.0; const float height = 500.0; float noiseX = ((fract(1.0 - coord.s * (width / 2.0)) * 0.25) + (fract(coord.t * (height / 2.0)) * 0.75)) * 2.0 - 1.0; - float noiseY = ((fract(1.0 - coord.s * (width / 2.0)) * 0.75) + (fract(coord.t * (height / 2.0)) * 0.25)) * 2.0 - 1.0; + float noiseY = ((fract(1.0 - coord.s * (width / 2.0)) * 0.75) + (fract(coord.t * (height / 2.0)) * 0.25)) * 2.0 - 1.0; return vec2(noiseX, noiseY); } @@ -40,4 +40,9 @@ float attenuate(const float dist) { // 1.0 / (quadratic * dist * dist); } +float safe_acos(const float x) { + // acos is undefined if |x| > 1 + return acos(clamp(x, -1.0, 1.0)); +} + #endif diff --git a/Shaders/std/shadows.glsl b/Shaders/std/shadows.glsl index baf940a4..2fea30c1 100755 --- a/Shaders/std/shadows.glsl +++ b/Shaders/std/shadows.glsl @@ -42,7 +42,16 @@ vec2 sampleCube(vec3 dir, out int faceIndex) { uv = vec2(dir.x < 0.0 ? dir.z : -dir.z, -dir.y); } // downscale uv a little to hide seams - return uv * 0.9976 * ma + 0.5; + // transform coordinates from clip space to texture space + #ifndef _FlipY + return uv * 0.9976 * ma + 0.5; + #else + #ifdef HLSL + return uv * 0.9976 * ma + 0.5; + #else + return vec2(uv.x * ma, uv.y * -ma) * 0.9976 + 0.5; + #endif + #endif } #endif @@ -87,6 +96,7 @@ float PCFCube(samplerCubeShadow shadowMapCube, const vec3 lp, vec3 ml, const flo #ifdef _ShadowMapAtlas // transform "out-of-bounds" coordinates to the correct face/coordinate system +// https://www.khronos.org/opengl/wiki/File:CubeMapAxes.png vec2 transformOffsetedUV(const int faceIndex, out int newFaceIndex, vec2 uv) { if (uv.x < 0.0) { if (faceIndex == 0) { // X+ @@ -189,7 +199,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float vec4 pointLightTile = pointLightDataArray[lightIndex + faceIndex]; // x: tile X offset, y: tile Y offset, z: tile size relative to atlas vec2 uvtiled = pointLightTile.z * uv + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif @@ -199,15 +209,15 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif - result += texture(shadowMap, vec3(pointLightTile.z * uvtiled + pointLightTile.xy, compare)); + result += texture(shadowMap, vec3(uvtiled, compare)); uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -215,7 +225,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -223,7 +233,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(-1.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -231,7 +241,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(0.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -239,7 +249,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, -1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -247,7 +257,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 0.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); @@ -255,7 +265,7 @@ float PCFFakeCube(sampler2DShadow shadowMap, const vec3 lp, vec3 ml, const float uvtiled = transformOffsetedUV(faceIndex, newFaceIndex, vec2(uv + (vec2(1.0, 1.0) / smSize))); pointLightTile = pointLightDataArray[lightIndex + newFaceIndex]; uvtiled = pointLightTile.z * uvtiled + pointLightTile.xy; - #ifdef _InvY + #ifdef _FlipY uvtiled.y = 1.0 - uvtiled.y; // invert Y coordinates for direct3d coordinate system #endif result += texture(shadowMap, vec3(uvtiled, compare)); diff --git a/Shaders/std/sky.glsl b/Shaders/std/sky.glsl new file mode 100644 index 00000000..eaac4352 --- /dev/null +++ b/Shaders/std/sky.glsl @@ -0,0 +1,155 @@ +/* Various sky functions + * ===================== + * + * Nishita model is based on https://github.com/wwwtyro/glsl-atmosphere (Unlicense License) + * + * Changes to the original implementation: + * - r and pSun parameters of nishita_atmosphere() are already normalized + * - Some original parameters of nishita_atmosphere() are replaced with pre-defined values + * - Implemented air, dust and ozone density node parameters (see Blender source) + * - Replaced the inner integral calculation with a LUT lookup + * + * Reference for the sun's limb darkening and ozone calculations: + * [Hill] Sebastien Hillaire. Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite + * (https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf) + * + * Cycles code used for reference: blender/intern/sky/source/sky_nishita.cpp + * (https://github.com/blender/blender/blob/4429b4b77ef6754739a3c2b4fabd0537999e9bdc/intern/sky/source/sky_nishita.cpp) + */ + +#ifndef _SKY_GLSL_ +#define _SKY_GLSL_ + +#include "std/math.glsl" + +uniform sampler2D nishitaLUT; +uniform vec2 nishitaDensity; + +#ifndef PI + #define PI 3.141592 +#endif +#ifndef HALF_PI + #define HALF_PI 1.570796 +#endif + +#define nishita_iSteps 16 + +// These values are taken from Cycles code if they +// exist there, otherwise they are taken from the example +// in the glsl-atmosphere repo +#define nishita_sun_intensity 22.0 +#define nishita_atmo_radius 6420e3 +#define nishita_rayleigh_scale 8e3 +#define nishita_rayleigh_coeff vec3(5.5e-6, 13.0e-6, 22.4e-6) +#define nishita_mie_scale 1.2e3 +#define nishita_mie_coeff 2e-5 +#define nishita_mie_dir 0.76 // Aerosols anisotropy ("direction") +#define nishita_mie_dir_sq 0.5776 // Squared aerosols anisotropy + +// Values from [Hill: 60] +#define sun_limb_darkening_col vec3(0.397, 0.503, 0.652) + +vec3 nishita_lookupLUT(const float height, const float sunTheta) { + vec2 coords = vec2( + sqrt(height * (1 / nishita_atmo_radius)), + 0.5 + 0.5 * sign(sunTheta - HALF_PI) * sqrt(abs(sunTheta * (1 / HALF_PI) - 1)) + ); + return textureLod(nishitaLUT, coords, 0.0).rgb; +} + +/* See raySphereIntersection() in armory/Sources/renderpath/Nishita.hx */ +vec2 nishita_rsi(const vec3 r0, const vec3 rd, const float sr) { + float a = dot(rd, rd); + float b = 2.0 * dot(rd, r0); + float c = dot(r0, r0) - (sr * sr); + float d = (b*b) - 4.0*a*c; + + // If d < 0.0 the ray does not intersect the sphere + return (d < 0.0) ? vec2(1e5,-1e5) : vec2((-b - sqrt(d))/(2.0*a), (-b + sqrt(d))/(2.0*a)); +} + +/* + * r: normalized ray direction + * r0: ray origin + * pSun: normalized sun direction + * rPlanet: planet radius + */ +vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const float rPlanet) { + // Calculate the step size of the primary ray + vec2 p = nishita_rsi(r0, r, nishita_atmo_radius); + if (p.x > p.y) return vec3(0.0); + p.y = min(p.y, nishita_rsi(r0, r, rPlanet).x); + float iStepSize = (p.y - p.x) / float(nishita_iSteps); + + // Primary ray time + float iTime = 0.0; + + // Accumulators for Rayleigh and Mie scattering. + vec3 totalRlh = vec3(0,0,0); + vec3 totalMie = vec3(0,0,0); + + // Optical depth accumulators for the primary ray + float iOdRlh = 0.0; + float iOdMie = 0.0; + + // Calculate the Rayleigh and Mie phases + float mu = dot(r, pSun); + float mumu = mu * mu; + float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu); + float pMie = 3.0 / (8.0 * PI) * ((1.0 - nishita_mie_dir_sq) * (mumu + 1.0)) / (pow(1.0 + nishita_mie_dir_sq - 2.0 * mu * nishita_mie_dir, 1.5) * (2.0 + nishita_mie_dir_sq)); + + // Sample the primary ray + for (int i = 0; i < nishita_iSteps; i++) { + + // Calculate the primary ray sample position and height + vec3 iPos = r0 + r * (iTime + iStepSize * 0.5); + float iHeight = length(iPos) - rPlanet; + + // Calculate the optical depth of the Rayleigh and Mie scattering for this step + float odStepRlh = exp(-iHeight / nishita_rayleigh_scale) * nishitaDensity.x * iStepSize; + float odStepMie = exp(-iHeight / nishita_mie_scale) * nishitaDensity.y * iStepSize; + + // Accumulate optical depth + iOdRlh += odStepRlh; + iOdMie += odStepMie; + + // Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the + // inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith). + float sunTheta = safe_acos(dot(normalize(iPos), normalize(pSun))); + vec3 jAttn = nishita_lookupLUT(iHeight, sunTheta); + + // Calculate attenuation + vec3 iAttn = exp(-( + nishita_mie_coeff * iOdMie + + nishita_rayleigh_coeff * iOdRlh + // + 0 for ozone + )); + vec3 attn = iAttn * jAttn; + + // Apply dithering to reduce visible banding + attn *= 0.98 + rand(r.xy) * 0.04; + + // Accumulate scattering + totalRlh += odStepRlh * attn; + totalMie += odStepMie * attn; + + iTime += iStepSize; + } + + return nishita_sun_intensity * (pRlh * nishita_rayleigh_coeff * totalRlh + pMie * nishita_mie_coeff * totalMie); +} + +vec3 sun_disk(const vec3 n, const vec3 light_dir, const float disk_size, const float intensity) { + // Normalized SDF + float dist = distance(n, light_dir) / disk_size; + + // Darken the edges of the sun + // [Hill: 28, 60] (according to [Nec96]) + float invDist = 1.0 - dist; + float mu = sqrt(invDist * invDist); + vec3 limb_darkening = 1.0 - (1.0 - pow(vec3(mu), sun_limb_darkening_col)); + + return 1 + (1.0 - step(1.0, dist)) * nishita_sun_intensity * intensity * limb_darkening; +} + +#endif diff --git a/Shaders/volumetric_light/volumetric_light.frag.glsl b/Shaders/volumetric_light/volumetric_light.frag.glsl index e1306b06..693d7b1d 100644 --- a/Shaders/volumetric_light/volumetric_light.frag.glsl +++ b/Shaders/volumetric_light/volumetric_light.frag.glsl @@ -12,7 +12,7 @@ uniform sampler2D gbufferD; uniform sampler2D snoise; #ifdef _Clusters -uniform vec4 lightsArray[maxLights * 2]; +uniform vec4 lightsArray[maxLights * 3]; #ifdef _Spot uniform vec4 lightsArraySpot[maxLights]; #endif @@ -44,7 +44,15 @@ uniform vec2 cameraPlane; uniform vec3 sunDir; uniform vec3 sunCol; #ifdef _ShadowMap + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + uniform sampler2DShadow shadowMapAtlasSun; + #else + uniform sampler2DShadow shadowMapAtlas; + #endif + #else uniform sampler2DShadow shadowMap; + #endif uniform float shadowsBias; #ifdef _CSM //!uniform vec4 casData[shadowmapCascades * 4 + 4]; @@ -95,7 +103,17 @@ void rayStep(inout vec3 curPos, inout float curOpticalDepth, inout float scatter #endif vec4 lPos = LWVP * vec4(curPos, 1.0); lPos.xyz /= lPos.w; - visibility = texture(shadowMap, vec3(lPos.xy, lPos.z - shadowsBias)); + visibility = texture( + #ifdef _ShadowMapAtlas + #ifndef _SingleAtlas + shadowMapAtlasSun + #else + shadowMapAtlas + #endif + #else + shadowMap + #endif + , vec3(lPos.xy, lPos.z - shadowsBias)); #endif #ifdef _SinglePoint diff --git a/Shaders/volumetric_light/volumetric_light.json b/Shaders/volumetric_light/volumetric_light.json index a8e96a56..dfae1e2c 100755 --- a/Shaders/volumetric_light/volumetric_light.json +++ b/Shaders/volumetric_light/volumetric_light.json @@ -63,7 +63,7 @@ }, { "name": "LWVP", - "link": "_biasLightWorldViewProjectionMatrix", + "link": "_biasLightWorldViewProjectionMatrixSun", "ifndef": ["_CSM"], "ifdef": ["_Sun", "_ShadowMap"] }, diff --git a/Sources/armory/logicnode/AddPhysicsConstraintNode.hx b/Sources/armory/logicnode/AddPhysicsConstraintNode.hx index 4f04e2b0..3013f775 100644 --- a/Sources/armory/logicnode/AddPhysicsConstraintNode.hx +++ b/Sources/armory/logicnode/AddPhysicsConstraintNode.hx @@ -1,13 +1,11 @@ package armory.logicnode; -import armory.trait.physics.PhysicsConstraint; -#if arm_physics -import armory.trait.physics.bullet.PhysicsConstraint.ConstraintType; -import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis; -#end import iron.object.Object; -import armory.trait.physics.RigidBody; -import armory.logicnode.PhysicsConstraintNode; + +#if arm_physics +import armory.trait.physics.PhysicsConstraint; +import armory.trait.physics.bullet.PhysicsConstraint.ConstraintType; +#end class AddPhysicsConstraintNode extends LogicNode { @@ -21,115 +19,96 @@ class AddPhysicsConstraintNode extends LogicNode { } override function run(from: Int) { - var pivotObject:Object = inputs[1].get(); + var pivotObject: Object = inputs[1].get(); rb1 = inputs[2].get(); rb2 = inputs[3].get(); - var disableCollisions: Bool = inputs[4].get(); - var breakable: Bool = inputs[5].get(); - var breakingThreshold: Float = inputs[6].get(); - var type: ConstraintType = 0; if (pivotObject == null || rb1 == null || rb2 == null) return; #if arm_physics + var disableCollisions: Bool = inputs[4].get(); + var breakable: Bool = inputs[5].get(); + var breakingThreshold: Float = inputs[6].get(); + var type: ConstraintType = 0; + var con: PhysicsConstraint = pivotObject.getTrait(PhysicsConstraint); - if(con == null) - { - switch(property0) - { - case 'Fixed': - type = Fixed; - case 'Point': - type = Point; - case 'Hinge': - type = Hinge; - case 'Slider': - type = Slider; - case 'Piston': - type = Piston; - case 'Generic Spring': - type = Generic; + if (con == null) { + switch (property0) { + case "Fixed": type = Fixed; + case "Point": type = Point; + case "Hinge": type = Hinge; + case "Slider": type = Slider; + case "Piston": type = Piston; + case "Generic Spring": type = Generic; } - if(! breakable) breakingThreshold = 0.0; + if (!breakable) breakingThreshold = 0.0; + + if (type != Generic) { - if(type != Generic) { - con = new PhysicsConstraint(rb1, rb2, type, disableCollisions, breakingThreshold); - switch (type) - { + switch (type) { case Hinge: - var setLimit:Bool = inputs[7].get(); - var low:Float = inputs[8].get(); - var up:Float = inputs[9].get(); + var setLimit: Bool = inputs[7].get(); + var low: Float = inputs[8].get(); + var up: Float = inputs[9].get(); con.setHingeConstraintLimits(setLimit, low, up); case Slider: - var setLimit:Bool = inputs[7].get(); - var low:Float = inputs[8].get(); - var up:Float = inputs[9].get(); + var setLimit: Bool = inputs[7].get(); + var low: Float = inputs[8].get(); + var up: Float = inputs[9].get(); con.setSliderConstraintLimits(setLimit, low, up); case Piston: - var setLinLimit:Bool = inputs[7].get(); - var linLow:Float = inputs[8].get(); - var linUp:Float = inputs[9].get(); - var setAngLimit:Bool = inputs[10].get(); - var angLow:Float = inputs[11].get(); - var angUp:Float = inputs[12].get(); + var setLinLimit: Bool = inputs[7].get(); + var linLow: Float = inputs[8].get(); + var linUp: Float = inputs[9].get(); + var setAngLimit: Bool = inputs[10].get(); + var angLow: Float = inputs[11].get(); + var angUp: Float = inputs[12].get(); con.setPistonConstraintLimits(setLinLimit, linLow, linUp, setAngLimit, angLow, angUp); - default: + default: } } - else - { + else { var spring: Bool = false; var prop: PhysicsConstraintNode; - for(inp in 7...inputs.length) - { + + for (inp in 7...inputs.length) { prop = inputs[inp].get(); - if(prop == null) continue; - if(prop.isSpring) - { + if (prop == null) continue; + if (prop.isSpring) { spring = true; break; } } - if(spring) { + if (spring) { con = new PhysicsConstraint(rb1, rb2, GenericSpring, disableCollisions, breakingThreshold); - } + } else { con = new PhysicsConstraint(rb1, rb2, Generic, disableCollisions, breakingThreshold); } - for(inp in 7...inputs.length) - { + for (inp in 7...inputs.length) { prop = inputs[inp].get(); - if(prop == null) continue; - (inp + ': '); + if (prop == null) continue; - if(prop.isSpring) - { + if (prop.isSpring) { con.setSpringParams(prop.isSpring, prop.value1, prop.value2, prop.axis, prop.isAngular); } - else - { + else { con.setGenericConstraintLimits(true, prop.value1, prop.value2, prop.axis, prop.isAngular); } } - - } - pivotObject.addTrait(con); - } #end - runOutput(0); } } diff --git a/Sources/armory/logicnode/AddRigidBodyNode.hx b/Sources/armory/logicnode/AddRigidBodyNode.hx index a599e568..601eb97d 100644 --- a/Sources/armory/logicnode/AddRigidBodyNode.hx +++ b/Sources/armory/logicnode/AddRigidBodyNode.hx @@ -1,15 +1,17 @@ package armory.logicnode; +import iron.object.Object; + #if arm_physics +import armory.trait.physics.RigidBody; import armory.trait.physics.bullet.RigidBody.Shape; #end -import iron.object.Object; -import armory.trait.physics.RigidBody; + class AddRigidBodyNode extends LogicNode { - public var property0: String;//Shape - public var property1: String;//Advanced + public var property0: String; //Shape + public var property1: Bool; //Advanced public var object: Object; public function new(tree: LogicTree) { @@ -18,6 +20,10 @@ class AddRigidBodyNode extends LogicNode { override function run(from: Int) { object = inputs[1].get(); + if (object == null) return; + +#if arm_physics + var mass: Float = inputs[2].get(); var active: Bool = inputs[3].get(); var animated: Bool = inputs[4].get(); @@ -38,8 +44,7 @@ class AddRigidBodyNode extends LogicNode { var shape: Shape = 1; - if(property1 == 'true') - { + if (property1) { margin = inputs[9].get(); marginLen = inputs[10].get(); linDamp = inputs[11].get(); @@ -49,50 +54,34 @@ class AddRigidBodyNode extends LogicNode { angVelThreshold = inputs[15].get(); group = inputs[16].get(); mask = inputs[17].get(); - } - - if (object == null) return; - -#if arm_physics var rb: RigidBody = object.getTrait(RigidBody); - if((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32 - if((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32 - if(rb == null) - { - - switch (property0){ - - case 'Box': - shape = Box; - case 'Sphere': - shape = Sphere; - case 'Capsule': - shape = Capsule; - case 'Cone': - shape = Cone; - case 'Cylinder': - shape = Cylinder; - case 'Convex Hull': - shape = ConvexHull; - case 'Mesh': - shape = Mesh; + if ((group < 0) || (group > 32)) group = 1; //Limiting max groups to 32 + if ((mask < 0) || (mask > 32)) mask = 1; //Limiting max masks to 32 + if (rb == null) { + switch (property0) { + case "Box": shape = Box; + case "Sphere": shape = Sphere; + case "Capsule": shape = Capsule; + case "Cone": shape = Cone; + case "Cylinder": shape = Cylinder; + case "Convex Hull": shape = ConvexHull; + case "Mesh": shape = Mesh; } rb = new RigidBody(shape, mass, friction, bounciness, group, mask); rb.animated = animated; - rb.staticObj = ! active; + rb.staticObj = !active; rb.isTriggerObject(trigger); - if(property1 == 'true') - { + + if (property1) { rb.linearDamping = linDamp; rb.angularDamping = angDamp; - if(margin) rb.collisionMargin = marginLen; - if(useDeactiv) { + if (margin) rb.collisionMargin = marginLen; + if (useDeactiv) { rb.setUpDeactivation(true, linearVelThreshold, angVelThreshold, 0.0); } - } object.addTrait(rb); diff --git a/Sources/armory/logicnode/BoneFKNode.hx b/Sources/armory/logicnode/BoneFKNode.hx index 8d8cabb1..9d72c876 100644 --- a/Sources/armory/logicnode/BoneFKNode.hx +++ b/Sources/armory/logicnode/BoneFKNode.hx @@ -1,5 +1,7 @@ package armory.logicnode; +import iron.math.Quat; +import iron.math.Vec4; import iron.object.Object; import iron.object.BoneAnimation; import iron.math.Mat4; @@ -26,19 +28,28 @@ class BoneFKNode extends LogicNode { var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; if (anim == null) anim = object.getParentArmature(object.name); - // Manipulating bone in world space + // Get bone in armature var bone = anim.getBone(boneName); - m = anim.getBoneMat(bone); - w = anim.getAbsMat(bone); function moveBone() { - m.setFrom(w); - m.multmat(transform); - iw.getInverse(w); - m.multmat(iw); + + var t2 = Mat4.identity(); + var loc= new Vec4(); + var rot = new Quat(); + var scl = new Vec4(); - // anim.removeUpdate(moveBone); - // notified = false; + //Set scale to Armature scale. Bone scaling not yet implemented + t2.setFrom(transform); + t2.decompose(loc, rot, scl); + scl = object.transform.world.getScale(); + t2.compose(loc, rot, scl); + + //Set the bone local transform from world transform + anim.setBoneMatFromWorldMat(t2, bone); + + //Remove this method from animation loop after FK + anim.removeUpdate(moveBone); + notified = false; } if (!notified) { diff --git a/Sources/armory/logicnode/BoneIKNode.hx b/Sources/armory/logicnode/BoneIKNode.hx index de187f0a..1f1cfd76 100644 --- a/Sources/armory/logicnode/BoneIKNode.hx +++ b/Sources/armory/logicnode/BoneIKNode.hx @@ -7,6 +7,13 @@ import iron.math.Vec4; class BoneIKNode extends LogicNode { var goal: Vec4; + var pole: Vec4; + var poleEnabled: Bool; + var chainLength: Int; + var maxIterartions: Int; + var precision: Float; + var rollAngle: Float; + var notified = false; public function new(tree: LogicTree) { @@ -19,6 +26,12 @@ class BoneIKNode extends LogicNode { var object: Object = inputs[1].get(); var boneName: String = inputs[2].get(); goal = inputs[3].get(); + poleEnabled = inputs[4].get(); + pole = inputs[5].get(); + chainLength = inputs[6].get(); + maxIterartions = inputs[7].get(); + precision = inputs[8].get(); + rollAngle = inputs[9].get(); if (object == null || goal == null) return; var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; @@ -26,11 +39,15 @@ class BoneIKNode extends LogicNode { var bone = anim.getBone(boneName); - function solveBone() { - anim.solveIK(bone, goal); + if(! poleEnabled) pole = null; - // anim.removeUpdate(solveBone); - // notified = false; + function solveBone() { + //Solve IK + anim.solveIK(bone, goal, precision, maxIterartions, chainLength, pole, rollAngle); + + //Remove this method from animation loop after IK + anim.removeUpdate(solveBone); + notified = false; } if (!notified) { diff --git a/Sources/armory/logicnode/ClampNode.hx b/Sources/armory/logicnode/ClampNode.hx index 8bd5f13d..911cf9d4 100644 --- a/Sources/armory/logicnode/ClampNode.hx +++ b/Sources/armory/logicnode/ClampNode.hx @@ -1,18 +1,18 @@ package armory.logicnode; +import kha.FastFloat; + class ClampNode extends LogicNode { public function new(tree: LogicTree) { super(tree); } - override function get(from: Int): Dynamic { - var value: kha.FastFloat = inputs[0].get(); - var min: kha.FastFloat = inputs[1].get(); - var max: kha.FastFloat = inputs[2].get(); + override function get(from: Int): FastFloat { + var value = inputs[0].get(); + var min = inputs[1].get(); + var max = inputs[2].get(); - if (value == null || min == null || max == null) return null; - - value <= min ? return min : value >= max ? return max : return value; + return value < min ? min : value > max ? max : value; } } diff --git a/Sources/armory/logicnode/CompareNode.hx b/Sources/armory/logicnode/CompareNode.hx index 88dc97be..cfc02823 100644 --- a/Sources/armory/logicnode/CompareNode.hx +++ b/Sources/armory/logicnode/CompareNode.hx @@ -19,9 +19,9 @@ class CompareNode extends LogicNode { switch (property0) { case "Equal": - cond = Std.is(v1, Vec4) ? v1.equals(v2) : v1 == v2; + cond = Std.isOfType(v1, Vec4) ? v1.equals(v2) : v1 == v2; case "Almost Equal": - cond = Std.is(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1; + cond = Std.isOfType(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1; case "Greater": cond = v1 > v2; case "Greater Equal": diff --git a/Sources/armory/logicnode/GateNode.hx b/Sources/armory/logicnode/GateNode.hx index ca310419..ae0014e8 100644 --- a/Sources/armory/logicnode/GateNode.hx +++ b/Sources/armory/logicnode/GateNode.hx @@ -18,9 +18,9 @@ class GateNode extends LogicNode { switch (property0) { case "Equal": - cond = Std.is(v1, Vec4) ? v1.equals(v2) : v1 == v2; + cond = Std.isOfType(v1, Vec4) ? v1.equals(v2) : v1 == v2; case "Almost Equal": - cond = Std.is(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1; + cond = Std.isOfType(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1; case "Greater": cond = v1 > v2; case "Greater Equal": diff --git a/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx b/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx new file mode 100644 index 00000000..e27dffa3 --- /dev/null +++ b/Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx @@ -0,0 +1,31 @@ +package armory.logicnode; + +import iron.object.Object; +import iron.object.BoneAnimation; + +class GetBoneFkIkOnlyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Bool { + #if arm_skin + + var object: Object = inputs[0].get(); + var boneName: String = inputs[1].get(); + + if (object == null) return null; + var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; + if (anim == null) anim = object.getParentArmature(object.name); + + // Get bone in armature + var bone = anim.getBone(boneName); + + //Get bone transform in world coordinates + return bone.is_ik_fk_only; + + + #end + } +} diff --git a/Sources/armory/logicnode/GetBoneTransformNode.hx b/Sources/armory/logicnode/GetBoneTransformNode.hx new file mode 100644 index 00000000..098fd519 --- /dev/null +++ b/Sources/armory/logicnode/GetBoneTransformNode.hx @@ -0,0 +1,30 @@ +package armory.logicnode; + +import iron.object.Object; +import iron.object.BoneAnimation; +import iron.math.Mat4; + +class GetBoneTransformNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Mat4 { + #if arm_skin + + var object: Object = inputs[0].get(); + var boneName: String = inputs[1].get(); + + if (object == null) return null; + var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; + if (anim == null) anim = object.getParentArmature(object.name); + + // Get bone in armature + var bone = anim.getBone(boneName); + + return anim.getAbsWorldMat(bone); + + #end + } +} diff --git a/Sources/armory/logicnode/GetGamepadStartedNode.hx b/Sources/armory/logicnode/GetGamepadStartedNode.hx new file mode 100644 index 00000000..f94b9e59 --- /dev/null +++ b/Sources/armory/logicnode/GetGamepadStartedNode.hx @@ -0,0 +1,33 @@ +package armory.logicnode; + +import iron.system.Input; + +class GetGamepadStartedNode extends LogicNode { + + var buttonStarted: Null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var g = Input.getGamepad(inputs[0].get()); + + buttonStarted = null; + + for (b in Gamepad.buttons) { + if (g.started(b)) { + buttonStarted = b; + break; + } + } + + if (buttonStarted != null) { + runOutput(0); + } + } + + override function get(from: Int) { + return buttonStarted; + } +} diff --git a/Sources/armory/logicnode/GetInputMapKeyNode.hx b/Sources/armory/logicnode/GetInputMapKeyNode.hx new file mode 100644 index 00000000..251a6d12 --- /dev/null +++ b/Sources/armory/logicnode/GetInputMapKeyNode.hx @@ -0,0 +1,24 @@ +package armory.logicnode; + +import armory.system.InputMap; + +class GetInputMapKeyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var inputMap = inputs[0].get(); + var key = inputs[1].get(); + + var k = InputMap.getInputMapKey(inputMap, key); + + if (k != null) { + if (from == 0) return k.scale; + else if (from == 1) return k.deadzone; + } + + return null; + } +} diff --git a/Sources/armory/logicnode/GetKeyboardStartedNode.hx b/Sources/armory/logicnode/GetKeyboardStartedNode.hx new file mode 100644 index 00000000..ebe752e5 --- /dev/null +++ b/Sources/armory/logicnode/GetKeyboardStartedNode.hx @@ -0,0 +1,32 @@ +package armory.logicnode; + +import iron.system.Input; + +class GetKeyboardStartedNode extends LogicNode { + + var kb = Input.getKeyboard(); + var keyStarted: Null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + keyStarted = null; + + for (k in Keyboard.keys) { + if (kb.started(k)) { + keyStarted = k; + break; + } + } + + if (keyStarted != null) { + runOutput(0); + } + } + + override function get(from: Int) { + return keyStarted; + } +} diff --git a/Sources/armory/logicnode/GetLocationNode.hx b/Sources/armory/logicnode/GetLocationNode.hx index 961f8280..00684473 100644 --- a/Sources/armory/logicnode/GetLocationNode.hx +++ b/Sources/armory/logicnode/GetLocationNode.hx @@ -10,9 +10,22 @@ class GetLocationNode extends LogicNode { override function get(from: Int): Dynamic { var object: Object = inputs[0].get(); + var relative: Bool = inputs[1].get(); if (object == null) return null; - return object.transform.world.getLoc(); + var loc = object.transform.world.getLoc(); + + if (relative && object.parent != null) { + loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence + + // Convert loc to parent local space + var dotX = loc.dot(object.parent.transform.right()); + var dotY = loc.dot(object.parent.transform.look()); + var dotZ = loc.dot(object.parent.transform.up()); + loc.set(dotX, dotY, dotZ); + } + + return loc; } } diff --git a/Sources/armory/logicnode/GetMouseStartedNode.hx b/Sources/armory/logicnode/GetMouseStartedNode.hx new file mode 100644 index 00000000..8f2d5f32 --- /dev/null +++ b/Sources/armory/logicnode/GetMouseStartedNode.hx @@ -0,0 +1,32 @@ +package armory.logicnode; + +import iron.system.Input; + +class GetMouseStartedNode extends LogicNode { + + var m = Input.getMouse(); + var buttonStarted: Null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + buttonStarted = null; + + for (b in Mouse.buttons) { + if (m.started(b)) { + buttonStarted = b; + break; + } + } + + if (buttonStarted != null) { + runOutput(0); + } + } + + override function get(from: Int) { + return buttonStarted; + } +} diff --git a/Sources/armory/logicnode/GetObjectNode.hx b/Sources/armory/logicnode/GetObjectNode.hx index d0bdb60e..08c30c4c 100644 --- a/Sources/armory/logicnode/GetObjectNode.hx +++ b/Sources/armory/logicnode/GetObjectNode.hx @@ -14,36 +14,7 @@ class GetObjectNode extends LogicNode { override function get(from: Int): Dynamic { var objectName: String = inputs[0].get(); - - if (property0 == null || property0 == iron.Scene.active.raw.name) { - return iron.Scene.active.getChild(objectName); - } - - #if arm_json - property0 += ".json"; - #elseif arm_compress - property0 += ".lz4"; - #end - - var outObj: Null = null; - - // Create the object in the active scene if it is from an inactive scene - iron.data.Data.getSceneRaw(property0, (rawScene: TSceneFormat) -> { - var objData: Null = null; - - for (o in rawScene.objects) { - if (o.name == objectName) { - objData = o; - break; - } - } - if (objData == null) return; - - iron.Scene.active.createObject(objData, rawScene, null, null, (newObj: Object) -> { - outObj = newObj; - }); - }); - - return outObj; + + return iron.Scene.active.getChild(objectName); } } diff --git a/Sources/armory/logicnode/GetSystemName.hx b/Sources/armory/logicnode/GetSystemName.hx index a346f01c..97be58b6 100644 --- a/Sources/armory/logicnode/GetSystemName.hx +++ b/Sources/armory/logicnode/GetSystemName.hx @@ -3,22 +3,24 @@ package armory.logicnode; class GetSystemName extends LogicNode { public function new(tree: LogicTree) { - super(tree); + super(tree); } - public static function equalsCI(a : String, b : String) return a.toLowerCase() == b.toLowerCase(); - override function get(from: Int): Dynamic { + var systemName: String = kha.System.systemId; return switch (from) { case 0: systemName; - case 1: equalsCI(kha.System.systemId, 'Windows'); - case 2: equalsCI(kha.System.systemId, 'Linux'); - case 3: equalsCI(kha.System.systemId, 'Mac'); - case 4: equalsCI(kha.System.systemId, 'HTML5'); - case 5: equalsCI(kha.System.systemId, 'Android'); + case 1: equalsCI(systemName, 'Windows'); + case 2: equalsCI(systemName, 'Linux'); + case 3: equalsCI(systemName, 'Mac'); + case 4: equalsCI(systemName, 'HTML5'); + case 5: equalsCI(systemName, 'Android'); default: null; } + } + static inline function equalsCI(a: String, b: String): Bool { + return a.toLowerCase() == b.toLowerCase(); } } diff --git a/Sources/armory/logicnode/GetTraitNameNode.hx b/Sources/armory/logicnode/GetTraitNameNode.hx index dd1ded50..ca04f375 100644 --- a/Sources/armory/logicnode/GetTraitNameNode.hx +++ b/Sources/armory/logicnode/GetTraitNameNode.hx @@ -16,12 +16,12 @@ class GetTraitNameNode extends LogicNode { case 0: { // Check CanvasScript var cname = cast Type.resolveClass("armory.trait.internal.CanvasScript"); - if (Std.is(trait, cname)) { + if (Std.isOfType(trait, cname)) { return trait.cnvName; } // Check WasmScript var cname = cast Type.resolveClass("armory.trait.internal.WasmScript"); - if (Std.is(trait, cname)) { + if (Std.isOfType(trait, cname)) { return trait.wasmName; } // Other diff --git a/Sources/armory/logicnode/LogicNode.hx b/Sources/armory/logicnode/LogicNode.hx index 9e5e367c..6c48fde3 100644 --- a/Sources/armory/logicnode/LogicNode.hx +++ b/Sources/armory/logicnode/LogicNode.hx @@ -1,31 +1,136 @@ package armory.logicnode; +#if arm_patch @:keep @:keepSub #end class LogicNode { var tree: LogicTree; - var inputs: Array = []; - var outputs: Array> = []; + var inputs: Array = []; + var outputs: Array> = []; - #if arm_debug + #if (arm_debug || arm_patch) public var name = ""; - public function watch(b: Bool) { // Watch in debug console - var nodes = armory.trait.internal.DebugConsole.watchNodes; - b ? nodes.push(this) : nodes.remove(this); - } + + #if (arm_debug) + public function watch(b: Bool) { // Watch in debug console + var nodes = armory.trait.internal.DebugConsole.watchNodes; + b ? nodes.push(this) : nodes.remove(this); + } + #end #end public function new(tree: LogicTree) { this.tree = tree; } - public function addInput(node: LogicNode, from: Int) { - inputs.push(new LogicNodeInput(node, from)); + /** + Resize the inputs array to a given size to minimize dynamic + reallocation and over-allocation later. + **/ + inline function preallocInputs(amount: Int) { + this.inputs.resize(amount); } - public function addOutputs(nodes: Array) { - outputs.push(nodes); + /** + Resize the outputs array to a given size to minimize dynamic + reallocation and over-allocation later. + **/ + inline function preallocOutputs(amount: Int) { + this.outputs.resize(amount); + for (i in 0...outputs.length) { + outputs[i] = []; + } } + /** + Add a link between to nodes to the tree. + **/ + public static function addLink(fromNode: LogicNode, toNode: LogicNode, fromIndex: Int, toIndex: Int): LogicNodeLink { + var link = new LogicNodeLink(fromNode, toNode, fromIndex, toIndex); + + if (toNode.inputs.length <= toIndex) { + toNode.inputs.resize(toIndex + 1); + } + toNode.inputs[toIndex] = link; + + var fromNodeOuts = fromNode.outputs; + var outLen = fromNodeOuts.length; + if (outLen <= fromIndex) { + fromNodeOuts.resize(fromIndex + 1); + + // Initialize with empty arrays + for (i in outLen...fromIndex + 1) { + fromNodeOuts[i] = []; + } + } + fromNodeOuts[fromIndex].push(link); + + return link; + } + + #if arm_patch + /** + Removes a link from the tree. + **/ + static function removeLink(link: LogicNodeLink) { + link.fromNode.outputs[link.fromIndex].remove(link); + + // Reuse the same link and connect a default input node to it. + // That's why this function is only available in arm_patch mode, we need + // access to the link's type and value. + link.fromNode = LogicNode.createSocketDefaultNode(link.toNode.tree, link.toType, link.toValue); + link.fromIndex = 0; + } + + /** + Removes all inputs and their links from this node. + Warning: this function changes the amount of node inputs to 0! + **/ + function clearInputs() { + for (link in inputs) { + link.fromNode.outputs[link.fromIndex].remove(link); + } + inputs.resize(0); + } + + /** + Removes all outputs and their links from this node. + Warning: this function changes the amount of node inputs to 0! + **/ + function clearOutputs() { + for (links in outputs) { + for (link in links) { + var defaultNode = LogicNode.createSocketDefaultNode(tree, link.toType, link.toValue); + link.fromNode = defaultNode; + link.fromIndex = 0; + defaultNode.outputs[0] = [link]; + } + } + outputs.resize(0); + } + + /** + Creates a default node for a socket so that get() and set() can be + used without null checks. + Loosely equivalent to `make_logic.build_default_node()` in Python. + **/ + static inline function createSocketDefaultNode(tree: LogicTree, socketType: String, value: Dynamic): LogicNode { + // Make sure to not add these nodes to the LogicTree.nodes array as they + // won't be garbage collected then if unlinked later. + return switch (socketType) { + case "VECTOR": new armory.logicnode.VectorNode(tree, value[0], value[1], value[2]); + case "RGBA": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2], value[3]); + case "RGB": new armory.logicnode.ColorNode(tree, value[0], value[1], value[2]); + case "VALUE": new armory.logicnode.FloatNode(tree, value); + case "INT": new armory.logicnode.IntegerNode(tree, value); + case "BOOLEAN": new armory.logicnode.BooleanNode(tree, value); + case "STRING": new armory.logicnode.StringNode(tree, value); + case "NONE": new armory.logicnode.NullNode(tree); + case "OBJECT": new armory.logicnode.ObjectNode(tree, value); + default: new armory.logicnode.DynamicNode(tree, value); + } + } + #end + /** Called when this node is activated. @param from impulse index @@ -38,42 +143,45 @@ class LogicNode { **/ function runOutput(i: Int) { if (i >= outputs.length) return; - for (o in outputs[i]) { - // Check which input activated the node - for (j in 0...o.inputs.length) { - if (o.inputs[j].node == this) { - o.run(j); - break; - } - } + for (outLink in outputs[i]) { + outLink.toNode.run(outLink.toIndex); } } - @:allow(armory.logicnode.LogicNodeInput) + @:allow(armory.logicnode.LogicNodeLink) function get(from: Int): Dynamic { return this; } - @:allow(armory.logicnode.LogicNodeInput) + @:allow(armory.logicnode.LogicNodeLink) function set(value: Dynamic) {} } -class LogicNodeInput { +@:allow(armory.logicnode.LogicNode) +@:allow(armory.logicnode.LogicTree) +class LogicNodeLink { - @:allow(armory.logicnode.LogicNode) - var node: LogicNode; - var from: Int; // Socket index + var fromNode: LogicNode; + var toNode: LogicNode; + var fromIndex: Int; + var toIndex: Int; - public function new(node: LogicNode, from: Int) { - this.node = node; - this.from = from; + #if arm_patch + var fromType: String; + var toType: String; + var toValue: Dynamic; + #end + + inline function new(fromNode: LogicNode, toNode: LogicNode, fromIndex: Int, toIndex: Int) { + this.fromNode = fromNode; + this.toNode = toNode; + this.fromIndex = fromIndex; + this.toIndex = toIndex; } - @:allow(armory.logicnode.LogicNode) - function get(): Dynamic { - return node.get(from); + inline function get(): Dynamic { + return fromNode.get(fromIndex); } - @:allow(armory.logicnode.LogicNode) - function set(value: Dynamic) { - node.set(value); + inline function set(value: Dynamic) { + fromNode.set(value); } } diff --git a/Sources/armory/logicnode/LogicTree.hx b/Sources/armory/logicnode/LogicTree.hx index ce2b26e4..ed06b08e 100644 --- a/Sources/armory/logicnode/LogicTree.hx +++ b/Sources/armory/logicnode/LogicTree.hx @@ -2,10 +2,26 @@ package armory.logicnode; class LogicTree extends iron.Trait { + #if arm_patch + /** + Stores all trait instances of the tree via its name. + **/ + public static var nodeTrees = new Map>(); + + /** + [node name => logic node] for later node replacement for live patching. + **/ + public var nodes: Map; + #end + public var loopBreak = false; // Trigger break from loop nodes public function new() { super(); + + #if arm_patch + nodes = new Map(); + #end } public function add() {} diff --git a/Sources/armory/logicnode/MathNode.hx b/Sources/armory/logicnode/MathNode.hx index 4905c47e..5adc477d 100644 --- a/Sources/armory/logicnode/MathNode.hx +++ b/Sources/armory/logicnode/MathNode.hx @@ -3,7 +3,7 @@ package armory.logicnode; class MathNode extends LogicNode { public var property0: String; // Operation - public var property1: String; // Clamp + public var property1: Bool; // Clamp public function new(tree: LogicTree) { super(tree); @@ -80,8 +80,8 @@ class MathNode extends LogicNode { } } // Clamp - if (property1 == "true") r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r); + if (property1) r = r < 0.0 ? 0.0 : (r > 1.0 ? 1.0 : r); return r; } -} \ No newline at end of file +} diff --git a/Sources/armory/logicnode/MergeNode.hx b/Sources/armory/logicnode/MergeNode.hx index 60eb6ca8..c2a22e6a 100644 --- a/Sources/armory/logicnode/MergeNode.hx +++ b/Sources/armory/logicnode/MergeNode.hx @@ -2,11 +2,31 @@ package armory.logicnode; class MergeNode extends LogicNode { + /** Execution mode. **/ + public var property0: String; + + var lastInputIndex = -1; + public function new(tree: LogicTree) { super(tree); + tree.notifyOnLateUpdate(lateUpdate); } override function run(from: Int) { + // Check if there already were executions on the same frame + if (lastInputIndex != -1 && property0 == "once_per_frame") { + return; + } + + lastInputIndex = from; runOutput(0); } + + override function get(from: Int): Dynamic { + return lastInputIndex; + } + + function lateUpdate() { + lastInputIndex = -1; + } } diff --git a/Sources/armory/logicnode/MixNode.hx b/Sources/armory/logicnode/MixNode.hx index 90f51bea..4f75884d 100644 --- a/Sources/armory/logicnode/MixNode.hx +++ b/Sources/armory/logicnode/MixNode.hx @@ -6,7 +6,7 @@ class MixNode extends LogicNode { public var property0: String; // Type public var property1: String; // Ease - public var property2: String; // Clamp + public var property2: Bool; // Clamp var ease: Float->Float = null; @@ -50,7 +50,9 @@ class MixNode extends LogicNode { var v2: Float = inputs[2].get(); var f = v1 + (v2 - v1) * ease(k); - if (property2 == "true") f = f < 0 ? 0 : f > 1 ? 1 : f; + // Clamp + if (property2) f = f < 0 ? 0 : f > 1 ? 1 : f; + return f; } } diff --git a/Sources/armory/logicnode/ObjectNode.hx b/Sources/armory/logicnode/ObjectNode.hx index 1506fdd2..bd3e8494 100644 --- a/Sources/armory/logicnode/ObjectNode.hx +++ b/Sources/armory/logicnode/ObjectNode.hx @@ -21,7 +21,7 @@ class ObjectNode extends LogicNode { override function set(value: Dynamic) { if (inputs.length > 0) inputs[0].set(value); else { - objectName = value.name; + objectName = value != null ? value.name : ""; this.value = value; } } diff --git a/Sources/armory/logicnode/OnCanvasElementNode.hx b/Sources/armory/logicnode/OnCanvasElementNode.hx index 8b3ff763..0fae83e5 100644 --- a/Sources/armory/logicnode/OnCanvasElementNode.hx +++ b/Sources/armory/logicnode/OnCanvasElementNode.hx @@ -4,7 +4,7 @@ import armory.trait.internal.CanvasScript; import iron.Scene; #if arm_ui -import zui.Canvas.Anchor; +import armory.ui.Canvas.Anchor; #end class OnCanvasElementNode extends LogicNode { diff --git a/Sources/armory/logicnode/OnInputMapNode.hx b/Sources/armory/logicnode/OnInputMapNode.hx new file mode 100644 index 00000000..3e354f29 --- /dev/null +++ b/Sources/armory/logicnode/OnInputMapNode.hx @@ -0,0 +1,35 @@ +package armory.logicnode; + +import armory.system.InputMap; + +class OnInputMapNode extends LogicNode { + + var inputMap: Null; + + public function new(tree: LogicTree) { + super(tree); + + tree.notifyOnUpdate(update); + } + + function update() { + var i = inputs[0].get(); + + inputMap = InputMap.getInputMap(i); + + if (inputMap != null) { + if (inputMap.started()) { + runOutput(0); + } + + if (inputMap.released()) { + runOutput(1); + } + } + } + + override function get(from: Int): Dynamic { + if (from == 2) return inputMap.value(); + else return inputMap.lastKeyPressed; + } +} diff --git a/Sources/armory/logicnode/OncePerFrameNode.hx b/Sources/armory/logicnode/OncePerFrameNode.hx new file mode 100644 index 00000000..0e2aadd5 --- /dev/null +++ b/Sources/armory/logicnode/OncePerFrameNode.hx @@ -0,0 +1,22 @@ +package armory.logicnode; + +class OncePerFrameNode extends LogicNode { + + var c = false; + + public function new(tree: LogicTree) { + super(tree); + tree.notifyOnUpdate(update); + } + + override function run(from: Int) { + if(c) { + c = false; + runOutput(0); + } + } + + function update() { + c = true; + } +} diff --git a/Sources/armory/logicnode/PauseTraitNode.hx b/Sources/armory/logicnode/PauseTraitNode.hx index a040f5d1..7dc3c084 100644 --- a/Sources/armory/logicnode/PauseTraitNode.hx +++ b/Sources/armory/logicnode/PauseTraitNode.hx @@ -8,7 +8,7 @@ class PauseTraitNode extends LogicNode { override function run(from: Int) { var trait: Dynamic = inputs[1].get(); - if (trait == null || !Std.is(trait, LogicTree)) return; + if (trait == null || !Std.isOfType(trait, LogicTree)) return; cast(trait, LogicTree).pause(); diff --git a/Sources/armory/logicnode/PhysicsConstraintNode.hx b/Sources/armory/logicnode/PhysicsConstraintNode.hx index 5446788b..a06eaa51 100644 --- a/Sources/armory/logicnode/PhysicsConstraintNode.hx +++ b/Sources/armory/logicnode/PhysicsConstraintNode.hx @@ -3,50 +3,39 @@ package armory.logicnode; #if arm_physics import armory.trait.physics.bullet.PhysicsConstraint.ConstraintAxis; #end -import iron.object.Object; class PhysicsConstraintNode extends LogicNode { - public var property0: String;//Linear or Angular - public var property1: String;//Axis - public var property2: String;//Is a spring - public var value1: Float;//Lower limit or Spring Stiffness - public var value2: Float;//Upper limit or Spring Damping + public var property0: String; //Linear or Angular + public var property1: String; //Axis + public var property2: Bool; //Is a spring + +#if arm_physics + public var value1: Float; //Lower limit or Spring Stiffness + public var value2: Float; //Upper limit or Spring Damping public var isAngular: Bool; public var axis: ConstraintAxis; public var isSpring: Bool; +#end public function new(tree: LogicTree) { super(tree); } override function get(from: Int): PhysicsConstraintNode { +#if arm_physics value1 = inputs[0].get(); value2 = inputs[1].get(); - if(property0 == 'Linear') { - isAngular = false; - } - else{ - isAngular = true; - } + isAngular = property0 != "Linear"; + isSpring = property2; - if(property2 == 'true'){ - isSpring = true; + switch (property1) { + case "X": axis = X; + case "Y": axis = Y; + case "Z": axis = Z; } - else { - isSpring = false; - } - - switch (property1){ - case 'X': - axis = X; - case 'Y': - axis = Y; - case 'Z': - axis = Z; - } - +#end return this; } } diff --git a/Sources/armory/logicnode/QuaternionNode.hx b/Sources/armory/logicnode/QuaternionNode.hx index 95880f4f..bf83f4de 100644 --- a/Sources/armory/logicnode/QuaternionNode.hx +++ b/Sources/armory/logicnode/QuaternionNode.hx @@ -11,10 +11,10 @@ class QuaternionNode extends LogicNode { super(tree); if (x != null) { - addInput(new FloatNode(tree, x), 0); - addInput(new FloatNode(tree, y), 0); - addInput(new FloatNode(tree, z), 0); - addInput(new FloatNode(tree, w), 0); + LogicNode.addLink(new FloatNode(tree, x), this, 0, 0); + LogicNode.addLink(new FloatNode(tree, y), this, 0, 1); + LogicNode.addLink(new FloatNode(tree, z), this, 0, 2); + LogicNode.addLink(new FloatNode(tree, w), this, 0, 3); } } @@ -27,15 +27,15 @@ class QuaternionNode extends LogicNode { switch (from){ case 0: return value; - case 1: - var value1 = new Vec4(); + case 1: + var value1 = new Vec4(); value1.x = value.x; value1.y = value.y; value1.z = value.z; value1.w = 0; // use 0 to avoid this vector being translated. return value1; case 2: - return value.w; + return value.w; default: return null; } diff --git a/Sources/armory/logicnode/RemoveInputMapKeyNode.hx b/Sources/armory/logicnode/RemoveInputMapKeyNode.hx new file mode 100644 index 00000000..44708c6d --- /dev/null +++ b/Sources/armory/logicnode/RemoveInputMapKeyNode.hx @@ -0,0 +1,19 @@ +package armory.logicnode; + +import armory.system.InputMap; + +class RemoveInputMapKeyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var inputMap = inputs[1].get(); + var key = inputs[2].get(); + + if (InputMap.removeInputMapKey(inputMap, key)) { + runOutput(0); + } + } +} diff --git a/Sources/armory/logicnode/ResumeTraitNode.hx b/Sources/armory/logicnode/ResumeTraitNode.hx index c3c8b26b..c8a62011 100644 --- a/Sources/armory/logicnode/ResumeTraitNode.hx +++ b/Sources/armory/logicnode/ResumeTraitNode.hx @@ -8,7 +8,7 @@ class ResumeTraitNode extends LogicNode { override function run(from: Int) { var trait: Dynamic = inputs[1].get(); - if (trait == null || !Std.is(trait, LogicTree)) return; + if (trait == null || !Std.isOfType(trait, LogicTree)) return; cast(trait, LogicTree).resume(); diff --git a/Sources/armory/logicnode/SelectNode.hx b/Sources/armory/logicnode/SelectNode.hx new file mode 100644 index 00000000..10ca5252 --- /dev/null +++ b/Sources/armory/logicnode/SelectNode.hx @@ -0,0 +1,37 @@ +package armory.logicnode; + +class SelectNode extends LogicNode { + + /** Execution mode. **/ + public var property0: String; + + var value: Dynamic = null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + // Get value according to the activated input (run() can only be called + // if the execution mode is from_input). + value = inputs[from + Std.int(inputs.length / 2)].get(); + + runOutput(0); + } + + override function get(from: Int): Dynamic { + if (property0 == "from_index") { + var index = inputs[0].get() + 2; + + // Return default value for invalid index + if (index < 2 || index >= inputs.length) { + return inputs[1].get(); + } + + return inputs[index].get(); + } + + // from_input + return value; + } +} diff --git a/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx b/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx new file mode 100644 index 00000000..4d9b59fb --- /dev/null +++ b/Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx @@ -0,0 +1,35 @@ +package armory.logicnode; + +import iron.math.Quat; +import iron.math.Vec4; +import iron.object.Object; +import iron.object.BoneAnimation; + +class SetBoneFkIkOnlyNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + #if arm_skin + + var object: Object = inputs[1].get(); + var boneName: String = inputs[2].get(); + var fk_ik_only: Bool = inputs[3].get(); + + if (object == null) return; + var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null; + if (anim == null) anim = object.getParentArmature(object.name); + + // Get bone in armature + var bone = anim.getBone(boneName); + + //Set bone animated by FK or IK only + bone.is_ik_fk_only = fk_ik_only; + + runOutput(0); + + #end + } +} diff --git a/Sources/armory/logicnode/SetInputMapKeyNode.hx b/Sources/armory/logicnode/SetInputMapKeyNode.hx new file mode 100644 index 00000000..e657456d --- /dev/null +++ b/Sources/armory/logicnode/SetInputMapKeyNode.hx @@ -0,0 +1,46 @@ +package armory.logicnode; + +import armory.system.InputMap; + +class SetInputMapKeyNode extends LogicNode { + + public var property0: String; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var inputMap = inputs[1].get(); + var key = inputs[2].get(); + var scale = inputs[3].get(); + var deadzone = inputs[4].get(); + var index = inputs[5].get(); + + var i = InputMap.getInputMap(inputMap); + + if (i == null) { + i = InputMap.addInputMap(inputMap); + } + + var k = InputMap.getInputMapKey(inputMap, key); + + if (k == null) { + switch(property0) { + case "keyboard": k = i.addKeyboard(key, scale); + case "mouse": k = i.addMouse(key, scale, deadzone); + case "gamepad": { + k = i.addGamepad(key, scale, deadzone); + k.setIndex(index); + } + } + + } else { + k.scale = scale; + k.deadzone = deadzone; + k.setIndex(index); + } + + runOutput(0); + } +} diff --git a/Sources/armory/logicnode/SetLocationNode.hx b/Sources/armory/logicnode/SetLocationNode.hx index 709e457b..4f879586 100644 --- a/Sources/armory/logicnode/SetLocationNode.hx +++ b/Sources/armory/logicnode/SetLocationNode.hx @@ -13,9 +13,21 @@ class SetLocationNode extends LogicNode { override function run(from: Int) { var object: Object = inputs[1].get(); var vec: Vec4 = inputs[2].get(); + var relative: Bool = inputs[3].get(); if (object == null || vec == null) return; + if (!relative && object.parent != null) { + var loc = vec.clone(); + loc.sub(object.parent.transform.world.getLoc()); // Remove parent location influence + + // Convert vec to parent local space + var dotX = loc.dot(object.parent.transform.right()); + var dotY = loc.dot(object.parent.transform.look()); + var dotZ = loc.dot(object.parent.transform.up()); + vec.set(dotX, dotY, dotZ); + } + object.transform.loc.setFrom(vec); object.transform.buildMatrix(); @@ -26,4 +38,4 @@ class SetLocationNode extends LogicNode { runOutput(0); } -} +} \ No newline at end of file diff --git a/Sources/armory/logicnode/SetMaterialImageParamNode.hx b/Sources/armory/logicnode/SetMaterialImageParamNode.hx index abee9941..c74e4553 100644 --- a/Sources/armory/logicnode/SetMaterialImageParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialImageParamNode.hx @@ -1,40 +1,40 @@ package armory.logicnode; +import iron.Scene; import iron.data.MaterialData; import iron.object.Object; +import armory.trait.internal.UniformsManager; class SetMaterialImageParamNode extends LogicNode { - - static var registered = false; - static var map = new Map>(); - + public function new(tree: LogicTree) { super(tree); - if (!registered) { - registered = true; - iron.object.Uniforms.externalTextureLinks.push(textureLink); - } + } override function run(from: Int) { - var mat = inputs[1].get(); - if (mat == null) return; - var entry = map.get(mat); - if (entry == null) { - entry = new Map(); - map.set(mat, entry); + var perObject: Null; + + var object = inputs[1].get(); + if(object == null) return; + + perObject = inputs[2].get(); + if(perObject == null) perObject = false; + + var mat = inputs[3].get(); + if(mat == null) return; + + if(! perObject){ + UniformsManager.removeObjectFromMap(object, Texture); + object = Scene.active.root; } - iron.data.Data.getImage(inputs[3].get(), function(image: kha.Image) { - entry.set(inputs[2].get(), image); // Node name, value + var img = inputs[5].get(); + if(img == null) return; + iron.data.Data.getImage(img, function(image: kha.Image) { + UniformsManager.setTextureValue(mat, object, inputs[4].get(), image); }); + runOutput(0); } - - static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { - if (mat == null) return null; - var entry = map.get(mat); - if (entry == null) return null; - return entry.get(link); - } } diff --git a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx index 2db7bbc9..a5289b06 100644 --- a/Sources/armory/logicnode/SetMaterialRgbParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialRgbParamNode.hx @@ -1,38 +1,35 @@ package armory.logicnode; +import iron.Scene; import iron.math.Vec4; import iron.data.MaterialData; import iron.object.Object; +import armory.trait.internal.UniformsManager; class SetMaterialRgbParamNode extends LogicNode { - - static var registered = false; - static var map = new Map>(); - + public function new(tree: LogicTree) { super(tree); - if (!registered) { - registered = true; - iron.object.Uniforms.externalVec3Links.push(vec3Link); - } } override function run(from: Int) { - var mat = inputs[1].get(); - if (mat == null) return; - var entry = map.get(mat); - if (entry == null) { - entry = new Map(); - map.set(mat, entry); + var perObject: Null; + + var object = inputs[1].get(); + if(object == null) return; + + perObject = inputs[2].get(); + if(perObject == null) perObject = false; + + var mat = inputs[3].get(); + if(mat == null) return; + + if(! perObject){ + UniformsManager.removeObjectFromMap(object, Vector); + object = Scene.active.root; } - entry.set(inputs[2].get(), inputs[3].get()); // Node name, value + + UniformsManager.setVec3Value(mat, object, inputs[4].get(), inputs[5].get()); runOutput(0); } - - static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { - if (mat == null) return null; - var entry = map.get(mat); - if (entry == null) return null; - return entry.get(link); - } } diff --git a/Sources/armory/logicnode/SetMaterialValueParamNode.hx b/Sources/armory/logicnode/SetMaterialValueParamNode.hx index 40729a64..737365cf 100644 --- a/Sources/armory/logicnode/SetMaterialValueParamNode.hx +++ b/Sources/armory/logicnode/SetMaterialValueParamNode.hx @@ -1,37 +1,35 @@ package armory.logicnode; +import iron.Scene; import iron.data.MaterialData; import iron.object.Object; +import armory.trait.internal.UniformsManager; class SetMaterialValueParamNode extends LogicNode { - - static var registered = false; - static var map = new Map>>(); - + public function new(tree: LogicTree) { super(tree); - if (!registered) { - registered = true; - iron.object.Uniforms.externalFloatLinks.push(floatLink); - } } override function run(from: Int) { - var mat = inputs[1].get(); - if (mat == null) return; - var entry = map.get(mat); - if (entry == null) { - entry = new Map(); - map.set(mat, entry); + var perObject: Null; + + var object = inputs[1].get(); + if(object == null) return; + + perObject = inputs[2].get(); + if(perObject == null) perObject = false; + + var mat = inputs[3].get(); + if(mat == null) return; + + if(! perObject){ + UniformsManager.removeObjectFromMap(object, Float); + object = Scene.active.root; } - entry.set(inputs[2].get(), inputs[3].get()); // Node name, value + + UniformsManager.setFloatValue(mat, object, inputs[4].get(), inputs[5].get()); runOutput(0); } - static function floatLink(object: Object, mat: MaterialData, link: String): Null { - if (mat == null) return null; - var entry = map.get(mat); - if (entry == null) return null; - return entry.get(link); - } } diff --git a/Sources/armory/logicnode/SetParentNode.hx b/Sources/armory/logicnode/SetParentNode.hx index 8d37720d..4ad179df 100644 --- a/Sources/armory/logicnode/SetParentNode.hx +++ b/Sources/armory/logicnode/SetParentNode.hx @@ -14,8 +14,9 @@ class SetParentNode extends LogicNode { var parent: Object; var isUnparent = false; - if (Std.is(inputs[2].node, ObjectNode)) { - var parentNode = cast(inputs[2].node, ObjectNode); + + if (Std.isOfType(inputs[2].fromNode, ObjectNode)) { + var parentNode = cast(inputs[2].fromNode, ObjectNode); isUnparent = parentNode.objectName == ""; } if (isUnparent) parent = iron.Scene.active.root; @@ -24,7 +25,7 @@ class SetParentNode extends LogicNode { if (object == null || parent == null || object.parent == parent) return; object.parent.removeChild(object, isUnparent); // keepTransform - + #if arm_physics var rigidBody = object.getTrait(RigidBody); if (rigidBody != null) rigidBody.setActivationState(0); diff --git a/Sources/armory/logicnode/SetTraitPausedNode.hx b/Sources/armory/logicnode/SetTraitPausedNode.hx index 87099948..53327e1f 100644 --- a/Sources/armory/logicnode/SetTraitPausedNode.hx +++ b/Sources/armory/logicnode/SetTraitPausedNode.hx @@ -10,7 +10,7 @@ class SetTraitPausedNode extends LogicNode { var trait: Dynamic = inputs[1].get(); var paused: Bool = inputs[2].get(); - if (trait == null || !Std.is(trait, LogicTree)) return; + if (trait == null || !Std.isOfType(trait, LogicTree)) return; paused ? cast(trait, LogicTree).pause() : cast(trait, LogicTree).resume(); diff --git a/Sources/armory/logicnode/SpawnObjectByNameNode.hx b/Sources/armory/logicnode/SpawnObjectByNameNode.hx new file mode 100644 index 00000000..5ecb1bc7 --- /dev/null +++ b/Sources/armory/logicnode/SpawnObjectByNameNode.hx @@ -0,0 +1,72 @@ +package armory.logicnode; + +import iron.data.SceneFormat.TSceneFormat; +import iron.data.Data; +import iron.object.Object; +import iron.math.Mat4; +import armory.trait.physics.RigidBody; + +class SpawnObjectByNameNode extends LogicNode { + + var object: Object; + var matrices: Array = []; + + /** Scene from which to take the object **/ + public var property0: Null; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var objectName = inputs[1].get(); + if (objectName == null) return; + + #if arm_json + property0 += ".json"; + #elseif arm_compress + property0 += ".lz4"; + #end + + var m: Mat4 = inputs[2].get(); + matrices.push(m != null ? m.clone() : null); + var spawnChildren: Bool = inputs.length > 3 ? inputs[3].get() : true; // TODO + + Data.getSceneRaw(property0, (rawScene: TSceneFormat) -> { + + //Check if object with given name present in the specified scene + var objPresent: Bool = false; + + for (o in rawScene.objects) { + if (o.name == objectName) { + objPresent = true; + break; + } + } + if (! objPresent) return; + + //Spawn object if present + iron.Scene.active.spawnObject(objectName, null, function(o: Object) { + object = o; + var matrix = matrices.pop(); // Async spawn in a loop, order is non-stable + if (matrix != null) { + object.transform.setMatrix(matrix); + #if arm_physics + var rigidBody = object.getTrait(RigidBody); + if (rigidBody != null) { + object.transform.buildMatrix(); + rigidBody.syncTransform(); + } + #end + } + object.visible = true; + runOutput(0); + }, spawnChildren, rawScene); + + }); + } + + override function get(from: Int): Dynamic { + return object; + } +} diff --git a/Sources/armory/logicnode/VectorMixNode.hx b/Sources/armory/logicnode/VectorMixNode.hx index 47286ac7..ae2b7938 100644 --- a/Sources/armory/logicnode/VectorMixNode.hx +++ b/Sources/armory/logicnode/VectorMixNode.hx @@ -7,7 +7,7 @@ class VectorMixNode extends LogicNode { public var property0: String; // Type public var property1: String; // Ease - public var property2: String; // Clamp + public var property2: Bool; // Clamp var v = new Vec4(); @@ -57,7 +57,7 @@ class VectorMixNode extends LogicNode { v.y = v1.y + (v2.y - v1.y) * f; v.z = v1.z + (v2.z - v1.z) * f; - if (property2 == "true") v.clamp(0, 1); + if (property2) v.clamp(0, 1); return v; } } diff --git a/Sources/armory/logicnode/VectorNode.hx b/Sources/armory/logicnode/VectorNode.hx index 2c186907..09cb0204 100644 --- a/Sources/armory/logicnode/VectorNode.hx +++ b/Sources/armory/logicnode/VectorNode.hx @@ -10,9 +10,9 @@ class VectorNode extends LogicNode { super(tree); if (x != null) { - addInput(new FloatNode(tree, x), 0); - addInput(new FloatNode(tree, y), 0); - addInput(new FloatNode(tree, z), 0); + LogicNode.addLink(new FloatNode(tree, x), this, 0, 0); + LogicNode.addLink(new FloatNode(tree, y), this, 0, 1); + LogicNode.addLink(new FloatNode(tree, z), this, 0, 2); } } diff --git a/Sources/armory/logicnode/VectorToObjectOrientationNode.hx b/Sources/armory/logicnode/VectorToObjectOrientationNode.hx index 2a72590a..27a4b994 100644 --- a/Sources/armory/logicnode/VectorToObjectOrientationNode.hx +++ b/Sources/armory/logicnode/VectorToObjectOrientationNode.hx @@ -3,8 +3,6 @@ package armory.logicnode; import iron.object.Object; import iron.math.Vec4; -using armory.object.TransformExtension; - class VectorToObjectOrientationNode extends LogicNode { public function new(tree: LogicTree) { @@ -18,7 +16,7 @@ class VectorToObjectOrientationNode extends LogicNode { if (object == null || vec == null) return null; - return object.transform.worldVecToOrientation(vec); + return vec.applyQuat(object.transform.rot); } } diff --git a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx index 493ed615..75f128c1 100644 --- a/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx +++ b/Sources/armory/logicnode/WorldVectorToLocalSpaceNode.hx @@ -3,8 +3,6 @@ package armory.logicnode; import iron.math.Vec4; import iron.object.Object; -using armory.object.TransformExtension; - class WorldVectorToLocalSpaceNode extends LogicNode { public function new(tree: LogicTree) { @@ -17,7 +15,8 @@ class WorldVectorToLocalSpaceNode extends LogicNode { if (object == null || worldVec == null) return null; - var localVec: Vec4 = new Vec4(); + var localVec = new Vec4(); + localVec.sub(object.transform.world.getLoc()); localVec.x = worldVec.dot(object.transform.right()); localVec.y = worldVec.dot(object.transform.look()); diff --git a/Sources/armory/math/Helper.hx b/Sources/armory/math/Helper.hx index 03351492..d3579d1e 100644 --- a/Sources/armory/math/Helper.hx +++ b/Sources/armory/math/Helper.hx @@ -72,4 +72,13 @@ class Helper { if (value <= leftMin) return rightMin; return map(value, leftMin, leftMax, rightMin, rightMax); } + + /** + Return the sign of the given value represented as `1.0` (positive value) + or `-1.0` (negative value). The sign of `0` is `0`. + **/ + public static inline function sign(value: Float): Float { + if (value == 0) return 0; + return (value < 0) ? -1.0 : 1.0; + } } diff --git a/Sources/armory/object/Uniforms.hx b/Sources/armory/object/Uniforms.hx index 91c55f30..70751dc6 100644 --- a/Sources/armory/object/Uniforms.hx +++ b/Sources/armory/object/Uniforms.hx @@ -11,176 +11,204 @@ class Uniforms { public static function register() { iron.object.Uniforms.externalTextureLinks = [textureLink]; - iron.object.Uniforms.externalVec2Links = []; + iron.object.Uniforms.externalVec2Links = [vec2Link]; iron.object.Uniforms.externalVec3Links = [vec3Link]; iron.object.Uniforms.externalVec4Links = []; iron.object.Uniforms.externalFloatLinks = [floatLink]; iron.object.Uniforms.externalIntLinks = []; } - public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { - #if arm_ltc - if (link == "_ltcMat") { - if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC(); - return armory.data.ConstData.ltcMatTex; + public static function textureLink(object: Object, mat: MaterialData, link: String): Null { + switch (link) { + case "_nishitaLUT": { + if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world); + return armory.renderpath.Nishita.data.lut; + } + #if arm_ltc + case "_ltcMat": { + if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC(); + return armory.data.ConstData.ltcMatTex; + } + case "_ltcMag": { + if (armory.data.ConstData.ltcMagTex == null) armory.data.ConstData.initLTC(); + return armory.data.ConstData.ltcMagTex; + } + #end } - else if (link == "_ltcMag") { - if (armory.data.ConstData.ltcMagTex == null) armory.data.ConstData.initLTC(); - return armory.data.ConstData.ltcMagTex; - } - #end + var target = iron.RenderPath.active.renderTargets.get(link.endsWith("_depth") ? link.substr(0, link.length - 6) : link); return target != null ? target.image : null; } - public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { + public static function vec3Link(object: Object, mat: MaterialData, link: String): Null { var v: Vec4 = null; - #if arm_hosek - if (link == "_hosekA") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); + switch (link) { + #if arm_hosek + case "_hosekA": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.A.x; + v.y = armory.renderpath.HosekWilkie.data.A.y; + v.z = armory.renderpath.HosekWilkie.data.A.z; + } } - if (armory.renderpath.HosekWilkie.data != null) { + case "_hosekB": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.B.x; + v.y = armory.renderpath.HosekWilkie.data.B.y; + v.z = armory.renderpath.HosekWilkie.data.B.z; + } + } + case "_hosekC": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.C.x; + v.y = armory.renderpath.HosekWilkie.data.C.y; + v.z = armory.renderpath.HosekWilkie.data.C.z; + } + } + case "_hosekD": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.D.x; + v.y = armory.renderpath.HosekWilkie.data.D.y; + v.z = armory.renderpath.HosekWilkie.data.D.z; + } + } + case "_hosekE": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.E.x; + v.y = armory.renderpath.HosekWilkie.data.E.y; + v.z = armory.renderpath.HosekWilkie.data.E.z; + } + } + case "_hosekF": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.F.x; + v.y = armory.renderpath.HosekWilkie.data.F.y; + v.z = armory.renderpath.HosekWilkie.data.F.z; + } + } + case "_hosekG": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.G.x; + v.y = armory.renderpath.HosekWilkie.data.G.y; + v.z = armory.renderpath.HosekWilkie.data.G.z; + } + } + case "_hosekH": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.H.x; + v.y = armory.renderpath.HosekWilkie.data.H.y; + v.z = armory.renderpath.HosekWilkie.data.H.z; + } + } + case "_hosekI": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.I.x; + v.y = armory.renderpath.HosekWilkie.data.I.y; + v.z = armory.renderpath.HosekWilkie.data.I.z; + } + } + case "_hosekZ": { + if (armory.renderpath.HosekWilkie.data == null) { + armory.renderpath.HosekWilkie.recompute(Scene.active.world); + } + if (armory.renderpath.HosekWilkie.data != null) { + v = iron.object.Uniforms.helpVec; + v.x = armory.renderpath.HosekWilkie.data.Z.x; + v.y = armory.renderpath.HosekWilkie.data.Z.y; + v.z = armory.renderpath.HosekWilkie.data.Z.z; + } + } + #end + #if rp_voxelao + case "_cameraPositionSnap": { v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.A.x; - v.y = armory.renderpath.HosekWilkie.data.A.y; - v.z = armory.renderpath.HosekWilkie.data.A.z; + var camera = iron.Scene.active.camera; + v.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz()); + var l = camera.lookWorld(); + var e = Main.voxelgiHalfExtents; + v.x += l.x * e * 0.9; + v.y += l.y * e * 0.9; + var f = Main.voxelgiVoxelSize * 8; // Snaps to 3 mip-maps range + v.set(Math.floor(v.x / f) * f, Math.floor(v.y / f) * f, Math.floor(v.z / f) * f); + } + #end + } + + return v; + } + + public static function vec2Link(object: Object, mat: MaterialData, link: String): Null { + var v: Vec4 = null; + switch (link) { + case "_nishitaDensity": { + var w = Scene.active.world; + if (w != null) { + v = iron.object.Uniforms.helpVec; + // We only need Rayleigh and Mie density in the sky shader -> Vec2 + v.x = w.raw.nishita_density[0]; + v.y = w.raw.nishita_density[1]; + } } } - else if (link == "_hosekB") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.B.x; - v.y = armory.renderpath.HosekWilkie.data.B.y; - v.z = armory.renderpath.HosekWilkie.data.B.z; - } - } - else if (link == "_hosekC") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.C.x; - v.y = armory.renderpath.HosekWilkie.data.C.y; - v.z = armory.renderpath.HosekWilkie.data.C.z; - } - } - else if (link == "_hosekD") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.D.x; - v.y = armory.renderpath.HosekWilkie.data.D.y; - v.z = armory.renderpath.HosekWilkie.data.D.z; - } - } - else if (link == "_hosekE") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.E.x; - v.y = armory.renderpath.HosekWilkie.data.E.y; - v.z = armory.renderpath.HosekWilkie.data.E.z; - } - } - else if (link == "_hosekF") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.F.x; - v.y = armory.renderpath.HosekWilkie.data.F.y; - v.z = armory.renderpath.HosekWilkie.data.F.z; - } - } - else if (link == "_hosekG") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.G.x; - v.y = armory.renderpath.HosekWilkie.data.G.y; - v.z = armory.renderpath.HosekWilkie.data.G.z; - } - } - else if (link == "_hosekH") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.H.x; - v.y = armory.renderpath.HosekWilkie.data.H.y; - v.z = armory.renderpath.HosekWilkie.data.H.z; - } - } - else if (link == "_hosekI") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.I.x; - v.y = armory.renderpath.HosekWilkie.data.I.y; - v.z = armory.renderpath.HosekWilkie.data.I.z; - } - } - else if (link == "_hosekZ") { - if (armory.renderpath.HosekWilkie.data == null) { - armory.renderpath.HosekWilkie.recompute(Scene.active.world); - } - if (armory.renderpath.HosekWilkie.data != null) { - v = iron.object.Uniforms.helpVec; - v.x = armory.renderpath.HosekWilkie.data.Z.x; - v.y = armory.renderpath.HosekWilkie.data.Z.y; - v.z = armory.renderpath.HosekWilkie.data.Z.z; - } - } - #end - #if rp_voxelao - if (link == "_cameraPositionSnap") { - v = iron.object.Uniforms.helpVec; - var camera = iron.Scene.active.camera; - v.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz()); - var l = camera.lookWorld(); - var e = Main.voxelgiHalfExtents; - v.x += l.x * e * 0.9; - v.y += l.y * e * 0.9; - var f = Main.voxelgiVoxelSize * 8; // Snaps to 3 mip-maps range - v.set(Math.floor(v.x / f) * f, Math.floor(v.y / f) * f, Math.floor(v.z / f) * f); - } - #end return v; } public static function floatLink(object: Object, mat: MaterialData, link: String): Null { - #if rp_dynres - if (link == "_dynamicScale") { - return armory.renderpath.DynamicResolutionScale.dynamicScale; + switch (link) { + #if rp_dynres + case "_dynamicScale": { + return armory.renderpath.DynamicResolutionScale.dynamicScale; + } + #end + #if arm_debug + case "_debugFloat": { + return armory.trait.internal.DebugConsole.debugFloat; + } + #end + #if rp_voxelao + case "_voxelBlend": { // Blend current and last voxels + var freq = armory.renderpath.RenderPathCreator.voxelFreq; + return (armory.renderpath.RenderPathCreator.voxelFrame % freq) / freq; + } + #end } - #end - #if arm_debug - if (link == "_debugFloat") { - return armory.trait.internal.DebugConsole.debugFloat; - } - #end - #if rp_voxelao - if (link == "_voxelBlend") { // Blend current and last voxels - var freq = armory.renderpath.RenderPathCreator.voxelFreq; - return (armory.renderpath.RenderPathCreator.voxelFrame % freq) / freq; - } - #end return null; } } diff --git a/Sources/armory/renderpath/Inc.hx b/Sources/armory/renderpath/Inc.hx index 940732a3..0e37fdef 100644 --- a/Sources/armory/renderpath/Inc.hx +++ b/Sources/armory/renderpath/Inc.hx @@ -57,7 +57,11 @@ class Inc { break; if (LightObject.discardLightCulled(light)) continue; if (light.data.raw.type == "point") { - for(k in 0...light.tileOffsetX.length) { + if (!light.data.raw.cast_shadow) { + j += 4 * 6; + continue; + } + for(k in 0...6) { LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas @@ -108,10 +112,17 @@ class Inc { lastFrame = RenderPath.active.frame; #end // add new lights to the atlases + #if arm_debug + beginShadowsLogicProfile(); + // reset data on rejected lights + for (atlas in ShadowMapAtlas.shadowMapAtlases) { + atlas.rejectedLights = []; + } + #end for (light in iron.Scene.active.lights) { if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0 && light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) { - light.lightInAtlas = ShadowMapAtlas.addLight(light); + ShadowMapAtlas.addLight(light); } } // update point light data before rendering @@ -151,8 +162,8 @@ class Inc { // set the tile offset for this tile and every linked tile to this one var j = 0; tile.forEachTileLinked(function (lTile) { - tile.light.tileOffsetX[j] = (lTile.coordsX == 0) ? 0.0 : lTile.coordsX / atlas.sizew; - tile.light.tileOffsetY[j] = (lTile.coordsY == 0) ? 0.0 : lTile.coordsY / atlas.sizew; + tile.light.tileOffsetX[j] = lTile.coordsX / atlas.sizew; + tile.light.tileOffsetY[j] = lTile.coordsY / atlas.sizew; tile.light.tileScale[j] = lTile.size / atlas.sizew; j++; }); @@ -164,6 +175,9 @@ class Inc { var face = 0; var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type); + #if arm_debug + beginShadowsRenderProfile(); + #end tile.forEachTileLinked(function (lTile) { if (faces > 1) { #if arm_csm @@ -180,6 +194,9 @@ class Inc { path.drawMeshesStream("shadowmap"); }); + #if arm_debug + endShadowsRenderProfile(); + #end path.currentFace = -1; } @@ -202,6 +219,9 @@ class Inc { tile.freeTile(); } } + #if arm_debug + endShadowsLogicProfile(); + #end #end // rp_shadowmap } #else @@ -517,6 +537,25 @@ class Inc { return null; #end } + + #if arm_debug + public static var shadowsLogicTime = 0.0; + public static var shadowsRenderTime = 0.0; + static var startShadowsLogicTime = 0.0; + static var startShadowsRenderTime = 0.0; + static var callBackSetup = false; + static function setupEndFrameCallback() { + if (!callBackSetup) { + callBackSetup = true; + iron.App.endFrameCallbacks.push(endFrame); + } + } + static function beginShadowsLogicProfile() { setupEndFrameCallback(); startShadowsLogicTime = kha.Scheduler.realTime(); } + static function beginShadowsRenderProfile() { startShadowsRenderTime = kha.Scheduler.realTime(); } + static function endShadowsLogicProfile() { shadowsLogicTime += kha.Scheduler.realTime() - startShadowsLogicTime - shadowsRenderTime; } + static function endShadowsRenderProfile() { shadowsRenderTime += kha.Scheduler.realTime() - startShadowsRenderTime; } + public static function endFrame() { shadowsLogicTime = 0; shadowsRenderTime = 0; } + #end } #if arm_shadowmap_atlas @@ -534,12 +573,17 @@ class ShadowMapAtlas { public var activeTiles: Array = []; public var depth = 1; #if arm_shadowmap_atlas_lod - static var tileSizes: Array = []; - static var tileSizeFactor: Array = []; + var tileSizes: Array = []; + var tileSizeFactor: Array = []; #end public var updateRenderTarget = false; public static var shadowMapAtlases:Map = new Map(); // map a shadowmap atlas to their light type + #if arm_debug + public var lightType: String; + public var rejectedLights: Array = []; + #end + function new(light: LightObject) { var maxTileSize = shadowMapAtlasSize(light); @@ -549,8 +593,15 @@ class ShadowMapAtlas { this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type); #if arm_shadowmap_atlas_lod - if (tileSizes.length == 0) - computeTileSizes(maxTileSize, depth); + computeTileSizes(maxTileSize, depth); + #end + + #if arm_debug + #if arm_shadowmap_atlas_single_map + this.lightType = "any"; + #else + this.lightType = light.data.raw.type; + #end #end } @@ -560,11 +611,7 @@ class ShadowMapAtlas { * @param light of type LightObject to be added to an yatlas * @return if the light was added succesfully */ - public static function addLight(light: LightObject): Bool { - // check if light can be added based on culling - if (light.culledLight || light.shadowMapScale == 0.0) - return false; - + public static function addLight(light: LightObject) { var atlasName = shadowMapAtlasName(light.data.raw.type); var atlas = shadowMapAtlases.get(atlasName); if (atlas == null) { @@ -575,11 +622,21 @@ class ShadowMapAtlas { // find a free tile for this light var mainTile = ShadowMapTile.assignTiles(light, atlas, null); - if (mainTile == null) - return false; - // push main tile to active tiles + if (mainTile == null) { + #if arm_debug + if (!atlas.rejectedLights.contains(light)) + atlas.rejectedLights.push(light); + #end + return; + } + atlas.activeTiles.push(mainTile); - return true; + // notify the tile on light remove + light.tileNotifyOnRemove = mainTile.notifyOnLightRemove; + // notify atlas when this tile is freed + mainTile.notifyOnFree = atlas.freeActiveTile; + // "lock" light to make sure it's not eligible to be added again + light.lightInAtlas = true; } static inline function shadowMapAtlasSize(light:LightObject):Int { @@ -602,17 +659,17 @@ class ShadowMapAtlas { } #if arm_shadowmap_atlas_lod - static function computeTileSizes(maxTileSize: Int, depth: Int): Void { + function computeTileSizes(maxTileSize: Int, depth: Int): Void { // find the highest value based on the calculation done in the cluster code var base = LightObject.zToShadowMapScale(0, 16); var subdiv = base / depth; for(i in 0...depth){ - tileSizes.push(Std.int(maxTileSize / Math.pow(2, i))); - tileSizeFactor.push(base); + this.tileSizes.push(Std.int(maxTileSize / Math.pow(2, i))); + this.tileSizeFactor.push(base); base -= subdiv; } - tileSizes.push(0); - tileSizeFactor.push(0.0); + this.tileSizes.push(0); + this.tileSizeFactor.push(0.0); } #end @@ -658,7 +715,9 @@ class ShadowMapAtlas { public static inline function getMaxAtlasSize(type: String): Int { #if arm_shadowmap_atlas_single_map - #if (rp_shadowmap_atlas_max_size == 1024) + #if (rp_shadowmap_atlas_max_size == 512) + return 512; + #elseif (rp_shadowmap_atlas_max_size == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size == 2048) return 2048; @@ -685,7 +744,9 @@ class ShadowMapAtlas { #end } case "spot": { - #if (rp_shadowmap_atlas_max_size_spot == 1024) + #if (rp_shadowmap_atlas_max_size_spot == 512) + return 512; + #elseif (rp_shadowmap_atlas_max_size_spot == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size_spot == 2048) return 2048; @@ -698,7 +759,9 @@ class ShadowMapAtlas { #end } case "sun": { - #if (rp_shadowmap_atlas_max_size_sun == 1024) + #if (rp_shadowmap_atlas_max_size_sun == 512) + return 512; + #elseif (rp_shadowmap_atlas_max_size_sun == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size_sun == 2048) return 2048; @@ -711,7 +774,9 @@ class ShadowMapAtlas { #end } default: { - #if (rp_shadowmap_atlas_max_size == 1024) + #if (rp_shadowmap_atlas_max_size == 512) + return 512; + #elseif (rp_shadowmap_atlas_max_size == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size == 2048) return 2048; @@ -727,6 +792,10 @@ class ShadowMapAtlas { } #end } + + function freeActiveTile(tile: ShadowMapTile) { + activeTiles.remove(tile); + } } class ShadowMapTile { @@ -792,7 +861,6 @@ class ShadowMapTile { static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array { var tilesFound: Array = []; - var updateAtlas = false; while (tilesFound.length < tilesPerLightType) { findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound); @@ -946,6 +1014,11 @@ class ShadowMapTile { } } + public function notifyOnLightRemove() { + unlockLight = true; + freeTile(); + } + inline function lockTile(light: LightObject): Void { if (this.light != null) return; @@ -959,6 +1032,7 @@ class ShadowMapTile { } public var unlockLight: Bool = false; + public var notifyOnFree: ShadowMapTile -> Void; public function freeTile(): Void { // prevent duplicates @@ -984,6 +1058,11 @@ class ShadowMapTile { tempTile.linkedTile = null; tempTile = linkedTile; } + // notify atlas that this tile has been freed + if (notifyOnFree != null) { + notifyOnFree(this); + notifyOnFree = null; + } } public inline function forEachTileLinked(action: ShadowMapTile->Void): Void { diff --git a/Sources/armory/renderpath/Nishita.hx b/Sources/armory/renderpath/Nishita.hx new file mode 100644 index 00000000..177cb517 --- /dev/null +++ b/Sources/armory/renderpath/Nishita.hx @@ -0,0 +1,226 @@ +package armory.renderpath; + +import kha.FastFloat; +import kha.arrays.Float32Array; +import kha.graphics4.TextureFormat; +import kha.graphics4.Usage; + +import iron.data.WorldData; +import iron.math.Vec2; +import iron.math.Vec3; + +import armory.math.Helper; + +/** + Utility class to control the Nishita sky model. +**/ +class Nishita { + + public static var data: NishitaData = null; + + /** + Recomputes the nishita lookup table after the density settings changed. + Do not call this method on every frame (it's slow)! + **/ + public static function recompute(world: WorldData) { + if (world == null || world.raw.nishita_density == null) return; + if (data == null) data = new NishitaData(); + + var density = world.raw.nishita_density; + data.computeLUT(new Vec3(density[0], density[1], density[2])); + } + + /** Sets the sky's density parameters and calls `recompute()` afterwards. **/ + public static function setDensity(world: WorldData, densityAir: FastFloat, densityDust: FastFloat, densityOzone: FastFloat) { + if (world == null) return; + + if (world.raw.nishita_density == null) world.raw.nishita_density = new Float32Array(3); + var density = world.raw.nishita_density; + density[0] = Helper.clamp(densityAir, 0, 10); + density[1] = Helper.clamp(densityDust, 0, 10); + density[2] = Helper.clamp(densityOzone, 0, 10); + + recompute(world); + } +} + +/** + This class holds the precalculated result of the inner scattering integral + of the Nishita sky model. The outer integral is calculated in + [`armory/Shaders/std/sky.glsl`](https://github.com/armory3d/armory/blob/master/Shaders/std/sky.glsl). + + @see `armory.renderpath.Nishita` +**/ +class NishitaData { + + public var lut: kha.Image; + + /** + The amount of individual sample heights stored in the LUT (and the width + of the LUT image). + **/ + public static var lutHeightSteps = 128; + /** + The amount of individual sun angle steps stored in the LUT (and the + height of the LUT image). + **/ + public static var lutAngleSteps = 128; + + /** + Amount of steps for calculating the inner scattering integral. Heigher + values are more precise but take longer to compute. + **/ + public static var jSteps = 8; + + /** Radius of the atmosphere in meters. **/ + public static var radiusAtmo = 6420000; + /** + Radius of the planet in meters. The default value is the earth radius as + defined in Cycles. + **/ + public static var radiusPlanet = 6360000; + + /** Rayleigh scattering coefficient. **/ + public static var rayleighCoeff = new Vec3(5.5e-6, 13.0e-6, 22.4e-6); + /** Rayleigh scattering scale parameter. **/ + public static var rayleighScale = 8e3; + + /** Mie scattering coefficient. **/ + public static var mieCoeff = 2e-5; + /** Mie scattering scale parameter. **/ + public static var mieScale = 1.2e3; + + /** Ozone scattering coefficient. **/ + // The ozone absorption coefficients are taken from Cycles code. + // Because Cycles calculates 21 wavelengths, we use the coefficients + // which are closest to the RGB wavelengths (645nm, 510nm, 440nm). + // Precalculating values by simulating Blender's spec_to_xyz() function + // to include all 21 wavelengths gave unrealistic results. + public static var ozoneCoeff = new Vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914); + + public function new() {} + + /** Approximates the density of ozone for a given sample height. **/ + function getOzoneDensity(height: FastFloat): FastFloat { + // Values are taken from Cycles code + if (height < 10000.0 || height >= 40000.0) { + return 0.0; + } + if (height < 25000.0) { + return (height - 10000.0) / 15000.0; + } + return -((height - 40000.0) / 15000.0); + } + + /** + Ray-sphere intersection test that assumes the sphere is centered at the + origin. There is no intersection when result.x > result.y. Otherwise + this function returns the distances to the two intersection points, + which might be equal. + **/ + function raySphereIntersection(rayOrigin: Vec3, rayDirection: Vec3, sphereRadius: Int): Vec2 { + // Algorithm is described here: https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection + var a = rayDirection.dot(rayDirection); + var b = 2.0 * rayDirection.dot(rayOrigin); + var c = rayOrigin.dot(rayOrigin) - (sphereRadius * sphereRadius); + var d = (b * b) - 4.0 * a * c; + + // Ray does not intersect the sphere + if (d < 0.0) return new Vec2(1e5, -1e5); + + return new Vec2( + (-b - Math.sqrt(d)) / (2.0 * a), + (-b + Math.sqrt(d)) / (2.0 * a) + ); + } + + /** + Computes the LUT texture for the given density values. + @param density 3D vector of air density, dust density, ozone density + **/ + public function computeLUT(density: Vec3) { + var imageData = new haxe.io.Float32Array(lutHeightSteps * lutAngleSteps * 4); + + for (x in 0...lutHeightSteps) { + var height = (x / (lutHeightSteps - 1)); + + // Use quadratic height for better horizon precision + height *= height; + height *= radiusAtmo; // Denormalize + + for (y in 0...lutAngleSteps) { + var sunTheta = y / (lutAngleSteps - 1) * 2 - 1; + + // Improve horizon precision + // See https://sebh.github.io/publications/egsr2020.pdf (5.3) + sunTheta = Helper.sign(sunTheta) * sunTheta * sunTheta; + sunTheta = sunTheta * Math.PI / 2 + Math.PI / 2; // Denormalize + + var jODepth = sampleSecondaryRay(height, sunTheta, density); + + var pixelIndex = (x + y * lutHeightSteps) * 4; + imageData[pixelIndex + 0] = jODepth.x; + imageData[pixelIndex + 1] = jODepth.y; + imageData[pixelIndex + 2] = jODepth.z; + imageData[pixelIndex + 3] = 1.0; // Unused + } + } + + lut = kha.Image.fromBytes(imageData.view.buffer, lutHeightSteps, lutAngleSteps, TextureFormat.RGBA128, Usage.StaticUsage); + } + + /** + Calculates the integral for the secondary ray. + **/ + public function sampleSecondaryRay(height: FastFloat, sunTheta: FastFloat, density: Vec3): Vec3 { + // Reconstruct values from the shader + var iPos = new Vec3(0, 0, height + radiusPlanet); + var pSun = new Vec3(0.0, Math.sin(sunTheta), Math.cos(sunTheta)).normalize(); + + var jTime: FastFloat = 0.0; + var jStepSize: FastFloat = raySphereIntersection(iPos, pSun, radiusAtmo).y / jSteps; + + // Optical depth accumulators for the secondary ray (Rayleigh, Mie, ozone) + var jODepth = new Vec3(); + + for (i in 0...jSteps) { + + // Calculate the secondary ray sample position and height + var jPos = iPos.clone().add(pSun.clone().mult(jTime + jStepSize * 0.5)); + var jHeight = jPos.length() - radiusPlanet; + + // Accumulate optical depth + var optDepthRayleigh = Math.exp(-jHeight / rayleighScale) * density.x; + var optDepthMie = Math.exp(-jHeight / mieScale) * density.y; + var optDepthOzone = getOzoneDensity(jHeight) * density.z; + jODepth.addf(optDepthRayleigh, optDepthMie, optDepthOzone); + + jTime += jStepSize; + } + + jODepth.mult(jStepSize); + + // Precalculate a part of the secondary attenuation. + // For one variable (e.g. x) in the vector, the formula is as follows: + // + // attn.x = exp(-(coeffX * (firstOpticalDepth.x + secondOpticalDepth.x))) + // + // We can split that up via: + // + // attn.x = exp(-(coeffX * firstOpticalDepth.x + coeffX * secondOpticalDepth.x)) + // = exp(-(coeffX * firstOpticalDepth.x)) * exp(-(coeffX * secondOpticalDepth.x)) + // + // The first factor of the resulting multiplication is calculated in the + // shader, but we can already precalculate the second one. As a side + // effect this keeps the range of the LUT values small because we don't + // store the optical depth but the attenuation. + var jAttenuation = new Vec3(); + var mie = mieCoeff * jODepth.y; + jAttenuation.addf(mie, mie, mie); + jAttenuation.add(rayleighCoeff.clone().mult(jODepth.x)); + jAttenuation.add(ozoneCoeff.clone().mult(jODepth.z)); + jAttenuation.exp(jAttenuation.mult(-1)); + + return jAttenuation; + } +} diff --git a/Sources/armory/system/InputMap.hx b/Sources/armory/system/InputMap.hx index 79b998e1..2bd2b493 100644 --- a/Sources/armory/system/InputMap.hx +++ b/Sources/armory/system/InputMap.hx @@ -4,347 +4,223 @@ import kha.FastFloat; import iron.system.Input; class InputMap { - var commands = new Map>>(); + + static var inputMaps = new Map(); + + public var keys(default, null) = new Array(); + public var lastKeyPressed(default, null) = ""; public function new() {} - public function addKeyboard(config: String) { - var command = new KeyboardCommand(); - return addCustomCommand(command, config); + public static function getInputMap(inputMap: String): Null { + if (inputMaps.exists(inputMap)) { + return inputMaps[inputMap]; + } + + return null; } - public function addGamepad(config: String) { - var command = new GamepadCommand(); - return addCustomCommand(command, config); + public static function addInputMap(inputMap: String): InputMap { + return inputMaps[inputMap] = new InputMap(); } - public function addCustomCommand(command: InputCommand, config: String) { - if (commands[config] == null) commands[config] = new Array(); - commands[config].push(command); - return command; - } -} - -class ActionMap extends InputMap { - - public inline function started(config: String) { - var started = false; - - for (c in commands[config]) { - if (c.started()) { - started = true; - break; + public static function getInputMapKey(inputMap: String, key: String): Null { + if (inputMaps.exists(inputMap)) { + for (k in inputMaps[inputMap].keys) { + if (k.key == key) { + return k; + } } } - return started; + return null; } - public inline function released(config: String) { - var released = false; + public static function removeInputMapKey(inputMap: String, key: String): Bool { + if (inputMaps.exists(inputMap)) { + var i = inputMaps[inputMap]; - for (c in commands[config]) { - if (c.released()) { - released = true; - break; + for (k in i.keys) { + if (k.key == key) { + return i.removeKey(k); + } } } - return released; + return false; } -} -class AxisMap extends InputMap { - var scale: FastFloat = 1.0; + public function addKeyboard(key: String, scale: FastFloat = 1.0): InputMapKey { + return addKey(new KeyboardKey(key, scale)); + } - public inline function getAxis(config: String) { - var axis = 0.0; + public function addMouse(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey { + return addKey(new MouseKey(key, scale, deadzone)); + } - for (c in commands[config]) { - var tempAxis = c.getAxis(); + public function addGamepad(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey { + return addKey(new GamepadKey(key, scale, deadzone)); + } - if (tempAxis != 0.0 && tempAxis != axis) { - axis += tempAxis; - scale = c.getScale(); - } + public function addKey(key: InputMapKey): InputMapKey { + keys.push(key); + return key; + } + + public function removeKey(key: InputMapKey): Bool { + return keys.remove(key); + } + + public function value(): FastFloat { + var v = 0.0; + + for (k in keys) { + v += k.value(); } - return axis; - } - - public inline function getScale() { - return scale; - } -} - -class InputCommand { - var keys = new Array(); - var modifiers = new Array(); - var displacementKeys = new Array(); - var displacementModifiers = new Array(); - var deadzone: FastFloat = 0.0; - var scale: FastFloat = 1.0; - - public function new() {} - - public function setKeys(keys: Array) { - return this.keys = keys; - } - - public function setMods(modifiers: Array) { - return this.modifiers = modifiers; - } - - public function setDisplacementKeys(keys: Array) { - return displacementKeys = keys; - } - - public function setDisplacementMods(modifiers: Array) { - return displacementModifiers = modifiers; - } - - public function setDeadzone(deadzone: FastFloat) { - return this.deadzone = deadzone; - } - - public function setScale(scale: FastFloat) { - return this.scale = scale; - } - - public function getScale() { - return scale; + return v; } public function started() { + for (k in keys) { + if (k.started()) { + lastKeyPressed = k.key; + return true; + } + } + return false; } public function released() { + for (k in keys) { + if (k.released()) { + lastKeyPressed = k.key; + return true; + } + } + + return false; + } +} + +class InputMapKey { + + public var key: String; + public var scale: FastFloat; + public var deadzone: FastFloat; + + public function new(key: String, scale = 1.0, deadzone = 0.0) { + this.key = key.toLowerCase(); + this.scale = scale; + this.deadzone = deadzone; + } + + public function started(): Bool { return false; } - public function getAxis(): FastFloat { + public function released(): Bool { + return false; + } + + public function value(): FastFloat { return 0.0; } -} -class KeyboardCommand extends InputCommand { - var keyboard = Input.getKeyboard(); - var mouse = Input.getMouse(); + public function setIndex(index: Int) {} - public inline override function started() { - for (k in keys) { - if (keyboard.started(k)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; - } + function evalDeadzone(value: FastFloat): FastFloat { + var v = 0.0; - for (m in displacementModifiers) { - if (!mouse.down(m)) return false; - } + if (value > deadzone) { + v = value - deadzone; - return true; - } + } else if (value < -deadzone) { + v = value + deadzone; } - for (k in displacementKeys) { - if (mouse.started(k)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; - } - - for (m in displacementModifiers) { - if (!mouse.down(m)) return false; - } - - return true; - } - } - - return false; + return v * scale; } - public inline override function released() { - for (k in keys) { - if (keyboard.released(k)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; - } + function evalPressure(value: FastFloat): FastFloat { + var v = value - deadzone; - for (m in displacementModifiers) { - if (!mouse.down(m)) return false; - } + if (v > 0.0) { + v /= (1.0 - deadzone); - return true; - } + } else { + v = 0.0; } - - for (k in displacementKeys) { - if (mouse.released(k)) { - for (m in modifiers) { - if (!keyboard.down(m)) return false; - } - - for (m in displacementModifiers) { - if (!mouse.down(m)) return false; - } - - return true; - } - } - - return false; - } - - public inline override function getAxis() { - var axis = 0.0; - var movementX = mouse.movementX; - var movementY = mouse.movementY; - var wheelDelta = mouse.wheelDelta; - - for (k in keys) { - if (keyboard.down(k)) { - axis++; - break; - } - } - - for (m in modifiers) { - if (keyboard.down(m)) { - axis --; - break; - } - } - - for (k in displacementKeys) { - switch (k) { - case "moved x": if (movementX > deadzone) axis++; - case "moved y": if (movementY > deadzone) axis--; - case "wheel": if (wheelDelta < -deadzone) axis++; - case "movement x": if (movementX > deadzone) return movementX - deadzone; - case "movement y": if (movementY > deadzone) return movementY - deadzone; - default: { - if (mouse.down(k)) { - axis ++; - break; - } - } - } - } - - for (m in displacementModifiers) { - switch (m) { - case "moved x": if (movementX < -deadzone) axis--; - case "moved y": if (movementY < -deadzone) axis++; - case "wheel": if (wheelDelta > deadzone) axis--; - case "movement x": if (movementX < -deadzone) return movementX + deadzone; - case "movement y": if (movementY < -deadzone) return movementY + deadzone; - default: { - if (mouse.down(m)) { - axis --; - break; - } - } - } - } - - return axis > 1 ? 1 : axis < -1 ? -1 : axis; + + return v; } } -class GamepadCommand extends InputCommand { - var gamepad = Input.getGamepad(0); +class KeyboardKey extends InputMapKey { + + var kb = Input.getKeyboard(); public inline override function started() { - for (k in keys) { - if (gamepad.started(k)) { - for (m in modifiers) { - if (gamepad.down(m) < deadzone) return false; - } - - return true; - } - } - - return false; + return kb.started(key); } public inline override function released() { - for (k in keys) { - if (gamepad.released(k)) { - for (m in modifiers) { - if (gamepad.down(m) < deadzone) return false; - } - - return true; - } - } - - return false; + return kb.released(key); } - public inline override function getAxis() { - var axis = 0.0; - var rsMovementX = gamepad.rightStick.movementX; - var rsMovementY = gamepad.rightStick.movementY; - var lsMovementX = gamepad.leftStick.movementX; - var lsMovementY = gamepad.leftStick.movementY; - var rtPressure = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0; - var ltPressure = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0; - - for (k in keys) { - switch(k) { - case "rtPressure": axis += rtPressure; - case "ltPressure": axis += ltPressure; - default: { - if (gamepad.down(k) > deadzone) { - axis++; - break; - } - } - } - } - - for (m in modifiers) { - switch (m) { - case "rtPressure": axis -= rtPressure; - case "ltPressure": axis -= ltPressure; - default: { - if (gamepad.down(m) > deadzone) { - axis--; - break; - } - } - } - } - - for (k in displacementKeys) { - switch(k) { - case "rs moved x": if (rsMovementX > deadzone) axis++; - case "rs moved y": if (rsMovementY > deadzone) axis++; - case "ls moved x": if (lsMovementX > deadzone) axis++; - case "ls moved y": if (lsMovementY > deadzone) axis++; - case "rs movement x": if (rsMovementX > deadzone) return rsMovementX - deadzone; - case "rs movement y": if (rsMovementY > deadzone) return rsMovementY - deadzone; - case "ls movement x": if (lsMovementX > deadzone) return lsMovementX - deadzone; - case "ls movement y": if (lsMovementY > deadzone) return lsMovementY - deadzone; - } - } - - for (m in displacementModifiers) { - switch (m) { - case "rs moved x": if (rsMovementX < -deadzone) axis--; - case "rs moved y": if (rsMovementY < -deadzone) axis--; - case "ls moved x": if (lsMovementX < -deadzone) axis--; - case "ls moved y": if (lsMovementY < -deadzone) axis--; - case "rs movement x": if (rsMovementX < -deadzone) return rsMovementX + deadzone; - case "rs movement y": if (rsMovementY < -deadzone) return rsMovementY + deadzone; - case "ls movement x": if (lsMovementX < -deadzone) return lsMovementX + deadzone; - case "ls movement y": if (lsMovementY < -deadzone) return lsMovementY + deadzone; - - } - } - - return axis > 1 ? 1 : axis < -1 ? -1 : axis; + public inline override function value(): FastFloat { + return kb.down(key) ? scale : 0.0; } -} \ No newline at end of file +} + +class MouseKey extends InputMapKey { + + var m = Input.getMouse(); + + public inline override function started() { + return m.started(key); + } + + public inline override function released() { + return m.released(key); + } + + public override function value(): FastFloat { + return switch (key) { + case "movement x": evalDeadzone(m.movementX); + case "movement y": evalDeadzone(m.movementY); + case "wheel": evalDeadzone(m.wheelDelta); + default: m.down(key) ? scale : 0.0; + } + } +} + +class GamepadKey extends InputMapKey { + + var g = Input.getGamepad(); + + public inline override function started() { + return g.started(key); + } + + public inline override function released() { + return g.released(key); + } + + public override function value(): FastFloat { + return switch(key) { + case "ls movement x": evalDeadzone(g.leftStick.movementX); + case "ls movement y": evalDeadzone(g.leftStick.movementY); + case "rs movement x": evalDeadzone(g.rightStick.movementX); + case "rs movement y": evalDeadzone(g.rightStick.movementY); + case "lt pressure": evalDeadzone(evalPressure(g.down("l2"))); + case "rt pressure": evalDeadzone(evalPressure(g.down("r2"))); + default: evalDeadzone(g.down(key)); + } + } + + public override function setIndex(index: Int) { + g = Input.getGamepad(index); + } +} diff --git a/Sources/armory/system/Logic.hx b/Sources/armory/system/Logic.hx index f7e4c442..e71390fa 100644 --- a/Sources/armory/system/Logic.hx +++ b/Sources/armory/system/Logic.hx @@ -94,6 +94,10 @@ class Logic { var v = createClassInstance(node.type, [tree]); nodeMap.set(name, v); + #if arm_patch + tree.nodes.set(name, v); + #end + // Properties for (i in 0...5) { for (b in node.buttons) { @@ -103,9 +107,13 @@ class Logic { } } + @:privateAccess v.preallocInputs(node.inputs.length); + @:privateAccess v.preallocOutputs(node.outputs.length); + // Create inputs var inp_node: armory.logicnode.LogicNode = null; var inp_from = 0; + var from_type: String; for (i in 0...node.inputs.length) { var inp = node.inputs[i]; // Is linked - find node @@ -117,6 +125,7 @@ class Logic { for (i in 0...n.outputs.length) { if (n.outputs[i] == socket) { inp_from = i; + from_type = socket.type; break; } } @@ -124,27 +133,33 @@ class Logic { else { // Not linked - create node with default values inp_node = build_default_node(inp); inp_from = 0; + from_type = inp.type; } // Add input - v.addInput(inp_node, inp_from); + var link = LogicNode.addLink(inp_node, v, inp_from, i); + #if arm_patch + link.fromType = from_type; + link.toType = inp.type; + link.toValue = getSocketDefaultValue(inp); + #end } // Create outputs - for (out in node.outputs) { - var outNodes: Array = []; + for (i in 0...node.outputs.length) { + var out = node.outputs[i]; var ls = getOutputLinks(out); - if (ls != null && ls.length > 0) { - for (l in ls) { - var n = getNode(l.to_id); - var out_name = build_node(n); - outNodes.push(nodeMap.get(out_name)); - } + + // Linked outputs are already handled after iterating over inputs + // above, so only unconnected outputs are handled here + if (ls == null || ls.length == 0) { + var link = LogicNode.addLink(v, build_default_node(out), i, 0); + + #if arm_patch + link.fromType = out.type; + link.toType = out.type; + link.toValue = getSocketDefaultValue(out); + #end } - else { // Not linked - create node with default values - outNodes.push(build_default_node(out)); - } - // Add outputs - v.addOutputs(outNodes); } return name; @@ -212,6 +227,22 @@ class Logic { return v; } + static function getSocketDefaultValue(socket: TNodeSocket): Any { + + var v: armory.logicnode.LogicNode = null; + + return switch (socket.type) { + case "OBJECT" | "VALUE" | "INT" | "BOOLEAN" | "STRING": + socket.default_value; + case "VECTOR" | "RGB": + socket.default_value == null ? [0, 0, 0] : [socket.default_value[0], socket.default_value[1], socket.default_value[2]]; + case "RGBA": + socket.default_value == null ? [0, 0, 0, 1] : [socket.default_value[0], socket.default_value[1], socket.default_value[2], socket.default_value[3]]; + default: + null; + } + } + static function createClassInstance(className: String, args: Array): Dynamic { var cname = Type.resolveClass(packageName + "." + className); if (cname == null) return null; diff --git a/Sources/armory/trait/ArcBall.hx b/Sources/armory/trait/ArcBall.hx index ddf800f7..7f4edad5 100644 --- a/Sources/armory/trait/ArcBall.hx +++ b/Sources/armory/trait/ArcBall.hx @@ -6,6 +6,9 @@ import iron.math.Vec4; class ArcBall extends Trait { + @prop + public var axis = new Vec4(0, 0, 1); + public function new() { super(); @@ -17,10 +20,8 @@ class ArcBall extends Trait { var mouse = Input.getMouse(); if (mouse.down()) { - object.transform.rotate(new Vec4(0, 0, 1), -mouse.movementX / 100); - object.transform.buildMatrix(); + object.transform.rotate(axis, -mouse.movementX / 100); object.transform.rotate(object.transform.world.right(), -mouse.movementY / 100); - object.transform.buildMatrix(); } } } diff --git a/Sources/armory/trait/FollowCamera.hx b/Sources/armory/trait/FollowCamera.hx index 6cfdf84e..99df3c70 100644 --- a/Sources/armory/trait/FollowCamera.hx +++ b/Sources/armory/trait/FollowCamera.hx @@ -33,7 +33,7 @@ class FollowCamera extends iron.Trait { trace("FollowCamera error, unable to set target object"); } - if (Std.is(object, iron.object.CameraObject)) { + if (Std.isOfType(object, iron.object.CameraObject)) { disabled = true; trace("FollowCamera error, this trait should not be placed directly on a camera objet. It should be placed on another object such as an Empty. The camera should be placed as a child to the Empty object with offset, creating a camera boom."); } diff --git a/Sources/armory/trait/PhysicsDrag.hx b/Sources/armory/trait/PhysicsDrag.hx index bef674b5..111a4403 100755 --- a/Sources/armory/trait/PhysicsDrag.hx +++ b/Sources/armory/trait/PhysicsDrag.hx @@ -2,6 +2,7 @@ package armory.trait; import iron.Trait; import iron.system.Input; +import iron.math.Vec3; import iron.math.Vec4; import iron.math.Mat4; import iron.math.RayCaster; @@ -14,6 +15,11 @@ class PhysicsDrag extends Trait { public function new() { super(); } #else + @prop public var linearLowerLimit = new Vec3(0,0,0); + @prop public var linearUpperLimit = new Vec3(0,0,0); + @prop public var angularLowerLimit = new Vec3(-10,-10,-10); + @prop public var angularUpperLimit = new Vec3(10,10,10); + var pickConstraint: bullet.Bt.Generic6DofConstraint = null; var pickDist: Float; var pickedBody: RigidBody = null; @@ -56,10 +62,10 @@ class PhysicsDrag extends Trait { tr.setOrigin(localPivot); pickConstraint = new bullet.Bt.Generic6DofConstraint(b.body, tr, false); - pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(0, 0, 0)); - pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(0, 0, 0)); - pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(-10, -10, -10)); - pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(10, 10, 10)); + pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(linearLowerLimit.x, linearLowerLimit.y, linearLowerLimit.z)); + pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(linearUpperLimit.x, linearUpperLimit.y, linearUpperLimit.z)); + pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(angularLowerLimit.x, angularLowerLimit.y, angularLowerLimit.z)); + pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(angularUpperLimit.x, angularUpperLimit.y, angularUpperLimit.z)); physics.world.addConstraint(pickConstraint, false); /*pickConstraint.setParam(4, 0.8, 0); diff --git a/Sources/armory/trait/internal/Bridge.hx b/Sources/armory/trait/internal/Bridge.hx index 1ce35562..632fad8f 100644 --- a/Sources/armory/trait/internal/Bridge.hx +++ b/Sources/armory/trait/internal/Bridge.hx @@ -11,6 +11,7 @@ class Bridge { public static var Input = iron.system.Input; public static var Object = iron.object.Object; public static var Data = iron.data.Data; + public static var Vec4 = iron.math.Vec4; public static function log(s: String) { trace(s); }; } diff --git a/Sources/armory/trait/internal/CanvasScript.hx b/Sources/armory/trait/internal/CanvasScript.hx index ba8415e6..92c585e1 100644 --- a/Sources/armory/trait/internal/CanvasScript.hx +++ b/Sources/armory/trait/internal/CanvasScript.hx @@ -3,7 +3,7 @@ package armory.trait.internal; import iron.Trait; #if arm_ui import zui.Zui; -import zui.Canvas; +import armory.ui.Canvas; #end class CanvasScript extends Trait { @@ -29,7 +29,7 @@ class CanvasScript extends Trait { iron.data.Data.getBlob(canvasName + ".json", function(blob: kha.Blob) { iron.data.Data.getBlob("_themes.json", function(tBlob: kha.Blob) { - if (tBlob.get_length() != 0) { + if (@:privateAccess tBlob.get_length() != 0) { Canvas.themes = haxe.Json.parse(tBlob.toString()); } else { @@ -37,23 +37,30 @@ class CanvasScript extends Trait { } if (Canvas.themes.length == 0) { - Canvas.themes.push(zui.Themes.light); + Canvas.themes.push(armory.ui.Themes.light); } - iron.data.Data.getFont(font, function(f: kha.Font) { + iron.data.Data.getFont(font, function(defaultFont: kha.Font) { var c: TCanvas = haxe.Json.parse(blob.toString()); if (c.theme == null) c.theme = Canvas.themes[0].NAME; - cui = new Zui({font: f, theme: Canvas.getTheme(c.theme)}); + cui = new Zui({font: defaultFont, theme: Canvas.getTheme(c.theme)}); if (c.assets == null || c.assets.length == 0) canvas = c; else { // Load canvas assets var loaded = 0; for (asset in c.assets) { var file = asset.name; - iron.data.Data.getImage(file, function(image: kha.Image) { - Canvas.assetMap.set(asset.id, image); - if (++loaded >= c.assets.length) canvas = c; - }); + if (Canvas.isFontAsset(file)) { + iron.data.Data.getFont(file, function(f: kha.Font) { + Canvas.assetMap.set(asset.id, f); + if (++loaded >= c.assets.length) canvas = c; + }); + } else { + iron.data.Data.getImage(file, function(image: kha.Image) { + Canvas.assetMap.set(asset.id, image); + if (++loaded >= c.assets.length) canvas = c; + }); + } } } }); @@ -62,7 +69,7 @@ class CanvasScript extends Trait { notifyOnRender2D(function(g: kha.graphics2.Graphics) { if (canvas == null) return; - + setCanvasDimensions(kha.System.windowWidth(), kha.System.windowHeight()); var events = Canvas.draw(cui, canvas, g); @@ -121,7 +128,7 @@ class CanvasScript extends Trait { public function setCanvasVisibility(visible: Bool){ for (e in canvas.elements) e.visible = visible; } - + /** * Set dimensions of canvas * @param x Width @@ -140,7 +147,7 @@ class CanvasScript extends Trait { } // Contains data - @:access(zui.Canvas) + @:access(armory.ui.Canvas) @:access(zui.Handle) public function getHandle(name: String): Handle { // Consider this a temporary solution diff --git a/Sources/armory/trait/internal/DebugConsole.hx b/Sources/armory/trait/internal/DebugConsole.hx index 03096bb3..cf7da8cf 100755 --- a/Sources/armory/trait/internal/DebugConsole.hx +++ b/Sources/armory/trait/internal/DebugConsole.hx @@ -8,6 +8,10 @@ import iron.object.MeshObject; import zui.Zui; import zui.Id; using armory.object.TransformExtension; +#if arm_shadowmap_atlas +import armory.renderpath.Inc.ShadowMapTile; +import armory.renderpath.Inc.ShadowMapAtlas; +#end #end #if arm_debug @@ -61,6 +65,15 @@ class DebugConsole extends Trait { var shortcut_scale_in = kha.input.KeyCode.OpenBracket; var shortcut_scale_out = kha.input.KeyCode.CloseBracket; + #if arm_shadowmap_atlas + var lightColorMap: Map = new Map(); + var lightColorMapCount = 0; + var smaLogicTime = 0.0; + var smaLogicTimeAvg = 0.0; + var smaRenderTime = 0.0; + var smaRenderTimeAvg = 0.0; + #end + public function new(scaleFactor = 1.0, scaleDebugConsole = 1.0, positionDebugConsole = 2, visibleDebugConsole = 1, keyCodeVisible = kha.input.KeyCode.Tilde, keyCodeScaleIn = kha.input.KeyCode.OpenBracket, keyCodeScaleOut = kha.input.KeyCode.CloseBracket) { super(); @@ -423,6 +436,7 @@ class DebugConsole extends Trait { light.data.raw.strength = ui.slider(lightHandle, "Strength", 0.0, 5.0, true) * 10; #if arm_shadowmap_atlas ui.text("status: " + (light.culledLight ? "culled" : "rendered")); + ui.text("shadow map size: " + light.data.raw.shadowmap_size); #end } else if (Std.is(selectedObject, iron.object.CameraObject)) { @@ -473,6 +487,16 @@ class DebugConsole extends Trait { ui.text("Physics"); ui.text(Math.round(physTimeAvg * 10000) / 10 + " ms", Align.Right); + #if arm_shadowmap_atlas + ui.row(lrow); + ui.text("Shadow Map Atlas (Logic)"); + ui.text(Math.round(smaLogicTimeAvg * 10000) / 10 + " ms", Align.Right); + + ui.row(lrow); + ui.text("Shadow Map Atlas (Render)"); + ui.text(Math.round(smaRenderTimeAvg * 10000) / 10 + " ms", Align.Right); + #end + ui.unindent(); } @@ -520,6 +544,182 @@ class DebugConsole extends Trait { ui.unindent(); } + #if arm_shadowmap_atlas + if (ui.panel(Id.handle({selected: false}), "Shadow Map Atlases")) { + inline function highLightNext(color: kha.Color = null) { + ui.g.color = color != null ? color : -13882324; + ui.g.fillRect(ui._x, ui._y, ui._windowW, ui.ELEMENT_H()); + ui.g.color = 0xffffffff; + } + + inline function drawScale(text: String, y: Float, fromX: Float, toX: Float, bottom = false) { + var _off = bottom ? -4 : 4; + ui.g.drawLine(fromX, y, toX, y); + ui.g.drawLine(fromX, y, fromX, y + _off); + ui.g.drawLine(toX, y, toX, y + _off); + + var _w = ui._w; + ui._w = Std.int(Math.abs(toX - fromX)); + ui.text(text, Align.Center); + ui._w = _w; + } + + /** + * create a kha Color from HSV (Hue, Saturation, Value) + * @param h expected Hue from [0, 1]. + * @param s expected Saturation from [0, 1]. + * @param v expected Value from [0, 1]. + * @return kha.Color + */ + function colorFromHSV(h: Float, s: Float, v: Float): kha.Color { + // https://stackoverflow.com/a/17243070 + var r = 0.0; var g = 0.0; var b = 0.0; + + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: { r = v; g = t; b = p; } + case 1: { r = q; g = v; b = p; } + case 2: { r = p; g = v; b = t; } + case 3: { r = p; g = q; b = v; } + case 4: { r = t; g = p; b = v; } + case 5: { r = v; g = p; b = q; } + } + + return kha.Color.fromFloats(r, g, b); + } + + function drawTiles(tile: ShadowMapTile, atlas: ShadowMapAtlas, atlasVisualSize: Float) { + var color: Null = kha.Color.fromFloats(0.1, 0.1, 0.1); + var borderColor = color; + var tileScale = (tile.size / atlas.sizew) * atlasVisualSize; //* 0.95; + var x = (tile.coordsX / atlas.sizew) * atlasVisualSize; + var y = (tile.coordsY / atlas.sizew) * atlasVisualSize; + + if (tile.light != null) { + color = lightColorMap.get(tile.light.name); + if (color == null) { + color = colorFromHSV(Math.random(), 0.7, Math.random() * 0.5 + 0.5); + + lightColorMap.set(tile.light.name, color); + lightColorMapCount++; + } + ui.fill(x + tileScale * 0.019, y + tileScale * 0.03, tileScale * 0.96, tileScale * 0.96, color); + } + ui.rect(x, y, tileScale, tileScale, borderColor); + + #if arm_shadowmap_atlas_lod + // draw children tiles + for (t in tile.tiles) + drawTiles(t, atlas, atlasVisualSize); + #end + } + + ui.indent(false); + ui.text("Constants:"); + highLightNext(); + ui.text('Tiles Used Per Point Light: ${ ShadowMapTile.tilesLightType("point") }'); + ui.text('Tiles Used Per Spot Light: ${ ShadowMapTile.tilesLightType("spot") }'); + highLightNext(); + ui.text('Tiles Used For Sun: ${ ShadowMapTile.tilesLightType("sun") }'); + ui.unindent(false); + + ui.indent(false); + var i = 0; + for (atlas in ShadowMapAtlas.shadowMapAtlases) { + if (ui.panel(Id.handle({selected: false}).nest(i), atlas.target )) { + ui.indent(false); + // Draw visual representation of the atlas + var atlasVisualSize = ui._windowW * 0.92; + + drawScale('${atlas.sizew}px', ui._y + ui.ELEMENT_H() * 0.9, ui._x, ui._x + atlasVisualSize); + + // reset light color map when lights are removed + if (lightColorMapCount > iron.Scene.active.lights.length) { + lightColorMap = new Map(); + lightColorMapCount = 0; + } + + for (tile in atlas.tiles) + drawTiles(tile, atlas, atlasVisualSize); + // set vertical space for atlas visual representation + ui._y += atlasVisualSize + 3; + + var tilesRow = atlas.currTileOffset == 0 ? 1 : atlas.currTileOffset; + var tileScale = atlasVisualSize / tilesRow; + drawScale('${atlas.baseTileSizeConst}px', ui._y, ui._x, ui._x + tileScale, true); + + // general atlas information + highLightNext(); + ui.text('Max Atlas Size: ${atlas.maxAtlasSizeConst}, ${atlas.maxAtlasSizeConst} px'); + highLightNext(); + + // show detailed information per light + if (ui.panel(Id.handle({selected: false}).nest(i).nest(0), "Lights in Atlas")) { + ui.indent(false); + var j = 1; + for (tile in atlas.activeTiles) { + var textCol = ui.t.TEXT_COL; + var lightCol = lightColorMap.get(tile.light.name); + if (lightCol != null) + ui.t.TEXT_COL = lightCol; + #if arm_shadowmap_atlas_lod + if (ui.panel(Id.handle({selected: false}).nest(i).nest(j), tile.light.name)) { + ui.t.TEXT_COL = textCol; + ui.indent(false); + ui.text('Shadow Map Size: ${tile.size}, ${tile.size} px'); + ui.unindent(false); + } + #else + ui.indent(false); + ui.text(tile.light.name); + ui.unindent(false); + #end + ui.t.TEXT_COL = textCol; + j++; + } + ui.unindent(false); + } + + // show unused tiles statistics + #if arm_shadowmap_atlas_lod + // WIP + #else + var unusedTiles = atlas.tiles.length; + #if arm_shadowmap_atlas_single_map + for (tile in atlas.activeTiles) + unusedTiles -= ShadowMapTile.tilesLightType(tile.light.data.raw.type); + #else + unusedTiles -= atlas.activeTiles.length * ShadowMapTile.tilesLightType(atlas.lightType); + #end + ui.text('Unused tiles: ${unusedTiles}'); + #end + + var rejectedLightsNames = ""; + if (atlas.rejectedLights.length > 0) { + for (l in atlas.rejectedLights) + rejectedLightsNames += l.name + ", "; + rejectedLightsNames = rejectedLightsNames.substr(0, rejectedLightsNames.length - 2); + highLightNext(kha.Color.fromFloats(0.447, 0.247, 0.188)); + ui.text('Not enough space in atlas for ${atlas.rejectedLights.length} light${atlas.rejectedLights.length > 1 ? "s" : ""}:'); + ui.indent(); + ui.text(${rejectedLightsNames}); + ui.unindent(false); + } + + ui.unindent(false); + } + i++; + } + ui.unindent(false); + ui.unindent(false); + } + #end + if (ui.panel(Id.handle({selected: false}), "Render Targets")) { ui.indent(); #if (kha_opengl || kha_webgl) @@ -682,6 +882,14 @@ class DebugConsole extends Trait { animTimeAvg = animTime / frames; physTimeAvg = physTime / frames; + #if arm_shadowmap_atlas + smaLogicTimeAvg = smaLogicTime / frames; + smaLogicTime = 0; + + smaRenderTimeAvg = smaRenderTime / frames; + smaRenderTime = 0; + #end + totalTime = 0; renderPathTime = 0; updateTime = 0; @@ -706,6 +914,10 @@ class DebugConsole extends Trait { #if arm_physics physTime += armory.trait.physics.PhysicsWorld.physTime; #end + #if arm_shadowmap_atlas + smaLogicTime += armory.renderpath.Inc.shadowsLogicTime; + smaRenderTime += armory.renderpath.Inc.shadowsRenderTime; + #end } static function roundfp(f: Float, precision = 2): Float { diff --git a/Sources/armory/trait/internal/LivePatch.hx b/Sources/armory/trait/internal/LivePatch.hx index 220d42ba..743f66be 100644 --- a/Sources/armory/trait/internal/LivePatch.hx +++ b/Sources/armory/trait/internal/LivePatch.hx @@ -1,8 +1,17 @@ package armory.trait.internal; +import armory.logicnode.LogicNode; +import armory.logicnode.LogicTree; + + +#if arm_patch @:expose("LivePatch") #end +@:access(armory.logicnode.LogicNode) +@:access(armory.logicnode.LogicNodeLink) class LivePatch extends iron.Trait { -#if arm_patch +#if !arm_patch + public function new() { super(); } +#else static var patchId = 0; @@ -23,9 +32,164 @@ class LivePatch extends iron.Trait { }); } -#else + public static function patchCreateNodeLink(treeName: String, fromNodeName: String, toNodeName: String, fromIndex: Int, toIndex: Int) { + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; - public function new() { super(); } + for (tree in trees) { + var fromNode = tree.nodes[fromNodeName]; + var toNode = tree.nodes[toNodeName]; + if (fromNode == null || toNode == null) return; + + LogicNode.addLink(fromNode, toNode, fromIndex, toIndex); + } + } + + public static function patchSetNodeLinks(treeName: String, nodeName: String, inputDatas: Array, outputDatas: Array>) { + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; + + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; + + node.clearInputs(); + node.clearOutputs(); + + for (inputData in inputDatas) { + var fromNode: LogicNode; + var fromIndex: Int; + + if (inputData.isLinked) { + fromNode = tree.nodes[inputData.fromNode]; + if (fromNode == null) continue; + fromIndex = inputData.fromIndex; + } + else { + fromNode = LogicNode.createSocketDefaultNode(node.tree, inputData.socketType, inputData.socketValue); + fromIndex = 0; + } + + LogicNode.addLink(fromNode, node, fromIndex, inputData.toIndex); + } + + for (outputData in outputDatas) { + for (linkData in outputData) { + var toNode: LogicNode; + var toIndex: Int; + + if (linkData.isLinked) { + toNode = tree.nodes[linkData.toNode]; + if (toNode == null) continue; + toIndex = linkData.toIndex; + } + else { + toNode = LogicNode.createSocketDefaultNode(node.tree, linkData.socketType, linkData.socketValue); + toIndex = 0; + } + + LogicNode.addLink(node, toNode, linkData.fromIndex, toIndex); + } + } + } + } + + public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) { + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; + + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; + + Reflect.setField(node, propName, value); + } + } + + public static function patchUpdateNodeInputVal(treeName: String, nodeName: String, socketIndex: Int, value: Dynamic) { + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; + + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; + + node.inputs[socketIndex].set(value); + } + } + + public static function patchNodeDelete(treeName: String, nodeName: String) { + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; + + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; + + node.clearOutputs(); + node.clearInputs(); + tree.nodes.remove(nodeName); + } + } + + public static function patchNodeCreate(treeName: String, nodeName: String, nodeType: String, propDatas: Array>, inputDatas: Array>, outputDatas: Array>) { + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; + + for (tree in trees) { + // No further constructor parameters required here, all variable nodes + // use optional further parameters and all values are set later in this + // function. + var newNode: LogicNode = Type.createInstance(Type.resolveClass(nodeType), [tree]); + newNode.name = nodeName; + tree.nodes[nodeName] = newNode; + + for (propData in propDatas) { + Reflect.setField(newNode, propData[0], propData[1]); + } + + var i = 0; + for (inputData in inputDatas) { + LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++); + } + + i = 0; + for (outputData in outputDatas) { + LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0); + } + } + } + + public static function patchNodeCopy(treeName: String, nodeName: String, newNodeName: String, copyProps: Array, inputDatas: Array>, outputDatas: Array>) { + if (!LogicTree.nodeTrees.exists(treeName)) return; + var trees = LogicTree.nodeTrees[treeName]; + + for (tree in trees) { + var node = tree.nodes[nodeName]; + if (node == null) return; + + // No further constructor parameters required here, all variable nodes + // use optional further parameters and all values are set later in this + // function. + var newNode: LogicNode = Type.createInstance(Type.getClass(node), [tree]); + newNode.name = newNodeName; + tree.nodes[newNodeName] = newNode; + + for (propName in copyProps) { + Reflect.setField(newNode, propName, Reflect.field(node, propName)); + } + + var i = 0; + for (inputData in inputDatas) { + LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++); + } + + i = 0; + for (outputData in outputDatas) { + LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0); + } + } + } #end } diff --git a/Sources/armory/trait/internal/MovieTexture.hx b/Sources/armory/trait/internal/MovieTexture.hx index 1a3dc03a..3adee41e 100644 --- a/Sources/armory/trait/internal/MovieTexture.hx +++ b/Sources/armory/trait/internal/MovieTexture.hx @@ -2,14 +2,29 @@ package armory.trait.internal; import kha.Image; import kha.Video; + import iron.Trait; import iron.object.MeshObject; +/** + Replaces the diffuse texture of the first material of the trait's object + with a video texture. + + @see https://github.com/armory3d/armory_examples/tree/master/material_movie +**/ class MovieTexture extends Trait { + /** + Caches all render targets used by this trait for re-use when having + multiple videos of the same size. The lookup only takes place on trait + initialization. + + Map layout: `[width => [height => image]]` + **/ + static var imageCache: Map> = new Map(); + var video: Video; - public static var image: Image; - public static var created = false; + var image: Image; var videoName: String; @@ -33,10 +48,7 @@ class MovieTexture extends Trait { this.videoName = videoName; - if (!created) { - created = true; - notifyOnInit(init); - } + notifyOnInit(init); } function init() { @@ -44,9 +56,21 @@ class MovieTexture extends Trait { video = vid; video.play(true); - image = Image.createRenderTarget(getPower2(video.width()), getPower2(video.height())); + var w = getPower2(video.width()); + var h = getPower2(video.height()); - var o = cast(object, iron.object.MeshObject); + // Lazily fill the outer map + var hMap: Map = imageCache[w]; + if (hMap == null) { + imageCache[w] = new Map(); + } + + image = imageCache[w][h]; + if (image == null) { + imageCache[w][h] = image = Image.createRenderTarget(w, h); + } + + var o = cast(object, MeshObject); o.materials[0].contexts[0].textures[0] = image; // Override diffuse texture notifyOnRender2D(render); }); diff --git a/Sources/armory/trait/internal/UniformsManager.hx b/Sources/armory/trait/internal/UniformsManager.hx new file mode 100644 index 00000000..e559fc89 --- /dev/null +++ b/Sources/armory/trait/internal/UniformsManager.hx @@ -0,0 +1,320 @@ +package armory.trait.internal; + +import iron.object.MeshObject; +import iron.Trait; +import kha.Image; +import iron.math.Vec4; +import iron.data.MaterialData; +import iron.Scene; +import iron.object.Object; +import iron.object.Uniforms; + + +class UniformsManager extends Trait{ + + static var floatsRegistered = false; + static var floatsMap = new Map>>>(); + + static var vectorsRegistered = false; + static var vectorsMap = new Map>>(); + + static var texturesRegistered = false; + static var texturesMap = new Map>>(); + + static var sceneRemoveInitalized = false; + + public var uniformExists = false; + + public function new(){ + super(); + + notifyOnInit(init); + + notifyOnRemove(removeObject); + + if(! sceneRemoveInitalized){ + + Scene.active.notifyOnRemove(removeScene); + } + } + + function init() { + var materials = cast(object, MeshObject).materials; + + for (material in materials){ + + var exists = registerShaderUniforms(material); + if(exists) { + uniformExists = true; + } + } + } + + static function removeScene() { + + removeObjectFromAllMaps(Scene.active.root); + } + + function removeObject() { + + removeObjectFromAllMaps(object); + } + + // Helper method to register float, vec3 and texture getter functions + static function register(type: UniformType){ + + switch (type){ + case Float:{ + if(! floatsRegistered){ + floatsRegistered = true; + Uniforms.externalFloatLinks.push(floatLink); + } + } + + case Vector:{ + if(! vectorsRegistered){ + vectorsRegistered = true; + Uniforms.externalVec3Links.push(vec3Link); + } + } + + case Texture:{ + if(! texturesRegistered){ + texturesRegistered = true; + Uniforms.externalTextureLinks.push(textureLink); + } + } + } + } + + // Register and map shader uniforms if it is an armory shader parameter + public static function registerShaderUniforms(material: MaterialData) : Bool { + + var uniformExist = false; + + if(! floatsMap.exists(Scene.active.root)) floatsMap.set(Scene.active.root, null); + if(! vectorsMap.exists(Scene.active.root)) vectorsMap.set(Scene.active.root, null); + if(! texturesMap.exists(Scene.active.root)) texturesMap.set(Scene.active.root, null); + + for(context in material.shader.raw.contexts){ // For each context in shader + for (constant in context.constants){ // For each constant in the context + if(constant.is_arm_parameter){ // Check if armory parameter + + uniformExist = true; + var object = Scene.active.root; // Map default uniforms to scene root + + switch (constant.type){ + case "float":{ + var link = constant.link; + var value = constant.float; + setFloatValue(material, object, link, value); + register(Float); + } + + case "vec3":{ + + var vec = new Vec4(); + vec.x = constant.vec3.get(0); + vec.y = constant.vec3.get(1); + vec.z = constant.vec3.get(2); + + setVec3Value(material, object, constant.link, vec); + register(Vector); + + } + } + } + } + for (texture in context.texture_units){ + if(texture.is_arm_parameter){ // Check if armory parameter + + uniformExist = true; + var object = Scene.active.root; // Map default texture to scene root + + if(texture.default_image_file == null){ + setTextureValue(material, object, texture.link, null); + + } + else{ + iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) { + setTextureValue(material, object, texture.link, image); + + }); + + } + register(Texture); + + } + + } + } + + return uniformExist; + + } + + // Method to set map Object -> Material -> Link -> FLoat + public static function setFloatValue(material: MaterialData, object: Object, link: String, value: Null){ + + if(object == null || material == null || link == null) return; + + var map = floatsMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + // Method to set map Object -> Material -> Link -> Vec3 + public static function setVec3Value(material: MaterialData, object: Object, link: String, value: Vec4){ + + if(object == null || material == null || link == null) return; + + var map = vectorsMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + // Method to set map Object -> Material -> Link -> Texture + public static function setTextureValue(material: MaterialData, object: Object, link: String, value: kha.Image){ + + if(object == null || material == null || link == null) return; + + var map = texturesMap; + + var matMap = map.get(object); + if (matMap == null) { + matMap = new Map(); + map.set(object, matMap); + } + + var entry = matMap.get(material); + if (entry == null) { + entry = new Map(); + matMap.set(material, entry); + } + + entry.set(link, value); // parameter name, value + } + + // Mehtod to get object specific material parameter float value + static function floatLink(object: Object, mat: MaterialData, link: String): Null { + + if(object == null || mat == null) return null; + + if(! floatsMap.exists(object)){ + object = Scene.active.root; + } + + var material = floatsMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } + + // Mehtod to get object specific material parameter vec3 value + static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { + + if(object == null || mat == null) return null; + + if(! vectorsMap.exists(object)){ + object = Scene.active.root; + } + + var material = vectorsMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } + + // Mehtod to get object specific material parameter texture value + static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { + + if(object == null || mat == null) return null; + + if(! texturesMap.exists(object)){ + object = Scene.active.root; + } + + var material = texturesMap.get(object); + if (material == null) return null; + + var entry = material.get(mat); + if (entry == null) return null; + + return entry.get(link); + } + + // Returns complete map of float value material paramets + public static function getFloatsMap():Map>>>{ + + return floatsMap; + } + + // Returns complete map of vec3 value material paramets + public static function getVectorsMap():Map>>{ + + return vectorsMap; + } + + // Returns complete map of texture value material paramets + public static function getTexturesMap():Map>>{ + + return texturesMap; + } + + // Remove all object specific material paramenter keys + public static function removeObjectFromAllMaps(object: Object) { + + floatsMap.remove(object); + vectorsMap.remove(object); + texturesMap.remove(object); + + } + + // Remove object specific material paramenter keys + public static function removeObjectFromMap(object: Object, type: UniformType) { + + switch (type){ + case Float: floatsMap.remove(object); + + case Vector: vectorsMap.remove(object); + + case Texture: texturesMap.remove(object); + } + } +} + +@:enum abstract UniformType(Int) from Int to Int { + var Float = 0; + var Vector = 1; + var Texture = 2; +} \ No newline at end of file diff --git a/Sources/armory/trait/physics/bullet/PhysicsConstraintExportHelper.hx b/Sources/armory/trait/physics/bullet/PhysicsConstraintExportHelper.hx index 11a0ac24..bb536bf2 100644 --- a/Sources/armory/trait/physics/bullet/PhysicsConstraintExportHelper.hx +++ b/Sources/armory/trait/physics/bullet/PhysicsConstraintExportHelper.hx @@ -18,8 +18,9 @@ class PhysicsConstraintExportHelper extends iron.Trait { var breakingThreshold: Float; var limits: Array; var constraintAdded: Bool = false; + var relativeConstraint: Bool = false; - public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, limits: Array = null) { + public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, relatieConstraint: Bool = false, limits: Array = null) { super(); this.body1 = body1; @@ -27,14 +28,26 @@ class PhysicsConstraintExportHelper extends iron.Trait { this.type = type; this.disableCollisions = disableCollisions; this.breakingThreshold = breakingThreshold; + this.relativeConstraint = relatieConstraint; this.limits = limits; notifyOnInit(init); notifyOnUpdate(update); } function init() { - var target1 = Scene.active.getChild(body1); - var target2 = Scene.active.getChild(body2); + var target1; + var target2; + + if(relativeConstraint) { + + target1 = object.parent.getChild(body1); + target2 = object.parent.getChild(body2); + } + else { + + target1 = Scene.active.getChild(body1); + target2 = Scene.active.getChild(body2); + } object.addTrait(new PhysicsConstraint(target1, target2, type, disableCollisions, breakingThreshold, limits)); constraintAdded = true; } diff --git a/Sources/armory/trait/physics/bullet/PhysicsWorld.hx b/Sources/armory/trait/physics/bullet/PhysicsWorld.hx index 2260ac70..f8709ab4 100644 --- a/Sources/armory/trait/physics/bullet/PhysicsWorld.hx +++ b/Sources/armory/trait/physics/bullet/PhysicsWorld.hx @@ -52,7 +52,6 @@ class PhysicsWorld extends Trait { public var rbMap: Map; public var conMap: Map; public var timeScale = 1.0; - var timeStep = 1 / 60; var maxSteps = 1; public var solverIterations = 10; public var hitPointWorld = new Vec4(); @@ -67,7 +66,7 @@ class PhysicsWorld extends Trait { public static var physTime = 0.0; #end - public function new(timeScale = 1.0, timeStep = 1 / 60, solverIterations = 10) { + public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10) { super(); if (nullvec) { @@ -81,8 +80,7 @@ class PhysicsWorld extends Trait { sceneRemoved = false; this.timeScale = timeScale; - this.timeStep = timeStep; - maxSteps = timeStep < 1 / 60 ? 10 : 1; + this.maxSteps = maxSteps; this.solverIterations = solverIterations; // First scene @@ -269,7 +267,13 @@ class PhysicsWorld extends Trait { if (preUpdates != null) for (f in preUpdates) f(); - world.stepSimulation(timeStep, maxSteps, t); + //Bullet physics fixed timescale + var fixedTime = 1.0 / 60; + + //This condition must be satisfied to not loose time + var currMaxSteps = t < (fixedTime * maxSteps) ? maxSteps : 1; + + world.stepSimulation(t, currMaxSteps, fixedTime); updateContacts(); for (rb in rbMap) @:privateAccess rb.physicsUpdate(); diff --git a/Sources/armory/trait/physics/bullet/RigidBody.hx b/Sources/armory/trait/physics/bullet/RigidBody.hx index 5ec1eb83..3c48adc7 100644 --- a/Sources/armory/trait/physics/bullet/RigidBody.hx +++ b/Sources/armory/trait/physics/bullet/RigidBody.hx @@ -149,7 +149,7 @@ class RigidBody extends iron.Trait { if (ready) return; ready = true; - if (!Std.is(object, MeshObject)) return; // No mesh data + if (!Std.isOfType(object, MeshObject)) return; // No mesh data transform = object.transform; physics = armory.trait.physics.PhysicsWorld.active; diff --git a/Sources/armory/ui/Canvas.hx b/Sources/armory/ui/Canvas.hx new file mode 100644 index 00000000..a676e5f9 --- /dev/null +++ b/Sources/armory/ui/Canvas.hx @@ -0,0 +1,415 @@ +package armory.ui; + +import zui.Zui; + +using kha.graphics2.GraphicsExtension; + +@:access(zui.Zui) +class Canvas { + + public static var assetMap = new Map(); // kha.Image | kha.Font + public static var themes = new Array(); + static var events:Array = []; + + public static var screenW = -1; + public static var screenH = -1; + public static var locale = "en"; + static var _ui: Zui; + static var h = new zui.Zui.Handle(); // TODO: needs one handle per canvas + + public static function draw(ui: Zui, canvas: TCanvas, g: kha.graphics2.Graphics): Array { + + screenW = kha.System.windowWidth(); + screenH = kha.System.windowHeight(); + + events.resize(0); + + _ui = ui; + + g.end(); + ui.begin(g); // Bake elements + g.begin(false); + + ui.g = g; + + for (elem in canvas.elements) { + if (elem.parent == null) drawElement(ui, canvas, elem); + } + + g.end(); + ui.end(); // Finish drawing + g.begin(false); + + return events; + } + + static function drawElement(ui: Zui, canvas: TCanvas, element: TElement, px = 0.0, py = 0.0) { + + if (element == null || element.visible == false) return; + + var anchorOffset = getAnchorOffset(canvas, element); + px += anchorOffset[0]; + py += anchorOffset[1]; + + ui._x = canvas.x + scaled(element.x) + px; + ui._y = canvas.y + scaled(element.y) + py; + ui._w = scaled(element.width); + + var rotated = element.rotation != null && element.rotation != 0; + if (rotated) ui.g.pushRotation(element.rotation, ui._x + scaled(element.width) / 2, ui._y + scaled(element.height) / 2); + + var font = ui.ops.font; + var fontAsset = isFontAsset(element.asset); + if (fontAsset) ui.ops.font = getAsset(canvas, element.asset); + + switch (element.type) { + case Text: + var size = ui.fontSize; + + ui.fontSize = scaled(element.height); + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.text(getText(canvas, element), element.alignment); + + ui.fontSize = size; + + case Button: + var eh = ui.t.ELEMENT_H; + var bh = ui.t.BUTTON_H; + ui.t.ELEMENT_H = element.height; + ui.t.BUTTON_H = element.height; + ui.t.BUTTON_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.BUTTON_TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).BUTTON_TEXT_COL); + ui.t.BUTTON_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.t.BUTTON_PRESSED_COL = getColor(element.color_press, getTheme(canvas.theme).BUTTON_PRESSED_COL); + if (ui.button(getText(canvas, element), element.alignment)) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + ui.t.ELEMENT_H = eh; + ui.t.BUTTON_H = bh; + + case Image: + var image = getAsset(canvas, element.asset); + if (image != null && !fontAsset) { + ui.imageScrollAlign = false; + var tint = element.color != null ? element.color : 0xffffffff; + if (ui.image(image, tint, scaled(element.height)) == zui.Zui.State.Released) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + ui.imageScrollAlign = true; + } + + case FRectangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillRect(ui._x, ui._y, ui._w, scaled(element.height)); + ui.g.color = col; + + case FCircle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillCircle(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2); + ui.g.color = col; + + case Rectangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawRect(ui._x, ui._y, ui._w, scaled(element.height), element.strength); + ui.g.color = col; + + case Circle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawCircle(ui._x+(scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2, element.strength); + ui.g.color = col; + + case FTriangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillTriangle(ui._x + (ui._w / 2), ui._y, ui._x, ui._y + scaled(element.height), ui._x + ui._w, ui._y + scaled(element.height)); + ui.g.color = col; + + case Triangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawLine(ui._x + (ui._w / 2), ui._y, ui._x, ui._y + scaled(element.height), element.strength); + ui.g.drawLine(ui._x, ui._y + scaled(element.height), ui._x + ui._w, ui._y + scaled(element.height), element.strength); + ui.g.drawLine(ui._x + ui._w, ui._y + scaled(element.height), ui._x + (ui._w / 2), ui._y, element.strength); + ui.g.color = col; + + case Check: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.check(h.nest(element.id), getText(canvas, element)); + + case Radio: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + zui.Ext.inlineRadio(ui, h.nest(element.id), getText(canvas, element).split(";")); + + case Combo: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.SEPARATOR_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.combo(h.nest(element.id), getText(canvas, element).split(";")); + + case Slider: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.slider(h.nest(element.id), getText(canvas, element), 0.0, 1.0, true, 100, true, element.alignment); + + case TextInput: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.textInput(h.nest(element.id), getText(canvas, element), element.alignment); + if (h.nest(element.id).changed) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + + case TextArea: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + h.nest(element.id).text = getText(canvas, element); + zui.Ext.textArea(ui,h.nest(element.id), element.alignment,element.editable); + if (h.nest(element.id).changed) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + + case KeyInput: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + Ext.keyInput(ui, h.nest(element.id), getText(canvas, element)); + + case ProgressBar: + var col = ui.g.color; + var progress = element.progress_at; + var totalprogress = element.progress_total; + ui.g.color = getColor(element.color_progress, getTheme(canvas.theme).TEXT_COL); + ui.g.fillRect(ui._x, ui._y, ui._w / totalprogress * Math.min(progress, totalprogress), scaled(element.height)); + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawRect(ui._x, ui._y, ui._w, scaled(element.height), element.strength); + ui.g.color = col; + + case CProgressBar: + var col = ui.g.color; + var progress = element.progress_at; + var totalprogress = element.progress_total; + ui.g.color = getColor(element.color_progress, getTheme(canvas.theme).TEXT_COL); + ui.g.drawArc(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2, -Math.PI / 2, ((Math.PI * 2) / totalprogress * progress) - Math.PI / 2, element.strength); + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillCircle(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), (ui._w / 2) - 10); + ui.g.color = col; + case Empty: + } + + ui.ops.font = font; + + if (element.children != null) { + for (id in element.children) { + drawElement(ui, canvas, elemById(canvas, id), scaled(element.x) + px, scaled(element.y) + py); + } + } + + if (rotated) ui.g.popTransformation(); + } + + static inline function getText(canvas: TCanvas, e: TElement): String { + return e.text; + } + + public static function getAsset(canvas: TCanvas, asset: String): Dynamic { // kha.Image | kha.Font { + for (a in canvas.assets) if (a.name == asset) return assetMap.get(a.id); + return null; + } + + static var elemId = -1; + public static function getElementId(canvas: TCanvas): Int { + if (elemId == -1) for (e in canvas.elements) if (elemId < e.id) elemId = e.id; + return ++elemId; + } + + static var assetId = -1; + public static function getAssetId(canvas: TCanvas): Int { + if (assetId == -1) for (a in canvas.assets) if (assetId < a.id) assetId = a.id; + return ++assetId; + } + + static function elemById(canvas: TCanvas, id: Int): TElement { + for (e in canvas.elements) if (e.id == id) return e; + return null; + } + + static inline function scaled(f: Float): Int { + return Std.int(f * _ui.SCALE()); + } + + public static inline function isFontAsset(assetName: Null): Bool { + return assetName != null && StringTools.endsWith(assetName.toLowerCase(), ".ttf"); + } + + public static inline function getColor(color: Null, defaultColor: Int): Int { + return color != null ? color : defaultColor; + } + + public static function getTheme(theme: String): zui.Themes.TTheme { + for (t in Canvas.themes) { + if (t.NAME == theme) return t; + } + return null; + } + + /** + Returns the positional scaled offset of the given element based on its anchor setting. + @param canvas The canvas object + @param element The element + @return Array [xOffset, yOffset] + **/ + public static function getAnchorOffset(canvas: TCanvas, element: TElement): Array { + var boxWidth, boxHeight: Float; + var offsetX = 0.0; + var offsetY = 0.0; + + if (element.parent == null) { + boxWidth = canvas.width; + boxHeight = canvas.height; + } + else { + var parent = elemById(canvas, element.parent); + boxWidth = scaled(parent.width); + boxHeight = scaled(parent.height); + } + + switch (element.anchor) { + case Top: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + case TopRight: + offsetX += boxWidth - scaled(element.width); + case CenterLeft: + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case Center: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case CenterRight: + offsetX += boxWidth - scaled(element.width); + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case BottomLeft: + offsetY += boxHeight - scaled(element.height); + case Bottom: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + offsetY += boxHeight - scaled(element.height); + case BottomRight: + offsetX += boxWidth - scaled(element.width); + offsetY += boxHeight - scaled(element.height); + } + + return [offsetX, offsetY]; + } +} + +typedef TCanvas = { + var name: String; + var x: Float; + var y: Float; + var width: Int; + var height: Int; + var elements: Array; + var theme: String; + @:optional var assets: Array; + @:optional var locales: Array; +} + +typedef TElement = { + var id: Int; + var type: ElementType; + var name: String; + var x: Float; + var y: Float; + var width: Int; + var height: Int; + @:optional var rotation: Null; + @:optional var text: String; + @:optional var event: String; + // null = follow theme settings + @:optional var color: Null; + @:optional var color_text: Null; + @:optional var color_hover: Null; + @:optional var color_press: Null; + @:optional var color_progress: Null; + @:optional var progress_at: Null; + @:optional var progress_total: Null; + @:optional var strength: Null; + @:optional var alignment: Null; + @:optional var anchor: Null; + @:optional var parent: Null; // id + @:optional var children: Array; // ids + @:optional var asset: String; + @:optional var visible: Null; + @:optional var editable: Null; +} + +typedef TAsset = { + var id: Int; + var name: String; + var file: String; +} + +typedef TLocale = { + var name: String; // "en" + var texts: Array; +} + +typedef TTranslatedText = { + var id: Int; // element id + var text: String; +} + +@:enum abstract ElementType(Int) from Int to Int { + var Text = 0; + var Image = 1; + var Button = 2; + var Empty = 3; + // var HLayout = 4; + // var VLayout = 5; + var Check = 6; + var Radio = 7; + var Combo = 8; + var Slider = 9; + var TextInput = 10; + var KeyInput = 11; + var FRectangle = 12; + var Rectangle = 13; + var FCircle = 14; + var Circle = 15; + var FTriangle = 16; + var Triangle = 17; + var ProgressBar = 18; + var CProgressBar = 19; + var TextArea = 20; +} + +@:enum abstract Anchor(Int) from Int to Int { + var TopLeft = 0; + var Top = 1; + var TopRight = 2; + var CenterLeft = 3; + var Center = 4; + var CenterRight = 5; + var BottomLeft = 6; + var Bottom = 7; + var BottomRight = 8; +} diff --git a/Sources/armory/ui/Ext.hx b/Sources/armory/ui/Ext.hx new file mode 100644 index 00000000..1197ef46 --- /dev/null +++ b/Sources/armory/ui/Ext.hx @@ -0,0 +1,331 @@ +package armory.ui; + +import zui.Zui; +import kha.input.Keyboard; +import kha.input.KeyCode; + +typedef ListOpts = { + ?addCb: String->Void, + ?removeCb: Int->Void, + ?getNameCb: Int->String, + ?setNameCb: Int->String->Void, + ?getLabelCb: Int->String, + ?itemDrawCb: Handle->Int->Void, + ?showRadio: Bool, // false + ?editable: Bool, // true + ?showAdd: Bool, // true + ?addLabel: String // 'Add' +} + +@:access(zui.Zui) +class Ext { + public static function keyInput(ui: Zui, handle: Handle, label = "", align: Align = Left): Int { + if (!ui.isVisible(ui.ELEMENT_H())) { + ui.endElement(); + return Std.int(handle.value); + } + + var hover = ui.getHover(); + if (hover && Zui.onTextHover != null) Zui.onTextHover(); + ui.g.color = hover ? ui.t.ACCENT_HOVER_COL : ui.t.ACCENT_COL; // Text bg + ui.drawRect(ui.g, ui.t.FILL_ACCENT_BG, ui._x + ui.buttonOffsetY, ui._y + ui.buttonOffsetY, ui._w - ui.buttonOffsetY * 2, ui.BUTTON_H()); + + var startEdit = ui.getReleased() || ui.tabPressed; + if (ui.textSelectedHandle != handle && startEdit) ui.startTextEdit(handle); + if (ui.textSelectedHandle == handle) Ext.listenToKey(ui, handle); + else handle.changed = false; + + if (label != "") { + ui.g.color = ui.t.LABEL_COL; // Label + var labelAlign = align == Align.Right ? Align.Left : Align.Right; + var xOffset = labelAlign == Align.Left ? 7 : 0; + ui.drawString(ui.g, label, xOffset, 0, labelAlign); + } + + handle.text = Ext.keycodeToString(Std.int(handle.value)); + + ui.g.color = ui.t.TEXT_COL; // Text + ui.textSelectedHandle != handle ? ui.drawString(ui.g, handle.text, null, 0, align) : ui.drawString(ui.g, ui.textSelected, null, 0, align); + + ui.endElement(); + + return Std.int(handle.value); + } + + static function listenToKey(ui: Zui, handle: Handle) { + if (ui.isKeyDown) { + handle.value = ui.key; + handle.changed = ui.changed = true; + + ui.textSelectedHandle = null; + ui.isTyping = false; + + if (Keyboard.get() != null) Keyboard.get().hide(); + } + else { + ui.textSelected = "Press a key..."; + } + } + + public static function list(ui: Zui, handle: Handle, ar: Array, ?opts: ListOpts ): Int { + var selected = 0; + if (opts == null) opts = {}; + + var addCb = opts.addCb != null ? opts.addCb : function(name: String) ar.push(name); + var removeCb = opts.removeCb != null ? opts.removeCb : function(i: Int) ar.splice(i, 1); + var getNameCb = opts.getNameCb != null ? opts.getNameCb : function(i: Int) return ar[i]; + var setNameCb = opts.setNameCb != null ? opts.setNameCb : function(i: Int, name: String) ar[i] = name; + var getLabelCb = opts.getLabelCb != null ? opts.getLabelCb : function(i: Int) return ""; + var itemDrawCb = opts.itemDrawCb; + var showRadio = opts.showRadio != null ? opts.showRadio : false; + var editable = opts.editable != null ? opts.editable : true; + var showAdd = opts.showAdd != null ? opts.showAdd : true; + var addLabel = opts.addLabel != null ? opts.addLabel : "Add"; + + var i = 0; + while (i < ar.length) { + if (showRadio) { // Prepend ratio button + ui.row([0.12, 0.68, 0.2]); + if (ui.radio(handle.nest(0), i, "")) { + selected = i; + } + } + else ui.row([0.8, 0.2]); + + var itemHandle = handle.nest(i); + itemHandle.text = getNameCb(i); + editable ? setNameCb(i, ui.textInput(itemHandle, getLabelCb(i))) : ui.text(getNameCb(i)); + if (ui.button("X")) removeCb(i); + else i++; + + if (itemDrawCb != null) itemDrawCb(itemHandle.nest(i), i - 1); + } + if (showAdd && ui.button(addLabel)) addCb("untitled"); + + return selected; + } + + public static function panelList(ui: Zui, handle: Handle, ar: Array, + addCb: String->Void = null, + removeCb: Int->Void = null, + getNameCb: Int->String = null, + setNameCb: Int->String->Void = null, + itemDrawCb: Handle->Int->Void = null, + editable = true, + showAdd = true, + addLabel: String = "Add") { + + if (addCb == null) addCb = function(name: String) { ar.push(name); }; + if (removeCb == null) removeCb = function(i: Int) { ar.splice(i, 1); }; + if (getNameCb == null) getNameCb = function(i: Int) { return ar[i]; }; + if (setNameCb == null) setNameCb = function(i: Int, name: String) { ar[i] = name; }; + + var i = 0; + while (i < ar.length) { + ui.row([0.12, 0.68, 0.2]); + var expanded = ui.panel(handle.nest(i), ""); + + var itemHandle = handle.nest(i); + editable ? setNameCb(i, ui.textInput(itemHandle, getNameCb(i))) : ui.text(getNameCb(i)); + if (ui.button("X")) removeCb(i); + else i++; + + if (itemDrawCb != null && expanded) itemDrawCb(itemHandle.nest(i), i - 1); + } + if (showAdd && ui.button(addLabel)) { + addCb("untitled"); + } + } + + public static function colorField(ui: Zui, handle:Handle, alpha = false): Int { + ui.g.color = handle.color; + + ui.drawRect(ui.g, true, ui._x + 2, ui._y + ui.buttonOffsetY, ui._w - 4, ui.BUTTON_H()); + ui.g.color = ui.getHover() ? ui.t.ACCENT_HOVER_COL : ui.t.ACCENT_COL; + ui.drawRect(ui.g, false, ui._x + 2, ui._y + ui.buttonOffsetY, ui._w - 4, ui.BUTTON_H(), 1.0); + + if (ui.getStarted()) { + Popup.showCustom( + new Zui(ui.ops), + function(ui:Zui) { + zui.Ext.colorWheel(ui, handle, alpha); + }, + Std.int(ui.inputX), Std.int(ui.inputY), 200, 500); + } + + ui.endElement(); + return handle.color; + } + + public static function colorPicker(ui: Zui, handle: Handle, alpha = false): Int { + var r = ui.slider(handle.nest(0, {value: handle.color.R}), "R", 0, 1, true); + var g = ui.slider(handle.nest(1, {value: handle.color.G}), "G", 0, 1, true); + var b = ui.slider(handle.nest(2, {value: handle.color.B}), "B", 0, 1, true); + var a = handle.color.A; + if (alpha) a = ui.slider(handle.nest(3, {value: a}), "A", 0, 1, true); + var col = kha.Color.fromFloats(r, g, b, a); + ui.text("", Right, col); + return col; + } + + /** + Keycodes can be found here: http://api.kha.tech/kha/input/KeyCode.html + **/ + static function keycodeToString(keycode: Int): String { + return switch (keycode) { + default: String.fromCharCode(keycode); + case -1: "None"; + case KeyCode.Unknown: "Unknown"; + case KeyCode.Back: "Back"; + case KeyCode.Cancel: "Cancel"; + case KeyCode.Help: "Help"; + case KeyCode.Backspace: "Backspace"; + case KeyCode.Tab: "Tab"; + case KeyCode.Clear: "Clear"; + case KeyCode.Return: "Return"; + case KeyCode.Shift: "Shift"; + case KeyCode.Control: "Ctrl"; + case KeyCode.Alt: "Alt"; + case KeyCode.Pause: "Pause"; + case KeyCode.CapsLock: "CapsLock"; + case KeyCode.Kana: "Kana"; + // case KeyCode.Hangul: "Hangul"; // Hangul == Kana + case KeyCode.Eisu: "Eisu"; + case KeyCode.Junja: "Junja"; + case KeyCode.Final: "Final"; + case KeyCode.Hanja: "Hanja"; + // case KeyCode.Kanji: "Kanji"; // Kanji == Hanja + case KeyCode.Escape: "Esc"; + case KeyCode.Convert: "Convert"; + case KeyCode.NonConvert: "NonConvert"; + case KeyCode.Accept: "Accept"; + case KeyCode.ModeChange: "ModeChange"; + case KeyCode.Space: "Space"; + case KeyCode.PageUp: "PageUp"; + case KeyCode.PageDown: "PageDown"; + case KeyCode.End: "End"; + case KeyCode.Home: "Home"; + case KeyCode.Left: "Left"; + case KeyCode.Up: "Up"; + case KeyCode.Right: "Right"; + case KeyCode.Down: "Down"; + case KeyCode.Select: "Select"; + case KeyCode.Print: "Print"; + case KeyCode.Execute: "Execute"; + case KeyCode.PrintScreen: "PrintScreen"; + case KeyCode.Insert: "Insert"; + case KeyCode.Delete: "Delete"; + case KeyCode.Colon: "Colon"; + case KeyCode.Semicolon: "Semicolon"; + case KeyCode.LessThan: "LessThan"; + case KeyCode.Equals: "Equals"; + case KeyCode.GreaterThan: "GreaterThan"; + case KeyCode.QuestionMark: "QuestionMark"; + case KeyCode.At: "At"; + case KeyCode.Win: "Win"; + case KeyCode.ContextMenu: "ContextMenu"; + case KeyCode.Sleep: "Sleep"; + case KeyCode.Numpad0: "Numpad0"; + case KeyCode.Numpad1: "Numpad1"; + case KeyCode.Numpad2: "Numpad2"; + case KeyCode.Numpad3: "Numpad3"; + case KeyCode.Numpad4: "Numpad4"; + case KeyCode.Numpad5: "Numpad5"; + case KeyCode.Numpad6: "Numpad6"; + case KeyCode.Numpad7: "Numpad7"; + case KeyCode.Numpad8: "Numpad8"; + case KeyCode.Numpad9: "Numpad9"; + case KeyCode.Multiply: "Multiply"; + case KeyCode.Add: "Add"; + case KeyCode.Separator: "Separator"; + case KeyCode.Subtract: "Subtract"; + case KeyCode.Decimal: "Decimal"; + case KeyCode.Divide: "Divide"; + case KeyCode.F1: "F1"; + case KeyCode.F2: "F2"; + case KeyCode.F3: "F3"; + case KeyCode.F4: "F4"; + case KeyCode.F5: "F5"; + case KeyCode.F6: "F6"; + case KeyCode.F7: "F7"; + case KeyCode.F8: "F8"; + case KeyCode.F9: "F9"; + case KeyCode.F10: "F10"; + case KeyCode.F11: "F11"; + case KeyCode.F12: "F12"; + case KeyCode.F13: "F13"; + case KeyCode.F14: "F14"; + case KeyCode.F15: "F15"; + case KeyCode.F16: "F16"; + case KeyCode.F17: "F17"; + case KeyCode.F18: "F18"; + case KeyCode.F19: "F19"; + case KeyCode.F20: "F20"; + case KeyCode.F21: "F21"; + case KeyCode.F22: "F22"; + case KeyCode.F23: "F23"; + case KeyCode.F24: "F24"; + case KeyCode.NumLock: "NumLock"; + case KeyCode.ScrollLock: "ScrollLock"; + case KeyCode.WinOemFjJisho: "WinOemFjJisho"; + case KeyCode.WinOemFjMasshou: "WinOemFjMasshou"; + case KeyCode.WinOemFjTouroku: "WinOemFjTouroku"; + case KeyCode.WinOemFjLoya: "WinOemFjLoya"; + case KeyCode.WinOemFjRoya: "WinOemFjRoya"; + case KeyCode.Circumflex: "Circumflex"; + case KeyCode.Exclamation: "Exclamation"; + case KeyCode.DoubleQuote: "DoubleQuote"; + case KeyCode.Hash: "Hash"; + case KeyCode.Dollar: "Dollar"; + case KeyCode.Percent: "Percent"; + case KeyCode.Ampersand: "Ampersand"; + case KeyCode.Underscore: "Underscore"; + case KeyCode.OpenParen: "OpenParen"; + case KeyCode.CloseParen: "CloseParen"; + case KeyCode.Asterisk: "Asterisk"; + case KeyCode.Plus: "Plus"; + case KeyCode.Pipe: "Pipe"; + case KeyCode.HyphenMinus: "HyphenMinus"; + case KeyCode.OpenCurlyBracket: "OpenCurlyBracket"; + case KeyCode.CloseCurlyBracket: "CloseCurlyBracket"; + case KeyCode.Tilde: "Tilde"; + case KeyCode.VolumeMute: "VolumeMute"; + case KeyCode.VolumeDown: "VolumeDown"; + case KeyCode.VolumeUp: "VolumeUp"; + case KeyCode.Comma: "Comma"; + case KeyCode.Period: "Period"; + case KeyCode.Slash: "Slash"; + case KeyCode.BackQuote: "BackQuote"; + case KeyCode.OpenBracket: "OpenBracket"; + case KeyCode.BackSlash: "BackSlash"; + case KeyCode.CloseBracket: "CloseBracket"; + case KeyCode.Quote: "Quote"; + case KeyCode.Meta: "Meta"; + case KeyCode.AltGr: "AltGr"; + case KeyCode.WinIcoHelp: "WinIcoHelp"; + case KeyCode.WinIco00: "WinIco00"; + case KeyCode.WinIcoClear: "WinIcoClear"; + case KeyCode.WinOemReset: "WinOemReset"; + case KeyCode.WinOemJump: "WinOemJump"; + case KeyCode.WinOemPA1: "WinOemPA1"; + case KeyCode.WinOemPA2: "WinOemPA2"; + case KeyCode.WinOemPA3: "WinOemPA3"; + case KeyCode.WinOemWSCTRL: "WinOemWSCTRL"; + case KeyCode.WinOemCUSEL: "WinOemCUSEL"; + case KeyCode.WinOemATTN: "WinOemATTN"; + case KeyCode.WinOemFinish: "WinOemFinish"; + case KeyCode.WinOemCopy: "WinOemCopy"; + case KeyCode.WinOemAuto: "WinOemAuto"; + case KeyCode.WinOemENLW: "WinOemENLW"; + case KeyCode.WinOemBackTab: "WinOemBackTab"; + case KeyCode.ATTN: "ATTN"; + case KeyCode.CRSEL: "CRSEL"; + case KeyCode.EXSEL: "EXSEL"; + case KeyCode.EREOF: "EREOF"; + case KeyCode.Play: "Play"; + case KeyCode.Zoom: "Zoom"; + case KeyCode.PA1: "PA1"; + case KeyCode.WinOemClear: "WinOemClear"; + } + } +} diff --git a/Sources/armory/ui/Popup.hx b/Sources/armory/ui/Popup.hx new file mode 100644 index 00000000..1cfa4b49 --- /dev/null +++ b/Sources/armory/ui/Popup.hx @@ -0,0 +1,127 @@ +package armory.ui; + +import zui.Zui; +import kha.System; + +@:access(zui.Zui) +class Popup { + public static var show = false; + + static var ui: Zui = null; + static var hwnd = new Handle(); + static var boxTitle = ""; + static var boxText = ""; + static var boxCommands: Zui->Void = null; + static var modalX = 0; + static var modalY = 0; + static var modalW = 400; + static var modalH = 160; + + public static function render(g: kha.graphics2.Graphics) { + if (boxCommands == null) { + ui.begin(g); + if (ui.window(hwnd, modalX, modalY, modalW, modalH)) { + drawTitle(g); + + for (line in boxText.split("\n")) { + ui.text(line); + } + + ui._y = ui._h - ui.t.BUTTON_H - 10; + ui.row([1/3, 1/3, 1/3]); + ui.endElement(); + if (ui.button("OK")) { + show = false; + } + } + ui.end(); + } + else { + ui.begin(g); + if (ui.window(hwnd, modalX, modalY, modalW, modalH)) { + drawTitle(g); + + ui._y += 10; + boxCommands(ui); + } + ui.end(); + } + } + + public static function drawTitle(g: kha.graphics2.Graphics) { + if (boxTitle != "") { + g.color = ui.t.SEPARATOR_COL; + ui.drawRect(g, true, ui._x, ui._y, ui._w, ui.t.BUTTON_H); + + g.color = ui.t.TEXT_COL; + ui.text(boxTitle); + } + } + + public static function update() { + var inUse = ui.comboSelectedHandle != null; + + // Close popup + if (ui.inputStarted && !inUse) { + if (ui.inputX < modalX || ui.inputX > modalX + modalW || ui.inputY < modalY || ui.inputY > modalY + modalH) { + show = false; + } + } + } + + /** + Displays a message box with a title, a text body and a centered "OK" button. + @param ui the Zui instance for the popup + @param title the title to display + @param text the text to display + **/ + public static function showMessage(ui: Zui, title: String, text: String) { + Popup.ui = ui; + init(); + + boxTitle = title; + boxText = text; + boxCommands = null; + } + + /** + Displays a popup box with custom drawing code. + @param ui the Zui instance for the popup + @param commands the function for drawing the popup's content + @param mx the x position of the popup. -1 = screen center (defaults to -1) + @param my the y position of the popup. -1 = screen center (defaults to -1) + @param mw the width of the popup (defaults to 400) + @param mh the height of the popup (defaults to 160) + **/ + public static function showCustom(ui: Zui, commands: Zui->Void = null, mx = -1, my = -1, mw = 400, mh = 160) { + Popup.ui = ui; + init(mx, my, mw, mh); + + boxTitle = ""; + boxText = ""; + boxCommands = commands; + } + + static function init(mx = -1, my = -1, mw = 400, mh = 160) { + var appW = System.windowWidth(); + var appH = System.windowHeight(); + + modalX = mx; + modalY = my; + modalW = Std.int(mw * ui.SCALE()); + modalH = Std.int(mh * ui.SCALE()); + + // Center popup window if no value is given + if (mx == -1) modalX = Std.int(appW / 2 - modalW / 2); + if (my == -1) modalY = Std.int(appH / 2 - modalH / 2); + + // Limit popup position to screen + modalX = Std.int(Math.max(0, Math.min(modalX, appW - modalW))); + modalY = Std.int(Math.max(0, Math.min(modalY, appH - modalH))); + + hwnd.dragX = 0; + hwnd.dragY = 0; + hwnd.scrollOffset = 0.0; + show = true; + } +} diff --git a/Sources/armory/ui/Themes.hx b/Sources/armory/ui/Themes.hx new file mode 100644 index 00000000..34d397a9 --- /dev/null +++ b/Sources/armory/ui/Themes.hx @@ -0,0 +1,42 @@ +package armory.ui; + +import zui.Themes; + +class Themes { + + // 2x scaled, for games + public static var light: TTheme = { + NAME: "Default Light", + WINDOW_BG_COL: 0xffefefef, + WINDOW_TINT_COL: 0xff222222, + ACCENT_COL: 0xffeeeeee, + ACCENT_HOVER_COL: 0xffbbbbbb, + ACCENT_SELECT_COL: 0xffaaaaaa, + BUTTON_COL: 0xffcccccc, + BUTTON_TEXT_COL: 0xff222222, + BUTTON_HOVER_COL: 0xffb3b3b3, + BUTTON_PRESSED_COL: 0xffb1b1b1, + TEXT_COL: 0xff999999, + LABEL_COL: 0xffaaaaaa, + SEPARATOR_COL: 0xff999999, + HIGHLIGHT_COL: 0xff205d9c, + CONTEXT_COL: 0xffaaaaaa, + PANEL_BG_COL: 0xffaaaaaa, + FONT_SIZE: 13 * 2, + ELEMENT_W: 100 * 2, + ELEMENT_H: 24 * 2, + ELEMENT_OFFSET: 4 * 2, + ARROW_SIZE: 5 * 2, + BUTTON_H: 22 * 2, + CHECK_SIZE: 15 * 2, + CHECK_SELECT_SIZE: 8 * 2, + SCROLL_W: 6 * 2, + TEXT_OFFSET: 8 * 2, + TAB_W: 12 * 2, + FILL_WINDOW_BG: false, + FILL_BUTTON_BG: true, + FILL_ACCENT_BG: false, + LINK_STYLE: Line, + FULL_TABS: false + }; +} diff --git a/blender/arm/__init__.py b/blender/arm/__init__.py index e69de29b..5937ac15 100755 --- a/blender/arm/__init__.py +++ b/blender/arm/__init__.py @@ -0,0 +1,44 @@ +import importlib +import sys +import types + +# This gets cleared if this package/the __init__ module is reloaded +_module_cache: dict[str, types.ModuleType] = {} + + +def enable_reload(module_name: str): + """Enable reloading for the next time the module with `module_name` + is executed. + """ + mod = sys.modules[module_name] + setattr(mod, module_name.replace('.', '_') + "_DO_RELOAD_MODULE", True) + + +def is_reload(module_name: str) -> bool: + """True if the module given by `module_name` should reload the + modules it imports. This is the case if `enable_reload()` was called + for the module before. + """ + mod = sys.modules[module_name] + return hasattr(mod, module_name.replace('.', '_') + "_DO_RELOAD_MODULE") + + +def reload_module(module: types.ModuleType) -> types.ModuleType: + """Wrapper around importlib.reload() to make sure no module is + reloaded twice. + + Make sure to call this function in the same order in which the + modules are imported to make sure that the reloading respects the + module dependencies. Otherwise modules could depend on other modules + that are not yet reloaded. + + If you import classes or functions from a module, make sure to + re-import them after the module is reloaded. + """ + mod = _module_cache.get(module.__name__, None) + + if mod is None: + mod = importlib.reload(module) + _module_cache[module.__name__] = mod + + return mod diff --git a/blender/arm/api.py b/blender/arm/api.py index ed53a290..28fbc85a 100644 --- a/blender/arm/api.py +++ b/blender/arm/api.py @@ -2,9 +2,16 @@ from typing import Callable, Dict, Optional from bpy.types import Material, UILayout +import arm from arm.material.shader import ShaderContext -drivers: Dict[str, Dict] = dict() +if arm.is_reload(__name__): + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import ShaderContext +else: + drivers: Dict[str, Dict] = dict() + + arm.enable_reload(__name__) def add_driver(driver_name: str, diff --git a/blender/arm/assets.py b/blender/arm/assets.py index b0c1bc97..23235966 100755 --- a/blender/arm/assets.py +++ b/blender/arm/assets.py @@ -6,6 +6,12 @@ import bpy import arm.log as log import arm.utils +if arm.is_reload(__name__): + log = arm.reload_module(log) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + assets = [] reserved_names = ['return.'] khafile_params = [] diff --git a/blender/arm/exporter.py b/blender/arm/exporter.py index 30154525..9a344e80 100755 --- a/blender/arm/exporter.py +++ b/blender/arm/exporter.py @@ -32,6 +32,19 @@ import arm.material.mat_batch as mat_batch import arm.utils import arm.profiler +if arm.is_reload(__name__): + assets = arm.reload_module(assets) + exporter_opt = arm.reload_module(exporter_opt) + log = arm.reload_module(log) + make_renderpath = arm.reload_module(make_renderpath) + cycles = arm.reload_module(cycles) + make_material = arm.reload_module(make_material) + mat_batch = arm.reload_module(mat_batch) + arm.utils = arm.reload_module(arm.utils) + arm.profiler = arm.reload_module(arm.profiler) +else: + arm.enable_reload(__name__) + @unique class NodeType(Enum): @@ -278,6 +291,8 @@ class ArmoryExporter: return out_track def export_object_transform(self, bobject: bpy.types.Object, o): + wrd = bpy.data.worlds['Arm'] + # Static transform o['transform'] = {'values': ArmoryExporter.write_matrix(bobject.matrix_local)} @@ -291,7 +306,7 @@ class ArmoryExporter: fp = self.get_meshes_file_path('action_' + action_name, compressed=ArmoryExporter.compress_enabled) assets.add(fp) ext = '.lz4' if ArmoryExporter.compress_enabled else '' - if ext == '' and not bpy.data.worlds['Arm'].arm_minimize: + if ext == '' and not wrd.arm_minimize: ext = '.json' if 'object_actions' not in o: @@ -307,6 +322,7 @@ class ArmoryExporter: self.export_pose_markers(out_anim, action) + unresolved_data_paths = set() for fcurve in action.fcurves: data_path = fcurve.data_path @@ -314,7 +330,11 @@ class ArmoryExporter: out_track = self.export_animation_track(fcurve, frame_range, FCURVE_TARGET_NAMES[data_path][fcurve.array_index]) except KeyError: if data_path not in FCURVE_TARGET_NAMES: - log.warn(f"Action {action_name}: The data path '{data_path}' is not supported (yet)!") + # This can happen if the target is simply not + # supported or the action shares both bone + # and object transform data (FCURVE_TARGET_NAMES + # only contains object transform targets) + unresolved_data_paths.add(data_path) continue # Missing target entry for array_index or something else else: @@ -322,8 +342,20 @@ class ArmoryExporter: out_anim['tracks'].append(out_track) + if len(unresolved_data_paths) > 0: + warning = ( + f'The action "{action_name}" has fcurve channels with data paths that could not be resolved.' + ' This can be caused by the following things:\n' + ' - The data paths are not supported.\n' + ' - The action exists on both armature and non-armature objects or has both bone and object transform data.' + ) + if wrd.arm_verbose_output: + warning += f'\n Unresolved data paths: {unresolved_data_paths}' + else: + warning += '\n To see the list of unresolved data paths please recompile with Armory Project > Verbose Output enabled.' + log.warn(warning) + if True: # not action.arm_cached or not os.path.exists(fp): - wrd = bpy.data.worlds['Arm'] if wrd.arm_verbose_output: print('Exporting object action ' + action_name) @@ -1157,7 +1189,16 @@ class ArmoryExporter: else: invscale_tex = 1 * 32767 if has_tang: - exportMesh.calc_tangents(uvmap=lay0.name) + try: + exportMesh.calc_tangents(uvmap=lay0.name) + except Exception as e: + if hasattr(e, 'message'): + log.error(e.message) + else: + # Assume it was caused because of encountering n-gons + log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping. +Make sure the mesh only has tris/quads.""") + tangdata = np.empty(num_verts * 3, dtype='= (2, 80, 72): - out_light['strength'] *= 0.01 + out_light['strength'] *= 0.01 out_light['fov'] = 1.5708 # pi/2 out_light['shadowmap_cube'] = True if light_ref.shadow_soft_size > 0.1: out_light['light_size'] = light_ref.shadow_soft_size * 10 elif objtype == 'SPOT': - out_light['strength'] *= 2.6 - if bpy.app.version >= (2, 80, 72): - out_light['strength'] *= 0.01 + out_light['strength'] *= 0.01 out_light['spot_size'] = math.cos(light_ref.spot_size / 2) # Cycles defaults to 0.15 out_light['spot_blend'] = light_ref.spot_blend / 10 elif objtype == 'AREA': - out_light['strength'] *= 80.0 / (light_ref.size * light_ref.size_y) - if bpy.app.version >= (2, 80, 72): - out_light['strength'] *= 0.01 + out_light['strength'] *= 0.01 out_light['size'] = light_ref.size out_light['size_y'] = light_ref.size_y @@ -1565,9 +1600,11 @@ class ArmoryExporter: asset_name = arm.utils.asset_name(bobject) + # Collection is in the same file if collection.library is None: - # collection is in the same file, but (likely) on another scene - if asset_name not in scene_objects: + # Only export linked objects (from other scenes for example), + # all other objects (in scene_objects) are already exported. + if bobject.name not in scene_objects: self.process_bobject(bobject) self.export_object(bobject, self.scene) else: @@ -2308,6 +2345,10 @@ class ArmoryExporter: return instanced_type, instanced_data + @staticmethod + def rigid_body_static(rb): + return (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic) + def post_export_object(self, bobject: bpy.types.Object, o, type): # Export traits self.export_traits(bobject, o) @@ -2334,7 +2375,7 @@ class ArmoryExporter: elif rb.collision_shape == 'CAPSULE': shape = 6 body_mass = rb.mass - is_static = (not rb.enabled and not rb.kinematic) or (rb.type == 'PASSIVE' and not rb.kinematic) + is_static = self.rigid_body_static(rb) if is_static: body_mass = 0 x = {} @@ -2405,7 +2446,7 @@ class ArmoryExporter: # Rigid body constraint rbc = bobject.rigid_body_constraint if rbc is not None and rbc.enabled: - self.add_rigidbody_constraint(o, rbc) + self.add_rigidbody_constraint(o, bobject, rbc) # Camera traits if type is NodeType.CAMERA: @@ -2426,6 +2467,13 @@ class ArmoryExporter: self.material_to_object_dict[mat] = [bobject] self.material_to_arm_object_dict[mat] = [o] + # Add UniformsManager trait + if type is NodeType.MESH: + uniformManager = {} + uniformManager['type'] = 'Script' + uniformManager['class_name'] = 'armory.trait.internal.UniformsManager' + o['traits'].append(uniformManager) + # Export constraints if len(bobject.constraints) > 0: o['constraints'] = [] @@ -2600,7 +2648,7 @@ class ArmoryExporter: rbw = self.scene.rigidbody_world if rbw is not None and rbw.enabled: - out_trait['parameters'] = [str(rbw.time_scale), str(1 / rbw.steps_per_second), str(rbw.solver_iterations)] + out_trait['parameters'] = [str(rbw.time_scale), str(rbw.substeps_per_frame), str(rbw.solver_iterations)] self.output['traits'].append(out_trait) @@ -2636,7 +2684,7 @@ class ArmoryExporter: } self.output['traits'].append(out_trait) - if wrd.arm_live_patch: + if arm.utils.is_livepatch_enabled(): if 'traits' not in self.output: self.output['traits'] = [] out_trait = {'type': 'Script', 'class_name': 'armory.trait.internal.LivePatch'} @@ -2712,7 +2760,7 @@ class ArmoryExporter: o['traits'].append(out_trait) @staticmethod - def add_rigidbody_constraint(o, rbc): + def add_rigidbody_constraint(o, bobject, rbc): rb1 = rbc.object1 rb2 = rbc.object2 if rb1 is None or rb2 is None: @@ -2731,7 +2779,8 @@ class ArmoryExporter: "'" + rb1.name + "'", "'" + rb2.name + "'", str(rbc.disable_collisions).lower(), - str(breaking_threshold) + str(breaking_threshold), + str(bobject.arm_relative_physics_constraint).lower() ] } if rbc.type == "FIXED": @@ -2847,6 +2896,7 @@ class ArmoryExporter: out_world['sun_direction'] = list(world.arm_envtex_sun_direction) out_world['turbidity'] = world.arm_envtex_turbidity out_world['ground_albedo'] = world.arm_envtex_ground_albedo + out_world['nishita_density'] = list(world.arm_nishita_density) disable_hdr = world.arm_envtex_name.endswith('.jpg') @@ -2861,16 +2911,9 @@ class ArmoryExporter: rpdat = arm.utils.get_rp() solid_mat = rpdat.arm_material_model == 'Solid' arm_irradiance = rpdat.arm_irradiance and not solid_mat - arm_radiance = False - radtex = world.arm_envtex_name.rsplit('.', 1)[0] + arm_radiance = rpdat.arm_radiance + radtex = world.arm_envtex_name.rsplit('.', 1)[0] # Remove file extension irrsharmonics = world.arm_envtex_irr_name - - # Radiance - if '_EnvTex' in world.world_defs: - arm_radiance = rpdat.arm_radiance - elif '_EnvSky' in world.world_defs: - arm_radiance = rpdat.arm_radiance - radtex = 'hosek' num_mips = world.arm_envtex_num_mips strength = world.arm_envtex_strength diff --git a/blender/arm/exporter_opt.py b/blender/arm/exporter_opt.py index 60c64ae6..d13b2b53 100644 --- a/blender/arm/exporter_opt.py +++ b/blender/arm/exporter_opt.py @@ -1,11 +1,20 @@ +""" +Exports smaller geometry but is slower. +To be replaced with https://github.com/zeux/meshoptimizer +""" + from mathutils import * import numpy as np -import arm.utils import arm.log as log +import arm.utils + +if arm.is_reload(__name__): + log = arm.reload_module(log) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) -# Exports smaller geometry but is slower -# To be replaced with https://github.com/zeux/meshoptimizer class Vertex: __slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index") diff --git a/blender/arm/handlers.py b/blender/arm/handlers.py index aa8460e8..f63ef3bb 100644 --- a/blender/arm/handlers.py +++ b/blender/arm/handlers.py @@ -1,11 +1,14 @@ import importlib import os +import queue import sys import bpy from bpy.app.handlers import persistent +import arm import arm.api +import arm.live_patch as live_patch import arm.logicnode.arm_nodes as arm_nodes import arm.nodes_logic import arm.make as make @@ -13,6 +16,19 @@ import arm.make_state as state import arm.props as props import arm.utils +if arm.is_reload(__name__): + arm.api = arm.reload_module(arm.api) + live_patch = arm.reload_module(live_patch) + arm_nodes = arm.reload_module(arm_nodes) + arm.nodes_logic = arm.reload_module(arm.nodes_logic) + make = arm.reload_module(make) + state = arm.reload_module(state) + props = arm.reload_module(props) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + @persistent def on_depsgraph_update_post(self): if state.proc_build != None: @@ -40,12 +56,10 @@ def on_depsgraph_update_post(self): # Send last operator to Krom wrd = bpy.data.worlds['Arm'] - if state.proc_play != None and \ - state.target == 'krom' and \ - wrd.arm_live_patch: + if state.proc_play is not None and state.target == 'krom' and wrd.arm_live_patch: ops = bpy.context.window_manager.operators - if len(ops) > 0 and ops[-1] != None: - send_operator(ops[-1]) + if len(ops) > 0 and ops[-1] is not None: + live_patch.on_operator(ops[-1].bl_idname) # Hacky solution to update armory props after operator executions last_operator = bpy.context.active_operator @@ -89,22 +103,51 @@ def send_operator(op): else: # Rebuild make.patch() -def always(): + +def always() -> float: # Force ui redraw - if state.redraw_ui and context_screen != None: + if state.redraw_ui and context_screen is not None: for area in context_screen.areas: if area.type == 'VIEW_3D' or area.type == 'PROPERTIES': area.tag_redraw() state.redraw_ui = False # TODO: depsgraph.updates only triggers material trees space = arm.utils.logic_editor_space(context_screen) - if space != None: + if space is not None: space.node_tree.arm_cached = False return 0.5 + +def poll_threads() -> float: + """Polls the thread callback queue and if a thread has finished, it + is joined with the main thread and the corresponding callback is + executed in the main thread. + """ + try: + thread, callback = make.thread_callback_queue.get(block=False) + except queue.Empty: + return 0.25 + + thread.join() + + try: + callback() + except Exception as e: + # If there is an exception, we can no longer return the time to + # the next call to this polling function, so to keep it running + # we re-register it and then raise the original exception. + bpy.app.timers.unregister(poll_threads) + bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True) + raise e + + # Quickly check if another thread has finished + return 0.01 + + appended_py_paths = [] context_screen = None + @persistent def on_load_post(context): global appended_py_paths @@ -181,7 +224,9 @@ def register(): bpy.app.handlers.load_post.append(on_load_post) bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post) # bpy.app.handlers.undo_post.append(on_undo_post) + bpy.app.timers.register(always, persistent=True) + bpy.app.timers.register(poll_threads, persistent=True) if arm.utils.get_fp() != '': appended_py_paths = [] @@ -198,6 +243,9 @@ def register(): def unregister(): + bpy.app.timers.unregister(poll_threads) + bpy.app.timers.unregister(always) + bpy.app.handlers.load_post.remove(on_load_post) bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update_post) # bpy.app.handlers.undo_post.remove(on_undo_post) diff --git a/blender/arm/keymap.py b/blender/arm/keymap.py index b4a019ea..099db0f8 100644 --- a/blender/arm/keymap.py +++ b/blender/arm/keymap.py @@ -1,8 +1,16 @@ import bpy + +import arm import arm.props_ui as props_ui +if arm.is_reload(__name__): + props_ui = arm.reload_module(props_ui) +else: + arm.enable_reload(__name__) + arm_keymaps = [] + def register(): wm = bpy.context.window_manager addon_keyconfig = wm.keyconfigs.addon @@ -17,8 +25,11 @@ def register(): km = addon_keyconfig.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW") km.keymap_items.new(props_ui.ArmoryPlayButton.bl_idname, type='F5', value='PRESS') + km.keymap_items.new("tlm.build_lightmaps", type='F6', value='PRESS') + km.keymap_items.new("tlm.clean_lightmaps", type='F7', value='PRESS') arm_keymaps.append(km) + def unregister(): wm = bpy.context.window_manager for km in arm_keymaps: diff --git a/blender/arm/lib/armpack.py b/blender/arm/lib/armpack.py index f2f5d23d..7962608f 100755 --- a/blender/arm/lib/armpack.py +++ b/blender/arm/lib/armpack.py @@ -20,9 +20,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # -import struct import io import numpy as np +import struct + def _pack_integer(obj, fp): if obj < 0: diff --git a/blender/arm/lib/server.py b/blender/arm/lib/server.py index 9774945c..21492324 100755 --- a/blender/arm/lib/server.py +++ b/blender/arm/lib/server.py @@ -1,7 +1,7 @@ +import atexit import http.server import socketserver import subprocess -import atexit haxe_server = None diff --git a/blender/arm/lightmapper/__init__.py b/blender/arm/lightmapper/__init__.py index eaf15cec..8df717b4 100644 --- a/blender/arm/lightmapper/__init__.py +++ b/blender/arm/lightmapper/__init__.py @@ -1 +1 @@ -__all__ = ('Operators', 'Properties', 'Utility', 'Keymap') \ No newline at end of file +__all__ = ('Operators', 'Panels', 'Properties', 'Preferences', 'Utility', 'Keymap') \ No newline at end of file diff --git a/blender/arm/lightmapper/assets/TLM_Overlay.png b/blender/arm/lightmapper/assets/TLM_Overlay.png new file mode 100644 index 00000000..50bef87f Binary files /dev/null and b/blender/arm/lightmapper/assets/TLM_Overlay.png differ diff --git a/blender/arm/lightmapper/assets/tlm_data.blend b/blender/arm/lightmapper/assets/tlm_data.blend index 409c894b..8dabd2cb 100644 Binary files a/blender/arm/lightmapper/assets/tlm_data.blend and b/blender/arm/lightmapper/assets/tlm_data.blend differ diff --git a/blender/arm/lightmapper/keymap/__init__.py b/blender/arm/lightmapper/keymap/__init__.py index 079b3c5d..e69de29b 100644 --- a/blender/arm/lightmapper/keymap/__init__.py +++ b/blender/arm/lightmapper/keymap/__init__.py @@ -1,7 +0,0 @@ -from . import keymap - -def register(): - keymap.register() - -def unregister(): - keymap.unregister() \ No newline at end of file diff --git a/blender/arm/lightmapper/keymap/keymap.py b/blender/arm/lightmapper/keymap/keymap.py index adfa72c2..96a52f61 100644 --- a/blender/arm/lightmapper/keymap/keymap.py +++ b/blender/arm/lightmapper/keymap/keymap.py @@ -9,6 +9,8 @@ def register(): winman = bpy.context.window_manager keyman = winman.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW") + + #TODO - In Armory3D, merge with keymap.py keyman.keymap_items.new('tlm.build_lightmaps', type='F6', value='PRESS') keyman.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS') tlm_keymaps.append(keyman) diff --git a/blender/arm/lightmapper/operators/__init__.py b/blender/arm/lightmapper/operators/__init__.py index 05a2ed7d..7098994f 100644 --- a/blender/arm/lightmapper/operators/__init__.py +++ b/blender/arm/lightmapper/operators/__init__.py @@ -6,10 +6,11 @@ classes = [ tlm.TLM_BuildLightmaps, tlm.TLM_CleanLightmaps, tlm.TLM_ExploreLightmaps, - tlm.TLM_EnableSelection, + tlm.TLM_EnableSet, tlm.TLM_DisableSelection, tlm.TLM_RemoveLightmapUV, tlm.TLM_SelectLightmapped, + tlm.TLM_ToggleTexelDensity, installopencv.TLM_Install_OpenCV, tlm.TLM_AtlasListNewItem, tlm.TLM_AtlastListDeleteItem, @@ -20,6 +21,8 @@ classes = [ tlm.TLM_StartServer, tlm.TLM_BuildEnvironmentProbes, tlm.TLM_CleanBuildEnvironmentProbes, + tlm.TLM_PrepareUVMaps, + tlm.TLM_LoadLightmaps, imagetools.TLM_ImageUpscale, imagetools.TLM_ImageDownscale diff --git a/blender/arm/lightmapper/operators/imagetools.py b/blender/arm/lightmapper/operators/imagetools.py index 83062973..9e203114 100644 --- a/blender/arm/lightmapper/operators/imagetools.py +++ b/blender/arm/lightmapper/operators/imagetools.py @@ -1,4 +1,4 @@ -import bpy, os, time +import bpy, os, time, importlib class TLM_ImageUpscale(bpy.types.Operator): bl_idname = "tlm.image_upscale" @@ -8,6 +8,69 @@ class TLM_ImageUpscale(bpy.types.Operator): def invoke(self, context, event): + cv2 = importlib.util.find_spec("cv2") + + if cv2 is None: + print("CV2 not found - Ignoring filtering") + return 0 + else: + cv2 = importlib.__import__("cv2") + + for area in bpy.context.screen.areas: + if area.type == "IMAGE_EDITOR": + active_image = area.spaces.active.image + + if active_image.source == "FILE": + img_path = active_image.filepath_raw + filename = os.path.basename(img_path) + + basename = os.path.splitext(filename)[0] + extension = os.path.splitext(filename)[1] + + size_x = active_image.size[0] + size_y = active_image.size[1] + + dir_path = os.path.dirname(os.path.realpath(img_path)) + + #newfile = os.path.join(dir_path, basename + "_" + str(size_x) + "_" + str(size_y) + extension) + newfile = os.path.join(dir_path, basename + extension) + os.rename(img_path, newfile) + + basefile = cv2.imread(newfile, cv2.IMREAD_UNCHANGED) + + scale_percent = 200 # percent of original size + width = int(basefile.shape[1] * scale_percent / 100) + height = int(basefile.shape[0] * scale_percent / 100) + dim = (width, height) + + if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest": + interp = cv2.INTER_NEAREST + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area": + interp = cv2.INTER_AREA + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear": + interp = cv2.INTER_LINEAR + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic": + interp = cv2.INTER_CUBIC + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos": + interp = cv2.INTER_LANCZOS4 + + resized = cv2.resize(basefile, dim, interpolation = interp) + + #resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension) + resizedFile = os.path.join(dir_path, basename + extension) + + cv2.imwrite(resizedFile, resized) + + active_image.filepath_raw = resizedFile + bpy.ops.image.reload() + + print(newfile) + print(img_path) + + else: + + print("Please save image") + print("Upscale") return {'RUNNING_MODAL'} @@ -20,6 +83,111 @@ class TLM_ImageDownscale(bpy.types.Operator): def invoke(self, context, event): - print("Downscale") + cv2 = importlib.util.find_spec("cv2") + + if cv2 is None: + print("CV2 not found - Ignoring filtering") + return 0 + else: + cv2 = importlib.__import__("cv2") + + for area in bpy.context.screen.areas: + if area.type == "IMAGE_EDITOR": + active_image = area.spaces.active.image + + if active_image.source == "FILE": + img_path = active_image.filepath_raw + filename = os.path.basename(img_path) + + basename = os.path.splitext(filename)[0] + extension = os.path.splitext(filename)[1] + + size_x = active_image.size[0] + size_y = active_image.size[1] + + dir_path = os.path.dirname(os.path.realpath(img_path)) + + #newfile = os.path.join(dir_path, basename + "_" + str(size_x) + "_" + str(size_y) + extension) + newfile = os.path.join(dir_path, basename + extension) + os.rename(img_path, newfile) + + basefile = cv2.imread(newfile, cv2.IMREAD_UNCHANGED) + + scale_percent = 50 # percent of original size + width = int(basefile.shape[1] * scale_percent / 100) + height = int(basefile.shape[0] * scale_percent / 100) + dim = (width, height) + + if dim[0] > 1 or dim[1] > 1: + + if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest": + interp = cv2.INTER_NEAREST + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area": + interp = cv2.INTER_AREA + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear": + interp = cv2.INTER_LINEAR + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic": + interp = cv2.INTER_CUBIC + elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos": + interp = cv2.INTER_LANCZOS4 + + resized = cv2.resize(basefile, dim, interpolation = interp) + + #resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension) + resizedFile = os.path.join(dir_path, basename + extension) + + cv2.imwrite(resizedFile, resized) + + active_image.filepath_raw = resizedFile + bpy.ops.image.reload() + + print(newfile) + print(img_path) + + else: + + print("Please save image") + + print("Upscale") + + return {'RUNNING_MODAL'} + +class TLM_ImageSwitchUp(bpy.types.Operator): + bl_idname = "tlm.image_switchup" + bl_label = "Quickswitch Up" + bl_description = "Switches to a cached upscaled image" + bl_options = {'REGISTER', 'UNDO'} + + def invoke(self, context, event): + + for area in bpy.context.screen.areas: + if area.type == "IMAGE_EDITOR": + active_image = area.spaces.active.image + + if active_image.source == "FILE": + img_path = active_image.filepath_raw + filename = os.path.basename(img_path) + + print("Switch up") + + return {'RUNNING_MODAL'} + +class TLM_ImageSwitchDown(bpy.types.Operator): + bl_idname = "tlm.image_switchdown" + bl_label = "Quickswitch Down" + bl_description = "Switches to a cached downscaled image" + bl_options = {'REGISTER', 'UNDO'} + + def invoke(self, context, event): + + for area in bpy.context.screen.areas: + if area.type == "IMAGE_EDITOR": + active_image = area.spaces.active.image + + if active_image.source == "FILE": + img_path = active_image.filepath_raw + filename = os.path.basename(img_path) + + print("Switch Down") return {'RUNNING_MODAL'} \ No newline at end of file diff --git a/blender/arm/lightmapper/operators/installopencv.py b/blender/arm/lightmapper/operators/installopencv.py index b3d173a6..21f5e37f 100644 --- a/blender/arm/lightmapper/operators/installopencv.py +++ b/blender/arm/lightmapper/operators/installopencv.py @@ -21,7 +21,10 @@ class TLM_Install_OpenCV(bpy.types.Operator): print("Module OpenCV") - pythonbinpath = bpy.app.binary_path_python + if (2, 91, 0) > bpy.app.version: + pythonbinpath = bpy.app.binary_path_python + else: + pythonbinpath = sys.executable if platform.system() == "Windows": pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib") diff --git a/blender/arm/lightmapper/operators/tlm.py b/blender/arm/lightmapper/operators/tlm.py index ace239d7..a46f3604 100644 --- a/blender/arm/lightmapper/operators/tlm.py +++ b/blender/arm/lightmapper/operators/tlm.py @@ -1,9 +1,36 @@ -import bpy, os, time, blf, webbrowser, platform +import bpy, os, time, blf, webbrowser, platform, numpy, bmesh import math, subprocess, multiprocessing +from .. utility import utility from .. utility import build from .. utility.cycles import cache from .. network import server +def setObjectLightmapByWeight(minimumRes, maximumRes, objWeight): + + availableResolutions = [32,64,128,256,512,1024,2048,4096,8192] + + minRes = minimumRes + minResIdx = availableResolutions.index(minRes) + maxRes = maximumRes + maxResIdx = availableResolutions.index(maxRes) + + exampleWeight = objWeight + + if minResIdx == maxResIdx: + pass + else: + + increment = 1.0/(maxResIdx-minResIdx) + + assortedRange = [] + + for a in numpy.arange(0.0, 1.0, increment): + assortedRange.append(round(a, 2)) + + assortedRange.append(1.0) + nearestWeight = min(assortedRange, key=lambda x:abs(x - exampleWeight)) + return (availableResolutions[assortedRange.index(nearestWeight) + minResIdx]) + class TLM_BuildLightmaps(bpy.types.Operator): bl_idname = "tlm.build_lightmaps" bl_label = "Build Lightmaps" @@ -52,13 +79,13 @@ class TLM_CleanLightmaps(bpy.types.Operator): for file in os.listdir(dirpath): os.remove(os.path.join(dirpath + "/" + file)) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_restore(obj) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_rename(obj) @@ -75,8 +102,8 @@ class TLM_CleanLightmaps(bpy.types.Operator): if image.name.endswith("_baked"): bpy.data.images.remove(image, do_unlink=True) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: @@ -92,14 +119,17 @@ class TLM_CleanLightmaps(bpy.types.Operator): bpy.ops.object.select_all(action='DESELECT') obj.select_set(True) bpy.context.view_layer.objects.active = obj - #print(x) uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + for i in range(0, len(uv_layers)): - if uv_layers[i].name == 'UVMap_Lightmap': + if uv_layers[i].name == uv_channel: uv_layers.active_index = i - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Lightmap shift A") break bpy.ops.object.mode_set(mode='EDIT') @@ -111,9 +141,11 @@ class TLM_CleanLightmaps(bpy.types.Operator): bpy.ops.object.mode_set(mode='OBJECT') if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - #print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name) print("Resized for obj: " + obj.name) + if "Lightmap" in obj: + del obj["Lightmap"] + return {'FINISHED'} class TLM_ExploreLightmaps(bpy.types.Operator): @@ -153,63 +185,285 @@ class TLM_ExploreLightmaps(bpy.types.Operator): return {'FINISHED'} -class TLM_EnableSelection(bpy.types.Operator): - """Enable for selection""" - bl_idname = "tlm.enable_selection" - bl_label = "Enable for selection" - bl_description = "Enable for selection" +class TLM_EnableSet(bpy.types.Operator): + """Enable for set""" + bl_idname = "tlm.enable_set" + bl_label = "Enable for set" + bl_description = "Enable for set" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): scene = context.scene - for obj in bpy.context.selected_objects: - obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True + weightList = {} #ObjName : [Dimension,Weight] + max = 0 - if scene.TLM_SceneProperties.tlm_override_object_settings: - obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution - obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode - obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = scene.TLM_SceneProperties.tlm_mesh_unwrap_margin - obj.TLM_ObjectProperties.tlm_postpack_object = scene.TLM_SceneProperties.tlm_postpack_object + if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene": + for obj in bpy.context.scene.objects: + if obj.type == "MESH": - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - obj.TLM_ObjectProperties.tlm_atlas_pointer = scene.TLM_SceneProperties.tlm_atlas_pointer + print("Enabling for scene: " + obj.name) + + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode + obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin + obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object - obj.TLM_ObjectProperties.tlm_postatlas_pointer = scene.TLM_SceneProperties.tlm_postatlas_pointer + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer + obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single": + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension": + obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z + weightList[obj.name] = [obj_dimensions, 0] + if obj_dimensions > max: + max = obj_dimensions + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface": + bm = bmesh.new() + bm.from_mesh(obj.data) + area = sum(f.calc_area() for f in bm.faces) + weightList[obj.name] = [area, 0] + if area > max: + max = area + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume": + bm = bmesh.new() + bm.from_mesh(obj.data) + volume = float( bm.calc_volume()) + weightList[obj.name] = [volume, 0] + if volume > max: + max = volume + + elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection": + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + + print("Enabling for selection: " + obj.name) + + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode + obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin + obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object + + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer + + obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single": + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension": + obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z + weightList[obj.name] = [obj_dimensions, 0] + if obj_dimensions > max: + max = obj_dimensions + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface": + bm = bmesh.new() + bm.from_mesh(obj.data) + area = sum(f.calc_area() for f in bm.faces) + weightList[obj.name] = [area, 0] + if area > max: + max = area + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume": + bm = bmesh.new() + bm.from_mesh(obj.data) + volume = float( bm.calc_volume()) + weightList[obj.name] = [volume, 0] + if volume > max: + max = volume + + else: #Enabled + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + print("Enabling for designated: " + obj.name) + + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode + obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin + obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object + + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer + + obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single": + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension": + obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z + weightList[obj.name] = [obj_dimensions, 0] + if obj_dimensions > max: + max = obj_dimensions + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface": + bm = bmesh.new() + bm.from_mesh(obj.data) + area = sum(f.calc_area() for f in bm.faces) + weightList[obj.name] = [area, 0] + if area > max: + max = area + elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume": + bm = bmesh.new() + bm.from_mesh(obj.data) + volume = float( bm.calc_volume()) + weightList[obj.name] = [volume, 0] + if volume > max: + max = volume + + if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene": + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single": + for key in weightList: + weightList[obj.name][1] = weightList[obj.name][0] / max + a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1]) + print(str(a) + "/" + str(weightList[obj.name][1])) + print("Scale: " + str(weightList[obj.name][0])) + print("Obj: " + obj.name) + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a) + + elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection": + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single": + for key in weightList: + weightList[obj.name][1] = weightList[obj.name][0] / max + a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1]) + print(str(a) + "/" + str(weightList[obj.name][1])) + print("Scale: " + str(weightList[obj.name][0])) + print("Obj: " + obj.name) + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a) + + + else: #Enabled + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single": + for key in weightList: + weightList[obj.name][1] = weightList[obj.name][0] / max + a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1]) + print(str(a) + "/" + str(weightList[obj.name][1])) + print("Scale: " + str(weightList[obj.name][0])) + print("Obj: " + obj.name) + print("") + obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a) + return{'FINISHED'} class TLM_DisableSelection(bpy.types.Operator): - """Disable for selection""" + """Disable for set""" bl_idname = "tlm.disable_selection" - bl_label = "Disable for selection" + bl_label = "Disable for set" bl_description = "Disable for selection" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - for obj in bpy.context.selected_objects: - obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False + scene = context.scene + + weightList = {} #ObjName : [Dimension,Weight] + max = 0 + + if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene": + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False + + elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection": + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False + + + else: #Enabled + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False + return{'FINISHED'} class TLM_RemoveLightmapUV(bpy.types.Operator): - """Remove Lightmap UV for selection""" + """Remove Lightmap UV for set""" bl_idname = "tlm.remove_uv_selection" bl_label = "Remove Lightmap UV" - bl_description = "Remove Lightmap UV for selection" + bl_description = "Remove Lightmap UV for set" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - for obj in bpy.context.selected_objects: - if obj.type == "MESH": - uv_layers = obj.data.uv_layers + if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene": + for obj in bpy.context.scene.objects: + if obj.type == "MESH": - for uvlayer in uv_layers: - if uvlayer.name == "UVMap_Lightmap": - uv_layers.remove(uvlayer) + uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + for uvlayer in uv_layers: + if uvlayer.name == uv_channel: + uv_layers.remove(uvlayer) + + elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection": + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + + uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + for uvlayer in uv_layers: + if uvlayer.name == uv_channel: + uv_layers.remove(uvlayer) + + else: #Enabled + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + for uvlayer in uv_layers: + if uvlayer.name == uv_channel: + uv_layers.remove(uvlayer) return{'FINISHED'} @@ -222,8 +476,8 @@ class TLM_SelectLightmapped(bpy.types.Operator): def execute(self, context): - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: obj.select_set(True) @@ -278,7 +532,7 @@ class TLM_AtlastListDeleteItem(bpy.types.Operator): list = scene.TLM_AtlasList index = scene.TLM_AtlasListItem - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: atlasName = scene.TLM_AtlasList[index].name @@ -310,7 +564,7 @@ class TLM_PostAtlastListDeleteItem(bpy.types.Operator): list = scene.TLM_PostAtlasList index = scene.TLM_PostAtlasListItem - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: atlasName = scene.TLM_PostAtlasList[index].name @@ -437,7 +691,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator): def invoke(self, context, event): - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.type == "LIGHT_PROBE": if obj.data.type == "CUBEMAP": @@ -500,7 +754,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator): cam.rotation_euler = positions[val] filename = os.path.join(directory, val) + "_" + camobj_name + ".hdr" - bpy.data.scenes['Scene'].render.filepath = filename + bpy.context.scene.render.filepath = filename print("Writing out: " + val) bpy.ops.render.render(write_still=True) @@ -642,7 +896,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator): subprocess.call([envpipe3], shell=True) - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: obj.select_set(False) cam_obj.select_set(True) @@ -686,7 +940,92 @@ class TLM_MergeAdjacentActors(bpy.types.Operator): scene = context.scene - + return {'FINISHED'} + +class TLM_PrepareUVMaps(bpy.types.Operator): + bl_idname = "tlm.prepare_uvmaps" + bl_label = "Prepare UV maps" + bl_description = "Prepare UV lightmaps for selected objects" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + scene = context.scene + + + + return {'FINISHED'} + +class TLM_LoadLightmaps(bpy.types.Operator): + bl_idname = "tlm.load_lightmaps" + bl_label = "Load Lightmaps" + bl_description = "Load lightmaps from selected folder" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + scene = context.scene + + utility.transfer_load() + + build.finish_assemble() + + return {'FINISHED'} + +class TLM_ToggleTexelDensity(bpy.types.Operator): + bl_idname = "tlm.toggle_texel_density" + bl_label = "Toggle Texel Density" + bl_description = "Toggle visualize lightmap texel density for selected objects" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + scene = context.scene + + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + uv_layers = obj.data.uv_layers + + #if the object has a td_vis in the uv maps, toggle off + #else toggle on + + if obj.TLM_ObjectProperties.tlm_use_default_channel: + + for i in range(0, len(uv_layers)): + if uv_layers[i].name == 'UVMap_Lightmap': + uv_layers.active_index = i + break + else: + + for i in range(0, len(uv_layers)): + if uv_layers[i].name == obj.TLM_ObjectProperties.tlm_uv_channel: + uv_layers.active_index = i + break + + #filepath = r"C:\path\to\image.png" + + #img = bpy.data.images.load(filepath) + + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + space_data = area.spaces.active + bpy.ops.screen.area_dupli('INVOKE_DEFAULT') + new_window = context.window_manager.windows[-1] + + area = new_window.screen.areas[-1] + area.type = 'VIEW_3D' + #bg = space_data.background_images.new() + print(bpy.context.object) + bpy.ops.object.bake_td_uv_to_vc() + + #bg.image = img + break + + + #set active uv_layer to + + + print("TLM_Viz_Toggle") return {'FINISHED'} @@ -698,7 +1037,4 @@ def TLM_HalfResolution(): pass def TLM_DivideLMGroups(): - pass - -def TLM_LoadFromFolder(): pass \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/__init__.py b/blender/arm/lightmapper/panels/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/blender/arm/lightmapper/panels/image.py b/blender/arm/lightmapper/panels/image.py new file mode 100644 index 00000000..929907e2 --- /dev/null +++ b/blender/arm/lightmapper/panels/image.py @@ -0,0 +1,66 @@ +import bpy, os, math, importlib + +from bpy.types import Menu, Operator, Panel, UIList + +from bpy.props import ( + StringProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + EnumProperty, + PointerProperty, +) + +class TLM_PT_Imagetools(bpy.types.Panel): + bl_label = "TLM Imagetools" + bl_space_type = "IMAGE_EDITOR" + bl_region_type = 'UI' + bl_category = "TLM Imagetools" + + def draw_header(self, _): + layout = self.layout + row = layout.row(align=True) + row.label(text ="Image Tools") + + def draw(self, context): + layout = self.layout + + activeImg = None + + for area in bpy.context.screen.areas: + if area.type == 'IMAGE_EDITOR': + activeImg = area.spaces.active.image + + if activeImg is not None and activeImg.name != "Render Result" and activeImg.name != "Viewer Node": + + cv2 = importlib.util.find_spec("cv2") + + if cv2 is None: + row = layout.row(align=True) + row.label(text ="OpenCV not installed.") + else: + + row = layout.row(align=True) + row.label(text ="Method") + row = layout.row(align=True) + row.prop(activeImg.TLM_ImageProperties, "tlm_image_scale_engine") + row = layout.row(align=True) + row.prop(activeImg.TLM_ImageProperties, "tlm_image_cache_switch") + row = layout.row(align=True) + row.operator("tlm.image_upscale") + if activeImg.TLM_ImageProperties.tlm_image_cache_switch: + row = layout.row(align=True) + row.label(text ="Switch up.") + row = layout.row(align=True) + row.operator("tlm.image_downscale") + if activeImg.TLM_ImageProperties.tlm_image_cache_switch: + row = layout.row(align=True) + row.label(text ="Switch down.") + if activeImg.TLM_ImageProperties.tlm_image_scale_engine == "OpenCV": + row = layout.row(align=True) + row.prop(activeImg.TLM_ImageProperties, "tlm_image_scale_method") + + else: + row = layout.row(align=True) + row.label(text ="Select an image") \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/light.py b/blender/arm/lightmapper/panels/light.py new file mode 100644 index 00000000..fd576af1 --- /dev/null +++ b/blender/arm/lightmapper/panels/light.py @@ -0,0 +1,17 @@ +import bpy +from bpy.props import * +from bpy.types import Menu, Panel + +class TLM_PT_LightMenu(bpy.types.Panel): + bl_label = "The Lightmapper" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "light" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scene = context.scene + obj = bpy.context.object + layout.use_property_split = True + layout.use_property_decorate = False \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/object.py b/blender/arm/lightmapper/panels/object.py new file mode 100644 index 00000000..7f0e7abe --- /dev/null +++ b/blender/arm/lightmapper/panels/object.py @@ -0,0 +1,118 @@ +import bpy +from bpy.props import * +from bpy.types import Menu, Panel + +class TLM_PT_ObjectMenu(bpy.types.Panel): + bl_label = "The Lightmapper" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scene = context.scene + obj = bpy.context.object + layout.use_property_split = True + layout.use_property_decorate = False + + if obj.type == "MESH": + row = layout.row(align=True) + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_use") + + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + row = layout.row() + row.prop(obj.TLM_ObjectProperties, "tlm_use_default_channel") + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + + row = layout.row() + row.prop_search(obj.TLM_ObjectProperties, "tlm_uv_channel", obj.data, "uv_layers", text='UV Channel') + + row = layout.row() + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_resolution") + if obj.TLM_ObjectProperties.tlm_use_default_channel: + row = layout.row() + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_unwrap_mode") + row = layout.row() + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + + if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0: + row = layout.row() + item = scene.TLM_AtlasList[scene.TLM_AtlasListItem] + row.prop_search(obj.TLM_ObjectProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group') + row = layout.row() + else: + row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") + row = layout.row() + + else: + row = layout.row() + row.prop(obj.TLM_ObjectProperties, "tlm_postpack_object") + row = layout.row() + + + if obj.TLM_ObjectProperties.tlm_postpack_object and obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA": + if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0: + row = layout.row() + item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem] + row.prop_search(obj.TLM_ObjectProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group') + row = layout.row() + + else: + row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") + row = layout.row() + + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_unwrap_margin") + row = layout.row() + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filter_override") + row = layout.row() + if obj.TLM_ObjectProperties.tlm_mesh_filter_override: + row = layout.row(align=True) + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_mode") + row = layout.row(align=True) + if obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Gaussian": + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_gaussian_strength") + row = layout.row(align=True) + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations") + elif obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Box": + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_box_strength") + row = layout.row(align=True) + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations") + elif obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Bilateral": + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_diameter") + row = layout.row(align=True) + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_color_deviation") + row = layout.row(align=True) + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_coordinate_deviation") + row = layout.row(align=True) + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations") + else: + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_median_kernel", expand=True) + row = layout.row(align=True) + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations") + + +class TLM_PT_MaterialMenu(bpy.types.Panel): + bl_label = "The Lightmapper" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "material" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scene = context.scene + obj = bpy.context.object + layout.use_property_split = True + layout.use_property_decorate = False + + mat = bpy.context.material + if mat == None: + return + + if obj.type == "MESH": + + row = layout.row() + row.prop(mat, "TLM_ignore") \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/scene.py b/blender/arm/lightmapper/panels/scene.py new file mode 100644 index 00000000..de36f22f --- /dev/null +++ b/blender/arm/lightmapper/panels/scene.py @@ -0,0 +1,584 @@ +import bpy, importlib, math +from bpy.props import * +from bpy.types import Menu, Panel +from .. utility import icon +from .. properties.denoiser import oidn, optix + +class TLM_PT_Settings(bpy.types.Panel): + bl_label = "Settings" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + + row = layout.row(align=True) + + #We list LuxCoreRender as available, by default we assume Cycles exists + row.prop(sceneProperties, "tlm_lightmap_engine") + + if sceneProperties.tlm_lightmap_engine == "Cycles": + + #CYCLES SETTINGS HERE + engineProperties = scene.TLM_EngineProperties + + row = layout.row(align=True) + row.label(text="General Settings") + row = layout.row(align=True) + row.operator("tlm.build_lightmaps") + row = layout.row(align=True) + row.operator("tlm.clean_lightmaps") + row = layout.row(align=True) + row.operator("tlm.explore_lightmaps") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_apply_on_unwrap") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_headless") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_alert_on_finish") + + if sceneProperties.tlm_alert_on_finish: + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_alert_sound") + + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_verbose") + #row = layout.row(align=True) + #row.prop(sceneProperties, "tlm_compile_statistics") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_override_bg_color") + if sceneProperties.tlm_override_bg_color: + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_override_color") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_reset_uv") + + row = layout.row(align=True) + try: + if bpy.context.scene["TLM_Buildstat"] is not None: + row.label(text="Last build completed in: " + str(bpy.context.scene["TLM_Buildstat"][0])) + except: + pass + + row = layout.row(align=True) + row.label(text="Cycles Settings") + + row = layout.row(align=True) + row.prop(engineProperties, "tlm_mode") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_quality") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_resolution_scale") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_bake_mode") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_target") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_lighting_mode") + # if scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectao": + # row = layout.row(align=True) + # row.prop(engineProperties, "tlm_premultiply_ao") + if scene.TLM_EngineProperties.tlm_bake_mode == "Background": + row = layout.row(align=True) + row.label(text="Warning! Background mode is currently unstable", icon_value=2) + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_network_render") + if sceneProperties.tlm_network_render: + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_network_paths") + #row = layout.row(align=True) + #row.prop(sceneProperties, "tlm_network_dir") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_caching_mode") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_directional_mode") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_lightmap_savedir") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_dilation_margin") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_exposure_multiplier") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_setting_supersample") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_metallic_clamp") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_texture_interpolation") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_texture_extrapolation") + + + + # elif sceneProperties.tlm_lightmap_engine == "LuxCoreRender": + + # engineProperties = scene.TLM_Engine2Properties + # row = layout.row(align=True) + # row.prop(engineProperties, "tlm_luxcore_dir") + # row = layout.row(align=True) + # row.operator("tlm.build_lightmaps") + # #LUXCORE SETTINGS HERE + # #luxcore_available = False + + # #Look for Luxcorerender in the renderengine classes + # # for engine in bpy.types.RenderEngine.__subclasses__(): + # # if engine.bl_idname == "LUXCORE": + # # luxcore_available = True + # # break + + # # row = layout.row(align=True) + # # if not luxcore_available: + # # row.label(text="Please install BlendLuxCore.") + # # else: + # # row.label(text="LuxCoreRender not yet available.") + + elif sceneProperties.tlm_lightmap_engine == "OctaneRender": + + engineProperties = scene.TLM_Engine3Properties + + #LUXCORE SETTINGS HERE + octane_available = True + + + + row = layout.row(align=True) + row.operator("tlm.build_lightmaps") + row = layout.row(align=True) + row.operator("tlm.clean_lightmaps") + row = layout.row(align=True) + row.operator("tlm.explore_lightmaps") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_verbose") + row = layout.row(align=True) + row.prop(engineProperties, "tlm_lightmap_savedir") + row = layout.row(align=True) + +class TLM_PT_Denoise(bpy.types.Panel): + bl_label = "Denoise" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw_header(self, context): + scene = context.scene + sceneProperties = scene.TLM_SceneProperties + self.layout.prop(sceneProperties, "tlm_denoise_use", text="") + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + layout.active = sceneProperties.tlm_denoise_use + + row = layout.row(align=True) + + #row.prop(sceneProperties, "tlm_denoiser", expand=True) + #row = layout.row(align=True) + row.prop(sceneProperties, "tlm_denoise_engine", expand=True) + row = layout.row(align=True) + + if sceneProperties.tlm_denoise_engine == "Integrated": + row.label(text="No options for Integrated.") + elif sceneProperties.tlm_denoise_engine == "OIDN": + denoiseProperties = scene.TLM_OIDNEngineProperties + row.prop(denoiseProperties, "tlm_oidn_path") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_oidn_verbose") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_oidn_threads") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_oidn_maxmem") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_oidn_affinity") + # row = layout.row(align=True) + # row.prop(denoiseProperties, "tlm_denoise_ao") + elif sceneProperties.tlm_denoise_engine == "Optix": + denoiseProperties = scene.TLM_OptixEngineProperties + row.prop(denoiseProperties, "tlm_optix_path") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_optix_verbose") + row = layout.row(align=True) + row.prop(denoiseProperties, "tlm_optix_maxmem") + #row = layout.row(align=True) + #row.prop(denoiseProperties, "tlm_denoise_ao") + +class TLM_PT_Filtering(bpy.types.Panel): + bl_label = "Filtering" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw_header(self, context): + scene = context.scene + sceneProperties = scene.TLM_SceneProperties + self.layout.prop(sceneProperties, "tlm_filtering_use", text="") + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + layout.active = sceneProperties.tlm_filtering_use + #row = layout.row(align=True) + #row.label(text="TODO MAKE CHECK") + #row = layout.row(align=True) + #row.prop(sceneProperties, "tlm_filtering_engine", expand=True) + row = layout.row(align=True) + + if sceneProperties.tlm_filtering_engine == "OpenCV": + + cv2 = importlib.util.find_spec("cv2") + + if cv2 is None: + row = layout.row(align=True) + row.label(text="OpenCV is not installed. Please install it as an administrator.") + row = layout.row(align=True) + row.operator("tlm.install_opencv_lightmaps") + else: + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_mode") + row = layout.row(align=True) + if scene.TLM_SceneProperties.tlm_filtering_mode == "Gaussian": + row.prop(scene.TLM_SceneProperties, "tlm_filtering_gaussian_strength") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") + elif scene.TLM_SceneProperties.tlm_filtering_mode == "Box": + row.prop(scene.TLM_SceneProperties, "tlm_filtering_box_strength") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") + + elif scene.TLM_SceneProperties.tlm_filtering_mode == "Bilateral": + row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_diameter") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_color_deviation") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_coordinate_deviation") + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") + else: + row.prop(scene.TLM_SceneProperties, "tlm_filtering_median_kernel", expand=True) + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") + else: + row = layout.row(align=True) + row.prop(scene.TLM_SceneProperties, "tlm_numpy_filtering_mode") + + +class TLM_PT_Encoding(bpy.types.Panel): + bl_label = "Encoding" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw_header(self, context): + scene = context.scene + sceneProperties = scene.TLM_SceneProperties + self.layout.prop(sceneProperties, "tlm_encoding_use", text="") + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + layout.active = sceneProperties.tlm_encoding_use + + sceneProperties = scene.TLM_SceneProperties + row = layout.row(align=True) + + if scene.TLM_EngineProperties.tlm_bake_mode == "Background": + row.label(text="Encoding options disabled in background mode") + row = layout.row(align=True) + + else: + + row.prop(sceneProperties, "tlm_encoding_device", expand=True) + row = layout.row(align=True) + + if sceneProperties.tlm_encoding_device == "CPU": + row.prop(sceneProperties, "tlm_encoding_mode_a", expand=True) + else: + row.prop(sceneProperties, "tlm_encoding_mode_b", expand=True) + + if sceneProperties.tlm_encoding_device == "CPU": + if sceneProperties.tlm_encoding_mode_a == "RGBM": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_encoding_range") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_decoder_setup") + if sceneProperties.tlm_encoding_mode_a == "RGBD": + pass + if sceneProperties.tlm_encoding_mode_a == "HDR": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_format") + else: + + if sceneProperties.tlm_encoding_mode_b == "RGBM": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_encoding_range") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_decoder_setup") + + if sceneProperties.tlm_encoding_mode_b == "LogLuv" and sceneProperties.tlm_encoding_device == "GPU": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_decoder_setup") + if sceneProperties.tlm_encoding_mode_b == "HDR": + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_format") + +class TLM_PT_Utility(bpy.types.Panel): + bl_label = "Utilities" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw(self, context): + layout = self.layout + scene = context.scene + layout.use_property_split = True + layout.use_property_decorate = False + sceneProperties = scene.TLM_SceneProperties + + row = layout.row(align=True) + row.label(text="Enable Lightmaps for set") + row = layout.row(align=True) + row.operator("tlm.enable_set") + row = layout.row(align=True) + row.prop(sceneProperties, "tlm_utility_set") + row = layout.row(align=True) + #row.label(text="ABCD") + row.prop(sceneProperties, "tlm_mesh_lightmap_unwrap_mode") + + if sceneProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + + if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0: + row = layout.row() + item = scene.TLM_AtlasList[scene.TLM_AtlasListItem] + row.prop_search(sceneProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group') + else: + row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") + + else: + + row = layout.row() + row.prop(sceneProperties, "tlm_postpack_object") + row = layout.row() + + if sceneProperties.tlm_postpack_object and sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA": + + if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0: + row = layout.row() + item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem] + row.prop_search(sceneProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group') + row = layout.row() + + else: + row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") + row = layout.row() + + row.prop(sceneProperties, "tlm_mesh_unwrap_margin") + row = layout.row() + row.prop(sceneProperties, "tlm_resolution_weight") + + if sceneProperties.tlm_resolution_weight == "Single": + row = layout.row() + row.prop(sceneProperties, "tlm_mesh_lightmap_resolution") + else: + row = layout.row() + row.prop(sceneProperties, "tlm_resolution_min") + row = layout.row() + row.prop(sceneProperties, "tlm_resolution_max") + + row = layout.row() + row.operator("tlm.disable_selection") + row = layout.row(align=True) + row.operator("tlm.select_lightmapped_objects") + row = layout.row(align=True) + row.operator("tlm.remove_uv_selection") + row = layout.row(align=True) + + + row.label(text="Environment Probes") + row = layout.row() + row.operator("tlm.build_environmentprobe") + row = layout.row() + row.operator("tlm.clean_environmentprobe") + row = layout.row() + row.prop(sceneProperties, "tlm_environment_probe_engine") + row = layout.row() + row.prop(sceneProperties, "tlm_cmft_path") + row = layout.row() + row.prop(sceneProperties, "tlm_environment_probe_resolution") + row = layout.row() + row.prop(sceneProperties, "tlm_create_spherical") + + if sceneProperties.tlm_create_spherical: + + row = layout.row() + row.prop(sceneProperties, "tlm_invert_direction") + row = layout.row() + row.prop(sceneProperties, "tlm_write_sh") + row = layout.row() + row.prop(sceneProperties, "tlm_write_radiance") + + row = layout.row(align=True) + row.label(text="Load lightmaps") + row = layout.row() + row.prop(sceneProperties, "tlm_load_folder") + row = layout.row() + row.operator("tlm.load_lightmaps") + +class TLM_PT_Additional(bpy.types.Panel): + bl_label = "Additional" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + bl_options = {'DEFAULT_CLOSED'} + bl_parent_id = "ARM_PT_BakePanel" + + @classmethod + def poll(self, context): + scene = context.scene + return scene.arm_bakemode == "Lightmap" + + def draw(self, context): + layout = self.layout + scene = context.scene + sceneProperties = scene.TLM_SceneProperties + atlasListItem = scene.TLM_AtlasListItem + atlasList = scene.TLM_AtlasList + postatlasListItem = scene.TLM_PostAtlasListItem + postatlasList = scene.TLM_PostAtlasList + + layout.label(text="Network Rendering") + row = layout.row() + row.operator("tlm.start_server") + layout.label(text="Atlas Groups") + row = layout.row() + row.prop(sceneProperties, "tlm_atlas_mode", expand=True) + + if sceneProperties.tlm_atlas_mode == "Prepack": + + rows = 2 + if len(atlasList) > 1: + rows = 4 + row = layout.row() + row.template_list("TLM_UL_AtlasList", "Atlas List", scene, "TLM_AtlasList", scene, "TLM_AtlasListItem", rows=rows) + col = row.column(align=True) + col.operator("tlm_atlaslist.new_item", icon='ADD', text="") + col.operator("tlm_atlaslist.delete_item", icon='REMOVE', text="") + + if atlasListItem >= 0 and len(atlasList) > 0: + item = atlasList[atlasListItem] + layout.prop(item, "tlm_atlas_lightmap_unwrap_mode") + layout.prop(item, "tlm_atlas_lightmap_resolution") + layout.prop(item, "tlm_atlas_unwrap_margin") + + amount = 0 + + for obj in bpy.context.scene.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name: + amount = amount + 1 + + layout.label(text="Objects: " + str(amount)) + + else: + + layout.label(text="Postpacking is unstable.") + + cv2 = importlib.util.find_spec("cv2") + + if cv2 is None: + + row = layout.row(align=True) + row.label(text="OpenCV is not installed. Install it through preferences.") + + else: + + rows = 2 + if len(atlasList) > 1: + rows = 4 + row = layout.row() + row.template_list("TLM_UL_PostAtlasList", "PostList", scene, "TLM_PostAtlasList", scene, "TLM_PostAtlasListItem", rows=rows) + col = row.column(align=True) + col.operator("tlm_postatlaslist.new_item", icon='ADD', text="") + col.operator("tlm_postatlaslist.delete_item", icon='REMOVE', text="") + + if postatlasListItem >= 0 and len(postatlasList) > 0: + item = postatlasList[postatlasListItem] + layout.prop(item, "tlm_atlas_lightmap_resolution") + + #Below list object counter + amount = 0 + utilized = 0 + atlasUsedArea = 0 + atlasSize = item.tlm_atlas_lightmap_resolution + + for obj in bpy.context.scene.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + if obj.TLM_ObjectProperties.tlm_postpack_object: + if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name: + amount = amount + 1 + + atlasUsedArea += int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) ** 2 + + row = layout.row() + row.prop(item, "tlm_atlas_repack_on_cleanup") + + #TODO SET A CHECK FOR THIS! ADD A CV2 CHECK TO UTILITY! + cv2 = True + + if cv2: + row = layout.row() + row.prop(item, "tlm_atlas_dilation") + layout.label(text="Objects: " + str(amount)) + + utilized = atlasUsedArea / (int(atlasSize) ** 2) + layout.label(text="Utilized: " + str(utilized * 100) + "%") + + if (utilized * 100) > 100: + layout.label(text="Warning! Overflow not yet supported") \ No newline at end of file diff --git a/blender/arm/lightmapper/panels/world.py b/blender/arm/lightmapper/panels/world.py new file mode 100644 index 00000000..b3c5d294 --- /dev/null +++ b/blender/arm/lightmapper/panels/world.py @@ -0,0 +1,17 @@ +import bpy +from bpy.props import * +from bpy.types import Menu, Panel + +class TLM_PT_WorldMenu(bpy.types.Panel): + bl_label = "The Lightmapper" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "world" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scene = context.scene + obj = bpy.context.object + layout.use_property_split = True + layout.use_property_decorate = False \ No newline at end of file diff --git a/blender/arm/lightmapper/preferences/__init__.py b/blender/arm/lightmapper/preferences/__init__.py new file mode 100644 index 00000000..94cdfaea --- /dev/null +++ b/blender/arm/lightmapper/preferences/__init__.py @@ -0,0 +1,16 @@ +import bpy +from bpy.utils import register_class, unregister_class +from . import addon_preferences +#from . import build, clean, explore, encode, installopencv + +classes = [ + addon_preferences.TLM_AddonPreferences +] + +def register(): + for cls in classes: + register_class(cls) + +def unregister(): + for cls in classes: + unregister_class(cls) \ No newline at end of file diff --git a/blender/arm/lightmapper/preferences/addon_preferences.py b/blender/arm/lightmapper/preferences/addon_preferences.py new file mode 100644 index 00000000..173ac71a --- /dev/null +++ b/blender/arm/lightmapper/preferences/addon_preferences.py @@ -0,0 +1,75 @@ +import bpy, platform +from os.path import basename, dirname +from bpy.types import AddonPreferences +from .. operators import installopencv +import importlib + +class TLM_AddonPreferences(AddonPreferences): + + bl_idname = "thelightmapper" + + def draw(self, context): + + layout = self.layout + + box = layout.box() + row = box.row() + row.label(text="OpenCV") + + cv2 = importlib.util.find_spec("cv2") + + if cv2 is not None: + row.label(text="OpenCV installed") + else: + if platform.system() == "Windows": + row.label(text="OpenCV not found - Install as administrator!", icon_value=2) + else: + row.label(text="OpenCV not found - Click to install!", icon_value=2) + row = box.row() + row.operator("tlm.install_opencv_lightmaps", icon="PREFERENCES") + + box = layout.box() + row = box.row() + row.label(text="Blender Xatlas") + if "blender_xatlas" in bpy.context.preferences.addons.keys(): + row.label(text="Blender Xatlas installed and available") + else: + row.label(text="Blender Xatlas not installed", icon_value=2) + row = box.row() + row.label(text="Github: https://github.com/mattedicksoncom/blender-xatlas") + + box = layout.box() + row = box.row() + row.label(text="RizomUV Bridge") + row.label(text="Coming soon") + + box = layout.box() + row = box.row() + row.label(text="UVPackmaster") + row.label(text="Coming soon") + + texel_density_addon = False + for addon in bpy.context.preferences.addons.keys(): + if addon.startswith("Texel_Density"): + texel_density_addon = True + + box = layout.box() + row = box.row() + row.label(text="Texel Density Checker") + if texel_density_addon: + row.label(text="Texel Density Checker installed and available") + else: + row.label(text="Texel Density Checker", icon_value=2) + row.label(text="Coming soon") + row = box.row() + row.label(text="Github: https://github.com/mrven/Blender-Texel-Density-Checker") + + box = layout.box() + row = box.row() + row.label(text="LuxCoreRender") + row.label(text="Coming soon") + + box = layout.box() + row = box.row() + row.label(text="OctaneRender") + row.label(text="Coming soon") \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/__init__.py b/blender/arm/lightmapper/properties/__init__.py index 052a173b..0a011de9 100644 --- a/blender/arm/lightmapper/properties/__init__.py +++ b/blender/arm/lightmapper/properties/__init__.py @@ -1,7 +1,7 @@ import bpy from bpy.utils import register_class, unregister_class -from . import scene, object, atlas -from . renderer import cycles, luxcorerender +from . import scene, object, atlas, image +from . renderer import cycles, luxcorerender, octanerender from . denoiser import oidn, optix classes = [ @@ -9,12 +9,14 @@ classes = [ object.TLM_ObjectProperties, cycles.TLM_CyclesSceneProperties, luxcorerender.TLM_LuxCoreSceneProperties, + octanerender.TLM_OctanerenderSceneProperties, oidn.TLM_OIDNEngineProperties, optix.TLM_OptixEngineProperties, atlas.TLM_AtlasListItem, atlas.TLM_UL_AtlasList, atlas.TLM_PostAtlasListItem, - atlas.TLM_UL_PostAtlasList + atlas.TLM_UL_PostAtlasList, + image.TLM_ImageProperties ] def register(): @@ -25,12 +27,14 @@ def register(): bpy.types.Object.TLM_ObjectProperties = bpy.props.PointerProperty(type=object.TLM_ObjectProperties) bpy.types.Scene.TLM_EngineProperties = bpy.props.PointerProperty(type=cycles.TLM_CyclesSceneProperties) bpy.types.Scene.TLM_Engine2Properties = bpy.props.PointerProperty(type=luxcorerender.TLM_LuxCoreSceneProperties) + bpy.types.Scene.TLM_Engine3Properties = bpy.props.PointerProperty(type=octanerender.TLM_OctanerenderSceneProperties) bpy.types.Scene.TLM_OIDNEngineProperties = bpy.props.PointerProperty(type=oidn.TLM_OIDNEngineProperties) bpy.types.Scene.TLM_OptixEngineProperties = bpy.props.PointerProperty(type=optix.TLM_OptixEngineProperties) bpy.types.Scene.TLM_AtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0) bpy.types.Scene.TLM_AtlasList = bpy.props.CollectionProperty(type=atlas.TLM_AtlasListItem) bpy.types.Scene.TLM_PostAtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0) bpy.types.Scene.TLM_PostAtlasList = bpy.props.CollectionProperty(type=atlas.TLM_PostAtlasListItem) + bpy.types.Image.TLM_ImageProperties = bpy.props.PointerProperty(type=image.TLM_ImageProperties) bpy.types.Material.TLM_ignore = bpy.props.BoolProperty(name="Skip material", description="Ignore material for lightmapped object", default=False) @@ -42,9 +46,11 @@ def unregister(): del bpy.types.Object.TLM_ObjectProperties del bpy.types.Scene.TLM_EngineProperties del bpy.types.Scene.TLM_Engine2Properties + del bpy.types.Scene.TLM_Engine3Properties del bpy.types.Scene.TLM_OIDNEngineProperties del bpy.types.Scene.TLM_OptixEngineProperties del bpy.types.Scene.TLM_AtlasListItem del bpy.types.Scene.TLM_AtlasList del bpy.types.Scene.TLM_PostAtlasListItem - del bpy.types.Scene.TLM_PostAtlasList \ No newline at end of file + del bpy.types.Scene.TLM_PostAtlasList + del bpy.types.Image.TLM_ImageProperties \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/atlas.py b/blender/arm/lightmapper/properties/atlas.py index baccd926..8154f72c 100644 --- a/blender/arm/lightmapper/properties/atlas.py +++ b/blender/arm/lightmapper/properties/atlas.py @@ -34,12 +34,16 @@ class TLM_PostAtlasListItem(bpy.types.PropertyGroup): max=1.0, subtype='FACTOR') - tlm_atlas_lightmap_unwrap_mode : EnumProperty( - items = [('Lightmap', 'Lightmap', 'TODO'), - ('SmartProject', 'Smart Project', 'TODO'), - ('Xatlas', 'Xatlas', 'TODO')], + unwrap_modes = [('Lightmap', 'Lightmap', 'Use Blender Lightmap Pack algorithm'), + ('SmartProject', 'Smart Project', 'Use Blender Smart Project algorithm')] + + if "blender_xatlas" in bpy.context.preferences.addons.keys(): + unwrap_modes.append(('Xatlas', 'Xatlas', 'Use Xatlas addon packing algorithm')) + + tlm_postatlas_lightmap_unwrap_mode : EnumProperty( + items = unwrap_modes, name = "Unwrap Mode", - description="TODO", + description="Atlas unwrapping method", default='SmartProject') class TLM_UL_PostAtlasList(bpy.types.UIList): @@ -51,7 +55,7 @@ class TLM_UL_PostAtlasList(bpy.types.UIList): #In list object counter amount = 0 - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name: @@ -69,9 +73,6 @@ class TLM_UL_PostAtlasList(bpy.types.UIList): layout.alignment = 'CENTER' layout.label(text="", icon = custom_icon) - - - class TLM_AtlasListItem(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object, description="The object to bake") tlm_atlas_lightmap_resolution : EnumProperty( @@ -95,12 +96,17 @@ class TLM_AtlasListItem(bpy.types.PropertyGroup): max=1.0, subtype='FACTOR') + unwrap_modes = [('Lightmap', 'Lightmap', 'Use Blender Lightmap Pack algorithm'), + ('SmartProject', 'Smart Project', 'Use Blender Smart Project algorithm'), + ('Copy', 'Copy existing', 'Use the existing UV channel')] + + if "blender_xatlas" in bpy.context.preferences.addons.keys(): + unwrap_modes.append(('Xatlas', 'Xatlas', 'Use Xatlas addon packing algorithm')) + tlm_atlas_lightmap_unwrap_mode : EnumProperty( - items = [('Lightmap', 'Lightmap', 'TODO'), - ('SmartProject', 'Smart Project', 'TODO'), - ('Xatlas', 'Xatlas', 'TODO')], + items = unwrap_modes, name = "Unwrap Mode", - description="TODO", + description="Atlas unwrapping method", default='SmartProject') class TLM_UL_AtlasList(bpy.types.UIList): @@ -111,7 +117,7 @@ class TLM_UL_AtlasList(bpy.types.UIList): amount = 0 - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name: diff --git a/blender/arm/lightmapper/properties/image.py b/blender/arm/lightmapper/properties/image.py index e6e75766..a81169c4 100644 --- a/blender/arm/lightmapper/properties/image.py +++ b/blender/arm/lightmapper/properties/image.py @@ -1,10 +1,26 @@ import bpy from bpy.props import * -class TLM_ObjectProperties(bpy.types.PropertyGroup): - tlm_image_scale_method : EnumProperty( - items = [('Native', 'Native', 'TODO'), - ('OpenCV', 'OpenCV', 'TODO')], +class TLM_ImageProperties(bpy.types.PropertyGroup): + tlm_image_scale_engine : EnumProperty( + items = [('OpenCV', 'OpenCV', 'TODO')], name = "Scaling engine", description="TODO", - default='Native') \ No newline at end of file + default='OpenCV') + + #('Native', 'Native', 'TODO'), + + tlm_image_scale_method : EnumProperty( + items = [('Nearest', 'Nearest', 'TODO'), + ('Area', 'Area', 'TODO'), + ('Linear', 'Linear', 'TODO'), + ('Cubic', 'Cubic', 'TODO'), + ('Lanczos', 'Lanczos', 'TODO')], + name = "Scaling method", + description="TODO", + default='Lanczos') + + tlm_image_cache_switch : BoolProperty( + name="Cache for quickswitch", + description="Caches scaled images for quick switching", + default=True) \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/object.py b/blender/arm/lightmapper/properties/object.py index 7738add9..c5c2fe47 100644 --- a/blender/arm/lightmapper/properties/object.py +++ b/blender/arm/lightmapper/properties/object.py @@ -7,7 +7,7 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup): tlm_atlas_pointer : StringProperty( name = "Atlas Group", - description = "Atlas Lightmap Group", + description = "", default = "") tlm_postatlas_pointer : StringProperty( @@ -51,8 +51,7 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup): unwrap_modes = [('Lightmap', 'Lightmap', 'TODO'), ('SmartProject', 'Smart Project', 'TODO'), - ('CopyExisting', 'Copy Existing', 'TODO'), - ('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO')] + ('AtlasGroupA', 'Atlas Group (Prepack)', 'Attaches the object to a prepack Atlas group. Will overwrite UV map on build.')] tlm_postpack_object : BoolProperty( #CHECK INSTEAD OF ATLASGROUPB name="Postpack object", @@ -145,4 +144,14 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup): name="Median kernel", default=3, min=1, - max=5) \ No newline at end of file + max=5) + + tlm_use_default_channel : BoolProperty( + name="Use default UV channel", + description="Will either use or create the default UV Channel 'UVMap_Lightmap' upon build.", + default=True) + + tlm_uv_channel : StringProperty( + name = "UV Channel", + description = "Use any custom UV Channel for the lightmap", + default = "UVMap") \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/renderer/cycles.py b/blender/arm/lightmapper/properties/renderer/cycles.py index ad99a664..d3763400 100644 --- a/blender/arm/lightmapper/properties/renderer/cycles.py +++ b/blender/arm/lightmapper/properties/renderer/cycles.py @@ -21,6 +21,16 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup): description="Select baking quality", default="0") + targets = [('texture', 'Image texture', 'Build to image texture')] + if (2, 92, 0) >= bpy.app.version: + targets.append(('vertex', 'Vertex colors', 'Build to vertex colors')) + + tlm_target : EnumProperty( + items = targets, + name = "Build Target", + description="Select target to build to", + default="texture") + tlm_resolution_scale : EnumProperty( items = [('1', '1/1', '1'), ('2', '1/2', '2'), @@ -45,10 +55,12 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup): description="Select bake mode", default="Foreground") + caching_modes = [('Copy', 'Copy', 'More overhead; allows for network.')] + + #caching_modes.append(('Cache', 'Cache', 'Cache in separate blend'),('Node', 'Node restore', 'EXPERIMENTAL! Use with care')) + tlm_caching_mode : EnumProperty( - items = [('Copy', 'Copy', 'More overhead; allows for network.'), - ('Cache', 'Cache', 'Cache in separate blend'), - ('Node', 'Node restore', 'EXPERIMENTAL! Use with care')], + items = caching_modes, name = "Caching mode", description="Select cache mode", default="Copy") @@ -88,8 +100,16 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup): tlm_lighting_mode : EnumProperty( items = [('combined', 'Combined', 'Bake combined lighting'), + ('combinedao', 'Combined+AO', 'Bake combined lighting with Ambient Occlusion'), ('indirect', 'Indirect', 'Bake indirect lighting'), - ('ao', 'AO', 'Bake only Ambient Occlusion')], +# ('indirectao', 'Indirect+AO', 'Bake indirect lighting with Ambient Occlusion'), + ('ao', 'AO', 'Bake only Ambient Occlusion'), + ('complete', 'Complete', 'Bake complete map')], name = "Lighting mode", description="TODO.", - default="combined") \ No newline at end of file + default="combined") + + tlm_premultiply_ao : BoolProperty( + name="Premultiply AO", + description="Ambient Occlusion will be premultiplied together with lightmaps, requiring less textures.", + default=True) \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/renderer/octanerender.py b/blender/arm/lightmapper/properties/renderer/octanerender.py index e69de29b..8c66cf13 100644 --- a/blender/arm/lightmapper/properties/renderer/octanerender.py +++ b/blender/arm/lightmapper/properties/renderer/octanerender.py @@ -0,0 +1,10 @@ +import bpy +from bpy.props import * + +class TLM_OctanerenderSceneProperties(bpy.types.PropertyGroup): + + tlm_lightmap_savedir : StringProperty( + name="Lightmap Directory", + description="TODO", + default="Lightmaps", + subtype="FILE_PATH") \ No newline at end of file diff --git a/blender/arm/lightmapper/properties/scene.py b/blender/arm/lightmapper/properties/scene.py index 6652b548..36c74fbc 100644 --- a/blender/arm/lightmapper/properties/scene.py +++ b/blender/arm/lightmapper/properties/scene.py @@ -1,11 +1,19 @@ -import bpy +import bpy, os from bpy.props import * +from .. utility import utility + +def transfer_load(): + load_folder = bpy.context.scene.TLM_SceneProperties.tlm_load_folder + lightmap_folder = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + print(load_folder) + print(lightmap_folder) + #transfer_assets(True, load_folder, lightmap_folder) class TLM_SceneProperties(bpy.types.PropertyGroup): engines = [('Cycles', 'Cycles', 'Use Cycles for lightmapping')] - engines.append(('LuxCoreRender', 'LuxCoreRender', 'Use LuxCoreRender for lightmapping')) + #engines.append(('LuxCoreRender', 'LuxCoreRender', 'Use LuxCoreRender for lightmapping')) #engines.append(('OctaneRender', 'Octane Render', 'Use Octane Render for lightmapping')) tlm_atlas_pointer : StringProperty( @@ -112,7 +120,7 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): #FILTERING SETTINGS GROUP tlm_filtering_use : BoolProperty( - name="Enable Filtering", + name="Enable denoising", description="Enable denoising for lightmaps", default=False) @@ -182,6 +190,17 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): min=1, max=5) + tlm_clamp_hdr : BoolProperty( + name="Enable HDR Clamp", + description="Clamp HDR Value", + default=False) + + tlm_clamp_hdr_value : IntProperty( + name="HDR Clamp value", + default=10, + min=0, + max=20) + #Encoding properties tlm_encoding_use : BoolProperty( name="Enable encoding", @@ -197,12 +216,13 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): encoding_modes_1 = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'), ('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'), - ('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')] + ('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.'), + ('SDR', 'SDR', '8-bit flat encoding.')] - encoding_modes_2 = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'), - ('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'), + encoding_modes_2 = [('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'), ('LogLuv', 'LogLuv', '8-bit HDR encoding. Different.'), - ('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')] + ('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.'), + ('SDR', 'SDR', '8-bit flat encoding.')] tlm_encoding_mode_a : EnumProperty( items = encoding_modes_1, @@ -275,8 +295,7 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): tlm_mesh_lightmap_unwrap_mode : EnumProperty( items = [('Lightmap', 'Lightmap', 'TODO'), ('SmartProject', 'Smart Project', 'TODO'), - ('CopyExisting', 'Copy Existing', 'TODO'), - ('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO'), + ('AtlasGroupA', 'Atlas Group (Prepack)', 'Attaches the object to a prepack Atlas group. Will overwrite UV map on build.'), ('Xatlas', 'Xatlas', 'TODO')], name = "Unwrap Mode", description="TODO", @@ -317,12 +336,30 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): tlm_metallic_clamp : EnumProperty( items = [('ignore', 'Ignore', 'Ignore clamping'), + ('skip', 'Skip', 'Skip baking metallic materials'), ('zero', 'Zero', 'Set zero'), ('limit', 'Limit', 'Clamp to 0.9')], name = "Metallic clamping", description="TODO.", default="ignore") + tlm_texture_interpolation : EnumProperty( + items = [('Smart', 'Smart', 'Bicubic when magnifying.'), + ('Cubic', 'Cubic', 'Cubic interpolation'), + ('Closest', 'Closest', 'No interpolation'), + ('Linear', 'Linear', 'Linear')], + name = "Texture interpolation", + description="Texture interpolation.", + default="Linear") + + tlm_texture_extrapolation : EnumProperty( + items = [('REPEAT', 'Repeat', 'Repeat in both direction.'), + ('EXTEND', 'Extend', 'Extend by repeating edge pixels.'), + ('CLIP', 'Clip', 'Clip to image size')], + name = "Texture extrapolation", + description="Texture extrapolation.", + default="EXTEND") + tlm_verbose : BoolProperty( name="Verbose", description="Verbose console output", @@ -409,4 +446,52 @@ class TLM_SceneProperties(bpy.types.PropertyGroup): ('CYCLES', 'Cycles', 'TODO')], name = "Probe Render Engine", description="TODO", - default='BLENDER_EEVEE') \ No newline at end of file + default='BLENDER_EEVEE') + + tlm_load_folder : StringProperty( + name="Load Folder", + description="Load existing lightmaps from folder", + subtype="DIR_PATH") + + tlm_utility_set : EnumProperty( + items = [('Scene', 'Scene', 'Set for all objects in the scene.'), + ('Selection', 'Selection', 'Set for selected objects.'), + ('Enabled', 'Enabled', 'Set for objects that has been enabled for lightmapping.')], + name = "Set", + description="Utility selection set", + default='Scene') + + tlm_resolution_weight : EnumProperty( + items = [('Single', 'Single', 'Set a single resolution for all objects.'), + ('Dimension', 'Dimension', 'Distribute resolutions based on object dimensions.'), + ('Surface', 'Surface', 'Distribute resolutions based on mesh surface area.'), + ('Volume', 'Volume', 'Distribute resolutions based on mesh volume.')], + name = "Resolution weight", + description="Method for setting resolution value", + default='Single') + #Todo add vertex color option + + tlm_resolution_min : EnumProperty( + items = [('32', '32', 'TODO'), + ('64', '64', 'TODO'), + ('128', '128', 'TODO'), + ('256', '256', 'TODO'), + ('512', '512', 'TODO'), + ('1024', '1024', 'TODO'), + ('2048', '2048', 'TODO'), + ('4096', '4096', 'TODO')], + name = "Minimum resolution", + description="Minimum distributed resolution", + default='32') + + tlm_resolution_max : EnumProperty( + items = [('64', '64', 'TODO'), + ('128', '128', 'TODO'), + ('256', '256', 'TODO'), + ('512', '512', 'TODO'), + ('1024', '1024', 'TODO'), + ('2048', '2048', 'TODO'), + ('4096', '4096', 'TODO')], + name = "Maximum resolution", + description="Maximum distributed resolution", + default='256') \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/build.py b/blender/arm/lightmapper/utility/build.py index a8ad80dd..891ebcf4 100644 --- a/blender/arm/lightmapper/utility/build.py +++ b/blender/arm/lightmapper/utility/build.py @@ -1,17 +1,20 @@ import bpy, os, subprocess, sys, platform, aud, json, datetime, socket -import threading + from . import encoding, pack from . cycles import lightmap, prepare, nodes, cache +from . luxcore import setup +from . octane import configure, lightmap2 from . denoiser import integrated, oidn, optix from . filtering import opencv +from . gui import Viewport from .. network import client + from os import listdir from os.path import isfile, join from time import time, sleep from importlib import util previous_settings = {} - postprocess_shutdown = False def prepare_build(self=0, background_mode=False, shutdown_after_build=False): @@ -21,15 +24,31 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): print("Building lightmaps") + if bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao": + + scene = bpy.context.scene + + if not "tlm_plus_mode" in bpy.app.driver_namespace or bpy.app.driver_namespace["tlm_plus_mode"] == 0: + filepath = bpy.data.filepath + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir) + if os.path.isdir(dirpath): + for file in os.listdir(dirpath): + os.remove(os.path.join(dirpath + "/" + file)) + bpy.app.driver_namespace["tlm_plus_mode"] = 1 + print("Plus Mode") + if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Foreground" or background_mode==True: global start_time start_time = time() + bpy.app.driver_namespace["tlm_start_time"] = time() scene = bpy.context.scene sceneProperties = scene.TLM_SceneProperties - #Timer start here bound to global + if not background_mode and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao": + #pass + setGui(1) if check_save(): print("Please save your file first") @@ -52,12 +71,6 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): self.report({'INFO'}, "Error:Filtering - OpenCV not installed") return{'FINISHED'} - #TODO DO some resolution change - #if checkAtlasSize(): - # print("Error: AtlasGroup overflow") - # self.report({'INFO'}, "Error: AtlasGroup overflow - Too many objects") - # return{'FINISHED'} - setMode() dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) @@ -67,19 +80,6 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): #Naming check naming_check() - # if sceneProperties.tlm_reset_uv or sceneProperties.tlm_atlas_mode == "Postpack": - # for obj in bpy.data.objects: - # if obj.type == "MESH": - # uv_layers = obj.data.uv_layers - - - - #for uvlayer in uv_layers: - # if uvlayer.name == "UVMap_Lightmap": - # uv_layers.remove(uvlayer) - - ## RENDER DEPENDENCY FROM HERE - if sceneProperties.tlm_lightmap_engine == "Cycles": prepare.init(self, previous_settings) @@ -90,18 +90,14 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): if sceneProperties.tlm_lightmap_engine == "OctaneRender": - pass - - #Renderer - Store settings - - #Renderer - Set settings - - #Renderer - Config objects, lights, world + configure.init(self, previous_settings) begin_build() else: + print("Baking in background") + filepath = bpy.data.filepath bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) @@ -111,22 +107,7 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): scene = bpy.context.scene sceneProperties = scene.TLM_SceneProperties - #We dynamically load the renderer and denoiser, instead of loading something we don't use - - if sceneProperties.tlm_lightmap_engine == "Cycles": - - pass - - if sceneProperties.tlm_lightmap_engine == "LuxCoreRender": - - pass - - if sceneProperties.tlm_lightmap_engine == "OctaneRender": - - pass - #Timer start here bound to global - if check_save(): print("Please save your file first") self.report({'INFO'}, "Please save your file first") @@ -168,23 +149,12 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): client.connect_client(HOST, PORT, bpy.data.filepath, 0) - # with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - # s.connect((HOST, PORT)) - # message = { - # "call" : 1, - # "command" : 1, - # "enquiry" : 0, - # "args" : bpy.data.filepath - # } - - # s.sendall(json.dumps(message).encode()) - # data = s.recv(1024) - # print(data.decode()) - finish_assemble() else: + print("Background driver process") + bpy.app.driver_namespace["alpha"] = 0 bpy.app.driver_namespace["tlm_process"] = False @@ -196,6 +166,8 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False): def distribute_building(): + print("Distributing lightmap building") + #CHECK IF THERE'S AN EXISTING SUBPROCESS if not os.path.isfile(os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir, "process.tlm")): @@ -215,8 +187,16 @@ def distribute_building(): with open(os.path.join(write_directory, "process.tlm"), 'w') as file: json.dump(process_status, file, indent=2) - bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) - + if (2, 91, 0) > bpy.app.version: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stdout=subprocess.PIPE) + else: + bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + else: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([bpy.app.binary_path,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stdout=subprocess.PIPE) + else: + bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([bpy.app.binary_path,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print("Started process: " + str(bpy.app.driver_namespace["tlm_process"]) + " at " + str(datetime.datetime.now())) @@ -269,6 +249,10 @@ def finish_assemble(self=0): if sceneProperties.tlm_lightmap_engine == "OctaneRender": pass + if not 'start_time' in globals(): + global start_time + start_time = time() + manage_build(True) def begin_build(): @@ -288,7 +272,8 @@ def begin_build(): pass if sceneProperties.tlm_lightmap_engine == "OctaneRender": - pass + + lightmap2.bake() #Denoiser if sceneProperties.tlm_denoise_use: @@ -429,7 +414,7 @@ def begin_build(): print("Encoding:" + str(file)) encoding.encodeImageRGBMCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) - if sceneProperties.tlm_encoding_mode_b == "RGBD": + if sceneProperties.tlm_encoding_mode_a == "RGBD": if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print("ENCODING RGBD") @@ -455,6 +440,36 @@ def begin_build(): print("Encoding:" + str(file)) encoding.encodeImageRGBDCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) + if sceneProperties.tlm_encoding_mode_a == "SDR": + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("EXR Format") + + ren = bpy.context.scene.render + ren.image_settings.file_format = "PNG" + #ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec" + + end = "_baked" + + baked_image_array = [] + + if sceneProperties.tlm_denoise_use: + + end = "_denoised" + + if sceneProperties.tlm_filtering_use: + + end = "_filtered" + + #For each image in folder ending in denoised/filtered + dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))] + + for file in dirfiles: + if file.endswith(end + ".hdr"): + + img = bpy.data.images.load(os.path.join(dirpath,file)) + img.save_render(img.filepath_raw[:-4] + ".png") + else: if sceneProperties.tlm_encoding_mode_b == "HDR": @@ -562,6 +577,33 @@ def begin_build(): print("Encoding:" + str(file)) encoding.encodeImageRGBDGPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) + if sceneProperties.tlm_encoding_mode_b == "PNG": + + ren = bpy.context.scene.render + ren.image_settings.file_format = "PNG" + #ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec" + + end = "_baked" + + baked_image_array = [] + + if sceneProperties.tlm_denoise_use: + + end = "_denoised" + + if sceneProperties.tlm_filtering_use: + + end = "_filtered" + + #For each image in folder ending in denoised/filtered + dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))] + + for file in dirfiles: + if file.endswith(end + ".hdr"): + + img = bpy.data.images.load(os.path.join(dirpath,file)) + img.save_render(img.filepath_raw[:-4] + ".png") + manage_build() def manage_build(background_pass=False): @@ -610,6 +652,10 @@ def manage_build(background_pass=False): formatEnc = "_encoded.png" + if sceneProperties.tlm_encoding_mode_a == "SDR": + + formatEnc = ".png" + else: print("GPU Encoding") @@ -632,6 +678,10 @@ def manage_build(background_pass=False): formatEnc = "_encoded.png" + if sceneProperties.tlm_encoding_mode_b == "SDR": + + formatEnc = ".png" + if not background_pass: nodes.exchangeLightmapsToPostfix("_baked", end, formatEnc) @@ -653,13 +703,13 @@ def manage_build(background_pass=False): filepath = bpy.data.filepath dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_restore(obj) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_rename(obj) @@ -672,77 +722,181 @@ def manage_build(background_pass=False): if "_Original" in mat.name: bpy.data.materials.remove(mat) - for obj in bpy.data.objects: - - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: - img_name = obj.name + '_baked' - Lightmapimage = bpy.data.images[img_name] - obj["Lightmap"] = Lightmapimage.filepath_raw + + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + atlasName = obj.TLM_ObjectProperties.tlm_atlas_pointer + img_name = atlasName + '_baked' + Lightmapimage = bpy.data.images[img_name] + obj["Lightmap"] = Lightmapimage.filepath_raw + elif obj.TLM_ObjectProperties.tlm_postpack_object: + atlasName = obj.TLM_ObjectProperties.tlm_postatlas_pointer + img_name = atlasName + '_baked' + ".hdr" + Lightmapimage = bpy.data.images[img_name] + obj["Lightmap"] = Lightmapimage.filepath_raw + else: + img_name = obj.name + '_baked' + Lightmapimage = bpy.data.images[img_name] + obj["Lightmap"] = Lightmapimage.filepath_raw for image in bpy.data.images: if image.name.endswith("_baked"): bpy.data.images.remove(image, do_unlink=True) - total_time = sec_to_hours((time() - start_time)) - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print(total_time) + if "tlm_plus_mode" in bpy.app.driver_namespace: #First DIR pass - bpy.context.scene["TLM_Buildstat"] = total_time + if bpy.app.driver_namespace["tlm_plus_mode"] == 1: #First DIR pass - reset_settings(previous_settings["settings"]) + filepath = bpy.data.filepath + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir) - if sceneProperties.tlm_lightmap_engine == "LuxCoreRender": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + cache.backup_material_restore(obj) - pass + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + cache.backup_material_rename(obj) - if sceneProperties.tlm_lightmap_engine == "OctaneRender": + for mat in bpy.data.materials: + if mat.users < 1: + bpy.data.materials.remove(mat) - pass + for mat in bpy.data.materials: + if mat.name.startswith("."): + if "_Original" in mat.name: + bpy.data.materials.remove(mat) - if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Background": - pass + for image in bpy.data.images: + if image.name.endswith("_baked"): + bpy.data.images.remove(image, do_unlink=True) - if scene.TLM_SceneProperties.tlm_alert_on_finish: + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) - alertSelect = scene.TLM_SceneProperties.tlm_alert_sound + files = os.listdir(dirpath) + + for index, file in enumerate(files): + + filename = extension = os.path.splitext(file)[0] + extension = os.path.splitext(file)[1] + + os.rename(os.path.join(dirpath, file), os.path.join(dirpath, filename + "_dir" + extension)) + + print("First DIR pass complete") + + bpy.app.driver_namespace["tlm_plus_mode"] = 2 + + prepare_build(self=0, background_mode=False, shutdown_after_build=False) + + if not background_pass and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao": + #pass + setGui(0) + + elif bpy.app.driver_namespace["tlm_plus_mode"] == 2: + + filepath = bpy.data.filepath + + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + + files = os.listdir(dirpath) + + for index, file in enumerate(files): + + filename = os.path.splitext(file)[0] + extension = os.path.splitext(file)[1] + + if not filename.endswith("_dir"): + os.rename(os.path.join(dirpath, file), os.path.join(dirpath, filename + "_ao" + extension)) + + print("Second AO pass complete") + + total_time = sec_to_hours((time() - start_time)) + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print(total_time) + + bpy.context.scene["TLM_Buildstat"] = total_time + + reset_settings(previous_settings["settings"]) + + bpy.app.driver_namespace["tlm_plus_mode"] = 0 + + if not background_pass: + + #TODO CHANGE! + + nodes.exchangeLightmapsToPostfix(end, end + "_dir", formatEnc) + + nodes.applyAOPass() - if alertSelect == "dash": - soundfile = "dash.ogg" - elif alertSelect == "pingping": - soundfile = "pingping.ogg" - elif alertSelect == "gentle": - soundfile = "gentle.ogg" else: - soundfile = "noot.ogg" - scriptDir = os.path.dirname(os.path.realpath(__file__)) - sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile)) + total_time = sec_to_hours((time() - start_time)) + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print(total_time) - device = aud.Device() - sound = aud.Sound.file(sound_path) - device.play(sound) + bpy.context.scene["TLM_Buildstat"] = total_time - print("Lightmap building finished") + reset_settings(previous_settings["settings"]) - if bpy.app.background: + print("Lightmap building finished") - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Writing background process report") - - write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + if sceneProperties.tlm_lightmap_engine == "LuxCoreRender": - if os.path.exists(os.path.join(write_directory, "process.tlm")): + pass - process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read()) + if sceneProperties.tlm_lightmap_engine == "OctaneRender": - process_status[1]["completed"] = True + pass - with open(os.path.join(write_directory, "process.tlm"), 'w') as file: - json.dump(process_status, file, indent=2) + if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Background": + pass - if postprocess_shutdown: - sys.exit() + if not background_pass and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao": + #pass + setGui(0) + + if scene.TLM_SceneProperties.tlm_alert_on_finish: + + alertSelect = scene.TLM_SceneProperties.tlm_alert_sound + + if alertSelect == "dash": + soundfile = "dash.ogg" + elif alertSelect == "pingping": + soundfile = "pingping.ogg" + elif alertSelect == "gentle": + soundfile = "gentle.ogg" + else: + soundfile = "noot.ogg" + + scriptDir = os.path.dirname(os.path.realpath(__file__)) + sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile)) + + device = aud.Device() + sound = aud.Sound.file(sound_path) + device.play(sound) + + if bpy.app.background: + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Writing background process report") + + write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + + if os.path.exists(os.path.join(write_directory, "process.tlm")): + + process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read()) + + process_status[1]["completed"] = True + + with open(os.path.join(write_directory, "process.tlm"), 'w') as file: + json.dump(process_status, file, indent=2) + + if postprocess_shutdown: + sys.exit() #TODO - SET BELOW TO UTILITY @@ -770,9 +924,8 @@ def reset_settings(prev_settings): def naming_check(): - for obj in bpy.data.objects: - - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: @@ -829,6 +982,9 @@ def check_save(): def check_denoiser(): + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Checking denoiser path") + scene = bpy.context.scene if scene.TLM_SceneProperties.tlm_denoise_use: @@ -847,8 +1003,8 @@ def check_denoiser(): return 0 def check_materials(): - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: for slot in obj.material_slots: mat = slot.material @@ -868,15 +1024,39 @@ def check_materials(): def sec_to_hours(seconds): a=str(seconds//3600) b=str((seconds%3600)//60) - c=str((seconds%3600)%60) + c=str(round((seconds%3600)%60,1)) d=["{} hours {} mins {} seconds".format(a, b, c)] return d def setMode(): + + obj = bpy.context.scene.objects[0] + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.mode_set(mode='OBJECT') #TODO Make some checks that returns to previous selection +def setGui(mode): + + if mode == 0: + + context = bpy.context + driver = bpy.app.driver_namespace + + if "TLM_UI" in driver: + driver["TLM_UI"].remove_handle() + + if mode == 1: + + #bpy.context.area.tag_redraw() + context = bpy.context + driver = bpy.app.driver_namespace + driver["TLM_UI"] = Viewport.ViewportDraw(context, "Building Lightmaps") + + bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) + def checkAtlasSize(): overflow = False @@ -897,7 +1077,7 @@ def checkAtlasSize(): utilized = 0 atlasUsedArea = 0 - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: @@ -912,4 +1092,5 @@ def checkAtlasSize(): if overflow == True: return True else: - return False \ No newline at end of file + return False + diff --git a/blender/arm/lightmapper/utility/cycles/cache.py b/blender/arm/lightmapper/utility/cycles/cache.py index 50542c6e..07f068b7 100644 --- a/blender/arm/lightmapper/utility/cycles/cache.py +++ b/blender/arm/lightmapper/utility/cycles/cache.py @@ -2,7 +2,6 @@ import bpy #Todo - Check if already exists, in case multiple objects has the same material - def backup_material_copy(slot): material = slot.material dup = material.copy() @@ -16,25 +15,49 @@ def backup_material_cache_restore(slot, path): if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print("Restore cache") -def backup_material_rename(obj): - if "TLM_PrevMatArray" in obj: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Renaming material for: " + obj.name) +# def backup_material_restore(obj): #?? +# if bpy.context.scene.TLM_SceneProperties.tlm_verbose: +# print("Restoring material for: " + obj.name) - for slot in obj.material_slots: +#Check if object has TLM_PrevMatArray +# if yes +# - check if array.len is bigger than 0: +# if yes: +# for slot in object: +# originalMaterial = TLM_PrevMatArray[index] +# +# +# if no: +# - In which cases are these? - if slot.material is not None: - if slot.material.name.endswith("_Original"): - newname = slot.material.name[1:-9] - if newname in bpy.data.materials: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Removing material: " + bpy.data.materials[newname].name) - bpy.data.materials.remove(bpy.data.materials[newname]) - slot.material.name = newname +# if no: +# - In which cases are there not? +# - If a lightmapped material was applied to a non-lightmap object? - del obj["TLM_PrevMatArray"] -def backup_material_restore(obj): + # if bpy.data.materials[originalMaterial].users > 0: #TODO - Check if all lightmapped + + # print("Material has multiple users") + + # if originalMaterial in bpy.data.materials: + # slot.material = bpy.data.materials[originalMaterial] + # slot.material.use_fake_user = False + # elif "." + originalMaterial + "_Original" in bpy.data.materials: + # slot.material = bpy.data.materials["." + originalMaterial + "_Original"] + # slot.material.use_fake_user = False + + # else: + + # print("Material has one user") + + # if "." + originalMaterial + "_Original" in bpy.data.materials: + # slot.material = bpy.data.materials["." + originalMaterial + "_Original"] + # slot.material.use_fake_user = False + # elif originalMaterial in bpy.data.materials: + # slot.material = bpy.data.materials[originalMaterial] + # slot.material.use_fake_user = False + +def backup_material_restore(obj): #?? if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print("Restoring material for: " + obj.name) @@ -59,9 +82,43 @@ def backup_material_restore(obj): originalMaterial = "" if slot.material is not None: - #slot.material.user_clear() Seems to be bad; See: https://developer.blender.org/T49837 - bpy.data.materials.remove(slot.material) + #if slot.material.users < 2: + #slot.material.user_clear() #Seems to be bad; See: https://developer.blender.org/T49837 + #bpy.data.materials.remove(slot.material) if "." + originalMaterial + "_Original" in bpy.data.materials: slot.material = bpy.data.materials["." + originalMaterial + "_Original"] - slot.material.use_fake_user = False \ No newline at end of file + slot.material.use_fake_user = False + + else: + + print("No previous material for " + obj.name) + + else: + + print("No previous material for " + obj.name) + +def backup_material_rename(obj): #?? + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Renaming material for: " + obj.name) + + + if "TLM_PrevMatArray" in obj: + + for slot in obj.material_slots: + + if slot.material is not None: + if slot.material.name.endswith("_Original"): + newname = slot.material.name[1:-9] + if newname in bpy.data.materials: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Removing material: " + bpy.data.materials[newname].name) + #if bpy.data.materials[newname].users < 2: + #bpy.data.materials.remove(bpy.data.materials[newname]) #TODO - Maybe remove this + slot.material.name = newname + + del obj["TLM_PrevMatArray"] + + else: + + print("No Previous material array for: " + obj.name) \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/cycles/lightmap.py b/blender/arm/lightmapper/utility/cycles/lightmap.py index 8588bee8..0d0c56e1 100644 --- a/blender/arm/lightmapper/utility/cycles/lightmap.py +++ b/blender/arm/lightmapper/utility/cycles/lightmap.py @@ -1,25 +1,26 @@ import bpy, os +from .. import build +from time import time, sleep -def bake(): +def bake(plus_pass=0): - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: bpy.ops.object.select_all(action='DESELECT') obj.select_set(False) iterNum = 0 currentIterNum = 0 - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: iterNum = iterNum + 1 if iterNum > 1: iterNum = iterNum - 1 - for obj in bpy.data.objects: - if obj.type == 'MESH': - + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: scene = bpy.context.scene @@ -32,19 +33,45 @@ def bake(): obj.hide_render = False scene.render.bake.use_clear = False - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name) + #os.system("cls") - if scene.TLM_EngineProperties.tlm_lighting_mode == "combined" or scene.TLM_EngineProperties.tlm_lighting_mode == "combinedAO": + #if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name) + #elapsed = build.sec_to_hours((time() - bpy.app.driver_namespace["tlm_start_time"])) + #print("Baked: " + str(currentIterNum) + " | Left: " + str(iterNum-currentIterNum)) + elapsedSeconds = time() - bpy.app.driver_namespace["tlm_start_time"] + bakedObjects = currentIterNum + bakedLeft = iterNum-currentIterNum + if bakedObjects == 0: + bakedObjects = 1 + averagePrBake = elapsedSeconds / bakedObjects + remaining = averagePrBake * bakedLeft + #print(time() - bpy.app.driver_namespace["tlm_start_time"]) + print("Elapsed time: " + str(round(elapsedSeconds, 2)) + "s | ETA remaining: " + str(round(remaining, 2)) + "s") #str(elapsed[0]) + #print("Averaged: " + str(averagePrBake)) + #print("Remaining: " + str(remaining)) + + if scene.TLM_EngineProperties.tlm_target == "vertex": + scene.render.bake_target = "VERTEX_COLORS" + + if scene.TLM_EngineProperties.tlm_lighting_mode == "combined": bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) - elif scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO": + elif scene.TLM_EngineProperties.tlm_lighting_mode == "indirect": bpy.ops.object.bake(type="DIFFUSE", pass_filter={"INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) elif scene.TLM_EngineProperties.tlm_lighting_mode == "ao": bpy.ops.object.bake(type="AO", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + elif scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao": + + if bpy.app.driver_namespace["tlm_plus_mode"] == 1: + bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + elif bpy.app.driver_namespace["tlm_plus_mode"] == 2: + bpy.ops.object.bake(type="AO", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + elif scene.TLM_EngineProperties.tlm_lighting_mode == "complete": - bpy.ops.object.bake(type="COMBINED", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + bpy.ops.object.bake(type="COMBINED", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) else: bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) + bpy.ops.object.select_all(action='DESELECT') currentIterNum = currentIterNum + 1 diff --git a/blender/arm/lightmapper/utility/cycles/nodes.py b/blender/arm/lightmapper/utility/cycles/nodes.py index bb42a895..9eb24d88 100644 --- a/blender/arm/lightmapper/utility/cycles/nodes.py +++ b/blender/arm/lightmapper/utility/cycles/nodes.py @@ -1,8 +1,8 @@ import bpy, os def apply_lightmaps(): - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: for slot in obj.material_slots: mat = slot.material @@ -32,8 +32,12 @@ def apply_lightmaps(): def apply_materials(): - for obj in bpy.data.objects: - if obj.type == "MESH": + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Applying materials") + + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: uv_layers = obj.data.uv_layers @@ -90,7 +94,7 @@ def apply_materials(): for node in nodes: if node.name == "Baked Image": lightmapNode = node - lightmapNode.location = -800, 300 + lightmapNode.location = -1200, 300 lightmapNode.name = "TLM_Lightmap" foundBakedNode = True @@ -98,9 +102,10 @@ def apply_materials(): if not foundBakedNode: lightmapNode = node_tree.nodes.new(type="ShaderNodeTexImage") - lightmapNode.location = -300, 300 + lightmapNode.location = -1200, 300 lightmapNode.name = "TLM_Lightmap" - lightmapNode.interpolation = "Smart" + lightmapNode.interpolation = bpy.context.scene.TLM_SceneProperties.tlm_texture_interpolation + lightmapNode.extension = bpy.context.scene.TLM_SceneProperties.tlm_texture_extrapolation if (obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA" and obj.TLM_ObjectProperties.tlm_atlas_pointer != ""): lightmapNode.image = bpy.data.images[obj.TLM_ObjectProperties.tlm_atlas_pointer + "_baked"] @@ -118,37 +123,32 @@ def apply_materials(): #Find mainnode mainNode = outputNode.inputs[0].links[0].from_node - #Clamp metallic - - # if scene.TLM_SceneProperties.tlm_metallic_clamp != "ignore": - # if mainNode.type == "BSDF_PRINCIPLED": - - # if len(mainNode.inputs[4].links) == 0: - - # if scene.TLM_SceneProperties.tlm_metallic_clamp == "zero": - # mainNode.inputs[4].default_value = 0.0 - # else: - # mainNode.inputs[4].default_value = 0.99 - - # else: - - # pass - #Add all nodes first #Add lightmap multipliction texture mixNode = node_tree.nodes.new(type="ShaderNodeMixRGB") mixNode.name = "Lightmap_Multiplication" - mixNode.location = -300, 300 + mixNode.location = -800, 300 if scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO": mixNode.blend_type = 'ADD' else: mixNode.blend_type = 'MULTIPLY' - mixNode.inputs[0].default_value = 1.0 + + if scene.TLM_EngineProperties.tlm_lighting_mode == "complete": + mixNode.inputs[0].default_value = 0.0 + else: + mixNode.inputs[0].default_value = 1.0 UVLightmap = node_tree.nodes.new(type="ShaderNodeUVMap") - UVLightmap.uv_map = "UVMap_Lightmap" + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + UVLightmap.uv_map = uv_channel + UVLightmap.name = "Lightmap_UV" - UVLightmap.location = -1000, 300 + UVLightmap.location = -1500, 300 if(scene.TLM_SceneProperties.tlm_decoder_setup): if scene.TLM_SceneProperties.tlm_encoding_device == "CPU": @@ -196,7 +196,7 @@ def apply_materials(): baseColorValue = mainNode.inputs[0].default_value baseColorNode = node_tree.nodes.new(type="ShaderNodeRGB") baseColorNode.outputs[0].default_value = baseColorValue - baseColorNode.location = ((mainNode.location[0] - 500, mainNode.location[1] - 300)) + baseColorNode.location = ((mainNode.location[0] - 1100, mainNode.location[1] - 300)) baseColorNode.name = "Lightmap_BasecolorNode_A" else: baseColorNode = mainNode.inputs[0].links[0].from_node @@ -235,13 +235,19 @@ def apply_materials(): mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[0]) #Connect mixnode to pbr node mat.node_tree.links.new(UVLightmap.outputs[0], lightmapNode.inputs[0]) #Connect uvnode to lightmapnode + #If skip metallic + if scene.TLM_SceneProperties.tlm_metallic_clamp == "skip": + if mainNode.inputs[4].default_value > 0.1: #DELIMITER + moutput = mainNode.inputs[0].links[0].from_node + mat.node_tree.links.remove(moutput.outputs[0].links[0]) + def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"): if bpy.context.scene.TLM_SceneProperties.tlm_verbose: print(ext_postfix, new_postfix, formatHDR) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: for slot in obj.material_slots: mat = slot.material @@ -267,6 +273,53 @@ def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"): for image in bpy.data.images: image.reload() +def applyAOPass(): + + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + for slot in obj.material_slots: + mat = slot.material + node_tree = mat.node_tree + nodes = mat.node_tree.nodes + + for node in nodes: + if node.name == "Baked Image" or node.name == "TLM_Lightmap": + + filepath = bpy.data.filepath + dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + + LightmapPath = node.image.filepath_raw + + filebase = os.path.basename(LightmapPath) + filename = os.path.splitext(filebase)[0] + extension = os.path.splitext(filebase)[1] + AOImagefile = filename[:-4] + "_ao" + AOImagePath = os.path.join(dirpath, AOImagefile + extension) + + AOMap = nodes.new('ShaderNodeTexImage') + AOMap.name = "TLM_AOMap" + AOImage = bpy.data.images.load(AOImagePath) + AOMap.image = AOImage + AOMap.location = -800, 0 + + AOMult = nodes.new(type="ShaderNodeMixRGB") + AOMult.name = "TLM_AOMult" + AOMult.blend_type = 'MULTIPLY' + AOMult.inputs[0].default_value = 1.0 + AOMult.location = -300, 300 + + multyNode = nodes["Lightmap_Multiplication"] + mainNode = nodes["Principled BSDF"] + UVMapNode = nodes["Lightmap_UV"] + + node_tree.links.remove(multyNode.outputs[0].links[0]) + + node_tree.links.new(multyNode.outputs[0], AOMult.inputs[1]) + node_tree.links.new(AOMap.outputs[0], AOMult.inputs[2]) + node_tree.links.new(AOMult.outputs[0], mainNode.inputs[0]) + node_tree.links.new(UVMapNode.outputs[0], AOMap.inputs[0]) + def load_library(asset_name): scriptDir = os.path.dirname(os.path.realpath(__file__)) diff --git a/blender/arm/lightmapper/utility/cycles/prepare.py b/blender/arm/lightmapper/utility/cycles/prepare.py index 6e5bd70f..6222d18c 100644 --- a/blender/arm/lightmapper/utility/cycles/prepare.py +++ b/blender/arm/lightmapper/utility/cycles/prepare.py @@ -1,4 +1,4 @@ -import bpy +import bpy, math from . import cache from .. utility import * @@ -31,13 +31,16 @@ def configure_lights(): def configure_meshes(self): - for obj in bpy.data.objects: - if obj.type == "MESH": + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Configuring meshes") + + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_restore(obj) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: cache.backup_material_rename(obj) @@ -59,9 +62,18 @@ def configure_meshes(self): scene = bpy.context.scene - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + if scene.TLM_SceneProperties.tlm_apply_on_unwrap: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Applying transform to: " + obj.name) + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + + obj.hide_select = False #Remember to toggle this back for slot in obj.material_slots: if "." + slot.name + '_Original' in bpy.data.materials: if bpy.context.scene.TLM_SceneProperties.tlm_verbose: @@ -77,33 +89,35 @@ def configure_meshes(self): bpy.ops.object.select_all(action='DESELECT') - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": uv_layers = obj.data.uv_layers - if not "UVMap_Lightmap" in uv_layers: + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + if not uv_channel in uv_layers: if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("UVMap made A") - uvmap = uv_layers.new(name="UVMap_Lightmap") + print("UV map created for object: " + obj.name) + uvmap = uv_layers.new(name=uv_channel) uv_layers.active_index = len(uv_layers) - 1 else: - print("Existing found...skipping") + print("Existing UV map found for object: " + obj.name) for i in range(0, len(uv_layers)): if uv_layers[i].name == 'UVMap_Lightmap': uv_layers.active_index = i - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Lightmap shift A") break atlas_items.append(obj) obj.select_set(True) - if scene.TLM_SceneProperties.tlm_apply_on_unwrap: - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) - if atlasgroup.tlm_atlas_lightmap_unwrap_mode == "SmartProject": if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Smart Project A for: " + str(atlas_items)) + print("Atlasgroup Smart Project for: " + str(atlas_items)) for obj in atlas_items: print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name) @@ -112,7 +126,12 @@ def configure_meshes(self): bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=atlasgroup.tlm_atlas_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) + #API changes in 2.91 causes errors: + if (2, 91, 0) > bpy.app.version: + bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) + else: + angle = math.radians(45.0) + bpy.ops.uv.smart_project(angle_limit=angle, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, area_weight=1.0, correct_aspect=True, scale_to_bounds=False) bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.mode_set(mode='OBJECT') elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Lightmap": @@ -123,8 +142,9 @@ def configure_meshes(self): bpy.ops.object.mode_set(mode='OBJECT') elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Xatlas": + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Temporary skip: COPYING SMART PROJECT") + print("Using Xatlas on Atlas Group: " + atlas) for obj in atlas_items: obj.select_set(True) @@ -139,176 +159,213 @@ def configure_meshes(self): else: if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Copied Existing A") + print("Copied Existing UV Map for Atlas Group: " + atlas) - for obj in bpy.data.objects: - if obj.type == "MESH": + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: iterNum = iterNum + 1 - for obj in bpy.data.objects: - if obj.type == "MESH": - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + for obj in bpy.context.scene.objects: + if obj.name in bpy.context.view_layer.objects: #Possible fix for view layer error + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: - objWasHidden = False + objWasHidden = False - #For some reason, a Blender bug might prevent invisible objects from being smart projected - #We will turn the object temporarily visible - obj.hide_viewport = False - obj.hide_set(False) + #For some reason, a Blender bug might prevent invisible objects from being smart projected + #We will turn the object temporarily visible + obj.hide_viewport = False + obj.hide_set(False) - currentIterNum = currentIterNum + 1 + currentIterNum = currentIterNum + 1 - #Configure selection - bpy.ops.object.select_all(action='DESELECT') - bpy.context.view_layer.objects.active = obj - obj.select_set(True) - obs = bpy.context.view_layer.objects - active = obs.active + #Configure selection + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = obj + obj.select_set(True) - #Provide material if none exists - preprocess_material(obj, scene) + obs = bpy.context.view_layer.objects + active = obs.active - #UV Layer management here - if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - uv_layers = obj.data.uv_layers - if not "UVMap_Lightmap" in uv_layers: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("UVMap made B") - uvmap = uv_layers.new(name="UVMap_Lightmap") - uv_layers.active_index = len(uv_layers) - 1 + #Provide material if none exists + preprocess_material(obj, scene) - #If lightmap - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap": - if scene.TLM_SceneProperties.tlm_apply_on_unwrap: - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) - bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin) - - #If smart project - elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject": + #UV Layer management here + if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + uv_layers = obj.data.uv_layers + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + if not uv_channel in uv_layers: if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Smart Project B") - if scene.TLM_SceneProperties.tlm_apply_on_unwrap: - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) - bpy.ops.object.select_all(action='DESELECT') - obj.select_set(True) - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='DESELECT') - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) - - elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Xatlas": + print("UV map created for obj: " + obj.name) + uvmap = uv_layers.new(name=uv_channel) + uv_layers.active_index = len(uv_layers) - 1 - if scene.TLM_SceneProperties.tlm_apply_on_unwrap: - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + #If lightmap + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap": + bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin) - #import blender_xatlas - #blender_xatlas.Unwrap_Lightmap_Group_Xatlas_2(bpy.context) + #If smart project + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject": - #bpy.ops.object.setup_unwrap() - Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj) - - elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer) - - else: #if copy existing - - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Copied Existing B") - - else: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Existing found...skipping") - for i in range(0, len(uv_layers)): - if uv_layers[i].name == 'UVMap_Lightmap': - uv_layers.active_index = i if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Lightmap shift B") - break + print("Smart Project B") + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + #API changes in 2.91 causes errors: + if (2, 91, 0) > bpy.app.version: + bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) + else: + angle = math.radians(45.0) + bpy.ops.uv.smart_project(angle_limit=angle, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, area_weight=1.0, correct_aspect=True, scale_to_bounds=False) + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.mode_set(mode='OBJECT') + + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Xatlas": + + Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj) - #Sort out nodes - for slot in obj.material_slots: + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - nodetree = slot.material.node_tree + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer) + + else: #if copy existing - outputNode = nodetree.nodes[0] #Presumed to be material output node + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Copied Existing UV Map for object: " + obj.name) - if(outputNode.type != "OUTPUT_MATERIAL"): + else: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Existing UV map found for obj: " + obj.name) + for i in range(0, len(uv_layers)): + if uv_layers[i].name == uv_channel: + uv_layers.active_index = i + break + + #print(x) + + #Sort out nodes + for slot in obj.material_slots: + + nodetree = slot.material.node_tree + + outputNode = nodetree.nodes[0] #Presumed to be material output node + + if(outputNode.type != "OUTPUT_MATERIAL"): + for node in nodetree.nodes: + if node.type == "OUTPUT_MATERIAL": + outputNode = node + break + + mainNode = outputNode.inputs[0].links[0].from_node + + if mainNode.type not in ['BSDF_PRINCIPLED','BSDF_DIFFUSE','GROUP']: + + #TODO! FIND THE PRINCIPLED PBR + self.report({'INFO'}, "The primary material node is not supported. Seeking first principled.") + + if len(find_node_by_type(nodetree.nodes, Node_Types.pbr_node)) > 0: + mainNode = find_node_by_type(nodetree.nodes, Node_Types.pbr_node)[0] + else: + self.report({'INFO'}, "No principled found. Seeking diffuse") + if len(find_node_by_type(nodetree.nodes, Node_Types.diffuse)) > 0: + mainNode = find_node_by_type(nodetree.nodes, Node_Types.diffuse)[0] + else: + self.report({'INFO'}, "No supported nodes. Continuing anyway.") + + if mainNode.type == 'GROUP': + if mainNode.node_tree != "Armory PBR": + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("The material group is not supported!") + + if (mainNode.type == "BSDF_PRINCIPLED"): + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("BSDF_Principled") + if scene.TLM_EngineProperties.tlm_directional_mode == "None": + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Directional mode") + if not len(mainNode.inputs[19].links) == 0: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("NOT LEN 0") + ninput = mainNode.inputs[19].links[0] + noutput = mainNode.inputs[19].links[0].from_node + nodetree.links.remove(noutput.outputs[0].links[0]) + + #Clamp metallic + if bpy.context.scene.TLM_SceneProperties.tlm_metallic_clamp == "limit": + MainMetNodeSocket = mainNode.inputs[4] + if not len(MainMetNodeSocket.links) == 0: + nodes = nodetree.nodes + MetClampNode = nodes.new('ShaderNodeClamp') + MetClampNode.location = (-200,150) + MetClampNode.inputs[2].default_value = 0.9 + minput = mainNode.inputs[4].links[0] #Metal input socket + moutput = mainNode.inputs[4].links[0].from_node #Metal output node + nodetree.links.remove(moutput.outputs[0].links[0]) #Works + nodetree.links.new(moutput.outputs[0], MetClampNode.inputs[0]) #minput node to clamp node + nodetree.links.new(MetClampNode.outputs[0],MainMetNodeSocket) #clamp node to metinput + else: + if mainNode.inputs[4].default_value > 0.9: + mainNode.inputs[4].default_value = 0.9 + elif bpy.context.scene.TLM_SceneProperties.tlm_metallic_clamp == "zero": + MainMetNodeSocket = mainNode.inputs[4] + if not len(MainMetNodeSocket.links) == 0: + nodes = nodetree.nodes + MetClampNode = nodes.new('ShaderNodeClamp') + MetClampNode.location = (-200,150) + MetClampNode.inputs[2].default_value = 0.0 + minput = mainNode.inputs[4].links[0] #Metal input socket + moutput = mainNode.inputs[4].links[0].from_node #Metal output node + nodetree.links.remove(moutput.outputs[0].links[0]) #Works + nodetree.links.new(moutput.outputs[0], MetClampNode.inputs[0]) #minput node to clamp node + nodetree.links.new(MetClampNode.outputs[0],MainMetNodeSocket) #clamp node to metinput + else: + mainNode.inputs[4].default_value = 0.0 + + if (mainNode.type == "BSDF_DIFFUSE"): + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("BSDF_Diffuse") + + # if (mainNode.type == "BSDF_DIFFUSE"): + # if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + # print("BSDF_Diffuse") + + #TODO FIX THIS PART! + #THIS IS USED IN CASES WHERE FOR SOME REASON THE USER FORGETS TO CONNECT SOMETHING INTO THE OUTPUT MATERIAL + for slot in obj.material_slots: + + nodetree = bpy.data.materials[slot.name].node_tree + nodes = nodetree.nodes + + #First search to get the first output material type for node in nodetree.nodes: if node.type == "OUTPUT_MATERIAL": - outputNode = node + mainNode = node break - mainNode = outputNode.inputs[0].links[0].from_node + #Fallback to get search + if not mainNode.type == "OUTPUT_MATERIAL": + mainNode = nodetree.nodes.get("Material Output") - if mainNode.type not in ['BSDF_PRINCIPLED','BSDF_DIFFUSE','GROUP']: + #Last resort to first node in list + if not mainNode.type == "OUTPUT_MATERIAL": + mainNode = nodetree.nodes[0].inputs[0].links[0].from_node - #TODO! FIND THE PRINCIPLED PBR - self.report({'INFO'}, "The primary material node is not supported. Seeking first principled.") + # for node in nodes: + # if "LM" in node.name: + # nodetree.links.new(node.outputs[0], mainNode.inputs[0]) - if len(find_node_by_type(nodetree.nodes, Node_Types.pbr_node)) > 0: - mainNode = find_node_by_type(nodetree.nodes, Node_Types.pbr_node)[0] - else: - self.report({'INFO'}, "No principled found. Seeking diffuse") - if len(find_node_by_type(nodetree.nodes, Node_Types.diffuse)) > 0: - mainNode = find_node_by_type(nodetree.nodes, Node_Types.diffuse)[0] - else: - self.report({'INFO'}, "No supported nodes. Continuing anyway.") - - if mainNode.type == 'GROUP': - if mainNode.node_tree != "Armory PBR": - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("The material group is not supported!") - - if (mainNode.type == "BSDF_PRINCIPLED"): - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("BSDF_Principled") - if scene.TLM_EngineProperties.tlm_directional_mode == "None": - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("Directional mode") - if not len(mainNode.inputs[19].links) == 0: - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("NOT LEN 0") - ninput = mainNode.inputs[19].links[0] - noutput = mainNode.inputs[19].links[0].from_node - nodetree.links.remove(noutput.outputs[0].links[0]) - - #Clamp metallic - if(mainNode.inputs[4].default_value == 1): - mainNode.inputs[4].default_value = 0.0 - - if (mainNode.type == "BSDF_DIFFUSE"): - if bpy.context.scene.TLM_SceneProperties.tlm_verbose: - print("BSDF_Diffuse") - - for slot in obj.material_slots: - - nodetree = bpy.data.materials[slot.name].node_tree - nodes = nodetree.nodes - - #First search to get the first output material type - for node in nodetree.nodes: - if node.type == "OUTPUT_MATERIAL": - mainNode = node - break - - #Fallback to get search - if not mainNode.type == "OUTPUT_MATERIAL": - mainNode = nodetree.nodes.get("Material Output") - - #Last resort to first node in list - if not mainNode.type == "OUTPUT_MATERIAL": - mainNode = nodetree.nodes[0].inputs[0].links[0].from_node - - for node in nodes: - if "LM" in node.name: - nodetree.links.new(node.outputs[0], mainNode.inputs[0]) - - for node in nodes: - if "Lightmap" in node.name: - nodes.remove(node) + # for node in nodes: + # if "Lightmap" in node.name: + # nodes.remove(node) def preprocess_material(obj, scene): if len(obj.material_slots) == 0: @@ -537,7 +594,7 @@ def store_existing(prev_container): selected = [] - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.select_get(): selected.append(obj.name) diff --git a/blender/arm/lightmapper/utility/denoiser/integrated.py b/blender/arm/lightmapper/utility/denoiser/integrated.py index e3061f30..38c1cabc 100644 --- a/blender/arm/lightmapper/utility/denoiser/integrated.py +++ b/blender/arm/lightmapper/utility/denoiser/integrated.py @@ -22,7 +22,7 @@ class TLM_Integrated_Denoise: bpy.ops.object.camera_add() #Just select the first camera we find, needed for the compositor - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.type == "CAMERA": bpy.context.scene.camera = obj return diff --git a/blender/arm/lightmapper/utility/encoding.py b/blender/arm/lightmapper/utility/encoding.py index 92a8e8d5..f716f342 100644 --- a/blender/arm/lightmapper/utility/encoding.py +++ b/blender/arm/lightmapper/utility/encoding.py @@ -332,6 +332,205 @@ def encodeImageRGBDGPU(image, maxRange, outDir, quality): #Todo - Find a way to save #bpy.ops.image.save_all_modified() +#TODO - FINISH THIS +def encodeImageRGBMGPU(image, maxRange, outDir, quality): + input_image = bpy.data.images[image.name] + image_name = input_image.name + + offscreen = gpu.types.GPUOffScreen(input_image.size[0], input_image.size[1]) + + image = input_image + + vertex_shader = ''' + + uniform mat4 ModelViewProjectionMatrix; + + in vec2 texCoord; + in vec2 pos; + out vec2 texCoord_interp; + + void main() + { + //gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f); + //gl_Position.z = 1.0; + gl_Position = vec4(pos.xy, 100, 100); + texCoord_interp = texCoord; + } + + ''' + fragment_shader = ''' + in vec2 texCoord_interp; + out vec4 fragColor; + + uniform sampler2D image; + + //Code from here: https://github.com/BabylonJS/Babylon.js/blob/master/src/Shaders/ShadersInclude/helperFunctions.fx + + const float PI = 3.1415926535897932384626433832795; + const float HALF_MIN = 5.96046448e-08; // Smallest positive half. + + const float LinearEncodePowerApprox = 2.2; + const float GammaEncodePowerApprox = 1.0 / LinearEncodePowerApprox; + const vec3 LuminanceEncodeApprox = vec3(0.2126, 0.7152, 0.0722); + + const float Epsilon = 0.0000001; + #define saturate(x) clamp(x, 0.0, 1.0) + + float maxEps(float x) { + return max(x, Epsilon); + } + + float toLinearSpace(float color) + { + return pow(color, LinearEncodePowerApprox); + } + + vec3 toLinearSpace(vec3 color) + { + return pow(color, vec3(LinearEncodePowerApprox)); + } + + vec4 toLinearSpace(vec4 color) + { + return vec4(pow(color.rgb, vec3(LinearEncodePowerApprox)), color.a); + } + + vec3 toGammaSpace(vec3 color) + { + return pow(color, vec3(GammaEncodePowerApprox)); + } + + vec4 toGammaSpace(vec4 color) + { + return vec4(pow(color.rgb, vec3(GammaEncodePowerApprox)), color.a); + } + + float toGammaSpace(float color) + { + return pow(color, GammaEncodePowerApprox); + } + + float square(float value) + { + return value * value; + } + + // Check if configurable value is needed. + const float rgbdMaxRange = 255.0; + + vec4 toRGBM(vec3 color) { + + vec4 rgbm; + color *= 1.0/6.0; + rgbm.a = saturate( max( max( color.r, color.g ), max( color.b, 1e-6 ) ) ); + rgbm.a = clamp(floor(D) / 255.0, 0., 1.); + rgbm.rgb = color / rgbm.a; + + return + + float maxRGB = maxEps(max(color.r, max(color.g, color.b))); + float D = max(rgbdMaxRange / maxRGB, 1.); + D = clamp(floor(D) / 255.0, 0., 1.); + vec3 rgb = color.rgb * D; + + // Helps with png quantization. + rgb = toGammaSpace(rgb); + + return vec4(rgb, D); + } + + vec3 fromRGBD(vec4 rgbd) { + // Helps with png quantization. + rgbd.rgb = toLinearSpace(rgbd.rgb); + + // return rgbd.rgb * ((rgbdMaxRange / 255.0) / rgbd.a); + + return rgbd.rgb / rgbd.a; + } + + void main() + { + + fragColor = toRGBM(texture(image, texCoord_interp).rgb); + + } + + ''' + + x_screen = 0 + off_x = -100 + off_y = -100 + y_screen_flip = 0 + sx = 200 + sy = 200 + + vertices = ( + (x_screen + off_x, y_screen_flip - off_y), + (x_screen + off_x, y_screen_flip - sy - off_y), + (x_screen + off_x + sx, y_screen_flip - sy - off_y), + (x_screen + off_x + sx, y_screen_flip - off_x)) + + if input_image.colorspace_settings.name != 'Linear': + input_image.colorspace_settings.name = 'Linear' + + # Removing .exr or .hdr prefix + if image_name[-4:] == '.exr' or image_name[-4:] == '.hdr': + image_name = image_name[:-4] + + target_image = bpy.data.images.get(image_name + '_encoded') + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print(image_name + '_encoded') + if not target_image: + target_image = bpy.data.images.new( + name = image_name + '_encoded', + width = input_image.size[0], + height = input_image.size[1], + alpha = True, + float_buffer = False + ) + + shader = gpu.types.GPUShader(vertex_shader, fragment_shader) + batch = batch_for_shader( + shader, 'TRI_FAN', + { + "pos": vertices, + "texCoord": ((0, 1), (0, 0), (1, 0), (1, 1)), + }, + ) + + if image.gl_load(): + raise Exception() + + with offscreen.bind(): + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, image.bindcode) + + shader.bind() + shader.uniform_int("image", 0) + batch.draw(shader) + + buffer = bgl.Buffer(bgl.GL_BYTE, input_image.size[0] * input_image.size[1] * 4) + bgl.glReadBuffer(bgl.GL_BACK) + bgl.glReadPixels(0, 0, input_image.size[0], input_image.size[1], bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer) + + offscreen.free() + + target_image.pixels = [v / 255 for v in buffer] + input_image = target_image + + #Save LogLuv + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print(input_image.name) + input_image.filepath_raw = outDir + "/" + input_image.name + ".png" + #input_image.filepath_raw = outDir + "_encoded.png" + input_image.file_format = "PNG" + bpy.context.scene.render.image_settings.quality = quality + #input_image.save_render(filepath = input_image.filepath_raw, scene = bpy.context.scene) + input_image.save() + + #Todo - Find a way to save + #bpy.ops.image.save_all_modified() + def encodeImageRGBMCPU(image, maxRange, outDir, quality): input_image = bpy.data.images[image.name] image_name = input_image.name @@ -431,21 +630,6 @@ def encodeImageRGBDCPU(image, maxRange, outDir, quality): result_pixel[i+1] = math.pow(result_pixel[i+1] * D, 1/2.2) result_pixel[i+2] = math.pow(result_pixel[i+2] * D, 1/2.2) result_pixel[i+3] = D - - - # for i in range(0,num_pixels,4): - - # m = saturate(max(result_pixel[i], result_pixel[i+1], result_pixel[i+2], 1e-6)) - # d = max(maxRange / m, 1) - # #d = saturate(math.floor(d) / 255.0) - # d = np.clip((math.floor(d) / 255.0), 0.0, 1.0) - - # #TODO TO GAMMA SPACE - - # result_pixel[i] = math.pow(result_pixel[i] * d * 255 / maxRange, 1/2.2) - # result_pixel[i+1] = math.pow(result_pixel[i+1] * d * 255 / maxRange, 1/2.2) - # result_pixel[i+2] = math.pow(result_pixel[i+2] * d * 255 / maxRange, 1/2.2) - # result_pixel[i+3] = d target_image.pixels = result_pixel @@ -457,25 +641,4 @@ def encodeImageRGBDCPU(image, maxRange, outDir, quality): input_image.filepath_raw = outDir + "/" + input_image.name + ".png" input_image.file_format = "PNG" bpy.context.scene.render.image_settings.quality = quality - input_image.save() - - # const float rgbdMaxRange = 255.0; - - # vec4 toRGBD(vec3 color) { - # float maxRGB = maxEps(max(color.r, max(color.g, color.b))); - # float D = max(rgbdMaxRange / maxRGB, 1.); - # D = clamp(floor(D) / 255.0, 0., 1.); - # vec3 rgb = color.rgb * D; - - # // Helps with png quantization. - # rgb = toGammaSpace(rgb); - - # return vec4(rgb, D); - # } - - # const float Epsilon = 0.0000001; - # #define saturate(x) clamp(x, 0.0, 1.0) - - # float maxEps(float x) { - # return max(x, Epsilon); - # } \ No newline at end of file + input_image.save() \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/filtering/opencv.py b/blender/arm/lightmapper/utility/filtering/opencv.py index 501b2b3d..c6b1b557 100644 --- a/blender/arm/lightmapper/utility/filtering/opencv.py +++ b/blender/arm/lightmapper/utility/filtering/opencv.py @@ -62,7 +62,7 @@ class TLM_CV_Filtering: #SEAM TESTING# ##################### - if obj_name in bpy.data.objects: + if obj_name in bpy.context.scene.objects: override = bpy.data.objects[obj_name].TLM_ObjectProperties.tlm_mesh_filter_override elif obj_name in scene.TLM_AtlasList: override = False diff --git a/blender/arm/lightmapper/utility/gui/Viewport.py b/blender/arm/lightmapper/utility/gui/Viewport.py new file mode 100644 index 00000000..be1e8d2b --- /dev/null +++ b/blender/arm/lightmapper/utility/gui/Viewport.py @@ -0,0 +1,67 @@ +import bpy, blf, bgl, os, gpu +from gpu_extras.batch import batch_for_shader + +class ViewportDraw: + + def __init__(self, context, text): + + bakefile = "TLM_Overlay.png" + scriptDir = os.path.dirname(os.path.realpath(__file__)) + bakefile_path = os.path.abspath(os.path.join(scriptDir, '..', '..', 'assets/' + bakefile)) + + image_name = "TLM_Overlay.png" + + bpy.ops.image.open(filepath=bakefile_path) + + print("Self path: " + bakefile_path) + + image = bpy.data.images[image_name] + + x = 15 + y = 15 + w = 400 + h = 200 + + self.shader = gpu.shader.from_builtin('2D_IMAGE') + self.batch = batch_for_shader( + self.shader, 'TRI_FAN', + { + "pos": ((x, y), (x+w, y), (x+w, y+h), (x, y+h)), + "texCoord": ((0, 0), (1, 0), (1, 1), (0, 1)), + }, + ) + + if image.gl_load(): + raise Exception() + + self.text = text + self.image = image + #self.handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_text_callback, (context,), 'WINDOW', 'POST_PIXEL') + self.handle2 = bpy.types.SpaceView3D.draw_handler_add(self.draw_image_callback, (context,), 'WINDOW', 'POST_PIXEL') + + def draw_text_callback(self, context): + + font_id = 0 + blf.position(font_id, 15, 15, 0) + blf.size(font_id, 20, 72) + blf.draw(font_id, "%s" % (self.text)) + + def draw_image_callback(self, context): + + if self.image: + bgl.glEnable(bgl.GL_BLEND) + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode) + + self.shader.bind() + self.shader.uniform_int("image", 0) + self.batch.draw(self.shader) + bgl.glDisable(bgl.GL_BLEND) + + def update_text(self, text): + + self.text = text + + def remove_handle(self): + #bpy.types.SpaceView3D.draw_handler_remove(self.handle, 'WINDOW') + bpy.types.SpaceView3D.draw_handler_remove(self.handle2, 'WINDOW') \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/luxcore/setup.py b/blender/arm/lightmapper/utility/luxcore/setup.py new file mode 100644 index 00000000..13bfc57b --- /dev/null +++ b/blender/arm/lightmapper/utility/luxcore/setup.py @@ -0,0 +1,259 @@ +import bpy + +from .. utility import * + +def init(self, prev_container): + + #TODO - JSON classes + export.scene = """scene.camera.cliphither = 0.1 +scene.camera.clipyon = 100 +scene.camera.shutteropen = 0 +scene.camera.shutterclose = 1 +scene.camera.autovolume.enable = 1 +scene.camera.lookat.orig = 7.358891 -6.925791 4.958309 +scene.camera.lookat.target = 6.707333 -6.31162 4.513038 +scene.camera.up = -0.3240135 0.3054208 0.8953956 +scene.camera.screenwindow = -1 1 -0.5625 0.5625 +scene.camera.lensradius = 0 +scene.camera.focaldistance = 10 +scene.camera.autofocus.enable = 0 +scene.camera.type = "perspective" +scene.camera.oculusrift.barrelpostpro.enable = 0 +scene.camera.fieldofview = 39.59776 +scene.camera.bokeh.blades = 0 +scene.camera.bokeh.power = 3 +scene.camera.bokeh.distribution.type = "NONE" +scene.camera.bokeh.scale.x = 0.7071068 +scene.camera.bokeh.scale.y = 0.7071068 +scene.lights.__WORLD_BACKGROUND_LIGHT__.gain = 2e-05 2e-05 2e-05 +scene.lights.__WORLD_BACKGROUND_LIGHT__.transformation = 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.id = 0 +scene.lights.__WORLD_BACKGROUND_LIGHT__.temperature = -1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.temperature.normalize = 0 +scene.lights.__WORLD_BACKGROUND_LIGHT__.visibility.indirect.diffuse.enable = 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.visibility.indirect.glossy.enable = 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.visibility.indirect.specular.enable = 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.type = "sky2" +scene.lights.__WORLD_BACKGROUND_LIGHT__.dir = 0 0 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.turbidity = 2.2 +scene.lights.__WORLD_BACKGROUND_LIGHT__.groundalbedo = 0.5 0.5 0.5 +scene.lights.__WORLD_BACKGROUND_LIGHT__.ground.enable = 0 +scene.lights.__WORLD_BACKGROUND_LIGHT__.ground.color = 0.5 0.5 0.5 +scene.lights.__WORLD_BACKGROUND_LIGHT__.ground.autoscale = 1 +scene.lights.__WORLD_BACKGROUND_LIGHT__.distribution.width = 512 +scene.lights.__WORLD_BACKGROUND_LIGHT__.distribution.height = 256 +scene.lights.__WORLD_BACKGROUND_LIGHT__.visibilitymapcache.enable = 0 +scene.lights.2382361116072.gain = 1 1 1 +scene.lights.2382361116072.transformation = -0.2908646 0.9551712 -0.05518906 0 -0.7711008 -0.1998834 0.6045247 0 0.5663932 0.2183912 0.7946723 0 4.076245 1.005454 5.903862 1 +scene.lights.2382361116072.id = 0 +scene.lights.2382361116072.temperature = -1 +scene.lights.2382361116072.temperature.normalize = 0 +scene.lights.2382361116072.type = "sphere" +scene.lights.2382361116072.color = 1 1 1 +scene.lights.2382361116072.power = 0 +scene.lights.2382361116072.normalizebycolor = 0 +scene.lights.2382361116072.efficency = 0 +scene.lights.2382361116072.position = 0 0 0 +scene.lights.2382361116072.radius = 0.1 +scene.materials.Material2382357175256.type = "disney" +scene.materials.Material2382357175256.basecolor = "0.7 0.7 0.7" +scene.materials.Material2382357175256.subsurface = "0" +scene.materials.Material2382357175256.roughness = "0.2" +scene.materials.Material2382357175256.metallic = "0" +scene.materials.Material2382357175256.specular = "0.5" +scene.materials.Material2382357175256.speculartint = "0" +scene.materials.Material2382357175256.clearcoat = "0" +scene.materials.Material2382357175256.clearcoatgloss = "1" +scene.materials.Material2382357175256.anisotropic = "0" +scene.materials.Material2382357175256.sheen = "0" +scene.materials.Material2382357175256.sheentint = "0" +scene.materials.Material2382357175256.transparency.shadow = 0 0 0 +scene.materials.Material2382357175256.id = 3364224 +scene.materials.Material2382357175256.emission.gain = 1 1 1 +scene.materials.Material2382357175256.emission.power = 0 +scene.materials.Material2382357175256.emission.normalizebycolor = 1 +scene.materials.Material2382357175256.emission.efficency = 0 +scene.materials.Material2382357175256.emission.theta = 90 +scene.materials.Material2382357175256.emission.id = 0 +scene.materials.Material2382357175256.emission.importance = 1 +scene.materials.Material2382357175256.emission.temperature = -1 +scene.materials.Material2382357175256.emission.temperature.normalize = 0 +scene.materials.Material2382357175256.emission.directlightsampling.type = "AUTO" +scene.materials.Material2382357175256.visibility.indirect.diffuse.enable = 1 +scene.materials.Material2382357175256.visibility.indirect.glossy.enable = 1 +scene.materials.Material2382357175256.visibility.indirect.specular.enable = 1 +scene.materials.Material2382357175256.shadowcatcher.enable = 0 +scene.materials.Material2382357175256.shadowcatcher.onlyinfinitelights = 0 +scene.materials.Material2382357175256.photongi.enable = 1 +scene.materials.Material2382357175256.holdout.enable = 0 +scene.materials.Material__0012382357172440.type = "disney" +scene.materials.Material__0012382357172440.basecolor = "0.7 0.7 0.7" +scene.materials.Material__0012382357172440.subsurface = "0" +scene.materials.Material__0012382357172440.roughness = "0.2" +scene.materials.Material__0012382357172440.metallic = "0" +scene.materials.Material__0012382357172440.specular = "0.5" +scene.materials.Material__0012382357172440.speculartint = "0" +scene.materials.Material__0012382357172440.clearcoat = "0" +scene.materials.Material__0012382357172440.clearcoatgloss = "1" +scene.materials.Material__0012382357172440.anisotropic = "0" +scene.materials.Material__0012382357172440.sheen = "0" +scene.materials.Material__0012382357172440.sheentint = "0" +scene.materials.Material__0012382357172440.transparency.shadow = 0 0 0 +scene.materials.Material__0012382357172440.id = 6728256 +scene.materials.Material__0012382357172440.emission.gain = 1 1 1 +scene.materials.Material__0012382357172440.emission.power = 0 +scene.materials.Material__0012382357172440.emission.normalizebycolor = 1 +scene.materials.Material__0012382357172440.emission.efficency = 0 +scene.materials.Material__0012382357172440.emission.theta = 90 +scene.materials.Material__0012382357172440.emission.id = 0 +scene.materials.Material__0012382357172440.emission.importance = 1 +scene.materials.Material__0012382357172440.emission.temperature = -1 +scene.materials.Material__0012382357172440.emission.temperature.normalize = 0 +scene.materials.Material__0012382357172440.emission.directlightsampling.type = "AUTO" +scene.materials.Material__0012382357172440.visibility.indirect.diffuse.enable = 1 +scene.materials.Material__0012382357172440.visibility.indirect.glossy.enable = 1 +scene.materials.Material__0012382357172440.visibility.indirect.specular.enable = 1 +scene.materials.Material__0012382357172440.shadowcatcher.enable = 0 +scene.materials.Material__0012382357172440.shadowcatcher.onlyinfinitelights = 0 +scene.materials.Material__0012382357172440.photongi.enable = 1 +scene.materials.Material__0012382357172440.holdout.enable = 0 +scene.objects.23823611086320.material = "Material2382357175256" +scene.objects.23823611086320.ply = "mesh-00000.ply" +scene.objects.23823611086320.camerainvisible = 0 +scene.objects.23823611086320.id = 1326487202 +scene.objects.23823611086320.appliedtransformation = 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 1 +scene.objects.23823611279760.material = "Material__0012382357172440" +scene.objects.23823611279760.ply = "mesh-00001.ply" +scene.objects.23823611279760.camerainvisible = 0 +scene.objects.23823611279760.id = 3772660237 +scene.objects.23823611279760.appliedtransformation = 5 0 0 0 0 5 0 0 0 0 5 0 0 0 0 1 +""" + + export.config = """context.verbose = 1 +accelerator.type = "AUTO" +accelerator.instances.enable = 1 +accelerator.motionblur.enable = 1 +accelerator.bvh.builder.type = "EMBREE_BINNED_SAH" +accelerator.bvh.treetype = 4 +accelerator.bvh.costsamples = 0 +accelerator.bvh.isectcost = 80 +accelerator.bvh.travcost = 10 +accelerator.bvh.emptybonus = 0.5 +scene.epsilon.min = "1e-05" +scene.epsilon.max = "0.1" +scene.file = "scene.scn" +images.scale = 1 +lightstrategy.type = "LOG_POWER" +native.threads.count = 8 +renderengine.type = "BAKECPU" +path.pathdepth.total = "7" +path.pathdepth.diffuse = "5" +path.pathdepth.glossy = "5" +path.pathdepth.specular = "6" +path.hybridbackforward.enable = "0" +path.hybridbackforward.partition = "0.8" +path.hybridbackforward.glossinessthreshold = "0.049" +path.russianroulette.depth = 3 +path.russianroulette.cap = 0.5 +path.clamping.variance.maxvalue = 0 +path.forceblackbackground.enable = "0" +sampler.type = "SOBOL" +sampler.imagesamples.enable = 1 +sampler.sobol.adaptive.strength = "0.9" +sampler.sobol.adaptive.userimportanceweight = 0.75 +sampler.sobol.bucketsize = "16" +sampler.sobol.tilesize = "16" +sampler.sobol.supersampling = "1" +sampler.sobol.overlapping = "1" +path.photongi.sampler.type = "METROPOLIS" +path.photongi.photon.maxcount = 100000000 +path.photongi.photon.maxdepth = 4 +path.photongi.photon.time.start = 0 +path.photongi.photon.time.end = -1 +path.photongi.visibility.lookup.radius = 0 +path.photongi.visibility.lookup.normalangle = 10 +path.photongi.visibility.targethitrate = 0.99 +path.photongi.visibility.maxsamplecount = 1048576 +path.photongi.glossinessusagethreshold = 0.05 +path.photongi.indirect.enabled = 0 +path.photongi.indirect.maxsize = 0 +path.photongi.indirect.haltthreshold = 0.05 +path.photongi.indirect.lookup.radius = 0 +path.photongi.indirect.lookup.normalangle = 10 +path.photongi.indirect.usagethresholdscale = 8 +path.photongi.indirect.filter.radiusscale = 3 +path.photongi.caustic.enabled = 0 +path.photongi.caustic.maxsize = 100000 +path.photongi.caustic.updatespp = 8 +path.photongi.caustic.updatespp.radiusreduction = 0.96 +path.photongi.caustic.updatespp.minradius = 0.003 +path.photongi.caustic.lookup.radius = 0.15 +path.photongi.caustic.lookup.normalangle = 10 +path.photongi.debug.type = "none" +path.photongi.persistent.file = "" +path.photongi.persistent.safesave = 1 +film.filter.type = "BLACKMANHARRIS" +film.filter.width = 2 +opencl.platform.index = -1 +film.width = 960 +film.height = 600 +film.safesave = 1 +film.noiseestimation.step = "32" +film.noiseestimation.warmup = "8" +film.noiseestimation.filter.scale = 4 +batch.haltnoisethreshold = 0.01 +batch.haltnoisethreshold.step = 64 +batch.haltnoisethreshold.warmup = 64 +batch.haltnoisethreshold.filter.enable = 1 +batch.haltnoisethreshold.stoprendering.enable = 1 +batch.halttime = "0" +batch.haltspp = 32 +film.outputs.safesave = 1 +film.outputs.0.type = "RGB_IMAGEPIPELINE" +film.outputs.0.filename = "RGB_IMAGEPIPELINE_0.png" +film.outputs.0.index = "0" +film.imagepipelines.000.0.type = "NOP" +film.imagepipelines.000.1.type = "TONEMAP_LINEAR" +film.imagepipelines.000.1.scale = "1" +film.imagepipelines.000.2.type = "GAMMA_CORRECTION" +film.imagepipelines.000.2.value = "2.2" +film.imagepipelines.000.radiancescales.0.enabled = "1" +film.imagepipelines.000.radiancescales.0.globalscale = "1" +film.imagepipelines.000.radiancescales.0.rgbscale = "1" "1" "1" +periodicsave.film.outputs.period = 0 +periodicsave.film.period = 0 +periodicsave.film.filename = "film.flm" +periodicsave.resumerendering.period = 0 +periodicsave.resumerendering.filename = "rendering.rsm" +resumerendering.filesafe = 1 +debug.renderconfig.parse.print = 0 +debug.scene.parse.print = 0 +screen.refresh.interval = 100 +screen.tool.type = "CAMERA_EDIT" +screen.tiles.pending.show = 1 +screen.tiles.converged.show = 0 +screen.tiles.notconverged.show = 0 +screen.tiles.passcount.show = 0 +screen.tiles.error.show = 0 +bake.minmapautosize = 64 +bake.maxmapautosize = 1024 +bake.powerof2autosize.enable = 1 +bake.skipexistingmapfiles = 1 +film.imagepipelines.1.0.type = "NOP" +bake.maps.0.type = "COMBINED" +bake.maps.0.filename = "23823611086320.exr" +bake.maps.0.imagepipelineindex = 1 +bake.maps.0.width = 512 +bake.maps.0.height = 512 +bake.maps.0.autosize.enabled = 1 +bake.maps.0.uvindex = 0 +bake.maps.0.objectnames = "23823611086320" +bake.maps.1.type = "COMBINED" +bake.maps.1.filename = "23823611279760.exr" +bake.maps.1.imagepipelineindex = 1 +bake.maps.1.width = 512 +bake.maps.1.height = 512 +bake.maps.1.autosize.enabled = 1 +bake.maps.1.uvindex = 0 +bake.maps.1.objectnames = "23823611279760" +""" \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/octane/configure.py b/blender/arm/lightmapper/utility/octane/configure.py new file mode 100644 index 00000000..ba6641ab --- /dev/null +++ b/blender/arm/lightmapper/utility/octane/configure.py @@ -0,0 +1,243 @@ +import bpy, math + +#from . import cache +from .. utility import * + +def init(self, prev_container): + + #store_existing(prev_container) + + #set_settings() + + configure_world() + + configure_lights() + + configure_meshes(self) + +def configure_world(): + pass + +def configure_lights(): + pass + +def configure_meshes(self): + + for mat in bpy.data.materials: + if mat.users < 1: + bpy.data.materials.remove(mat) + + for mat in bpy.data.materials: + if mat.name.startswith("."): + if "_Original" in mat.name: + bpy.data.materials.remove(mat) + + for image in bpy.data.images: + if image.name.endswith("_baked"): + bpy.data.images.remove(image, do_unlink=True) + + iterNum = 1 + currentIterNum = 0 + + scene = bpy.context.scene + + for obj in scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + obj.hide_select = False #Remember to toggle this back + + currentIterNum = currentIterNum + 1 + + obj.octane.baking_group_id = 1 + currentIterNum #0 doesn't exist, 1 is neutral and 2 is first baked object + + print("Obj: " + obj.name + " set to baking group: " + str(obj.octane.baking_group_id)) + + for slot in obj.material_slots: + if "." + slot.name + '_Original' in bpy.data.materials: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("The material: " + slot.name + " shifted to " + "." + slot.name + '_Original') + slot.material = bpy.data.materials["." + slot.name + '_Original'] + + + objWasHidden = False + + #For some reason, a Blender bug might prevent invisible objects from being smart projected + #We will turn the object temporarily visible + obj.hide_viewport = False + obj.hide_set(False) + + #Configure selection + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + obs = bpy.context.view_layer.objects + active = obs.active + + uv_layers = obj.data.uv_layers + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + if not uv_channel in uv_layers: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("UV map created for obj: " + obj.name) + uvmap = uv_layers.new(name=uv_channel) + uv_layers.active_index = len(uv_layers) - 1 + print("Setting active UV to: " + uv_layers.active_index) + + #If lightmap + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap": + bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin) + + #If smart project + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject": + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Smart Project B") + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + #API changes in 2.91 causes errors: + if (2, 91, 0) > bpy.app.version: + bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False) + else: + angle = math.radians(45.0) + bpy.ops.uv.smart_project(angle_limit=angle, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, area_weight=1.0, correct_aspect=True, scale_to_bounds=False) + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.mode_set(mode='OBJECT') + + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Xatlas": + + Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj) + + elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer) + + else: #if copy existing + + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Copied Existing UV Map for object: " + obj.name) + + else: + if bpy.context.scene.TLM_SceneProperties.tlm_verbose: + print("Existing UV map found for obj: " + obj.name) + for i in range(0, len(uv_layers)): + if uv_layers[i].name == uv_channel: + uv_layers.active_index = i + break + + set_camera() + +def set_camera(): + + cam_name = "TLM-BakeCam" + + if not cam_name in bpy.context.scene: + camera = bpy.data.cameras.new(cam_name) + camobj_name = "TLM-BakeCam-obj" + cam_obj = bpy.data.objects.new(camobj_name, camera) + bpy.context.collection.objects.link(cam_obj) + cam_obj.location = ((0,0,0)) + + bpy.context.scene.camera = cam_obj + +def set_settings(): + + scene = bpy.context.scene + cycles = scene.cycles + scene.render.engine = "CYCLES" + sceneProperties = scene.TLM_SceneProperties + engineProperties = scene.TLM_EngineProperties + cycles.device = scene.TLM_EngineProperties.tlm_mode + + if cycles.device == "GPU": + scene.render.tile_x = 256 + scene.render.tile_y = 256 + else: + scene.render.tile_x = 32 + scene.render.tile_y = 32 + + if engineProperties.tlm_quality == "0": + cycles.samples = 32 + cycles.max_bounces = 1 + cycles.diffuse_bounces = 1 + cycles.glossy_bounces = 1 + cycles.transparent_max_bounces = 1 + cycles.transmission_bounces = 1 + cycles.volume_bounces = 1 + cycles.caustics_reflective = False + cycles.caustics_refractive = False + elif engineProperties.tlm_quality == "1": + cycles.samples = 64 + cycles.max_bounces = 2 + cycles.diffuse_bounces = 2 + cycles.glossy_bounces = 2 + cycles.transparent_max_bounces = 2 + cycles.transmission_bounces = 2 + cycles.volume_bounces = 2 + cycles.caustics_reflective = False + cycles.caustics_refractive = False + elif engineProperties.tlm_quality == "2": + cycles.samples = 512 + cycles.max_bounces = 2 + cycles.diffuse_bounces = 2 + cycles.glossy_bounces = 2 + cycles.transparent_max_bounces = 2 + cycles.transmission_bounces = 2 + cycles.volume_bounces = 2 + cycles.caustics_reflective = False + cycles.caustics_refractive = False + elif engineProperties.tlm_quality == "3": + cycles.samples = 1024 + cycles.max_bounces = 256 + cycles.diffuse_bounces = 256 + cycles.glossy_bounces = 256 + cycles.transparent_max_bounces = 256 + cycles.transmission_bounces = 256 + cycles.volume_bounces = 256 + cycles.caustics_reflective = False + cycles.caustics_refractive = False + elif engineProperties.tlm_quality == "4": + cycles.samples = 2048 + cycles.max_bounces = 512 + cycles.diffuse_bounces = 512 + cycles.glossy_bounces = 512 + cycles.transparent_max_bounces = 512 + cycles.transmission_bounces = 512 + cycles.volume_bounces = 512 + cycles.caustics_reflective = True + cycles.caustics_refractive = True + else: #Custom + pass + +def store_existing(prev_container): + + scene = bpy.context.scene + cycles = scene.cycles + + selected = [] + + for obj in bpy.context.scene.objects: + if obj.select_get(): + selected.append(obj.name) + + prev_container["settings"] = [ + cycles.samples, + cycles.max_bounces, + cycles.diffuse_bounces, + cycles.glossy_bounces, + cycles.transparent_max_bounces, + cycles.transmission_bounces, + cycles.volume_bounces, + cycles.caustics_reflective, + cycles.caustics_refractive, + cycles.device, + scene.render.engine, + bpy.context.view_layer.objects.active, + selected, + [scene.render.resolution_x, scene.render.resolution_y] + ] \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/octane/lightmap2.py b/blender/arm/lightmapper/utility/octane/lightmap2.py new file mode 100644 index 00000000..ad843276 --- /dev/null +++ b/blender/arm/lightmapper/utility/octane/lightmap2.py @@ -0,0 +1,71 @@ +import bpy, os + +def bake(): + + cam_name = "TLM-BakeCam-obj" + + if cam_name in bpy.context.scene.objects: + + print("Camera found...") + + camera = bpy.context.scene.objects[cam_name] + + camera.data.octane.baking_camera = True + + for obj in bpy.context.scene.objects: + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(False) + + iterNum = 2 + currentIterNum = 1 + + for obj in bpy.context.scene.objects: + if obj.type == "MESH": + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + iterNum = iterNum + 1 + + if iterNum > 1: + iterNum = iterNum - 1 + + for obj in bpy.context.scene.objects: + if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects: + if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: + + currentIterNum = currentIterNum + 1 + + scene = bpy.context.scene + + print("Baking obj: " + obj.name) + + print("Baking ID: " + str(currentIterNum) + " out of " + str(iterNum)) + + bpy.ops.object.select_all(action='DESELECT') + + camera.data.octane.baking_group_id = currentIterNum + + savedir = os.path.dirname(bpy.data.filepath) + user_dir = scene.TLM_Engine3Properties.tlm_lightmap_savedir + directory = os.path.join(savedir, user_dir) + + image_settings = bpy.context.scene.render.image_settings + image_settings.file_format = "HDR" + image_settings.color_depth = '32' + + filename = os.path.join(directory, "LM") + "_" + obj.name + ".hdr" + bpy.context.scene.render.filepath = filename + + resolution = int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) + + bpy.context.scene.render.resolution_x = resolution + bpy.context.scene.render.resolution_y = resolution + + bpy.ops.render.render(write_still=True) + + else: + + print("No baking camera found") + + + + + print("Baking in Octane!") \ No newline at end of file diff --git a/blender/arm/lightmapper/utility/pack.py b/blender/arm/lightmapper/utility/pack.py index 3c2768c7..c2e1f15c 100644 --- a/blender/arm/lightmapper/utility/pack.py +++ b/blender/arm/lightmapper/utility/pack.py @@ -106,7 +106,7 @@ def postpack(): rect = [] #For each object that targets the atlas - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: @@ -156,7 +156,13 @@ def postpack(): obj = bpy.data.objects[aob] for idx, layer in enumerate(obj.data.uv_layers): - if layer.name == "UVMap_Lightmap": + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel + else: + uv_channel = "UVMap_Lightmap" + + if layer.name == uv_channel: obj.data.uv_layers.active_index = idx print("UVLayer set to: " + str(obj.data.uv_layers.active_index)) @@ -194,7 +200,7 @@ def postpack(): print("Written: " + str(os.path.join(lightmap_directory, atlas.name + end + formatEnc))) #Change the material for each material, slot - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: @@ -219,7 +225,7 @@ def postpack(): existing_image.user_clear() #Add dilation map here... - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: diff --git a/blender/arm/lightmapper/utility/utility.py b/blender/arm/lightmapper/utility/utility.py index 75929157..b206c773 100644 --- a/blender/arm/lightmapper/utility/utility.py +++ b/blender/arm/lightmapper/utility/utility.py @@ -1,5 +1,5 @@ import bpy.ops as O -import bpy, os, re, sys, importlib, struct, platform, subprocess, threading, string, bmesh +import bpy, os, re, sys, importlib, struct, platform, subprocess, threading, string, bmesh, shutil, glob, uuid from io import StringIO from threading import Thread from queue import Queue, Empty @@ -81,15 +81,8 @@ def save_image(image): image.filepath_raw = savepath - # if "Normal" in image.name: - # bpy.context.scene.render.image_settings.quality = 90 - # image.save_render( filepath = image.filepath_raw, scene = bpy.context.scene ) - # else: image.save() - - - def get_file_size(filepath): size = "Unpack Files" try: @@ -141,7 +134,7 @@ def check_is_org_material(self,material): def clean_empty_materials(self): - for obj in bpy.data.objects: + for obj in bpy.context.scene.objects: for slot in obj.material_slots: mat = slot.material if mat is None: @@ -319,6 +312,11 @@ def lightmap_to_ao(material,lightmap_node): # https://github.com/mattedicksoncom/blender-xatlas/ ########################################################### +def gen_safe_name(): + genId = uuid.uuid4().hex + # genId = "u_" + genId.replace("-","_") + return "u_" + genId + def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): blender_xatlas = importlib.util.find_spec("blender_xatlas") @@ -330,32 +328,54 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): packOptions = bpy.context.scene.pack_tool chartOptions = bpy.context.scene.chart_tool + sharedProperties = bpy.context.scene.shared_properties + #sharedProperties.unwrapSelection context = bpy.context - - if obj.type == 'MESH': - context.view_layer.objects.active = obj - if obj.data.users > 1: - obj.data = obj.data.copy() #make single user copy - uv_layers = obj.data.uv_layers - #setup the lightmap uvs - uvName = "UVMap_Lightmap" - if sharedProperties.lightmapUVChoiceType == "NAME": - uvName = sharedProperties.lightmapUVName - elif sharedProperties.lightmapUVChoiceType == "INDEX": - if sharedProperties.lightmapUVIndex < len(uv_layers): - uvName = uv_layers[sharedProperties.lightmapUVIndex].name + #save whatever mode the user was in + startingMode = bpy.context.object.mode + selected_objects = bpy.context.selected_objects - if not uvName in uv_layers: - uvmap = uv_layers.new(name=uvName) - uv_layers.active_index = len(uv_layers) - 1 - else: - for i in range(0, len(uv_layers)): - if uv_layers[i].name == uvName: - uv_layers.active_index = i - obj.select_set(True) + #check something is actually selected + #external function/operator will select them + if len(selected_objects) == 0: + print("Nothing Selected") + self.report({"WARNING"}, "Nothing Selected, please select Something") + return {'FINISHED'} + + #store the names of objects to be lightmapped + rename_dict = dict() + safe_dict = dict() + + #make sure all the objects have ligthmap uvs + for obj in selected_objects: + if obj.type == 'MESH': + safe_name = gen_safe_name(); + rename_dict[obj.name] = (obj.name,safe_name) + safe_dict[safe_name] = obj.name + context.view_layer.objects.active = obj + if obj.data.users > 1: + obj.data = obj.data.copy() #make single user copy + uv_layers = obj.data.uv_layers + + #setup the lightmap uvs + uvName = "UVMap_Lightmap" + if sharedProperties.lightmapUVChoiceType == "NAME": + uvName = sharedProperties.lightmapUVName + elif sharedProperties.lightmapUVChoiceType == "INDEX": + if sharedProperties.lightmapUVIndex < len(uv_layers): + uvName = uv_layers[sharedProperties.lightmapUVIndex].name + + if not uvName in uv_layers: + uvmap = uv_layers.new(name=uvName) + uv_layers.active_index = len(uv_layers) - 1 + else: + for i in range(0, len(uv_layers)): + if uv_layers[i].name == uvName: + uv_layers.active_index = i + obj.select_set(True) #save all the current edges if sharedProperties.packOnly: @@ -381,8 +401,11 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): bpy.ops.object.mode_set(mode='OBJECT') + #Create a fake obj export to a string + #Will strip this down further later fakeFile = StringIO() blender_xatlas.export_obj_simple.save( + rename_dict=rename_dict, context=bpy.context, filepath=fakeFile, mainUVChoiceType=sharedProperties.mainUVChoiceType, @@ -393,20 +416,26 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): use_mesh_modifiers=True, use_edges=True, use_smooth_groups=False, - use_smooth_groups_bitflags=False, + use_smooth_groups_bitflags=False, use_normals=True, use_uvs=True, use_materials=False, use_triangles=False, - use_nurbs=False, - use_vertex_groups=False, + use_nurbs=False, + use_vertex_groups=False, use_blen_objects=True, group_by_object=False, group_by_material=False, keep_vertex_order=False, ) - file_path = os.path.dirname(os.path.abspath(blender_xatlas.__file__)) + #print just for reference + # print(fakeFile.getvalue()) + + #get the path to xatlas + #file_path = os.path.dirname(os.path.abspath(__file__)) + scriptsDir = bpy.utils.user_resource('SCRIPTS', "addons") + file_path = os.path.join(scriptsDir, "blender_xatlas") if platform.system() == "Windows": xatlas_path = os.path.join(file_path, "xatlas", "xatlas-blender.exe") elif platform.system() == "Linux": @@ -458,6 +487,8 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): shell=True ) + print(xatlas_path) + #shove the fake file in stdin stdin = xatlas_process.stdin value = bytes(fakeFile.getvalue() + "\n", 'UTF-8') #The \n is needed to end the input properly @@ -482,17 +513,17 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): obName: string = "" uvArray: List[float] = field(default_factory=list) faceArray: List[int] = field(default_factory=list) - + convertedObjects = [] uvArrayComplete = [] - + #search through the out put for STARTOBJ #then start reading the objects obTest = None startRead = False for line in outObj.splitlines(): - + line_split = line.split() if not line_split: @@ -504,14 +535,14 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): print("Start reading the objects----------------------------------------") startRead = True # obTest = uvObject() - + if startRead: #if it's a new obj if line_start == 'o': #if there is already an object append it if obTest is not None: convertedObjects.append(obTest) - + obTest = uvObject() #create new uv object obTest.obName = line_split[1] @@ -536,9 +567,9 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): #append the final object convertedObjects.append(obTest) - # print(convertedObjects) - - + print(convertedObjects) + + #apply the output------------------------------------------------------------- #copy the uvs to the original objects # objIndex = 0 @@ -548,7 +579,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): bpy.ops.object.select_all(action='DESELECT') obTest = importObject - + obTest.obName = safe_dict[obTest.obName] #probably shouldn't just replace it bpy.context.scene.objects[obTest.obName].select_set(True) context.view_layer.objects.active = bpy.context.scene.objects[obTest.obName] bpy.ops.object.mode_set(mode = 'OBJECT') @@ -563,7 +594,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): nFaces = len(bm.faces) #need to ensure lookup table for some reason? - if hasattr(bm.faces, "ensure_lookup_table"): + if hasattr(bm.faces, "ensure_lookup_table"): bm.faces.ensure_lookup_table() #loop through the faces @@ -601,7 +632,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): currentObject = bpy.context.scene.objects[edgeList['object']] bm = bmesh.new() bm.from_mesh(currentObject.data) - if hasattr(bm.edges, "ensure_lookup_table"): + if hasattr(bm.edges, "ensure_lookup_table"): bm.edges.ensure_lookup_table() #assume that all the triangulated edges come after the original edges @@ -617,6 +648,27 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): bm.free() bpy.ops.object.mode_set(mode='EDIT') - #End setting the quads back again------------------------------------------------------------ + #End setting the quads back again------------------------------------------------------------- - print("Finished Xatlas----------------------------------------") \ No newline at end of file + #select the original objects that were selected + for objectName in rename_dict: + if objectName[0] in bpy.context.scene.objects: + current_object = bpy.context.scene.objects[objectName[0]] + current_object.select_set(True) + context.view_layer.objects.active = current_object + + bpy.ops.object.mode_set(mode=startingMode) + + print("Finished Xatlas----------------------------------------") + return {'FINISHED'} + +def transfer_assets(copy, source, destination): + for filename in glob.glob(os.path.join(source, '*.*')): + shutil.copy(filename, destination) + +def transfer_load(): + load_folder = bpy.path.abspath(os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_SceneProperties.tlm_load_folder)) + lightmap_folder = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) + print(load_folder) + print(lightmap_folder) + transfer_assets(True, load_folder, lightmap_folder) \ No newline at end of file diff --git a/blender/arm/live_patch.py b/blender/arm/live_patch.py new file mode 100644 index 00000000..6344a004 --- /dev/null +++ b/blender/arm/live_patch.py @@ -0,0 +1,391 @@ +import os +import shutil +from typing import Any, Type + +import bpy + +import arm.assets +from arm.exporter import ArmoryExporter +import arm.log as log +from arm.logicnode.arm_nodes import ArmLogicTreeNode +import arm.make as make +import arm.make_state as state +import arm.node_utils +import arm.utils + +if arm.is_reload(__name__): + arm.assets = arm.reload_module(arm.assets) + arm.exporter = arm.reload_module(arm.exporter) + from arm.exporter import ArmoryExporter + log = arm.reload_module(log) + arm.logicnode.arm_nodes = arm.reload_module(arm.logicnode.arm_nodes) + from arm.logicnode.arm_nodes import ArmLogicTreeNode + make = arm.reload_module(make) + state = arm.reload_module(state) + arm.node_utils = arm.reload_module(arm.node_utils) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + patch_id = 0 + """Current patch id""" + + __running = False + """Whether live patch is currently active""" + +# Any object can act as a message bus owner +msgbus_owner = object() + + +def start(): + """Start the live patch session.""" + log.debug("Live patch session started") + + listen(bpy.types.Object, "location", "obj_location") + listen(bpy.types.Object, "rotation_euler", "obj_rotation") + listen(bpy.types.Object, "scale", "obj_scale") + + # 'energy' is defined in sub classes only, also workaround for + # https://developer.blender.org/T88408 + for light_type in (bpy.types.AreaLight, bpy.types.PointLight, bpy.types.SpotLight, bpy.types.SunLight): + listen(light_type, "color", "light_color") + listen(light_type, "energy", "light_energy") + + global __running + __running = True + + +def stop(): + """Stop the live patch session.""" + global __running, patch_id + if __running: + __running = False + patch_id = 0 + + log.debug("Live patch session stopped") + bpy.msgbus.clear_by_owner(msgbus_owner) + + +def patch_export(): + """Re-export the current scene and update the game accordingly.""" + if not __running or state.proc_build is not None: + return + + arm.assets.invalidate_enabled = False + + with arm.utils.WorkingDir(arm.utils.get_fp()): + asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm' + ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene) + + dir_std_shaders_dst = os.path.join(arm.utils.build_dir(), 'compiled', 'Shaders', 'std') + if not os.path.isdir(dir_std_shaders_dst): + dir_std_shaders_src = os.path.join(arm.utils.get_sdk_path(), 'armory', 'Shaders', 'std') + shutil.copytree(dir_std_shaders_src, dir_std_shaders_dst) + + node_path = arm.utils.get_node_path() + khamake_path = arm.utils.get_khamake_path() + cmd = [ + node_path, khamake_path, 'krom', + '--shaderversion', '330', + '--parallelAssetConversion', '4', + '--to', arm.utils.build_dir() + '/debug', + '--nohaxe', + '--noproject' + ] + + arm.assets.invalidate_enabled = True + state.proc_build = make.run_proc(cmd, patch_done) + + +def patch_done(): + """Signal Iron to reload the running scene after a re-export.""" + js = 'iron.Scene.patch();' + write_patch(js) + state.proc_build = None + + +def write_patch(js: str): + """Write the given javascript code to 'krom.patch'.""" + global patch_id + with open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w') as f: + patch_id += 1 + f.write(str(patch_id) + '\n') + f.write(js) + + +def listen(rna_type: Type[bpy.types.bpy_struct], prop: str, event_id: str): + """Subscribe to '.'. The event_id can be choosen + freely but must match with the id used in send_event(). + """ + bpy.msgbus.subscribe_rna( + key=(rna_type, prop), + owner=msgbus_owner, + args=(event_id, ), + notify=send_event + # options={"PERSISTENT"} + ) + + +def send_event(event_id: str, opt_data: Any = None): + """Send the result of the given event to Krom.""" + if not __running: + return + + if hasattr(bpy.context, 'object') and bpy.context.object is not None: + obj = bpy.context.object.name + + if bpy.context.object.mode == "OBJECT": + if event_id == "obj_location": + vec = bpy.context.object.location + js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.loc.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;' + write_patch(js) + + elif event_id == 'obj_scale': + vec = bpy.context.object.scale + js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.scale.set({vec[0]}, {vec[1]}, {vec[2]}); o.transform.dirty = true;' + write_patch(js) + + elif event_id == 'obj_rotation': + vec = bpy.context.object.rotation_euler.to_quaternion() + js = f'var o = iron.Scene.active.getChild("{obj}"); o.transform.rot.set({vec[1]}, {vec[2]}, {vec[3]}, {vec[0]}); o.transform.dirty = true;' + write_patch(js) + + elif event_id == 'light_color': + light: bpy.types.Light = bpy.context.object.data + vec = light.color + js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.color[0]={vec[0]}; lRaw.color[1]={vec[1]}; lRaw.color[2]={vec[2]};' + write_patch(js) + + elif event_id == 'light_energy': + light: bpy.types.Light = bpy.context.object.data + + # Align strength to Armory, see exporter.export_light() + # TODO: Use exporter.export_light() and simply reload all raw light data in Iron? + strength_fac = 1.0 + if light.type == 'SUN': + strength_fac = 0.325 + elif light.type in ('POINT', 'SPOT', 'AREA'): + strength_fac = 0.01 + + js = f'var lRaw = iron.Scene.active.getLight("{light.name}").data.raw; lRaw.strength={light.energy * strength_fac};' + write_patch(js) + + else: + patch_export() + + if event_id == 'ln_insert_link': + node: ArmLogicTreeNode + link: bpy.types.NodeLink + node, link = opt_data + + # This event is called twice for a connection but we only need + # send it once + if node == link.from_node: + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + + # [1:] is used here because make_logic already uses that for + # node names if arm_debug is used + from_node_name = arm.node_utils.get_export_node_name(node)[1:] + to_node_name = arm.node_utils.get_export_node_name(link.to_node)[1:] + + from_index = arm.node_utils.get_socket_index(node.outputs, link.from_socket) + to_index = arm.node_utils.get_socket_index(link.to_node.inputs, link.to_socket) + + js = f'LivePatch.patchCreateNodeLink("{tree_name}", "{from_node_name}", "{to_node_name}", "{from_index}", "{to_index}");' + write_patch(js) + + elif event_id == 'ln_update_prop': + node: ArmLogicTreeNode + prop_name: str + node, prop_name = opt_data + + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + + value = arm.node_utils.haxe_format_prop_value(node, prop_name) + + if prop_name.endswith('_get'): + # Hack because some nodes use a different Python property + # name than they use in Haxe + prop_name = prop_name[:-4] + + js = f'LivePatch.patchUpdateNodeProp("{tree_name}", "{node_name}", "{prop_name}", {value});' + write_patch(js) + + elif event_id == 'ln_socket_val': + node: ArmLogicTreeNode + socket: bpy.types.NodeSocket + node, socket = opt_data + + socket_index = arm.node_utils.get_socket_index(node.inputs, socket) + + if socket_index != -1: + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + + value = socket.get_default_value() + inp_type = socket.arm_socket_type + + if inp_type in ('VECTOR', 'RGB'): + value = f'new iron.Vec4({arm.node_utils.haxe_format_socket_val(value, array_outer_brackets=False)}, 1.0)' + elif inp_type == 'RGBA': + value = f'new iron.Vec4({arm.node_utils.haxe_format_socket_val(value, array_outer_brackets=False)})' + elif inp_type == 'OBJECT': + value = f'iron.Scene.active.getChild("{value}")' if value != '' else 'null' + else: + value = arm.node_utils.haxe_format_socket_val(value) + + js = f'LivePatch.patchUpdateNodeInputVal("{tree_name}", "{node_name}", {socket_index}, {value});' + write_patch(js) + + elif event_id == 'ln_create': + node: ArmLogicTreeNode = opt_data + + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + node_type = 'armory.logicnode.' + node.bl_idname[2:] + + prop_names = list(arm.node_utils.get_haxe_property_names(node)) + prop_py_names, prop_hx_names = zip(*prop_names) if len(prop_names) > 0 else ([], []) + prop_values = (getattr(node, prop_name) for prop_name in prop_py_names) + prop_datas = arm.node_utils.haxe_format_socket_val(list(zip(prop_hx_names, prop_values))) + + inp_data = [(inp.arm_socket_type, inp.get_default_value()) for inp in node.inputs] + inp_data = arm.node_utils.haxe_format_socket_val(inp_data) + out_data = [(out.arm_socket_type, out.get_default_value()) for out in node.outputs] + out_data = arm.node_utils.haxe_format_socket_val(out_data) + + js = f'LivePatch.patchNodeCreate("{tree_name}", "{node_name}", "{node_type}", {prop_datas}, {inp_data}, {out_data});' + write_patch(js) + + elif event_id == 'ln_delete': + node: ArmLogicTreeNode = opt_data + + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + + js = f'LivePatch.patchNodeDelete("{tree_name}", "{node_name}");' + write_patch(js) + + elif event_id == 'ln_copy': + newnode: ArmLogicTreeNode + node: ArmLogicTreeNode + newnode, node = opt_data + + # Use newnode to get the tree, node has no id_data at this moment + tree_name = arm.node_utils.get_export_tree_name(newnode.get_tree()) + + newnode_name = arm.node_utils.get_export_node_name(newnode)[1:] + node_name = arm.node_utils.get_export_node_name(node)[1:] + + props_list = '[' + ','.join(f'"{p}"' for _, p in arm.node_utils.get_haxe_property_names(node)) + ']' + + inp_data = [(inp.arm_socket_type, inp.get_default_value()) for inp in newnode.inputs] + inp_data = arm.node_utils.haxe_format_socket_val(inp_data) + out_data = [(out.arm_socket_type, out.get_default_value()) for out in newnode.outputs] + out_data = arm.node_utils.haxe_format_socket_val(out_data) + + js = f'LivePatch.patchNodeCopy("{tree_name}", "{node_name}", "{newnode_name}", {props_list}, {inp_data}, {out_data});' + write_patch(js) + + elif event_id == 'ln_update_sockets': + node: ArmLogicTreeNode = opt_data + + tree_name = arm.node_utils.get_export_tree_name(node.get_tree()) + node_name = arm.node_utils.get_export_node_name(node)[1:] + + inp_data = '[' + for idx, inp in enumerate(node.inputs): + inp_data += '{' + # is_linked can be true even if there are no links if the + # user starts dragging a connection away before releasing + # the mouse + if inp.is_linked and len(inp.links) > 0: + inp_data += 'isLinked: true,' + inp_data += f'fromNode: "{arm.node_utils.get_export_node_name(inp.links[0].from_node)[1:]}",' + inp_data += f'fromIndex: {arm.node_utils.get_socket_index(inp.links[0].from_node.outputs, inp.links[0].from_socket)},' + else: + inp_data += 'isLinked: false,' + inp_data += f'socketType: "{inp.arm_socket_type}",' + inp_data += f'socketValue: {arm.node_utils.haxe_format_socket_val(inp.get_default_value())},' + + inp_data += f'toIndex: {idx}' + inp_data += '},' + inp_data += ']' + + out_data = '[' + for idx, out in enumerate(node.outputs): + out_data += '[' + for link in out.links: + out_data += '{' + if out.is_linked: + out_data += 'isLinked: true,' + out_data += f'toNode: "{arm.node_utils.get_export_node_name(link.to_node)[1:]}",' + out_data += f'toIndex: {arm.node_utils.get_socket_index(link.to_node.inputs, link.to_socket)},' + else: + out_data += 'isLinked: false,' + out_data += f'socketType: "{out.arm_socket_type}",' + out_data += f'socketValue: {arm.node_utils.haxe_format_socket_val(out.get_default_value())},' + + out_data += f'fromIndex: {idx}' + out_data += '},' + out_data += '],' + out_data += ']' + + js = f'LivePatch.patchSetNodeLinks("{tree_name}", "{node_name}", {inp_data}, {out_data});' + write_patch(js) + + +def on_operator(operator_id: str): + """As long as bpy.msgbus doesn't listen to changes made by + operators (*), additionally notify the callback manually. + + (*) https://developer.blender.org/T72109 + """ + if not __running: + return + + if operator_id in IGNORE_OPERATORS: + return + + if operator_id == 'TRANSFORM_OT_translate': + send_event('obj_location') + elif operator_id in ('TRANSFORM_OT_rotate', 'TRANSFORM_OT_trackball'): + send_event('obj_rotation') + elif operator_id == 'TRANSFORM_OT_resize': + send_event('obj_scale') + + # Rebuild + else: + patch_export() + + +# Don't re-export the scene for the following operators +IGNORE_OPERATORS = ( + 'ARM_OT_node_add_input', + 'ARM_OT_node_add_input_output', + 'ARM_OT_node_add_input_value', + 'ARM_OT_node_add_output', + 'ARM_OT_node_call_func', + 'ARM_OT_node_remove_input', + 'ARM_OT_node_remove_input_output', + 'ARM_OT_node_remove_input_value', + 'ARM_OT_node_remove_output', + 'ARM_OT_node_search', + + 'NODE_OT_delete', + 'NODE_OT_duplicate_move', + 'NODE_OT_hide_toggle', + 'NODE_OT_link', + 'NODE_OT_move_detach_links', + 'NODE_OT_select', + 'NODE_OT_translate_attach', + 'NODE_OT_translate_attach_remove_on_cancel', + + 'OBJECT_OT_editmode_toggle', + 'OUTLINER_OT_item_activate', + 'UI_OT_button_string_clear', + 'UI_OT_eyedropper_id', + 'VIEW3D_OT_select', + 'VIEW3D_OT_select_box', +) diff --git a/blender/arm/log.py b/blender/arm/log.py index f28a1524..2dca68da 100644 --- a/blender/arm/log.py +++ b/blender/arm/log.py @@ -29,12 +29,15 @@ else: info_text = '' num_warnings = 0 +num_errors = 0 -def clear(clear_warnings=False): - global info_text, num_warnings +def clear(clear_warnings=False, clear_errors=False): + global info_text, num_warnings, num_errors info_text = '' if clear_warnings: num_warnings = 0 + if clear_errors: + num_errors = 0 def format_text(text): return (text[:80] + '..') if len(text) > 80 else text # Limit str size @@ -62,4 +65,6 @@ def warn(text): print_warn(text) def error(text): + global num_errors + num_errors += 1 log('ERROR: ' + text, ERROR) diff --git a/blender/arm/logicnode/__init__.py b/blender/arm/logicnode/__init__.py index 716ecd37..f8134402 100644 --- a/blender/arm/logicnode/__init__.py +++ b/blender/arm/logicnode/__init__.py @@ -1,13 +1,29 @@ import importlib import inspect import pkgutil +import sys +import arm import arm.logicnode.arm_nodes as arm_nodes +from arm.logicnode.arm_props import * import arm.logicnode.arm_sockets as arm_sockets +from arm.logicnode.replacement import NodeReplacement + +if arm.is_reload(__name__): + arm_nodes = arm.reload_module(arm_nodes) + arm.logicnode.arm_props = arm.reload_module(arm.logicnode.arm_props) + from arm.logicnode.arm_props import * + arm_sockets = arm.reload_module(arm_sockets) + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + from arm.logicnode.replacement import NodeReplacement + + HAS_RELOADED = True +else: + arm.enable_reload(__name__) def init_categories(): - # Register node menu categories + """Register default node menu categories.""" arm_nodes.add_category('Logic', icon='OUTLINER', section="basic", description="Logic nodes are used to control execution flow using branching, loops, gates etc.") arm_nodes.add_category('Event', icon='INFO', section="basic") @@ -56,8 +72,15 @@ def init_nodes(): # The package must be loaded as well so that the modules from that package can be accessed (see the # pkgutil.walk_packages documentation for more information on this) loader.find_module(module_name).load_module(module_name) - else: - _module = importlib.import_module(module_name) + + # Only look at modules in sub packages + elif module_name.rsplit('.', 1)[0] != __package__: + if 'HAS_RELOADED' not in globals(): + _module = importlib.import_module(module_name) + else: + # Reload the module if the SDK was reloaded at least once + _module = importlib.reload(sys.modules[module_name]) + for name, obj in inspect.getmembers(_module, inspect.isclass): if name == "ArmLogicTreeNode": continue diff --git a/blender/arm/logicnode/animation/LN_action.py b/blender/arm/logicnode/animation/LN_action.py index d156819b..e7913910 100644 --- a/blender/arm/logicnode/animation/LN_action.py +++ b/blender/arm/logicnode/animation/LN_action.py @@ -6,8 +6,7 @@ class AnimActionNode(ArmLogicTreeNode): bl_label = 'Action' arm_version = 1 - def init(self, context): - super(AnimActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAnimAction', 'Action') self.add_output('ArmNodeSocketAnimAction', 'Action', is_var=True) diff --git a/blender/arm/logicnode/animation/LN_blend_action.py b/blender/arm/logicnode/animation/LN_blend_action.py index 087301e2..0214e28d 100644 --- a/blender/arm/logicnode/animation/LN_blend_action.py +++ b/blender/arm/logicnode/animation/LN_blend_action.py @@ -6,12 +6,11 @@ class BlendActionNode(ArmLogicTreeNode): bl_label = 'Blend Action' arm_version = 1 - def init(self, context): - super(BlendActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action 1') self.add_input('ArmNodeSocketAnimAction', 'Action 2') - self.add_input('NodeSocketFloat', 'Factor', default_value = 0.5) + self.add_input('ArmFloatSocket', 'Factor', default_value = 0.5) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_bone_fk.py b/blender/arm/logicnode/animation/LN_bone_fk.py index e5aceff7..d994584d 100644 --- a/blender/arm/logicnode/animation/LN_bone_fk.py +++ b/blender/arm/logicnode/animation/LN_bone_fk.py @@ -7,11 +7,10 @@ class BoneFKNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'armature' - def init(self, context): - super(BoneFKNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Bone') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmStringSocket', 'Bone') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_bone_ik.py b/blender/arm/logicnode/animation/LN_bone_ik.py index dd155015..66e4d32d 100644 --- a/blender/arm/logicnode/animation/LN_bone_ik.py +++ b/blender/arm/logicnode/animation/LN_bone_ik.py @@ -1,17 +1,50 @@ from arm.logicnode.arm_nodes import * class BoneIKNode(ArmLogicTreeNode): - """Applies inverse kinematics in the given object bone.""" + """Performs inverse kinematics on the selected armature with specified bone. + + @input Object: Armature on which IK should be performed. + + @input Bone: Effector or tip bone for the inverse kinematics + + @input Goal Position: Position in world coordinates the effector bone will track to + + @input Enable Pole: Bend IK solution towards pole location + + @input Pole Position: Location of the pole in world coordinates + + @input Chain Length: Number of bones to include in the IK solver including the effector. If set to 0, all bones from effector to the root bone of the armature will be considered. + + @input Max Iterations: Maximum allowed FABRIK iterations to solve for IK. For longer chains, more iterations are needed. + + @input Precision: Presition of IK to stop at. It is described as a tolerence in length. Typically 0.01 is a good value. + + @input Roll Angle: Roll the bones along their local axis with specified radians. set 0 for no extra roll. + """ bl_idname = 'LNBoneIKNode' bl_label = 'Bone IK' - arm_version = 1 + arm_version = 2 arm_section = 'armature' - def init(self, context): - super(BoneIKNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Bone') - self.add_input('NodeSocketVector', 'Goal') + self.add_input('ArmStringSocket', 'Bone') + self.add_input('ArmVectorSocket', 'Goal Position') + self.add_input('ArmBoolSocket', 'Enable Pole') + self.add_input('ArmVectorSocket', 'Pole Position') + self.add_input('ArmIntSocket', 'Chain Length') + self.add_input('ArmIntSocket', 'Max Iterations', 10) + self.add_input('ArmFloatSocket', 'Precision', 0.01) + self.add_input('ArmFloatSocket', 'Roll Angle') self.add_output('ArmNodeSocketAction', 'Out') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + return NodeReplacement( + 'LNBoneIKNode', self.arm_version, 'LNBoneIKNode', 2, + in_socket_mapping={0:0, 1:1, 2:2, 3:3}, out_socket_mapping={0:0} + ) diff --git a/blender/arm/logicnode/animation/LN_get_action_state.py b/blender/arm/logicnode/animation/LN_get_action_state.py index d2c9f804..1b506613 100644 --- a/blender/arm/logicnode/animation/LN_get_action_state.py +++ b/blender/arm/logicnode/animation/LN_get_action_state.py @@ -6,10 +6,9 @@ class AnimationStateNode(ArmLogicTreeNode): bl_label = 'Get Action State' arm_version = 1 - def init(self, context): - super(AnimationStateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketString', 'Action') - self.add_output('NodeSocketInt', 'Frame') - self.add_output('NodeSocketBool', 'Is Paused') + self.add_output('ArmStringSocket', 'Action') + self.add_output('ArmIntSocket', 'Frame') + self.add_output('ArmBoolSocket', 'Is Paused') diff --git a/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py b/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py new file mode 100644 index 00000000..21afb1ee --- /dev/null +++ b/blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py @@ -0,0 +1,13 @@ +from arm.logicnode.arm_nodes import * + +class GetBoneFkIkOnlyNode(ArmLogicTreeNode): + """Get if a particular bone is animated by Forward kinematics or Inverse kinematics only.""" + bl_idname = 'LNGetBoneFkIkOnlyNode' + bl_label = 'Get Bone FK IK Only' + arm_version = 1 + arm_section = 'armature' + + def arm_init(self, context): + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('ArmStringSocket', 'Bone') + self.add_output('ArmBoolSocket', 'FK or IK only') diff --git a/blender/arm/logicnode/animation/LN_get_bone_transform.py b/blender/arm/logicnode/animation/LN_get_bone_transform.py new file mode 100644 index 00000000..1ce69e7a --- /dev/null +++ b/blender/arm/logicnode/animation/LN_get_bone_transform.py @@ -0,0 +1,13 @@ +from arm.logicnode.arm_nodes import * + +class GetBoneTransformNode(ArmLogicTreeNode): + """Returns bone transform in world space.""" + bl_idname = 'LNGetBoneTransformNode' + bl_label = 'Get Bone Transform' + arm_version = 1 + arm_section = 'armature' + + def arm_init(self, context): + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('ArmStringSocket', 'Bone') + self.add_output('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/animation/LN_get_tilesheet_state.py b/blender/arm/logicnode/animation/LN_get_tilesheet_state.py index b32e7d25..09adee81 100644 --- a/blender/arm/logicnode/animation/LN_get_tilesheet_state.py +++ b/blender/arm/logicnode/animation/LN_get_tilesheet_state.py @@ -7,10 +7,9 @@ class GetTilesheetStateNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'tilesheet' - def init(self, context): - super(GetTilesheetStateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketString', 'Name') - self.add_output('NodeSocketInt', 'Frame') - self.add_output('NodeSocketBool', 'Is Paused') + self.add_output('ArmStringSocket', 'Name') + self.add_output('ArmIntSocket', 'Frame') + self.add_output('ArmBoolSocket', 'Is Paused') diff --git a/blender/arm/logicnode/animation/LN_on_action_marker.py b/blender/arm/logicnode/animation/LN_on_action_marker.py index a935015b..07c4e690 100644 --- a/blender/arm/logicnode/animation/LN_on_action_marker.py +++ b/blender/arm/logicnode/animation/LN_on_action_marker.py @@ -6,9 +6,8 @@ class OnActionMarkerNode(ArmLogicTreeNode): bl_label = 'On Action Marker' arm_version = 1 - def init(self, context): - super(OnActionMarkerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Marker') + self.add_input('ArmStringSocket', 'Marker') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_play_action_from.py b/blender/arm/logicnode/animation/LN_play_action_from.py index b28221c1..31a218ea 100644 --- a/blender/arm/logicnode/animation/LN_play_action_from.py +++ b/blender/arm/logicnode/animation/LN_play_action_from.py @@ -6,15 +6,14 @@ class PlayActionFromNode(ArmLogicTreeNode): bl_label = 'Play Action From' arm_version = 2 - def init(self, context): - super(PlayActionFromNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action') - self.add_input('NodeSocketInt', 'Start Frame') - self.add_input('NodeSocketFloat', 'Blend', default_value = 0.25) - self.add_input('NodeSocketFloat', 'Speed', default_value = 1.0) - self.add_input('NodeSocketBool', 'Loop', default_value = False) + self.add_input('ArmIntSocket', 'Start Frame') + self.add_input('ArmFloatSocket', 'Blend', default_value = 0.25) + self.add_input('ArmFloatSocket', 'Speed', default_value = 1.0) + self.add_input('ArmBoolSocket', 'Loop', default_value = False) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Done') @@ -22,7 +21,7 @@ class PlayActionFromNode(ArmLogicTreeNode): def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): raise LookupError() - + return NodeReplacement( 'LNPlayActionFromNode', self.arm_version, 'LNPlayActionFromNode', 2, in_socket_mapping={0:0, 1:1, 2:2, 3:3, 4:4}, out_socket_mapping={0:0, 1:1} diff --git a/blender/arm/logicnode/animation/LN_play_tilesheet.py b/blender/arm/logicnode/animation/LN_play_tilesheet.py index 533231d8..6e008f9e 100644 --- a/blender/arm/logicnode/animation/LN_play_tilesheet.py +++ b/blender/arm/logicnode/animation/LN_play_tilesheet.py @@ -7,11 +7,10 @@ class PlayTilesheetNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'tilesheet' - def init(self, context): - super(PlayTilesheetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Name') + self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/animation/LN_set_action_paused.py b/blender/arm/logicnode/animation/LN_set_action_paused.py index 6bdb5535..6c994959 100644 --- a/blender/arm/logicnode/animation/LN_set_action_paused.py +++ b/blender/arm/logicnode/animation/LN_set_action_paused.py @@ -6,10 +6,9 @@ class SetActionPausedNode(ArmLogicTreeNode): bl_label = 'Set Action Paused' arm_version = 1 - def init(self, context): - super(SetActionPausedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Paused') + self.add_input('ArmBoolSocket', 'Paused') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_action_speed.py b/blender/arm/logicnode/animation/LN_set_action_speed.py index c01fdd00..e90cec67 100644 --- a/blender/arm/logicnode/animation/LN_set_action_speed.py +++ b/blender/arm/logicnode/animation/LN_set_action_speed.py @@ -6,10 +6,9 @@ class SetActionSpeedNode(ArmLogicTreeNode): bl_label = 'Set Action Speed' arm_version = 1 - def init(self, context): - super(SetActionSpeedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketFloat', 'Speed', default_value=1.0) + self.add_input('ArmFloatSocket', 'Speed', default_value=1.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py b/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py new file mode 100644 index 00000000..8ed6ce45 --- /dev/null +++ b/blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py @@ -0,0 +1,16 @@ +from arm.logicnode.arm_nodes import * + +class SetBoneFkIkOnlyNode(ArmLogicTreeNode): + """Set particular bone to be animated by Forward kinematics or Inverse kinematics only. All other animations will be ignored""" + bl_idname = 'LNSetBoneFkIkOnlyNode' + bl_label = 'Set Bone FK IK Only' + arm_version = 1 + arm_section = 'armature' + + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('ArmStringSocket', 'Bone') + self.add_input('ArmBoolSocket', 'FK or IK only') + + self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_parent_bone.py b/blender/arm/logicnode/animation/LN_set_parent_bone.py index fb954610..5aa686e3 100644 --- a/blender/arm/logicnode/animation/LN_set_parent_bone.py +++ b/blender/arm/logicnode/animation/LN_set_parent_bone.py @@ -7,11 +7,10 @@ class SetParentBoneNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'armature' - def init(self, context): - super(SetParentBoneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketObject', 'Parent', default_value='Parent') - self.add_input('NodeSocketString', 'Bone', default_value='Bone') + self.add_input('ArmStringSocket', 'Bone', default_value='Bone') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_particle_speed.py b/blender/arm/logicnode/animation/LN_set_particle_speed.py index 272a11f3..2fe92ee4 100644 --- a/blender/arm/logicnode/animation/LN_set_particle_speed.py +++ b/blender/arm/logicnode/animation/LN_set_particle_speed.py @@ -6,10 +6,9 @@ class SetParticleSpeedNode(ArmLogicTreeNode): bl_label = 'Set Particle Speed' arm_version = 1 - def init(self, context): - super(SetParticleSpeedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketFloat', 'Speed', default_value=1.0) + self.add_input('ArmFloatSocket', 'Speed', default_value=1.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py b/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py index 06322ad7..393a259f 100644 --- a/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py +++ b/blender/arm/logicnode/animation/LN_set_tilesheet_paused.py @@ -7,10 +7,9 @@ class SetTilesheetPausedNode(ArmLogicTreeNode): arm_section = 'tilesheet' arm_version = 1 - def init(self, context): - super(SetTilesheetPausedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Paused') + self.add_input('ArmBoolSocket', 'Paused') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/arm_nodes.py b/blender/arm/logicnode/arm_nodes.py index a516d734..92b4d83c 100644 --- a/blender/arm/logicnode/arm_nodes.py +++ b/blender/arm/logicnode/arm_nodes.py @@ -1,6 +1,6 @@ -import itertools from collections import OrderedDict -from typing import Any, Generator, List, Optional, Type, Dict +import itertools +from typing import Any, Generator, List, Optional, Type from typing import OrderedDict as ODict # Prevent naming conflicts import bpy.types @@ -8,10 +8,22 @@ from bpy.props import * from nodeitems_utils import NodeItem from arm.logicnode.arm_sockets import ArmCustomSocket -# Pass NodeReplacment forward to individual node modules that import arm_nodes +import arm # we cannot import arm.livepatch here or we have a circular import +# Pass custom property types and NodeReplacement forward to individual +# node modules that import arm_nodes +from arm.logicnode.arm_props import * from arm.logicnode.replacement import NodeReplacement import arm.node_utils +if arm.is_reload(__name__): + arm.logicnode.arm_props = arm.reload_module(arm.logicnode.arm_props) + from arm.logicnode.arm_props import * + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + from arm.logicnode.replacement import NodeReplacement + arm.node_utils = arm.reload_module(arm.node_utils) +else: + arm.enable_reload(__name__) + # When passed as a category to add_node(), this will use the capitalized # name of the package of the node as the category to make renaming # categories easier. @@ -22,6 +34,10 @@ category_items: ODict[str, List['ArmNodeCategory']] = OrderedDict() array_nodes = dict() +# See ArmLogicTreeNode.update() +# format: [tree pointer => (num inputs, num input links, num outputs, num output links)] +last_node_state: dict[int, tuple[int, int, int, int]] = {} + class ArmLogicTreeNode(bpy.types.Node): arm_category = PKG_AS_CATEGORY @@ -35,6 +51,14 @@ class ArmLogicTreeNode(bpy.types.Node): else: self.arm_version = 1 + if not hasattr(self, 'arm_init'): + # Show warning for older node packages + arm.log.warn(f'Node {self.bl_idname} has no arm_init function and might not work correctly!') + else: + self.arm_init(context) + + arm.live_patch.send_event('ln_create', self) + @classmethod def poll(cls, ntree): return ntree.bl_idname == 'ArmLogicTreeType' @@ -49,6 +73,68 @@ class ArmLogicTreeNode(bpy.types.Node): def on_unregister(cls): pass + def get_tree(self): + return self.id_data + + def update(self): + """Called if the node was updated in some way, for example + if socket connections change. This callback is not called if + socket values were changed. + """ + def num_connected(sockets): + return sum([socket.is_linked for socket in sockets]) + + # If a link between sockets is removed, there is currently no + # _reliable_ way in the Blender API to check which connection + # was removed (*). + # + # So instead we just check _if_ the number of links or sockets + # has changed (the update function is called before and after + # each link removal). Because we listen for those updates in + # general, we automatically also listen to link creation events, + # which is more stable than using the dedicated callback for + # that (`insert_link()`), because adding links can remove other + # links and we would need to react to that as well. + # + # (*) https://devtalk.blender.org/t/how-to-detect-which-link-was-deleted-by-user-in-node-editor + + self_id = self.as_pointer() + + current_state = (len(self.inputs), num_connected(self.inputs), len(self.outputs), num_connected(self.outputs)) + if self_id not in last_node_state: + # Lazily initialize the last_node_state dict to also store + # state for nodes that already exist in the tree + last_node_state[self_id] = current_state + + if last_node_state[self_id] != current_state: + arm.live_patch.send_event('ln_update_sockets', self) + last_node_state[self_id] = current_state + + def free(self): + """Called before the node is deleted.""" + arm.live_patch.send_event('ln_delete', self) + + def copy(self, node): + """Called if the node was copied. `self` holds the copied node, + `node` the original one. + """ + arm.live_patch.send_event('ln_copy', (self, node)) + + def on_prop_update(self, context: bpy.types.Context, prop_name: str): + """Called if a property created with a function from the + arm_props module is changed. If the property has a custom update + function, it is called before `on_prop_update()`. + """ + arm.live_patch.send_event('ln_update_prop', (self, prop_name)) + + def on_socket_val_update(self, context: bpy.types.Context, socket: bpy.types.NodeSocket): + arm.live_patch.send_event('ln_socket_val', (self, socket)) + + def insert_link(self, link: bpy.types.NodeLink): + """Called on *both* nodes when a link between two nodes is created.""" + # arm.live_patch.send_event('ln_insert_link', (self, link)) + pass + def get_replacement_node(self, node_tree: bpy.types.NodeTree): # needs to be overridden by individual node classes with arm_version>1 """(only called if the node's version is inferior to the node class's version) @@ -89,7 +175,7 @@ class ArmLogicTreeNode(bpy.types.Node): socket.default_value_raw = default_value else: raise ValueError('specified a default value for an input node that doesn\'t accept one') - else: + else: # should not happen anymore? socket.default_value = default_value if is_var and not socket.display_shape.endswith('_DOT'): @@ -106,8 +192,12 @@ class ArmLogicTreeNode(bpy.types.Node): """ socket = self.outputs.new(socket_type, socket_name) + # FIXME: …a default_value on an output socket? Why is that a thing? if default_value is not None: - socket.default_value = default_value + if socket.arm_socket_type != 'NONE': + socket.default_value_raw = default_value + else: + raise ValueError('specified a default value for an input node that doesn\'t accept one') if is_var and not socket.display_shape.endswith('_DOT'): socket.display_shape += '_DOT' @@ -119,9 +209,10 @@ class ArmNodeAddInputButton(bpy.types.Operator): """Add a new input socket to the node set by node_index.""" bl_idname = 'arm.node_add_input' bl_label = 'Add Input' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') + socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket') name_format: StringProperty(name='Name Format', default='Input {0}') index_name_offset: IntProperty(name='Index Name Offset', default=0) @@ -139,29 +230,31 @@ class ArmNodeAddInputButton(bpy.types.Operator): # Reset to default again for subsequent calls of this operator self.node_index = '' - self.socket_type = 'NodeSocketShader' + self.socket_type = 'ArmDynamicSocket' self.name_format = 'Input {0}' self.index_name_offset = 0 return{'FINISHED'} class ArmNodeAddInputValueButton(bpy.types.Operator): - """Add new input""" - bl_idname = 'arm.node_add_input_value' - bl_label = 'Add Input' - node_index: StringProperty(name='Node Index', default='') - socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') + """Add new input""" + bl_idname = 'arm.node_add_input_value' + bl_label = 'Add Input' + bl_options = {'UNDO', 'INTERNAL'} + node_index: StringProperty(name='Node Index', default='') + socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket') - def execute(self, context): - global array_nodes - inps = array_nodes[self.node_index].inputs - inps.new(self.socket_type, 'Value') - return{'FINISHED'} + def execute(self, context): + global array_nodes + inps = array_nodes[self.node_index].inputs + inps.new(self.socket_type, 'Value') + return{'FINISHED'} class ArmNodeRemoveInputButton(bpy.types.Operator): """Remove last input""" bl_idname = 'arm.node_remove_input' bl_label = 'Remove Input' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') count: IntProperty(name='Number of inputs to remove', default=1, min=1) min_inputs: IntProperty(name='Number of inputs to keep', default=0, min=0) @@ -180,6 +273,7 @@ class ArmNodeRemoveInputValueButton(bpy.types.Operator): """Remove last input""" bl_idname = 'arm.node_remove_input_value' bl_label = 'Remove Input' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') target_name: StringProperty(name='Name of socket to remove', default='Value') @@ -196,9 +290,10 @@ class ArmNodeAddOutputButton(bpy.types.Operator): """Add a new output socket to the node set by node_index""" bl_idname = 'arm.node_add_output' bl_label = 'Add Output' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') + socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket') name_format: StringProperty(name='Name Format', default='Output {0}') index_name_offset: IntProperty(name='Index Name Offset', default=0) @@ -216,7 +311,7 @@ class ArmNodeAddOutputButton(bpy.types.Operator): # Reset to default again for subsequent calls of this operator self.node_index = '' - self.socket_type = 'NodeSocketShader' + self.socket_type = 'ArmDynamicSocket' self.name_format = 'Output {0}' self.index_name_offset = 0 @@ -226,6 +321,7 @@ class ArmNodeRemoveOutputButton(bpy.types.Operator): """Remove last output""" bl_idname = 'arm.node_remove_output' bl_label = 'Remove Output' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') count: IntProperty(name='Number of outputs to remove', default=1, min=1) @@ -243,10 +339,11 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator): """Add new input and output""" bl_idname = 'arm.node_add_input_output' bl_label = 'Add Input Output' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - in_socket_type: StringProperty(name='In Socket Type', default='NodeSocketShader') - out_socket_type: StringProperty(name='Out Socket Type', default='NodeSocketShader') + in_socket_type: StringProperty(name='In Socket Type', default='ArmDynamicSocket') + out_socket_type: StringProperty(name='Out Socket Type', default='ArmDynamicSocket') in_name_format: StringProperty(name='In Name Format', default='Input {0}') out_name_format: StringProperty(name='Out Name Format', default='Output {0}') in_index_name_offset: IntProperty(name='Index Name Offset', default=0) @@ -274,8 +371,8 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator): # Reset to default again for subsequent calls of this operator self.node_index = '' - self.in_socket_type = 'NodeSocketShader' - self.out_socket_type = 'NodeSocketShader' + self.in_socket_type = 'ArmDynamicSocket' + self.out_socket_type = 'ArmDynamicSocket' self.in_name_format = 'Input {0}' self.out_name_format = 'Output {0}' self.in_index_name_offset = 0 @@ -286,6 +383,7 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator): """Remove last input and output""" bl_idname = 'arm.node_remove_input_output' bl_label = 'Remove Input Output' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') in_count: IntProperty(name='Number of inputs to remove', default=1, min=1) out_count: IntProperty(name='Number of inputs to remove', default=1, min=1) @@ -306,10 +404,34 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator): return{'FINISHED'} +class ArmNodeCallFuncButton(bpy.types.Operator): + """Operator that calls a function on a specified + node (used for dynamic callbacks).""" + bl_idname = 'arm.node_call_func' + bl_label = 'Execute' + bl_options = {'UNDO', 'INTERNAL'} + + node_index: StringProperty(name='Node Index', default='') + callback_name: StringProperty(name='Callback Name', default='') + + def execute(self, context): + node = array_nodes[self.node_index] + if hasattr(node, self.callback_name): + getattr(node, self.callback_name)() + else: + return {'CANCELLED'} + + # Reset to default again for subsequent calls of this operator + self.node_index = '' + self.callback_name = '' + + return {'FINISHED'} + + class ArmNodeSearch(bpy.types.Operator): bl_idname = "arm.node_search" bl_label = "Search..." - bl_options = {"REGISTER"} + bl_options = {"REGISTER", "INTERNAL"} bl_property = "item" def get_search_items(self, context): @@ -499,12 +621,16 @@ def reset_globals(): category_items = OrderedDict() -bpy.utils.register_class(ArmNodeSearch) -bpy.utils.register_class(ArmNodeAddInputButton) -bpy.utils.register_class(ArmNodeAddInputValueButton) -bpy.utils.register_class(ArmNodeRemoveInputButton) -bpy.utils.register_class(ArmNodeRemoveInputValueButton) -bpy.utils.register_class(ArmNodeAddOutputButton) -bpy.utils.register_class(ArmNodeRemoveOutputButton) -bpy.utils.register_class(ArmNodeAddInputOutputButton) -bpy.utils.register_class(ArmNodeRemoveInputOutputButton) +REG_CLASSES = ( + ArmNodeSearch, + ArmNodeAddInputButton, + ArmNodeAddInputValueButton, + ArmNodeRemoveInputButton, + ArmNodeRemoveInputValueButton, + ArmNodeAddOutputButton, + ArmNodeRemoveOutputButton, + ArmNodeAddInputOutputButton, + ArmNodeRemoveInputOutputButton, + ArmNodeCallFuncButton +) +register, unregister = bpy.utils.register_classes_factory(REG_CLASSES) diff --git a/blender/arm/logicnode/arm_props.py b/blender/arm/logicnode/arm_props.py new file mode 100644 index 00000000..c87e7534 --- /dev/null +++ b/blender/arm/logicnode/arm_props.py @@ -0,0 +1,281 @@ +"""Custom bpy property creators for logic nodes. Please be aware that +the code in this file is usually run once at registration and not for +each individual node instance when it is created. + +The functions for creating typed properties wrap the private __haxe_prop +function to allow for IDE autocompletion. + +Some default parameters in the signature of functions in this module are +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 +for parity with the Blender API. +""" +from typing import Any, Callable, Sequence, Union + +import bpy +from bpy.props import * + +__all__ = [ + 'HaxeBoolProperty', + 'HaxeBoolVectorProperty', + 'HaxeCollectionProperty', + 'HaxeEnumProperty', + 'HaxeFloatProperty', + 'HaxeFloatVectorProperty', + 'HaxeIntProperty', + 'HaxeIntVectorProperty', + 'HaxePointerProperty', + 'HaxeStringProperty', + 'RemoveHaxeProperty' +] + + +def __haxe_prop(prop_type: Callable, prop_name: str, *args, **kwargs) -> Any: + """Declares a logic node property as a property that will be + used ingame for a logic node.""" + update_callback: Callable = kwargs.get('update', None) + if update_callback is None: + def wrapper(self: bpy.types.Node, context: bpy.types.Context): + self.on_prop_update(context, prop_name) + kwargs['update'] = wrapper + else: + def wrapper(self: bpy.types.Node, context: bpy.types.Context): + update_callback(self, context) + self.on_prop_update(context, prop_name) + kwargs['update'] = wrapper + + # Tags are not allowed on classes other than bpy.types.ID or + # bpy.types.Bone, remove them here to prevent registration errors + if 'tags' in kwargs: + del kwargs['tags'] + + return prop_type(*args, **kwargs) + + +def HaxeBoolProperty( + prop_name: str, + *, # force passing further arguments as keywords, see PEP 3102 + name: str = "", + description: str = "", + default=False, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + update=None, + get=None, + set=None +) -> 'bpy.types.BoolProperty': + """Declares a new BoolProperty that has a Haxe counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(BoolProperty, **locals()) + + +def HaxeBoolVectorProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default: list = (False, False, False), + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + size: int = 3, + update=None, + get=None, + set=None +) -> list['bpy.types.BoolProperty']: + """Declares a new BoolVectorProperty that has a Haxe counterpart + with the given prop_name (Python and Haxe names must be identical + for now). + """ + return __haxe_prop(BoolVectorProperty, **locals()) + + +def HaxeCollectionProperty( + prop_name: str, + *, + type=None, + name: str = "", + description: str = "", + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set() +) -> 'bpy.types.CollectionProperty': + """Declares a new CollectionProperty that has a Haxe counterpart + with the given prop_name (Python and Haxe names must be identical + for now). + """ + return __haxe_prop(CollectionProperty, **locals()) + + +def HaxeEnumProperty( + prop_name: str, + *, + items: Sequence, + name: str = "", + description: str = "", + default: Union[str, set[str]] = None, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + update=None, + get=None, + set=None +) -> 'bpy.types.EnumProperty': + """Declares a new EnumProperty that has a Haxe counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(EnumProperty, **locals()) + + +def HaxeFloatProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default=0.0, + min: float = -3.402823e+38, + max: float = 3.402823e+38, + soft_min: float = -3.402823e+38, + soft_max: float = 3.402823e+38, + step: int = 3, + precision: int = 2, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + unit: str = 'NONE', + update=None, + get=None, + set=None +) -> 'bpy.types.FloatProperty': + """Declares a new FloatProperty that has a Haxe counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(FloatProperty, **locals()) + + +def HaxeFloatVectorProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default: list = (0.0, 0.0, 0.0), + min: float = 'sys.float_info.min', + max: float = 'sys.float_info.max', + soft_min: float = 'sys.float_info.min', + soft_max: float = 'sys.float_info.max', + step: int = 3, + precision: int = 2, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + unit: str = 'NONE', + size: int = 3, + update=None, + get=None, + set=None +) -> list['bpy.types.FloatProperty']: + """Declares a new FloatVectorProperty that has a Haxe counterpart + with the given prop_name (Python and Haxe names must be identical + for now). + """ + return __haxe_prop(FloatVectorProperty, **locals()) + + +def HaxeIntProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default=0, + min: int = -2**31, + max: int = 2**31 - 1, + soft_min: int = -2**31, + soft_max: int = 2**31 - 1, + step: int = 1, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + update=None, + get=None, + set=None +) -> 'bpy.types.IntProperty': + """Declares a new IntProperty that has a Haxe counterpart with the + given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(IntProperty, **locals()) + + +def HaxeIntVectorProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default: list = (0, 0, 0), + min: int = -2**31, + max: int = 2**31 - 1, + soft_min: int = -2**31, + soft_max: int = 2**31 - 1, + step: int = 1, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + size: int = 3, + update=None, + get=None, + set=None +) -> list['bpy.types.IntProperty']: + """Declares a new IntVectorProperty that has a Haxe counterpart with + the given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(IntVectorProperty, **locals()) + + +def HaxePointerProperty( + prop_name: str, + *, + type=None, + name: str = "", + description: str = "", + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + poll=None, + update=None +) -> 'bpy.types.PointerProperty': + """Declares a new PointerProperty that has a Haxe counterpart with + the given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(PointerProperty, **locals()) + + +def RemoveHaxeProperty(cls, attr: str): + RemoveProperty(cls, attr) + + +def HaxeStringProperty( + prop_name: str, + *, + name: str = "", + description: str = "", + default: str = "", + maxlen: int = 0, + options: set = {'ANIMATABLE'}, + override: set = set(), + tags: set = set(), + subtype: str = 'NONE', + update=None, + get=None, + set=None +) -> 'bpy.types.StringProperty': + """Declares a new StringProperty that has a Haxe counterpart with + the given prop_name (Python and Haxe names must be identical for now). + """ + return __haxe_prop(StringProperty, **locals()) diff --git a/blender/arm/logicnode/arm_sockets.py b/blender/arm/logicnode/arm_sockets.py index 8a165e98..0a81d728 100644 --- a/blender/arm/logicnode/arm_sockets.py +++ b/blender/arm/logicnode/arm_sockets.py @@ -1,11 +1,20 @@ from math import pi, cos, sin, sqrt import bpy -from bpy.props import PointerProperty, EnumProperty, FloatProperty, FloatVectorProperty +from bpy.props import * from bpy.types import NodeSocket import mathutils import arm.utils +if arm.is_reload(__name__): + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + +def _on_update_socket(self, context): + self.node.on_socket_val_update(context, self) + class ArmCustomSocket(NodeSocket): """ @@ -43,7 +52,7 @@ class ArmAnimActionSocket(ArmCustomSocket): arm_socket_type = 'STRING' default_value_get: PointerProperty(name='Action', type=bpy.types.Action) # legacy version of the line after this one - default_value_raw: PointerProperty(name='Action', type=bpy.types.Action) + default_value_raw: PointerProperty(name='Action', type=bpy.types.Action, update=_on_update_socket) def __init__(self): super().__init__() @@ -72,13 +81,13 @@ class ArmAnimActionSocket(ArmCustomSocket): class ArmRotationSocket(ArmCustomSocket): - bl_idname = 'ArmNodeSocketRotation' + bl_idname = 'ArmRotationSocket' bl_label = 'Rotation Socket' arm_socket_type = 'ROTATION' # the internal representation is a quaternion, AKA a '4D vector' (using mathutils.Vector((x,y,z,w))) def get_default_value(self): if self.default_value_raw is None: - return Vector((0.0,0.0,0.0,1.0)) + return mathutils.Vector((0.0,0.0,0.0,1.0)) else: return self.default_value_raw @@ -247,7 +256,12 @@ class ArmRotationSocket(ArmCustomSocket): default_value_s2: FloatProperty(update=do_update_raw) default_value_s3: FloatProperty(update=do_update_raw) - default_value_raw: FloatVectorProperty(size=4, default=(0,0,0,1)) + default_value_raw: FloatVectorProperty( + name='Value', + description='Raw quaternion obtained for the default value of a ArmRotationSocket socket', + size=4, default=(0,0,0,1), + update = _on_update_socket + ) class ArmArraySocket(ArmCustomSocket): @@ -262,13 +276,114 @@ class ArmArraySocket(ArmCustomSocket): return 0.8, 0.4, 0.0, 1 +class ArmBoolSocket(ArmCustomSocket): + bl_idname = 'ArmBoolSocket' + bl_label = 'Boolean Socket' + arm_socket_type = 'BOOLEAN' + + default_value_raw: BoolProperty( + name='Value', + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout(self, layout) + + def draw_color(self, context, node): + return 0.8, 0.651, 0.839, 1 + + def get_default_value(self): + return self.default_value_raw + + +class ArmColorSocket(ArmCustomSocket): + bl_idname = 'ArmColorSocket' + bl_label = 'Color Socket' + arm_socket_type = 'RGBA' + + default_value_raw: FloatVectorProperty( + name='Value', + size=4, + subtype='COLOR', + min=0.0, + max=1.0, + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout(self, layout) + + def draw_color(self, context, node): + return 0.78, 0.78, 0.161, 1 + + def get_default_value(self): + return self.default_value_raw + + +class ArmDynamicSocket(ArmCustomSocket): + bl_idname = 'ArmDynamicSocket' + bl_label = 'Dynamic Socket' + arm_socket_type = 'NONE' + + def draw(self, context, layout, node, text): + layout.label(text=self.name) + + def draw_color(self, context, node): + return 0.388, 0.78, 0.388, 1 + + +class ArmFloatSocket(ArmCustomSocket): + bl_idname = 'ArmFloatSocket' + bl_label = 'Float Socket' + arm_socket_type = 'VALUE' + + default_value_raw: FloatProperty( + name='Value', + description='Input value used for unconnected socket', + precision=3, + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout(self, layout) + + def draw_color(self, context, node): + return 0.631, 0.631, 0.631, 1 + + def get_default_value(self): + return self.default_value_raw + + +class ArmIntSocket(ArmCustomSocket): + bl_idname = 'ArmIntSocket' + bl_label = 'Integer Socket' + arm_socket_type = 'INT' + + default_value_raw: IntProperty( + name='Value', + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout(self, layout) + + def draw_color(self, context, node): + return 0.059, 0.522, 0.149, 1 + + def get_default_value(self): + return self.default_value_raw + + class ArmObjectSocket(ArmCustomSocket): bl_idname = 'ArmNodeSocketObject' bl_label = 'Object Socket' arm_socket_type = 'OBJECT' default_value_get: PointerProperty(name='Object', type=bpy.types.Object) # legacy version of the line after this one - default_value_raw: PointerProperty(name='Object', type=bpy.types.Object) + default_value_raw: PointerProperty(name='Object', type=bpy.types.Object, update=_on_update_socket) def __init__(self): super().__init__() @@ -296,17 +411,83 @@ class ArmObjectSocket(ArmCustomSocket): return 0.15, 0.55, 0.75, 1 -def register(): - bpy.utils.register_class(ArmActionSocket) - bpy.utils.register_class(ArmAnimActionSocket) - bpy.utils.register_class(ArmRotationSocket) - bpy.utils.register_class(ArmArraySocket) - bpy.utils.register_class(ArmObjectSocket) + +class ArmStringSocket(ArmCustomSocket): + bl_idname = 'ArmStringSocket' + bl_label = 'String Socket' + arm_socket_type = 'STRING' + + default_value_raw: StringProperty( + name='Value', + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + draw_socket_layout_split(self, layout) + + def draw_color(self, context, node): + return 0.439, 0.698, 1, 1 + + def get_default_value(self): + return self.default_value_raw -def unregister(): - bpy.utils.unregister_class(ArmObjectSocket) - bpy.utils.unregister_class(ArmArraySocket) - bpy.utils.unregister_class(ArmAnimActionSocket) - bpy.utils.unregister_class(ArmRotationSocket) - bpy.utils.unregister_class(ArmActionSocket) +class ArmVectorSocket(ArmCustomSocket): + bl_idname = 'ArmVectorSocket' + bl_label = 'Vector Socket' + arm_socket_type = 'VECTOR' + + default_value_raw: FloatVectorProperty( + name='Value', + size=3, + description='Input value used for unconnected socket', + update=_on_update_socket + ) + + def draw(self, context, layout, node, text): + if not self.is_output and not self.is_linked: + col = layout.column(align=True) + col.prop(self, 'default_value_raw', text='') + else: + layout.label(text=self.name) + + def draw_color(self, context, node): + return 0.388, 0.388, 0.78, 1 + + def get_default_value(self): + return self.default_value_raw + + +def draw_socket_layout(socket: bpy.types.NodeSocket, layout: bpy.types.UILayout, prop_name='default_value_raw'): + if not socket.is_output and not socket.is_linked: + layout.prop(socket, prop_name, text=socket.name) + else: + layout.label(text=socket.name) + + +def draw_socket_layout_split(socket: bpy.types.NodeSocket, layout: bpy.types.UILayout, prop_name='default_value_raw'): + if not socket.is_output and not socket.is_linked: + # Blender layouts use 0.4 splits + layout = layout.split(factor=0.4, align=True) + + layout.label(text=socket.name) + + if not socket.is_output and not socket.is_linked: + layout.prop(socket, prop_name, text='') + +REG_CLASSES = ( + ArmActionSocket, + ArmAnimActionSocket, + ArmRotationSocket, + ArmArraySocket, + ArmBoolSocket, + ArmColorSocket, + ArmDynamicSocket, + ArmFloatSocket, + ArmIntSocket, + ArmObjectSocket, + ArmStringSocket, + ArmVectorSocket, +) +register, unregister = bpy.utils.register_classes_factory(REG_CLASSES) diff --git a/blender/arm/logicnode/array/LN_array.py b/blender/arm/logicnode/array/LN_array.py index cb5cb451..65473756 100644 --- a/blender/arm/logicnode/array/LN_array.py +++ b/blender/arm/logicnode/array/LN_array.py @@ -10,17 +10,16 @@ class ArrayNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(ArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_add.py b/blender/arm/logicnode/array/LN_array_add.py index 3bd708ea..0fc31a6d 100644 --- a/blender/arm/logicnode/array/LN_array_add.py +++ b/blender/arm/logicnode/array/LN_array_add.py @@ -15,13 +15,12 @@ class ArrayAddNode(ArmLogicTreeNode): super(ArrayAddNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(ArrayAddNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketBool', 'Modify Original', default_value=True) - self.add_input('NodeSocketBool', 'Unique Values') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmBoolSocket', 'Modify Original', default_value=True) + self.add_input('ArmBoolSocket', 'Unique Values') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketArray', 'Array') @@ -31,6 +30,6 @@ class ArrayAddNode(ArmLogicTreeNode): op = row.operator('arm.node_add_input_value', text='Add Input', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op2 = row.operator('arm.node_remove_input_value', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_boolean.py b/blender/arm/logicnode/array/LN_array_boolean.py index 6794a57e..7c0b333d 100644 --- a/blender/arm/logicnode/array/LN_array_boolean.py +++ b/blender/arm/logicnode/array/LN_array_boolean.py @@ -11,17 +11,16 @@ class BooleanArrayNode(ArmLogicTreeNode): super(BooleanArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(BooleanArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketBool' + op.socket_type = 'ArmBoolSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_color.py b/blender/arm/logicnode/array/LN_array_color.py index f2066440..148fd2da 100644 --- a/blender/arm/logicnode/array/LN_array_color.py +++ b/blender/arm/logicnode/array/LN_array_color.py @@ -11,17 +11,16 @@ class ColorArrayNode(ArmLogicTreeNode): super(ColorArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(ColorArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketColor' + op.socket_type = 'ArmColorSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_contains.py b/blender/arm/logicnode/array/LN_array_contains.py index a9a3e463..c81ec46a 100644 --- a/blender/arm/logicnode/array/LN_array_contains.py +++ b/blender/arm/logicnode/array/LN_array_contains.py @@ -6,9 +6,8 @@ class ArrayContainsNode(ArmLogicTreeNode): bl_label = 'Array Contains' arm_version = 1 - def init(self, context): - super(ArrayContainsNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') - self.add_output('NodeSocketBool', 'Contains') + self.add_output('ArmBoolSocket', 'Contains') diff --git a/blender/arm/logicnode/array/LN_array_float.py b/blender/arm/logicnode/array/LN_array_float.py index 1eeaf132..2c3ec3d8 100644 --- a/blender/arm/logicnode/array/LN_array_float.py +++ b/blender/arm/logicnode/array/LN_array_float.py @@ -11,17 +11,16 @@ class FloatArrayNode(ArmLogicTreeNode): super(FloatArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(FloatArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_get.py b/blender/arm/logicnode/array/LN_array_get.py index 2ccfe176..598a60f5 100644 --- a/blender/arm/logicnode/array/LN_array_get.py +++ b/blender/arm/logicnode/array/LN_array_get.py @@ -6,9 +6,8 @@ class ArrayGetNode(ArmLogicTreeNode): bl_label = 'Array Get' arm_version = 1 - def init(self, context): - super(ArrayGetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') + self.add_input('ArmIntSocket', 'Index') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_integer.py b/blender/arm/logicnode/array/LN_array_integer.py index 5e6cb2f1..1b01e721 100644 --- a/blender/arm/logicnode/array/LN_array_integer.py +++ b/blender/arm/logicnode/array/LN_array_integer.py @@ -11,17 +11,16 @@ class IntegerArrayNode(ArmLogicTreeNode): super(IntegerArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(IntegerArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketInt' + op.socket_type = 'ArmIntSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_length.py b/blender/arm/logicnode/array/LN_array_length.py index b6666533..3ef4e9f1 100644 --- a/blender/arm/logicnode/array/LN_array_length.py +++ b/blender/arm/logicnode/array/LN_array_length.py @@ -6,8 +6,7 @@ class ArrayLengthNode(ArmLogicTreeNode): bl_label = 'Array Length' arm_version = 1 - def init(self, context): - super(ArrayLengthNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') diff --git a/blender/arm/logicnode/array/LN_array_loop_node.py b/blender/arm/logicnode/array/LN_array_loop_node.py index 1e791989..aefd7331 100644 --- a/blender/arm/logicnode/array/LN_array_loop_node.py +++ b/blender/arm/logicnode/array/LN_array_loop_node.py @@ -7,12 +7,11 @@ class ArrayLoopNode(ArmLogicTreeNode): bl_label = 'Array Loop' arm_version = 1 - def init(self, context): - super(ArrayLoopNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') self.add_output('ArmNodeSocketAction', 'Loop') - self.add_output('NodeSocketShader', 'Value') - self.add_output('NodeSocketInt', 'Index') + self.add_output('ArmDynamicSocket', 'Value') + self.add_output('ArmIntSocket', 'Index') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/array/LN_array_object.py b/blender/arm/logicnode/array/LN_array_object.py index 8328ebc7..513e211c 100644 --- a/blender/arm/logicnode/array/LN_array_object.py +++ b/blender/arm/logicnode/array/LN_array_object.py @@ -11,10 +11,9 @@ class ObjectArrayNode(ArmLogicTreeNode): super(ObjectArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(ObjectArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) diff --git a/blender/arm/logicnode/array/LN_array_pop.py b/blender/arm/logicnode/array/LN_array_pop.py index 8bd93a8d..37b9ad5c 100644 --- a/blender/arm/logicnode/array/LN_array_pop.py +++ b/blender/arm/logicnode/array/LN_array_pop.py @@ -8,8 +8,7 @@ class ArrayPopNode(ArmLogicTreeNode): bl_label = 'Array Pop' arm_version = 1 - def init(self, context): - super(ArrayPopNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_remove_by_index.py b/blender/arm/logicnode/array/LN_array_remove_by_index.py index 9a81760b..efc69b43 100644 --- a/blender/arm/logicnode/array/LN_array_remove_by_index.py +++ b/blender/arm/logicnode/array/LN_array_remove_by_index.py @@ -8,11 +8,10 @@ class ArrayRemoveIndexNode(ArmLogicTreeNode): bl_label = 'Array Remove by Index' arm_version = 1 - def init(self, context): - super(ArrayRemoveIndexNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') + self.add_input('ArmIntSocket', 'Index') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_remove_by_value.py b/blender/arm/logicnode/array/LN_array_remove_by_value.py index f7634ea7..5314b714 100644 --- a/blender/arm/logicnode/array/LN_array_remove_by_value.py +++ b/blender/arm/logicnode/array/LN_array_remove_by_value.py @@ -11,20 +11,19 @@ class ArrayRemoveValueNode(ArmLogicTreeNode): # def __init__(self): # array_nodes[str(id(self))] = self - def init(self, context): - super(ArrayRemoveValueNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') # def draw_buttons(self, context, layout): # row = layout.row(align=True) # op = row.operator('arm.node_add_input_value', text='New', icon='PLUS', emboss=True) # op.node_index = str(id(self)) - # op.socket_type = 'NodeSocketShader' + # op.socket_type = 'ArmDynamicSocket' # op2 = row.operator('arm.node_remove_input_value', text='', icon='X', emboss=True) # op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_set.py b/blender/arm/logicnode/array/LN_array_set.py index 7c1f0197..e1eac977 100644 --- a/blender/arm/logicnode/array/LN_array_set.py +++ b/blender/arm/logicnode/array/LN_array_set.py @@ -6,11 +6,10 @@ class ArraySetNode(ArmLogicTreeNode): bl_label = 'Array Set' arm_version = 1 - def init(self, context): - super(ArraySetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmIntSocket', 'Index') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/array/LN_array_shift.py b/blender/arm/logicnode/array/LN_array_shift.py index 6a40faf2..15ee7b78 100644 --- a/blender/arm/logicnode/array/LN_array_shift.py +++ b/blender/arm/logicnode/array/LN_array_shift.py @@ -8,8 +8,7 @@ class ArrayShiftNode(ArmLogicTreeNode): bl_label = 'Array Shift' arm_version = 1 - def init(self, context): - super(ArrayShiftNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/array/LN_array_slice.py b/blender/arm/logicnode/array/LN_array_slice.py index 29ca2533..be2b1bd4 100644 --- a/blender/arm/logicnode/array/LN_array_slice.py +++ b/blender/arm/logicnode/array/LN_array_slice.py @@ -8,10 +8,9 @@ class ArraySliceNode(ArmLogicTreeNode): bl_label = 'Array Slice' arm_version = 1 - def init(self, context): - super(ArraySliceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') - self.add_input('NodeSocketInt', 'End') + self.add_input('ArmIntSocket', 'Index') + self.add_input('ArmIntSocket', 'End') self.add_output('ArmNodeSocketArray', 'Array') diff --git a/blender/arm/logicnode/array/LN_array_splice.py b/blender/arm/logicnode/array/LN_array_splice.py index 06aa7481..1f367cc5 100644 --- a/blender/arm/logicnode/array/LN_array_splice.py +++ b/blender/arm/logicnode/array/LN_array_splice.py @@ -8,11 +8,10 @@ class ArraySpliceNode(ArmLogicTreeNode): bl_label = 'Array Splice' arm_version = 1 - def init(self, context): - super(ArraySpliceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketInt', 'Index') - self.add_input('NodeSocketInt', 'Length') + self.add_input('ArmIntSocket', 'Index') + self.add_input('ArmIntSocket', 'Length') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/array/LN_array_string.py b/blender/arm/logicnode/array/LN_array_string.py index d1a82bb4..0bf6ed27 100644 --- a/blender/arm/logicnode/array/LN_array_string.py +++ b/blender/arm/logicnode/array/LN_array_string.py @@ -11,17 +11,16 @@ class StringArrayNode(ArmLogicTreeNode): super(StringArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(StringArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketString' + op.socket_type = 'ArmStringSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/array/LN_array_vector.py b/blender/arm/logicnode/array/LN_array_vector.py index 94ac05e0..bf1715d7 100644 --- a/blender/arm/logicnode/array/LN_array_vector.py +++ b/blender/arm/logicnode/array/LN_array_vector.py @@ -11,17 +11,16 @@ class VectorArrayNode(ArmLogicTreeNode): super(VectorArrayNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(VectorArrayNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array', is_var=True) - self.add_output('NodeSocketInt', 'Length') + self.add_output('ArmIntSocket', 'Length') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketVector' + op.socket_type = 'ArmVectorSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/camera/LN_get_camera_active.py b/blender/arm/logicnode/camera/LN_get_camera_active.py index 54c3fe7a..9956d45e 100644 --- a/blender/arm/logicnode/camera/LN_get_camera_active.py +++ b/blender/arm/logicnode/camera/LN_get_camera_active.py @@ -8,6 +8,5 @@ class ActiveCameraNode(ArmLogicTreeNode): bl_label = 'Get Camera Active' arm_version = 1 - def init(self, context): - super(ActiveCameraNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketObject', 'Camera') diff --git a/blender/arm/logicnode/camera/LN_get_camera_fov.py b/blender/arm/logicnode/camera/LN_get_camera_fov.py index d35c3181..b5e70466 100644 --- a/blender/arm/logicnode/camera/LN_get_camera_fov.py +++ b/blender/arm/logicnode/camera/LN_get_camera_fov.py @@ -8,8 +8,7 @@ class GetCameraFovNode(ArmLogicTreeNode): bl_label = 'Get Camera FOV' arm_version = 1 - def init(self, context): - super(GetCameraFovNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketFloat', 'FOV') + self.add_output('ArmFloatSocket', 'FOV') diff --git a/blender/arm/logicnode/camera/LN_set_camera_active.py b/blender/arm/logicnode/camera/LN_set_camera_active.py index dd05a919..fa078d10 100644 --- a/blender/arm/logicnode/camera/LN_set_camera_active.py +++ b/blender/arm/logicnode/camera/LN_set_camera_active.py @@ -8,8 +8,7 @@ class SetCameraNode(ArmLogicTreeNode): bl_label = 'Set Camera Active' arm_version = 1 - def init(self, context): - super(SetCameraNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Camera') diff --git a/blender/arm/logicnode/camera/LN_set_camera_fov.py b/blender/arm/logicnode/camera/LN_set_camera_fov.py index 4785c18c..1974c0d0 100644 --- a/blender/arm/logicnode/camera/LN_set_camera_fov.py +++ b/blender/arm/logicnode/camera/LN_set_camera_fov.py @@ -8,10 +8,9 @@ class SetCameraFovNode(ArmLogicTreeNode): bl_label = 'Set Camera FOV' arm_version = 1 - def init(self, context): - super(SetCameraFovNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Camera') - self.add_input('NodeSocketFloat', 'FOV', default_value=0.9) + self.add_input('ArmFloatSocket', 'FOV', default_value=0.9) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py b/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py index 027b2015..de3cee1d 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_checkbox.py @@ -6,8 +6,7 @@ class CanvasGetCheckboxNode(ArmLogicTreeNode): bl_label = 'Get Canvas Checkbox' arm_version = 1 - def init(self, context): - super(CanvasGetCheckboxNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Element') - self.add_output('NodeSocketBool', 'Is Checked') + self.add_output('ArmBoolSocket', 'Is Checked') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py b/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py index 09e10e71..e8d1a7d3 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_input_text.py @@ -6,8 +6,7 @@ class CanvasGetInputTextNode(ArmLogicTreeNode): bl_label = 'Get Canvas Input Text' arm_version = 1 - def init(self, context): - super(CanvasGetInputTextNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Element') - self.add_output('NodeSocketString', 'Text') + self.add_output('ArmStringSocket', 'Text') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_location.py b/blender/arm/logicnode/canvas/LN_get_canvas_location.py index 3ea3c2d8..71e9491f 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_location.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_location.py @@ -6,11 +6,10 @@ class CanvasGetLocationNode(ArmLogicTreeNode): bl_label = 'Get Canvas Location' arm_version = 1 - def init(self, context): - super(CanvasGetLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketInt', 'X') - self.add_output('NodeSocketInt', 'Y') + self.add_output('ArmIntSocket', 'X') + self.add_output('ArmIntSocket', 'Y') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_position.py b/blender/arm/logicnode/canvas/LN_get_canvas_position.py index d7704fd6..47efa750 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_position.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_position.py @@ -6,8 +6,7 @@ class CanvasGetPositionNode(ArmLogicTreeNode): bl_label = 'Get Canvas Position' arm_version = 1 - def init(self, context): - super(CanvasGetPositionNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Element') - self.add_output('NodeSocketInt', 'Position') + self.add_output('ArmIntSocket', 'Position') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py b/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py index b7aa0795..5e9435ca 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_progress_bar.py @@ -6,11 +6,10 @@ class CanvasGetPBNode(ArmLogicTreeNode): bl_label = 'Get Canvas Progress Bar' arm_version = 1 - def init(self, context): - super(CanvasGetPBNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketInt', 'At') - self.add_output('NodeSocketInt', 'Max') + self.add_output('ArmIntSocket', 'At') + self.add_output('ArmIntSocket', 'Max') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py b/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py index 342c9288..3b7ffa9e 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_rotation.py @@ -6,10 +6,9 @@ class CanvasGetRotationNode(ArmLogicTreeNode): bl_label = 'Get Canvas Rotation' arm_version = 1 - def init(self, context): - super(CanvasGetRotationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketFloat', 'Rad') + self.add_output('ArmFloatSocket', 'Rad') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_scale.py b/blender/arm/logicnode/canvas/LN_get_canvas_scale.py index 631a7185..9e65ea39 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_scale.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_scale.py @@ -6,11 +6,10 @@ class CanvasGetScaleNode(ArmLogicTreeNode): bl_label = 'Get Canvas Scale' arm_version = 1 - def init(self, context): - super(CanvasGetScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketInt', 'Height') - self.add_output('NodeSocketInt', 'Width') + self.add_output('ArmIntSocket', 'Height') + self.add_output('ArmIntSocket', 'Width') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_slider.py b/blender/arm/logicnode/canvas/LN_get_canvas_slider.py index 70ccbac3..c0d09cc4 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_slider.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_slider.py @@ -6,8 +6,7 @@ class CanvasGetSliderNode(ArmLogicTreeNode): bl_label = 'Get Canvas Slider' arm_version = 1 - def init(self, context): - super(CanvasGetSliderNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Element') - self.add_output('NodeSocketFloat', 'Float') + self.add_output('ArmFloatSocket', 'Float') diff --git a/blender/arm/logicnode/canvas/LN_get_canvas_visible.py b/blender/arm/logicnode/canvas/LN_get_canvas_visible.py index 6c983e62..3fb4ae1b 100644 --- a/blender/arm/logicnode/canvas/LN_get_canvas_visible.py +++ b/blender/arm/logicnode/canvas/LN_get_canvas_visible.py @@ -9,8 +9,7 @@ class CanvasGetVisibleNode(ArmLogicTreeNode): bl_label = 'Get Canvas Visible' arm_version = 1 - def init(self, context): - super(CanvasGetVisibleNode, self).init(context) - self.inputs.new('NodeSocketString', 'Element') + def arm_init(self, context): + self.inputs.new('ArmStringSocket', 'Element') - self.outputs.new('NodeSocketBool', 'Is Visible') + self.outputs.new('ArmBoolSocket', 'Is Visible') diff --git a/blender/arm/logicnode/canvas/LN_on_canvas_element.py b/blender/arm/logicnode/canvas/LN_on_canvas_element.py index 4eab0872..fe65ad0c 100644 --- a/blender/arm/logicnode/canvas/LN_on_canvas_element.py +++ b/blender/arm/logicnode/canvas/LN_on_canvas_element.py @@ -6,24 +6,26 @@ class OnCanvasElementNode(ArmLogicTreeNode): bl_label = 'On Canvas Element' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items=[('click', 'Click', 'Listen to mouse clicks'), ('hover', 'Hover', 'Listen to mouse hover')], name='Listen to', default='click') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items=[('started', 'Started', 'Started'), ('down', 'Down', 'Down'), ('released', 'Released', 'Released')], name='Status', default='started') - property2: EnumProperty( + property2: HaxeEnumProperty( + 'property2', items=[('left', 'Left', 'Left mouse button'), ('middle', 'Middle', 'Middle mouse button'), ('right', 'Right', 'Right mouse button')], name='Mouse Button', default='left') - def init(self, context): - super(OnCanvasElementNode, self).init(context) - self.add_input('NodeSocketString', 'Element') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Element') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_asset.py b/blender/arm/logicnode/canvas/LN_set_canvas_asset.py index 174b4b01..bf734e70 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_asset.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_asset.py @@ -6,10 +6,9 @@ class CanvasSetAssetNode(ArmLogicTreeNode): bl_label = 'Set Canvas Asset' arm_version = 1 - def init(self, context): - super(CanvasSetAssetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketString', 'Asset') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmStringSocket', 'Asset') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py b/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py index a3759a68..ad41a7ae 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_checkbox.py @@ -6,10 +6,9 @@ class CanvasSetCheckBoxNode(ArmLogicTreeNode): bl_label = 'Set Canvas Checkbox' arm_version = 1 - def init(self, context): - super(CanvasSetCheckBoxNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketBool', 'Check') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmBoolSocket', 'Check') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_location.py b/blender/arm/logicnode/canvas/LN_set_canvas_location.py index 12886e37..03402117 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_location.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_location.py @@ -6,11 +6,10 @@ class CanvasSetLocationNode(ArmLogicTreeNode): bl_label = 'Set Canvas Location' arm_version = 1 - def init(self, context): - super(CanvasSetLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketFloat', 'X') - self.add_input('NodeSocketFloat', 'Y') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmFloatSocket', 'X') + self.add_input('ArmFloatSocket', 'Y') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py b/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py index bd39bd1d..b5d59085 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_progress_bar.py @@ -6,11 +6,10 @@ class CanvasSetPBNode(ArmLogicTreeNode): bl_label = 'Set Canvas Progress Bar' arm_version = 1 - def init(self, context): - super(CanvasSetPBNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketInt', 'At') - self.add_input('NodeSocketInt', 'Max') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmIntSocket', 'At') + self.add_input('ArmIntSocket', 'Max') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py b/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py index 4379f61a..ee9f6502 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_rotation.py @@ -6,10 +6,9 @@ class CanvasSetRotationNode(ArmLogicTreeNode): bl_label = 'Set Canvas Rotation' arm_version = 1 - def init(self, context): - super(CanvasSetRotationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketFloat', 'Rad') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmFloatSocket', 'Rad') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_scale.py b/blender/arm/logicnode/canvas/LN_set_canvas_scale.py index 1718c9ad..7afbf7ab 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_scale.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_scale.py @@ -6,11 +6,10 @@ class CanvasSetScaleNode(ArmLogicTreeNode): bl_label = 'Set Canvas Scale' arm_version = 1 - def init(self, context): - super(CanvasSetScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketInt', 'Height') - self.add_input('NodeSocketInt', 'Width') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmIntSocket', 'Height') + self.add_input('ArmIntSocket', 'Width') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_slider.py b/blender/arm/logicnode/canvas/LN_set_canvas_slider.py index 7c7cadda..9988266b 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_slider.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_slider.py @@ -6,10 +6,9 @@ class CanvasSetSliderNode(ArmLogicTreeNode): bl_label = 'Set Canvas Slider' arm_version = 1 - def init(self, context): - super(CanvasSetSliderNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketFloat', 'Float') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmFloatSocket', 'Float') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_text.py b/blender/arm/logicnode/canvas/LN_set_canvas_text.py index 598a4794..462b7ba2 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_text.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_text.py @@ -6,10 +6,9 @@ class CanvasSetTextNode(ArmLogicTreeNode): bl_label = 'Set Canvas Text' arm_version = 1 - def init(self, context): - super(CanvasSetTextNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketString', 'Text') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmStringSocket', 'Text') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py b/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py index 2ac59b27..5863510f 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_text_color.py @@ -6,13 +6,12 @@ class CanvasSetTextColorNode(ArmLogicTreeNode): bl_label = 'Set Canvas Text Color' arm_version = 1 - def init(self, context): - super(CanvasSetTextColorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketFloat', 'R') - self.add_input('NodeSocketFloat', 'G') - self.add_input('NodeSocketFloat', 'B') - self.add_input('NodeSocketFloat', 'A') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmFloatSocket', 'R') + self.add_input('ArmFloatSocket', 'G') + self.add_input('ArmFloatSocket', 'B') + self.add_input('ArmFloatSocket', 'A') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/canvas/LN_set_canvas_visible.py b/blender/arm/logicnode/canvas/LN_set_canvas_visible.py index 8e8444f4..dbe88b26 100644 --- a/blender/arm/logicnode/canvas/LN_set_canvas_visible.py +++ b/blender/arm/logicnode/canvas/LN_set_canvas_visible.py @@ -6,10 +6,9 @@ class CanvasSetVisibleNode(ArmLogicTreeNode): bl_label = 'Set Canvas Visible' arm_version = 1 - def init(self, context): - super(CanvasSetVisibleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Element') - self.add_input('NodeSocketBool', 'Visible') + self.add_input('ArmStringSocket', 'Element') + self.add_input('ArmBoolSocket', 'Visible') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py b/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py index 6bf63494..83cea8fa 100644 --- a/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py +++ b/blender/arm/logicnode/deprecated/LN_get_mouse_lock.py @@ -14,9 +14,8 @@ class GetMouseLockNode(ArmLogicTreeNode): arm_category = 'Input' arm_section = 'mouse' - def init(self, context): - super(GetMouseLockNode, self).init(context) - self.outputs.new('NodeSocketBool', 'Is Locked') + def arm_init(self, context): + self.outputs.new('ArmBoolSocket', 'Is Locked') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): diff --git a/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py b/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py index 0884b8eb..95c5d0f1 100644 --- a/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py +++ b/blender/arm/logicnode/deprecated/LN_get_mouse_visible.py @@ -14,9 +14,8 @@ class GetMouseVisibleNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - def init(self, context): - super(GetMouseVisibleNode, self).init(context) - self.outputs.new('NodeSocketBool', 'Is Visible') + def arm_init(self, context): + self.outputs.new('ArmBoolSocket', 'Is Visible') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): diff --git a/blender/arm/logicnode/deprecated/LN_mouse_coords.py b/blender/arm/logicnode/deprecated/LN_mouse_coords.py index df0ced8a..60b58cdd 100644 --- a/blender/arm/logicnode/deprecated/LN_mouse_coords.py +++ b/blender/arm/logicnode/deprecated/LN_mouse_coords.py @@ -11,11 +11,10 @@ class MouseCoordsNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - def init(self, context): - super(MouseCoordsNode, self).init(context) - self.add_output('NodeSocketVector', 'Coords') - self.add_output('NodeSocketVector', 'Movement') - self.add_output('NodeSocketInt', 'Wheel') + def arm_init(self, context): + self.add_output('ArmVectorSocket', 'Coords') + self.add_output('ArmVectorSocket', 'Movement') + self.add_output('ArmIntSocket', 'Wheel') def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): diff --git a/blender/arm/logicnode/deprecated/LN_on_gamepad.py b/blender/arm/logicnode/deprecated/LN_on_gamepad.py index e2867026..3de6a284 100644 --- a/blender/arm/logicnode/deprecated/LN_on_gamepad.py +++ b/blender/arm/logicnode/deprecated/LN_on_gamepad.py @@ -11,7 +11,8 @@ class OnGamepadNode(ArmLogicTreeNode): arm_section = 'gamepad' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Down', 'Down', 'Down'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released')], @@ -19,7 +20,8 @@ class OnGamepadNode(ArmLogicTreeNode): # ('Moved Right', 'Moved Right', 'Moved Right'),], name='', default='Started') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('cross', 'cross / a', 'cross / a'), ('circle', 'circle / b', 'circle / b'), ('square', 'square / x', 'square / x'), @@ -40,10 +42,9 @@ class OnGamepadNode(ArmLogicTreeNode): ('touchpad', 'touchpad', 'touchpad'),], name='', default='cross') - def init(self, context): - super(OnGamepadNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') - self.add_input('NodeSocketInt', 'Gamepad') + self.add_input('ArmIntSocket', 'Gamepad') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/deprecated/LN_on_keyboard.py b/blender/arm/logicnode/deprecated/LN_on_keyboard.py index 29d6d2a3..77699451 100644 --- a/blender/arm/logicnode/deprecated/LN_on_keyboard.py +++ b/blender/arm/logicnode/deprecated/LN_on_keyboard.py @@ -11,13 +11,15 @@ class OnKeyboardNode(ArmLogicTreeNode): arm_section = 'keyboard' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Down', 'Down', 'Down'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released')], name='', default='Started') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('a', 'a', 'a'), ('b', 'b', 'b'), ('c', 'c', 'c'), @@ -72,8 +74,7 @@ class OnKeyboardNode(ArmLogicTreeNode): ('down', 'down', 'down'),], name='', default='space') - def init(self, context): - super(OnKeyboardNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/deprecated/LN_on_mouse.py b/blender/arm/logicnode/deprecated/LN_on_mouse.py index c4ac60b9..087ef551 100644 --- a/blender/arm/logicnode/deprecated/LN_on_mouse.py +++ b/blender/arm/logicnode/deprecated/LN_on_mouse.py @@ -11,20 +11,21 @@ class OnMouseNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Down', 'Down', 'Down'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released'), ('Moved', 'Moved', 'Moved')], name='', default='Down') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('left', 'left', 'left'), ('right', 'right', 'right'), ('middle', 'middle', 'middle')], name='', default='left') - def init(self, context): - super(OnMouseNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/deprecated/LN_on_surface.py b/blender/arm/logicnode/deprecated/LN_on_surface.py index 066eb4a6..706c45c6 100644 --- a/blender/arm/logicnode/deprecated/LN_on_surface.py +++ b/blender/arm/logicnode/deprecated/LN_on_surface.py @@ -11,15 +11,15 @@ class OnSurfaceNode(ArmLogicTreeNode): arm_section = 'surface' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Touched', 'Touched', 'Touched'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released'), ('Moved', 'Moved', 'Moved')], name='', default='Touched') - def init(self, context): - super(OnSurfaceNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/deprecated/LN_on_virtual_button.py b/blender/arm/logicnode/deprecated/LN_on_virtual_button.py index 21ba0382..e2377d0b 100644 --- a/blender/arm/logicnode/deprecated/LN_on_virtual_button.py +++ b/blender/arm/logicnode/deprecated/LN_on_virtual_button.py @@ -11,15 +11,15 @@ class OnVirtualButtonNode(ArmLogicTreeNode): arm_section = 'virtual' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Down', 'Down', 'Down'), ('Started', 'Started', 'Started'), ('Released', 'Released', 'Released')], name='', default='Started') - property1: StringProperty(name='', default='button') + property1: HaxeStringProperty('property1', name='', default='button') - def init(self, context): - super(OnVirtualButtonNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/deprecated/LN_pause_action.py b/blender/arm/logicnode/deprecated/LN_pause_action.py index 2def9733..b515ec0d 100644 --- a/blender/arm/logicnode/deprecated/LN_pause_action.py +++ b/blender/arm/logicnode/deprecated/LN_pause_action.py @@ -10,8 +10,7 @@ class PauseActionNode(ArmLogicTreeNode): arm_category = 'Animation' arm_version = 2 - def init(self, context): - super(PauseActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_pause_tilesheet.py b/blender/arm/logicnode/deprecated/LN_pause_tilesheet.py index 87185558..38313026 100644 --- a/blender/arm/logicnode/deprecated/LN_pause_tilesheet.py +++ b/blender/arm/logicnode/deprecated/LN_pause_tilesheet.py @@ -11,8 +11,7 @@ class PauseTilesheetNode(ArmLogicTreeNode): arm_section = 'tilesheet' arm_version = 2 - def init(self, context): - super(PauseTilesheetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_pause_trait.py b/blender/arm/logicnode/deprecated/LN_pause_trait.py index f5e921a7..b3aecd2c 100644 --- a/blender/arm/logicnode/deprecated/LN_pause_trait.py +++ b/blender/arm/logicnode/deprecated/LN_pause_trait.py @@ -10,8 +10,7 @@ class PauseTraitNode(ArmLogicTreeNode): arm_category = 'Trait' arm_version = 2 - def init(self, context): - super(PauseTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_play_action.py b/blender/arm/logicnode/deprecated/LN_play_action.py index c914a0b0..5ac411c9 100644 --- a/blender/arm/logicnode/deprecated/LN_play_action.py +++ b/blender/arm/logicnode/deprecated/LN_play_action.py @@ -10,11 +10,10 @@ class PlayActionNode(ArmLogicTreeNode): arm_category = 'Animation' arm_version = 2 - def init(self, context): - super(PlayActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketAnimAction', 'Action') - self.add_input('NodeSocketFloat', 'Blend', default_value=0.2) + self.add_input('ArmFloatSocket', 'Blend', default_value=0.2) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/deprecated/LN_quaternion.py b/blender/arm/logicnode/deprecated/LN_quaternion.py index 25a23a8f..70c4ccb3 100644 --- a/blender/arm/logicnode/deprecated/LN_quaternion.py +++ b/blender/arm/logicnode/deprecated/LN_quaternion.py @@ -6,20 +6,19 @@ class QuaternionNode(ArmLogicTreeNode): """TO DO.""" bl_idname = 'LNQuaternionNode' bl_label = 'Quaternion' - bl_label = 'Create a quaternion variable (transported through a vector socket)' + bl_description = 'Create a quaternion variable (transported through a vector socket)' arm_section = 'quaternions' arm_version = 2 # deprecate - def init(self, context): - super(QuaternionNode, self).init(context) - self.add_input('NodeSocketFloat', 'X') - self.add_input('NodeSocketFloat', 'Y') - self.add_input('NodeSocketFloat', 'Z') - self.add_input('NodeSocketFloat', 'W', default_value=1.0) + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'X') + self.add_input('ArmFloatSocket', 'Y') + self.add_input('ArmFloatSocket', 'Z') + self.add_input('ArmFloatSocket', 'W', default_value=1.0) - self.add_output('NodeSocketVector', 'Quaternion') - self.add_output('NodeSocketVector', 'XYZ') - self.add_output('NodeSocketFloat', 'W') + self.add_output('ArmVectorSocket', 'Quaternion') + self.add_output('ArmVectorSocket', 'XYZ') + self.add_output('ArmVectorSocket', 'W') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_resume_action.py b/blender/arm/logicnode/deprecated/LN_resume_action.py index 4df1d8dc..9af44ae0 100644 --- a/blender/arm/logicnode/deprecated/LN_resume_action.py +++ b/blender/arm/logicnode/deprecated/LN_resume_action.py @@ -10,8 +10,7 @@ class ResumeActionNode(ArmLogicTreeNode): arm_category = 'Animation' arm_version = 2 - def init(self, context): - super(ResumeActionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_resume_tilesheet.py b/blender/arm/logicnode/deprecated/LN_resume_tilesheet.py index 68ad6da2..b807dcc9 100644 --- a/blender/arm/logicnode/deprecated/LN_resume_tilesheet.py +++ b/blender/arm/logicnode/deprecated/LN_resume_tilesheet.py @@ -10,8 +10,7 @@ class ResumeTilesheetNode(ArmLogicTreeNode): arm_category = 'Animation' arm_version = 2 - def init(self, context): - super(ResumeTilesheetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_resume_trait.py b/blender/arm/logicnode/deprecated/LN_resume_trait.py index 0ea926e9..89c15bdf 100644 --- a/blender/arm/logicnode/deprecated/LN_resume_trait.py +++ b/blender/arm/logicnode/deprecated/LN_resume_trait.py @@ -10,8 +10,7 @@ class ResumeTraitNode(ArmLogicTreeNode): arm_category = 'Trait' arm_version = 2 - def init(self, context): - super(ResumeTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py b/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py index a1b2b5c4..104f774b 100644 --- a/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py +++ b/blender/arm/logicnode/deprecated/LN_rotate_object_around_axis.py @@ -11,12 +11,11 @@ class RotateObjectAroundAxisNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 2 - def init(self, context): - super(RotateObjectAroundAxisNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Axis', default_value=[0, 0, 1]) - self.add_input('NodeSocketFloat', 'Angle') + self.add_input('ArmVectorSocket', 'Axis', default_value=[0, 0, 1]) + self.add_input('ArmFloatSocket', 'Angle') self.add_output('ArmNodeSocketAction', 'Out') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_scale_object.py b/blender/arm/logicnode/deprecated/LN_scale_object.py index 4b3bb898..29d5ae3d 100644 --- a/blender/arm/logicnode/deprecated/LN_scale_object.py +++ b/blender/arm/logicnode/deprecated/LN_scale_object.py @@ -11,9 +11,8 @@ class ScaleObjectNode(ArmLogicTreeNode): arm_section = 'scale' arm_version = 2 - def init(self, context): - super(ScaleObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Scale') + self.add_input('ArmVectorSocket', 'Scale') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_separate_quaternion.py b/blender/arm/logicnode/deprecated/LN_separate_quaternion.py index 21f18946..a07d20ae 100644 --- a/blender/arm/logicnode/deprecated/LN_separate_quaternion.py +++ b/blender/arm/logicnode/deprecated/LN_separate_quaternion.py @@ -10,14 +10,13 @@ class SeparateQuaternionNode(ArmLogicTreeNode): arm_version = 2 # deprecate - def init(self, context): - super(SeparateQuaternionNode, self).init(context) - self.add_input('NodeSocketVector', 'Quaternion') + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Quaternion') - self.add_output('NodeSocketFloat', 'X') - self.add_output('NodeSocketFloat', 'Y') - self.add_output('NodeSocketFloat', 'Z') - self.add_output('NodeSocketFloat', 'W') + self.add_output('ArmFloatSocket', 'X') + self.add_output('ArmFloatSocket', 'Y') + self.add_output('ArmFloatSocket', 'Z') + self.add_output('ArmFloatSocket', 'W') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py b/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py index 5612e87e..a27f42c3 100644 --- a/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py +++ b/blender/arm/logicnode/deprecated/LN_set_mouse_lock.py @@ -11,10 +11,9 @@ class SetMouseLockNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - def init(self, context): - super(SetMouseLockNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Lock') + self.add_input('ArmBoolSocket', 'Lock') self.add_output('ArmNodeSocketAction', 'Out') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py b/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py index c85738e6..73e40a5b 100644 --- a/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py +++ b/blender/arm/logicnode/deprecated/LN_set_mouse_visible.py @@ -11,10 +11,9 @@ class ShowMouseNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 2 - def init(self, context): - super(ShowMouseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Show') + self.add_input('ArmBoolSocket', 'Show') self.add_output('ArmNodeSocketAction', 'Out') def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/deprecated/LN_set_object_material.py b/blender/arm/logicnode/deprecated/LN_set_object_material.py index 25dc3f24..5d01c135 100644 --- a/blender/arm/logicnode/deprecated/LN_set_object_material.py +++ b/blender/arm/logicnode/deprecated/LN_set_object_material.py @@ -10,9 +10,8 @@ class SetMaterialNode(ArmLogicTreeNode): arm_category = 'Material' arm_version = 2 - def init(self, context): - super(SetMaterialNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Material') + self.add_input('ArmDynamicSocket', 'Material') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/deprecated/LN_surface_coords.py b/blender/arm/logicnode/deprecated/LN_surface_coords.py index dff2b872..3fc6d1ee 100644 --- a/blender/arm/logicnode/deprecated/LN_surface_coords.py +++ b/blender/arm/logicnode/deprecated/LN_surface_coords.py @@ -12,7 +12,6 @@ class SurfaceCoordsNode(ArmLogicTreeNode): arm_is_obsolete = 'is_obsolete' arm_version = 2 - def init(self, context): - super(SurfaceCoordsNode, self).init(context) - self.add_output('NodeSocketVector', 'Coords') - self.add_output('NodeSocketVector', 'Movement') + def arm_init(self, context): + self.add_output('ArmVectorSocket', 'Coords') + self.add_output('ArmVectorSocket', 'Movement') diff --git a/blender/arm/logicnode/event/LN_on_application_state.py b/blender/arm/logicnode/event/LN_on_application_state.py index e624e77d..d38f8255 100644 --- a/blender/arm/logicnode/event/LN_on_application_state.py +++ b/blender/arm/logicnode/event/LN_on_application_state.py @@ -6,8 +6,7 @@ class OnApplicationStateNode(ArmLogicTreeNode): bl_label = 'On Application State' arm_version = 1 - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'On Foreground') self.add_output('ArmNodeSocketAction', 'On Background') self.add_output('ArmNodeSocketAction', 'On Shutdown') diff --git a/blender/arm/logicnode/event/LN_on_event.py b/blender/arm/logicnode/event/LN_on_event.py index 0d3df334..4cb7c28b 100644 --- a/blender/arm/logicnode/event/LN_on_event.py +++ b/blender/arm/logicnode/event/LN_on_event.py @@ -10,10 +10,9 @@ class OnEventNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'custom' - property0: StringProperty(name='', default='') + property0: HaxeStringProperty('property0', name='', default='') - def init(self, context): - super(OnEventNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/event/LN_on_init.py b/blender/arm/logicnode/event/LN_on_init.py index 3c75947c..b7246258 100644 --- a/blender/arm/logicnode/event/LN_on_init.py +++ b/blender/arm/logicnode/event/LN_on_init.py @@ -6,6 +6,5 @@ class OnInitNode(ArmLogicTreeNode): bl_label = 'On Init' arm_version = 1 - def init(self, context): - super(OnInitNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/event/LN_on_timer.py b/blender/arm/logicnode/event/LN_on_timer.py index 209c0bc8..f5c98e3e 100644 --- a/blender/arm/logicnode/event/LN_on_timer.py +++ b/blender/arm/logicnode/event/LN_on_timer.py @@ -9,9 +9,8 @@ class OnTimerNode(ArmLogicTreeNode): bl_label = 'On Timer' arm_version = 1 - def init(self, context): - super(OnTimerNode, self).init(context) - self.add_input('NodeSocketFloat', 'Duration') - self.add_input('NodeSocketBool', 'Repeat') + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Duration') + self.add_input('ArmBoolSocket', 'Repeat') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/event/LN_on_update.py b/blender/arm/logicnode/event/LN_on_update.py index df185213..c73ac019 100644 --- a/blender/arm/logicnode/event/LN_on_update.py +++ b/blender/arm/logicnode/event/LN_on_update.py @@ -10,14 +10,14 @@ class OnUpdateNode(ArmLogicTreeNode): bl_idname = 'LNOnUpdateNode' bl_label = 'On Update' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Update', 'Update', 'Update'), ('Late Update', 'Late Update', 'Late Update'), ('Physics Pre-Update', 'Physics Pre-Update', 'Physics Pre-Update')], name='On', default='Update') - def init(self, context): - super(OnUpdateNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/event/LN_send_event_to_object.py b/blender/arm/logicnode/event/LN_send_event_to_object.py index e9ebce12..8470a704 100644 --- a/blender/arm/logicnode/event/LN_send_event_to_object.py +++ b/blender/arm/logicnode/event/LN_send_event_to_object.py @@ -13,10 +13,9 @@ class SendEventNode(ArmLogicTreeNode): arm_section = 'custom' arm_version = 1 - def init(self, context): - super(SendEventNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Event') + self.add_input('ArmStringSocket', 'Event') self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/event/LN_send_global_event.py b/blender/arm/logicnode/event/LN_send_global_event.py index a85b0b6d..aba0ac0e 100644 --- a/blender/arm/logicnode/event/LN_send_global_event.py +++ b/blender/arm/logicnode/event/LN_send_global_event.py @@ -12,9 +12,8 @@ class SendGlobalEventNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'custom' - def init(self, context): - super(SendGlobalEventNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Event') + self.add_input('ArmStringSocket', 'Event') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/input/LN_gamepad.py b/blender/arm/logicnode/input/LN_gamepad.py index b27e705b..83e31ac1 100644 --- a/blender/arm/logicnode/input/LN_gamepad.py +++ b/blender/arm/logicnode/input/LN_gamepad.py @@ -15,7 +15,8 @@ class GamepadNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'gamepad' - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The gamepad button starts to be pressed'), ('down', 'Down', 'The gamepad button is pressed'), ('released', 'Released', 'The gamepad button stops being pressed')], @@ -23,7 +24,8 @@ class GamepadNode(ArmLogicTreeNode): # ('Moved Right', 'Moved Right', 'Moved Right'),], name='', default='down') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('cross', 'cross / a', 'cross / a'), ('circle', 'circle / b', 'circle / b'), ('square', 'square / x', 'square / x'), @@ -44,12 +46,11 @@ class GamepadNode(ArmLogicTreeNode): ('touchpad', 'touchpad', 'touchpad'),], name='', default='cross') - def init(self, context): - super(GamepadNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') - self.add_input('NodeSocketInt', 'Gamepad') + self.add_input('ArmIntSocket', 'Gamepad') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_gamepad_coords.py b/blender/arm/logicnode/input/LN_gamepad_coords.py index 959db1e8..2648479a 100644 --- a/blender/arm/logicnode/input/LN_gamepad_coords.py +++ b/blender/arm/logicnode/input/LN_gamepad_coords.py @@ -11,13 +11,12 @@ class GamepadCoordsNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'gamepad' - def init(self, context): - super(GamepadCoordsNode, self).init(context) - self.add_output('NodeSocketVector', 'Left Stick') - self.add_output('NodeSocketVector', 'Right Stick') - self.add_output('NodeSocketVector', 'Left Movement') - self.add_output('NodeSocketVector', 'Right Movement') - self.add_output('NodeSocketFloat', 'Left Trigger') - self.add_output('NodeSocketFloat', 'Right Trigger') + def arm_init(self, context): + self.add_output('ArmVectorSocket', 'Left Stick') + self.add_output('ArmVectorSocket', 'Right Stick') + self.add_output('ArmVectorSocket', 'Left Movement') + self.add_output('ArmVectorSocket', 'Right Movement') + self.add_output('ArmFloatSocket', 'Left Trigger') + self.add_output('ArmFloatSocket', 'Right Trigger') - self.add_input('NodeSocketInt', 'Gamepad') + self.add_input('ArmIntSocket', 'Gamepad') diff --git a/blender/arm/logicnode/input/LN_get_cursor_location.py b/blender/arm/logicnode/input/LN_get_cursor_location.py index bc740be7..6e6eedac 100644 --- a/blender/arm/logicnode/input/LN_get_cursor_location.py +++ b/blender/arm/logicnode/input/LN_get_cursor_location.py @@ -7,9 +7,8 @@ class GetCursorLocationNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - def init(self, context): - super(GetCursorLocationNode, self).init(context) - self.add_output('NodeSocketInt', 'X') - self.add_output('NodeSocketInt', 'Y') - self.add_output('NodeSocketInt', 'Inverted X') - self.add_output('NodeSocketInt', 'Inverted Y') + def arm_init(self, context): + self.add_output('ArmIntSocket', 'X') + self.add_output('ArmIntSocket', 'Y') + self.add_output('ArmIntSocket', 'Inverted X') + self.add_output('ArmIntSocket', 'Inverted Y') diff --git a/blender/arm/logicnode/input/LN_get_cursor_state.py b/blender/arm/logicnode/input/LN_get_cursor_state.py index 221bf550..0d1c7c8f 100644 --- a/blender/arm/logicnode/input/LN_get_cursor_state.py +++ b/blender/arm/logicnode/input/LN_get_cursor_state.py @@ -16,8 +16,7 @@ class GetCursorStateNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - def init(self, context): - super(GetCursorStateNode, self).init(context) - self.outputs.new('NodeSocketBool', 'Is Hidden Locked') - self.outputs.new('NodeSocketBool', 'Is Hidden') - self.outputs.new('NodeSocketBool', 'Is Locked') + def arm_init(self, context): + self.outputs.new('ArmBoolSocket', 'Is Hidden Locked') + self.outputs.new('ArmBoolSocket', 'Is Hidden') + self.outputs.new('ArmBoolSocket', 'Is Locked') diff --git a/blender/arm/logicnode/input/LN_get_gamepad_started.py b/blender/arm/logicnode/input/LN_get_gamepad_started.py new file mode 100644 index 00000000..08fd5fd0 --- /dev/null +++ b/blender/arm/logicnode/input/LN_get_gamepad_started.py @@ -0,0 +1,14 @@ +from arm.logicnode.arm_nodes import * + +class GetGamepadStartedNode(ArmLogicTreeNode): + """.""" + bl_idname = 'LNGetGamepadStartedNode' + bl_label = 'Get Gamepad Started' + arm_version = 1 + + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmIntSocket', 'Index') + + self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('ArmStringSocket', 'Button') diff --git a/blender/arm/logicnode/input/LN_get_input_map_key.py b/blender/arm/logicnode/input/LN_get_input_map_key.py new file mode 100644 index 00000000..5368a9d3 --- /dev/null +++ b/blender/arm/logicnode/input/LN_get_input_map_key.py @@ -0,0 +1,14 @@ +from arm.logicnode.arm_nodes import * + +class GetInputMapKeyNode(ArmLogicTreeNode): + """Get key data if it exists in the input map.""" + bl_idname = 'LNGetInputMapKeyNode' + bl_label = 'Get Input Map Key' + arm_version = 1 + + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Input Map') + self.add_input('ArmStringSocket', 'Key') + + self.add_output('ArmFloatSocket', 'Scale', default_value = 1.0) + self.add_output('ArmFloatSocket', 'Deadzone') diff --git a/blender/arm/logicnode/input/LN_get_keyboard_started.py b/blender/arm/logicnode/input/LN_get_keyboard_started.py new file mode 100644 index 00000000..1810c6e0 --- /dev/null +++ b/blender/arm/logicnode/input/LN_get_keyboard_started.py @@ -0,0 +1,13 @@ +from arm.logicnode.arm_nodes import * + +class GetKeyboardStartedNode(ArmLogicTreeNode): + """.""" + bl_idname = 'LNGetKeyboardStartedNode' + bl_label = 'Get Keyboard Started' + arm_version = 1 + + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') + + self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('ArmStringSocket', 'Key') diff --git a/blender/arm/logicnode/input/LN_get_mouse_movement.py b/blender/arm/logicnode/input/LN_get_mouse_movement.py index ee217b5c..fbef5311 100644 --- a/blender/arm/logicnode/input/LN_get_mouse_movement.py +++ b/blender/arm/logicnode/input/LN_get_mouse_movement.py @@ -9,16 +9,15 @@ class GetMouseMovementNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - def init(self, context): - super(GetMouseMovementNode, self).init(context) + def arm_init(self, context): - self.add_input('NodeSocketFloat', 'X Multiplier', default_value=-1.0) - self.add_input('NodeSocketFloat', 'Y Multiplier', default_value=-1.0) - self.add_input('NodeSocketFloat', 'Wheel Delta Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'X Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'Y Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'Wheel Delta Multiplier', default_value=-1.0) - self.add_output('NodeSocketFloat', 'X') - self.add_output('NodeSocketFloat', 'Y') - self.add_output('NodeSocketFloat', 'Multiplied X') - self.add_output('NodeSocketFloat', 'Multiplied Y') - self.add_output('NodeSocketInt', 'Wheel Delta') - self.add_output('NodeSocketFloat', 'Multiplied Wheel Delta') + self.add_output('ArmFloatSocket', 'X') + self.add_output('ArmFloatSocket', 'Y') + self.add_output('ArmFloatSocket', 'Multiplied X') + self.add_output('ArmFloatSocket', 'Multiplied Y') + self.add_output('ArmIntSocket', 'Wheel Delta') + self.add_output('ArmFloatSocket', 'Multiplied Wheel Delta') diff --git a/blender/arm/logicnode/input/LN_get_mouse_started.py b/blender/arm/logicnode/input/LN_get_mouse_started.py new file mode 100644 index 00000000..9a6fce1f --- /dev/null +++ b/blender/arm/logicnode/input/LN_get_mouse_started.py @@ -0,0 +1,13 @@ +from arm.logicnode.arm_nodes import * + +class GetMouseStartedNode(ArmLogicTreeNode): + """.""" + bl_idname = 'LNGetMouseStartedNode' + bl_label = 'Get Mouse Started' + arm_version = 1 + + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') + + self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('ArmStringSocket', 'Button') diff --git a/blender/arm/logicnode/input/LN_get_touch_location.py b/blender/arm/logicnode/input/LN_get_touch_location.py index 88d6f6df..3aea57b6 100644 --- a/blender/arm/logicnode/input/LN_get_touch_location.py +++ b/blender/arm/logicnode/input/LN_get_touch_location.py @@ -7,9 +7,8 @@ class GetTouchLocationNode(ArmLogicTreeNode): arm_section = 'surface' arm_version = 1 - def init(self, context): - super(GetTouchLocationNode, self).init(context) - self.add_output('NodeSocketInt', 'X') - self.add_output('NodeSocketInt', 'Y') - self.add_output('NodeSocketInt', 'Inverted X') - self.add_output('NodeSocketInt', 'Inverted Y') + def arm_init(self, context): + self.add_output('ArmIntSocket', 'X') + self.add_output('ArmIntSocket', 'Y') + self.add_output('ArmIntSocket', 'Inverted X') + self.add_output('ArmIntSocket', 'Inverted Y') diff --git a/blender/arm/logicnode/input/LN_get_touch_movement.py b/blender/arm/logicnode/input/LN_get_touch_movement.py index 80f918f0..8c37c844 100644 --- a/blender/arm/logicnode/input/LN_get_touch_movement.py +++ b/blender/arm/logicnode/input/LN_get_touch_movement.py @@ -7,12 +7,11 @@ class GetTouchMovementNode(ArmLogicTreeNode): arm_section = 'surface' arm_version = 1 - def init(self, context): - super(GetTouchMovementNode, self).init(context) - self.add_input('NodeSocketFloat', 'X Multiplier', default_value=-1.0) - self.add_input('NodeSocketFloat', 'Y Multiplier', default_value=-1.0) + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'X Multiplier', default_value=-1.0) + self.add_input('ArmFloatSocket', 'Y Multiplier', default_value=-1.0) - self.add_output('NodeSocketFloat', 'X') - self.add_output('NodeSocketFloat', 'Y') - self.add_output('NodeSocketFloat', 'Multiplied X') - self.add_output('NodeSocketFloat', 'Multiplied Y') + self.add_output('ArmFloatSocket', 'X') + self.add_output('ArmFloatSocket', 'Y') + self.add_output('ArmFloatSocket', 'Multiplied X') + self.add_output('ArmFloatSocket', 'Multiplied Y') diff --git a/blender/arm/logicnode/input/LN_keyboard.py b/blender/arm/logicnode/input/LN_keyboard.py index 8e04c8df..7a78e651 100644 --- a/blender/arm/logicnode/input/LN_keyboard.py +++ b/blender/arm/logicnode/input/LN_keyboard.py @@ -7,13 +7,15 @@ class KeyboardNode(ArmLogicTreeNode): arm_section = 'keyboard' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The keyboard button starts to be pressed'), ('down', 'Down', 'The keyboard button is pressed'), ('released', 'Released', 'The keyboard button stops being pressed')], name='', default='down') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('a', 'a', 'a'), ('b', 'b', 'b'), ('c', 'c', 'c'), @@ -68,10 +70,9 @@ class KeyboardNode(ArmLogicTreeNode): ('down', 'down', 'down'),], name='', default='space') - def init(self, context): - super(KeyboardNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_mouse.py b/blender/arm/logicnode/input/LN_mouse.py index cfdb052c..03624cd7 100644 --- a/blender/arm/logicnode/input/LN_mouse.py +++ b/blender/arm/logicnode/input/LN_mouse.py @@ -7,22 +7,23 @@ class MouseNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The mouse button startes to be pressed'), ('down', 'Down', 'The mouse button is pressed'), ('released', 'Released', 'The mouse button stops being pressed'), ('moved', 'Moved', 'Moved')], name='', default='down') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('left', 'Left', 'Left mouse button'), ('middle', 'Middle', 'Middle mouse button'), ('right', 'Right', 'Right mouse button')], name='', default='left') - def init(self, context): - super(MouseNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_on_input_map.py b/blender/arm/logicnode/input/LN_on_input_map.py new file mode 100644 index 00000000..c851eefe --- /dev/null +++ b/blender/arm/logicnode/input/LN_on_input_map.py @@ -0,0 +1,15 @@ +from arm.logicnode.arm_nodes import * + +class OnInputMapNode(ArmLogicTreeNode): + """Send a signal if any input map key is started or released.""" + bl_idname = 'LNOnInputMapNode' + bl_label = 'On Input Map' + arm_version = 1 + + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Input Map') + + self.add_output('ArmNodeSocketAction', 'Started') + self.add_output('ArmNodeSocketAction', 'Released') + self.add_output('ArmFloatSocket', 'Value') + self.add_output('ArmStringSocket', 'Key Pressed') diff --git a/blender/arm/logicnode/input/LN_on_swipe.py b/blender/arm/logicnode/input/LN_on_swipe.py index cfd9b17c..8bd6226e 100644 --- a/blender/arm/logicnode/input/LN_on_swipe.py +++ b/blender/arm/logicnode/input/LN_on_swipe.py @@ -5,8 +5,9 @@ class NodeAddOutputButton(bpy.types.Operator): """Add 4 States""" bl_idname = 'arm.add_output_4_parameters' bl_label = 'Add 4 States' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') - socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') + socket_type: StringProperty(name='Socket Type', default='ArmDynamicSocket') name_format: StringProperty(name='Name Format', default='Output {0}') index_name_offset: IntProperty(name='Index Name Offset', default=0) @@ -20,10 +21,10 @@ class NodeAddOutputButton(bpy.types.Operator): global array_nodes node = array_nodes[self.node_index] outs = node.outputs - outs.new('NodeSocketBool', self.get_name_state(len(outs), node.min_outputs)) - outs.new('NodeSocketBool', self.get_name_state(len(outs), node.min_outputs)) - outs.new('NodeSocketBool', self.get_name_state(len(outs), node.min_outputs)) - outs.new('NodeSocketBool', self.get_name_state(len(outs), node.min_outputs)) + outs.new('ArmBoolSocket', self.get_name_state(len(outs), node.min_outputs)) + outs.new('ArmBoolSocket', self.get_name_state(len(outs), node.min_outputs)) + outs.new('ArmBoolSocket', self.get_name_state(len(outs), node.min_outputs)) + outs.new('ArmBoolSocket', self.get_name_state(len(outs), node.min_outputs)) return{'FINISHED'} # Custom class for remove output parameters (in 4 directions) @@ -31,6 +32,7 @@ class NodeRemoveOutputButton(bpy.types.Operator): """Remove 4 last states""" bl_idname = 'arm.remove_output_4_parameters' bl_label = 'Remove 4 States' + bl_options = {'UNDO', 'INTERNAL'} node_index: StringProperty(name='Node Index', default='') def execute(self, context): @@ -57,16 +59,15 @@ class OnSwipeNode(ArmLogicTreeNode): super(OnSwipeNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(OnSwipeNode, self).init(context) - self.inputs.new('NodeSocketFloat', 'Time') + def arm_init(self, context): + self.inputs.new('ArmFloatSocket', 'Time') self.inputs[-1].default_value = 0.15 - self.inputs.new('NodeSocketInt', 'Min Length (px)') + self.inputs.new('ArmIntSocket', 'Min Length (px)') self.inputs[-1].default_value = 100 self.outputs.new('ArmNodeSocketAction', 'Out') - self.outputs.new('NodeSocketVector', 'Direction') - self.outputs.new('NodeSocketInt', 'Length (px)') - self.outputs.new('NodeSocketInt', 'Angle (0-360)') + self.outputs.new('ArmVectorSocket', 'Direction') + self.outputs.new('ArmIntSocket', 'Length (px)') + self.outputs.new('ArmIntSocket', 'Angle (0-360)') # Draw node buttons def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/input/LN_on_tap_screen.py b/blender/arm/logicnode/input/LN_on_tap_screen.py index 9d27e05c..61c4a0fa 100644 --- a/blender/arm/logicnode/input/LN_on_tap_screen.py +++ b/blender/arm/logicnode/input/LN_on_tap_screen.py @@ -18,17 +18,16 @@ class OnTapScreen(ArmLogicTreeNode): arm_section = 'Input' arm_version = 1 - def init(self, context): - super(OnTapScreen, self).init(context) - self.add_input('NodeSocketFloat', 'Duration') + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Duration') self.inputs[-1].default_value = 0.3 - self.add_input('NodeSocketFloat', 'Interval') + self.add_input('ArmFloatSocket', 'Interval') self.inputs[-1].default_value = 0.0 - self.add_input('NodeSocketInt', 'Repeat') + self.add_input('ArmIntSocket', 'Repeat') self.inputs[-1].default_value = 2 self.add_output('ArmNodeSocketAction', 'Done') self.add_output('ArmNodeSocketAction', 'Fail') self.add_output('ArmNodeSocketAction', 'Tap') - self.add_output('NodeSocketInt', 'Tap Number') - self.add_output('NodeSocketVector', 'Coords') + self.add_output('ArmIntSocket', 'Tap Number') + self.add_output('ArmVectorSocket', 'Coords') diff --git a/blender/arm/logicnode/input/LN_remove_input_map_key.py b/blender/arm/logicnode/input/LN_remove_input_map_key.py new file mode 100644 index 00000000..8da8562b --- /dev/null +++ b/blender/arm/logicnode/input/LN_remove_input_map_key.py @@ -0,0 +1,14 @@ +from arm.logicnode.arm_nodes import * + +class RemoveInputMapKeyNode(ArmLogicTreeNode): + """Remove input map key.""" + bl_idname = 'LNRemoveInputMapKeyNode' + bl_label = 'Remove Input Map Key' + arm_version = 1 + + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmStringSocket', 'Input Map') + self.add_input('ArmStringSocket', 'Key') + + self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/input/LN_sensor_coords.py b/blender/arm/logicnode/input/LN_sensor_coords.py index 9687630e..a92f38f0 100644 --- a/blender/arm/logicnode/input/LN_sensor_coords.py +++ b/blender/arm/logicnode/input/LN_sensor_coords.py @@ -7,6 +7,5 @@ class SensorCoordsNode(ArmLogicTreeNode): arm_section = 'sensor' arm_version = 1 - def init(self, context): - super(SensorCoordsNode, self).init(context) - self.add_output('NodeSocketVector', 'Coords') + def arm_init(self, context): + self.add_output('ArmVectorSocket', 'Coords') diff --git a/blender/arm/logicnode/input/LN_set_cursor_state.py b/blender/arm/logicnode/input/LN_set_cursor_state.py index ac83802c..0c8c2d6e 100644 --- a/blender/arm/logicnode/input/LN_set_cursor_state.py +++ b/blender/arm/logicnode/input/LN_set_cursor_state.py @@ -14,17 +14,17 @@ class SetCursorStateNode(ArmLogicTreeNode): arm_section = 'mouse' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('hide locked', 'Hide Locked', 'The mouse cursor is hidden and locked'), ('hide', 'Hide', 'The mouse cursor is hidden'), ('lock', 'Lock', 'The mouse cursor is locked'), ], name='', default='hide locked') - def init(self, context): - super(SetCursorStateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'State') + self.add_input('ArmBoolSocket', 'State') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/input/LN_set_input_map_key.py b/blender/arm/logicnode/input/LN_set_input_map_key.py new file mode 100644 index 00000000..75997d7d --- /dev/null +++ b/blender/arm/logicnode/input/LN_set_input_map_key.py @@ -0,0 +1,27 @@ +from arm.logicnode.arm_nodes import * + +class SetInputMapKeyNode(ArmLogicTreeNode): + """Set input map key.""" + bl_idname = 'LNSetInputMapKeyNode' + bl_label = 'Set Input Map Key' + arm_version = 1 + + property0: HaxeEnumProperty( + 'property0', + items = [('keyboard', 'Keyboard', 'Keyboard input'), + ('mouse', 'Mouse', 'Mouse input'), + ('gamepad', 'Gamepad', 'Gamepad input')], + name='', default='keyboard') + + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmStringSocket', 'Input Map') + self.add_input('ArmStringSocket', 'Key') + self.add_input('ArmFloatSocket', 'Scale', default_value=1.0) + self.add_input('ArmFloatSocket', 'Deadzone') + self.add_input('ArmIntSocket', 'Index') + + self.add_output('ArmNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_touch.py b/blender/arm/logicnode/input/LN_touch.py index a5774ea5..f6ff0593 100644 --- a/blender/arm/logicnode/input/LN_touch.py +++ b/blender/arm/logicnode/input/LN_touch.py @@ -7,17 +7,17 @@ class SurfaceNode(ArmLogicTreeNode): arm_section = 'surface' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The screen surface starts to be touched'), ('down', 'Down', 'The screen surface is touched'), ('released', 'Released', 'The screen surface stops being touched'), ('moved', 'Moved', 'Moved')], name='', default='down') - def init(self, context): - super(SurfaceNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/input/LN_virtual_button.py b/blender/arm/logicnode/input/LN_virtual_button.py index d2bce52f..3850a26e 100644 --- a/blender/arm/logicnode/input/LN_virtual_button.py +++ b/blender/arm/logicnode/input/LN_virtual_button.py @@ -7,17 +7,17 @@ class VirtualButtonNode(ArmLogicTreeNode): arm_section = 'virtual' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('started', 'Started', 'The virtual button starts to be pressed'), ('down', 'Down', 'The virtual button is pressed'), ('released', 'Released', 'The virtual button stops being pressed')], name='', default='down') - property1: StringProperty(name='', default='button') + property1: HaxeStringProperty('property1', name='', default='button') - def init(self, context): - super(VirtualButtonNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketBool', 'State') + self.add_output('ArmBoolSocket', 'State') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/light/LN_set_light_color.py b/blender/arm/logicnode/light/LN_set_light_color.py index a62d8366..5af7cde7 100644 --- a/blender/arm/logicnode/light/LN_set_light_color.py +++ b/blender/arm/logicnode/light/LN_set_light_color.py @@ -6,10 +6,9 @@ class SetLightColorNode(ArmLogicTreeNode): bl_label = 'Set Light Color' arm_version = 1 - def init(self, context): - super(SetLightColorNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Light') - self.add_input('NodeSocketColor', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) + self.add_input('ArmColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/light/LN_set_light_strength.py b/blender/arm/logicnode/light/LN_set_light_strength.py index cd04b753..bc9fd67e 100644 --- a/blender/arm/logicnode/light/LN_set_light_strength.py +++ b/blender/arm/logicnode/light/LN_set_light_strength.py @@ -6,10 +6,9 @@ class SetLightStrengthNode(ArmLogicTreeNode): bl_label = 'Set Light Strength' arm_version = 1 - def init(self, context): - super(SetLightStrengthNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Light') - self.add_input('NodeSocketFloat', 'Strength', default_value=250) + self.add_input('ArmFloatSocket', 'Strength', default_value=250) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_alternate_output.py b/blender/arm/logicnode/logic/LN_alternate_output.py index 839c75bd..8f0a81e6 100644 --- a/blender/arm/logicnode/logic/LN_alternate_output.py +++ b/blender/arm/logicnode/logic/LN_alternate_output.py @@ -8,8 +8,7 @@ class AlternateNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(AlternateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', '0') diff --git a/blender/arm/logicnode/logic/LN_branch.py b/blender/arm/logicnode/logic/LN_branch.py index 45037e1d..5693f882 100644 --- a/blender/arm/logicnode/logic/LN_branch.py +++ b/blender/arm/logicnode/logic/LN_branch.py @@ -8,10 +8,9 @@ class BranchNode(ArmLogicTreeNode): bl_label = 'Branch' arm_version = 1 - def init(self, context): - super(BranchNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Bool') + self.add_input('ArmBoolSocket', 'Bool') self.add_output('ArmNodeSocketAction', 'True') self.add_output('ArmNodeSocketAction', 'False') diff --git a/blender/arm/logicnode/logic/LN_call_function.py b/blender/arm/logicnode/logic/LN_call_function.py index 0dce0371..819407ad 100644 --- a/blender/arm/logicnode/logic/LN_call_function.py +++ b/blender/arm/logicnode/logic/LN_call_function.py @@ -13,20 +13,19 @@ class CallFunctionNode(ArmLogicTreeNode): super(CallFunctionNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(CallFunctionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait/Any') - self.add_input('NodeSocketString', 'Function') + self.add_input('ArmDynamicSocket', 'Trait/Any') + self.add_input('ArmStringSocket', 'Function') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='Add Arg', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op.name_format = "Arg {0}" op.index_name_offset = -2 op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/logic/LN_function.py b/blender/arm/logicnode/logic/LN_function.py index ee2ecc7d..2d3fe0dd 100644 --- a/blender/arm/logicnode/logic/LN_function.py +++ b/blender/arm/logicnode/logic/LN_function.py @@ -15,8 +15,7 @@ class FunctionNode(ArmLogicTreeNode): super(FunctionNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(FunctionNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') function_name: StringProperty(name="Name") @@ -27,7 +26,7 @@ class FunctionNode(ArmLogicTreeNode): row = layout.row(align=True) op = row.operator('arm.node_add_output', text='Add Arg', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op.name_format = "Arg {0}" op.index_name_offset = 0 op2 = row.operator('arm.node_remove_output', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/logic/LN_function_output.py b/blender/arm/logicnode/logic/LN_function_output.py index 6ac65e7c..073b364f 100644 --- a/blender/arm/logicnode/logic/LN_function_output.py +++ b/blender/arm/logicnode/logic/LN_function_output.py @@ -10,10 +10,9 @@ class FunctionOutputNode(ArmLogicTreeNode): arm_section = 'function' arm_version = 1 - def init(self, context): - super(FunctionOutputNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') function_name: StringProperty(name="Name") diff --git a/blender/arm/logicnode/logic/LN_gate.py b/blender/arm/logicnode/logic/LN_gate.py index 5fbc6818..e67b9f23 100644 --- a/blender/arm/logicnode/logic/LN_gate.py +++ b/blender/arm/logicnode/logic/LN_gate.py @@ -20,7 +20,8 @@ class GateNode(ArmLogicTreeNode): arm_version = 1 min_inputs = 3 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Equal', 'Equal', 'Equal'), ('Almost Equal', 'Almost Equal', 'Almost Equal'), ('Greater', 'Greater', 'Greater'), @@ -31,17 +32,16 @@ class GateNode(ArmLogicTreeNode): ('And', 'And', 'And')], name='', default='Equal', update=remove_extra_inputs) - property1: FloatProperty(name='Tolerance', description='Precision for float compare', default=0.0001) + property1: HaxeFloatProperty('property1', name='Tolerance', description='Precision for float compare', default=0.0001) def __init__(self): super(GateNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(GateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Input 1') - self.add_input('NodeSocketShader', 'Input 2') + self.add_input('ArmDynamicSocket', 'Input 1') + self.add_input('ArmDynamicSocket', 'Input 2') self.add_output('ArmNodeSocketAction', 'True') self.add_output('ArmNodeSocketAction', 'False') @@ -56,6 +56,6 @@ class GateNode(ArmLogicTreeNode): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/logic/LN_invert_boolean.py b/blender/arm/logicnode/logic/LN_invert_boolean.py index 924ab58e..c981c7cc 100644 --- a/blender/arm/logicnode/logic/LN_invert_boolean.py +++ b/blender/arm/logicnode/logic/LN_invert_boolean.py @@ -6,8 +6,7 @@ class NotNode(ArmLogicTreeNode): bl_label = 'Invert Boolean' arm_version = 1 - def init(self, context): - super(NotNode, self).init(context) - self.add_input('NodeSocketBool', 'Bool In') + def arm_init(self, context): + self.add_input('ArmBoolSocket', 'Bool In') - self.add_output('NodeSocketBool', 'Bool Out') + self.add_output('ArmBoolSocket', 'Bool Out') diff --git a/blender/arm/logicnode/logic/LN_invert_output.py b/blender/arm/logicnode/logic/LN_invert_output.py index 04c7d596..7a30dbb8 100644 --- a/blender/arm/logicnode/logic/LN_invert_output.py +++ b/blender/arm/logicnode/logic/LN_invert_output.py @@ -8,8 +8,7 @@ class InverseNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(InverseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_is_false.py b/blender/arm/logicnode/logic/LN_is_false.py index 8eb86772..304c025d 100644 --- a/blender/arm/logicnode/logic/LN_is_false.py +++ b/blender/arm/logicnode/logic/LN_is_false.py @@ -10,9 +10,8 @@ class IsFalseNode(ArmLogicTreeNode): bl_label = 'Is False' arm_version = 1 - def init(self, context): - super(IsFalseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Bool') + self.add_input('ArmBoolSocket', 'Bool') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_is_not_null.py b/blender/arm/logicnode/logic/LN_is_not_null.py index 4e7e0cb4..e18ee5a4 100644 --- a/blender/arm/logicnode/logic/LN_is_not_null.py +++ b/blender/arm/logicnode/logic/LN_is_not_null.py @@ -9,9 +9,8 @@ class IsNotNoneNode(ArmLogicTreeNode): bl_label = 'Is Not Null' arm_version = 1 - def init(self, context): - super(IsNotNoneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_is_null.py b/blender/arm/logicnode/logic/LN_is_null.py index b8f77a80..da2e120c 100644 --- a/blender/arm/logicnode/logic/LN_is_null.py +++ b/blender/arm/logicnode/logic/LN_is_null.py @@ -10,9 +10,8 @@ class IsNoneNode(ArmLogicTreeNode): bl_label = 'Is Null' arm_version = 1 - def init(self, context): - super(IsNoneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_is_true.py b/blender/arm/logicnode/logic/LN_is_true.py index 21f255d0..17f0b47b 100644 --- a/blender/arm/logicnode/logic/LN_is_true.py +++ b/blender/arm/logicnode/logic/LN_is_true.py @@ -9,9 +9,8 @@ class IsTrueNode(ArmLogicTreeNode): bl_label = 'Is True' arm_version = 1 - def init(self, context): - super(IsTrueNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Bool') + self.add_input('ArmBoolSocket', 'Bool') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_loop.py b/blender/arm/logicnode/logic/LN_loop.py index 9d69085e..212c514d 100644 --- a/blender/arm/logicnode/logic/LN_loop.py +++ b/blender/arm/logicnode/logic/LN_loop.py @@ -20,14 +20,13 @@ class LoopNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(LoopNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketInt', 'From') - self.add_input('NodeSocketInt', 'To') + self.add_input('ArmIntSocket', 'From') + self.add_input('ArmIntSocket', 'To') self.add_output('ArmNodeSocketAction', 'Loop') - self.add_output('NodeSocketInt', 'Index') + self.add_output('ArmIntSocket', 'Index') self.add_output('ArmNodeSocketAction', 'Done') def draw_label(self) -> str: diff --git a/blender/arm/logicnode/logic/LN_loop_break.py b/blender/arm/logicnode/logic/LN_loop_break.py index 3c0d8a2c..326d15b8 100644 --- a/blender/arm/logicnode/logic/LN_loop_break.py +++ b/blender/arm/logicnode/logic/LN_loop_break.py @@ -12,6 +12,5 @@ class LoopBreakNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(LoopBreakNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') diff --git a/blender/arm/logicnode/logic/LN_merge.py b/blender/arm/logicnode/logic/LN_merge.py index 5c3b8a9f..a209c690 100644 --- a/blender/arm/logicnode/logic/LN_merge.py +++ b/blender/arm/logicnode/logic/LN_merge.py @@ -1,26 +1,59 @@ from arm.logicnode.arm_nodes import * + class MergeNode(ArmLogicTreeNode): - """Activates the output when any connected input is activated. + """Activates the output when at least one connected input is activated. + If multiple inputs are active, the behaviour is specified by the + `Execution Mode` option. + + @output Active Input Index: [*Available if Execution Mode is set to + Once Per Input*] The index of the last input that activated the output, + -1 if there was no execution yet on the current frame. + + @option Execution Mode: The node's behaviour if multiple inputs are + active on the same frame. + + - `Once Per Input`: If multiple inputs are active on one frame, activate + the output for each active input individually (simple forwarding). + + - `Once Per Frame`: If multiple inputs are active on one frame, + trigger the output only once. @option New: Add a new input socket. @option X Button: Remove the lowermost input socket.""" bl_idname = 'LNMergeNode' bl_label = 'Merge' arm_section = 'flow' - arm_version = 1 + arm_version = 2 + + def update_exec_mode(self, context): + self.outputs['Active Input Index'].hide = self.property0 == 'once_per_frame' + + property0: HaxeEnumProperty( + 'property0', + name='Execution Mode', + description='The node\'s behaviour if multiple inputs are active on the same frame', + items=[('once_per_input', 'Once Per Input', + 'If multiple inputs are active on one frame, activate the' + ' output for each active input individually (simple forwarding)'), + ('once_per_frame', 'Once Per Frame', + 'If multiple inputs are active on one frame, trigger the output only once')], + default='once_per_input', + update=update_exec_mode, + ) def __init__(self): super(MergeNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(MergeNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('ArmIntSocket', 'Active Input Index') def draw_buttons(self, context, layout): - row = layout.row(align=True) + layout.prop(self, 'property0', text='') + row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) op.socket_type = 'ArmNodeSocketAction' @@ -32,3 +65,24 @@ class MergeNode(ArmLogicTreeNode): return self.bl_label return f'{self.bl_label}: [{len(self.inputs)}]' + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + newnode = node_tree.nodes.new('LNMergeNode') + newnode.property0 = self.property0 + + # Recreate all original inputs + array_nodes[str(id(newnode))] = newnode + for idx, input in enumerate(self.inputs): + bpy.ops.arm.node_add_input('EXEC_DEFAULT', node_index=str(id(newnode)), socket_type='ArmNodeSocketAction') + + for link in input.links: + node_tree.links.new(link.from_socket, newnode.inputs[idx]) + + # Recreate outputs + for link in self.outputs[0].links: + node_tree.links.new(newnode.outputs[0], link.to_socket) + + return newnode diff --git a/blender/arm/logicnode/logic/LN_null.py b/blender/arm/logicnode/logic/LN_null.py index 610328a1..ed0d9601 100644 --- a/blender/arm/logicnode/logic/LN_null.py +++ b/blender/arm/logicnode/logic/LN_null.py @@ -6,6 +6,5 @@ class NoneNode(ArmLogicTreeNode): bl_label = 'Null' arm_version = 1 - def init(self, context): - super(NoneNode, self).init(context) - self.add_output('NodeSocketShader', 'Null') + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Null') diff --git a/blender/arm/logicnode/logic/LN_once_per_frame.py b/blender/arm/logicnode/logic/LN_once_per_frame.py new file mode 100644 index 00000000..56c82fc5 --- /dev/null +++ b/blender/arm/logicnode/logic/LN_once_per_frame.py @@ -0,0 +1,15 @@ +from arm.logicnode.arm_nodes import * + + +class OncePerFrameNode(ArmLogicTreeNode): + """Activates the output only once per frame if receives one or more inputs in that frame + If there is no input, there will be no output""" + bl_idname = 'LNOncePerFrameNode' + bl_label = 'Once Per Frame' + arm_section = 'flow' + arm_version = 1 + + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') + + self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/logic/LN_output_sequence.py b/blender/arm/logicnode/logic/LN_output_sequence.py index 3d973cdd..f6166a69 100644 --- a/blender/arm/logicnode/logic/LN_output_sequence.py +++ b/blender/arm/logicnode/logic/LN_output_sequence.py @@ -11,8 +11,7 @@ class SequenceNode(ArmLogicTreeNode): super(SequenceNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(SequenceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/logic/LN_output_to_boolean.py b/blender/arm/logicnode/logic/LN_output_to_boolean.py index f7ed282c..d03c79cf 100644 --- a/blender/arm/logicnode/logic/LN_output_to_boolean.py +++ b/blender/arm/logicnode/logic/LN_output_to_boolean.py @@ -7,8 +7,7 @@ class ToBoolNode(ArmLogicTreeNode): bl_label = 'Output to Boolean' arm_version = 1 - def init(self, context): - super(ToBoolNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_output('NodeSocketBool', 'Bool') + self.add_output('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/logic/LN_select.py b/blender/arm/logicnode/logic/LN_select.py new file mode 100644 index 00000000..55f39a83 --- /dev/null +++ b/blender/arm/logicnode/logic/LN_select.py @@ -0,0 +1,121 @@ +from bpy.types import NodeSocketInterfaceInt + +from arm.logicnode.arm_nodes import * + + +class SelectNode(ArmLogicTreeNode): + """Selects one of multiple values (of arbitrary types) based on some + input state. The exact behaviour of this node is specified by the + `Execution Mode` option (see below). + + @output Out: [*Available if Execution Mode is set to From Input*] + Activated after the node was executed. + + @output Value: The last selected value. This value is not reset + until the next execution of this node. + + @option Execution Mode: Specifies the condition that determines + what value to choose. + + - `From Index`: Select the value at the given index. If there is + no value at that index, the value plugged in to the + `Default` input is used instead (`null` if unconnected). + + - `From Input`: This mode uses input pairs of one action socket + and one value socket. Depending on which action socket is + activated, the associated value socket (the value with the + same index as the activated action input) is forwarded to + the `Value` output. + + @option New: Add a new value to the list of values. + @option X Button: Remove the value with the highest index.""" + bl_idname = 'LNSelectNode' + bl_label = 'Select' + arm_version = 1 + min_inputs = 2 + + def update_exec_mode(self, context): + self.set_mode() + + property0: HaxeEnumProperty( + 'property0', + name='Execution Mode', + description="The node's behaviour.", + items=[ + ('from_index', 'From Index', 'Choose the value from the given index'), + ('from_input', 'From Input', 'Choose the value with the same position as the active input')], + default='from_index', + update=update_exec_mode, + ) + + # The number of choices, NOT of individual inputs. This needs to be + # a property in order to be saved with each individual node + num_choices: IntProperty(default=1, min=0) + + def __init__(self): + super().__init__() + array_nodes[str(id(self))] = self + + def arm_init(self, context): + self.set_mode() + + def set_mode(self): + self.inputs.clear() + self.outputs.clear() + + if self.property0 == 'from_index': + self.add_input('ArmIntSocket', 'Index') + self.add_input('ArmDynamicSocket', 'Default') + self.num_choices = 0 + + # from_input + else: + # We could also start with index 1 here, but we need to use + # 0 for the "from_index" mode and it makes the code simpler + # if we stick to the same convention for both exec modes + self.add_input('ArmNodeSocketAction', 'Input 0') + self.add_input('ArmDynamicSocket', 'Value 0') + self.num_choices = 1 + + self.add_output('ArmNodeSocketAction', 'Out') + + self.add_output('ArmDynamicSocket', 'Value') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0', text='') + + row = layout.row(align=True) + + op = row.operator('arm.node_call_func', text='New', icon='PLUS', emboss=True) + op.node_index = str(id(self)) + op.callback_name = 'add_input_func' + + op = row.operator('arm.node_call_func', text='', icon='X', emboss=True) + op.node_index = str(id(self)) + op.callback_name = 'remove_input_func' + + def add_input_func(self): + if self.property0 == 'from_input': + self.add_input('ArmNodeSocketAction', f'Input {self.num_choices}') + + # Move new action input up to the end of all other action inputs + self.inputs.move(from_index=len(self.inputs) - 1, to_index=self.num_choices) + + self.add_input('ArmDynamicSocket', f'Value {self.num_choices}') + + self.num_choices += 1 + + def remove_input_func(self): + if self.property0 == 'from_input': + if len(self.inputs) > self.min_inputs: + self.inputs.remove(self.inputs[self.num_choices - 1]) + + if len(self.inputs) > self.min_inputs: + 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}]' diff --git a/blender/arm/logicnode/logic/LN_switch_output.py b/blender/arm/logicnode/logic/LN_switch_output.py index 95a9ddba..688245e2 100644 --- a/blender/arm/logicnode/logic/LN_switch_output.py +++ b/blender/arm/logicnode/logic/LN_switch_output.py @@ -15,10 +15,9 @@ class SwitchNode(ArmLogicTreeNode): super(SwitchNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(SwitchNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Default') @@ -26,7 +25,7 @@ class SwitchNode(ArmLogicTreeNode): row = layout.row(align=True) op = row.operator('arm.node_add_input_output', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.in_socket_type = 'NodeSocketShader' + op.in_socket_type = 'ArmDynamicSocket' op.out_socket_type = 'ArmNodeSocketAction' op.in_name_format = 'Case {0}' op.out_name_format = 'Case {0}' diff --git a/blender/arm/logicnode/logic/LN_value_changed.py b/blender/arm/logicnode/logic/LN_value_changed.py index 42b9ae0a..82af3d5c 100644 --- a/blender/arm/logicnode/logic/LN_value_changed.py +++ b/blender/arm/logicnode/logic/LN_value_changed.py @@ -6,10 +6,9 @@ class ValueChangedNode(ArmLogicTreeNode): bl_label = 'Value Changed' arm_version = 1 - def init(self, context): - super(ValueChangedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Changed') self.add_output('ArmNodeSocketAction', 'Unchanged') diff --git a/blender/arm/logicnode/logic/LN_while_true.py b/blender/arm/logicnode/logic/LN_while_true.py index ba99b9ad..b2a7b670 100644 --- a/blender/arm/logicnode/logic/LN_while_true.py +++ b/blender/arm/logicnode/logic/LN_while_true.py @@ -15,10 +15,9 @@ class WhileNode(ArmLogicTreeNode): arm_section = 'flow' arm_version = 1 - def init(self, context): - super(WhileNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Condition') + self.add_input('ArmBoolSocket', 'Condition') self.add_output('ArmNodeSocketAction', 'Loop') self.add_output('ArmNodeSocketAction', 'Done') diff --git a/blender/arm/logicnode/material/LN_get_object_material.py b/blender/arm/logicnode/material/LN_get_object_material.py index 864c48ee..8e64630d 100644 --- a/blender/arm/logicnode/material/LN_get_object_material.py +++ b/blender/arm/logicnode/material/LN_get_object_material.py @@ -6,9 +6,8 @@ class GetMaterialNode(ArmLogicTreeNode): bl_label = 'Get Object Material' arm_version = 1 - def init(self, context): - super(GetMaterialNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketInt', 'Slot') + self.add_input('ArmIntSocket', 'Slot') - self.add_output('NodeSocketShader', 'Material') + self.add_output('ArmDynamicSocket', 'Material') diff --git a/blender/arm/logicnode/material/LN_material.py b/blender/arm/logicnode/material/LN_material.py index 03e923b6..082f2a80 100644 --- a/blender/arm/logicnode/material/LN_material.py +++ b/blender/arm/logicnode/material/LN_material.py @@ -18,11 +18,10 @@ class MaterialNode(ArmLogicTreeNode): return self.property0.name return arm.utils.asset_name(bpy.data.materials[self.property0.name]) - property0: PointerProperty(name='', type=bpy.types.Material) + property0: HaxePointerProperty('property0', name='', type=bpy.types.Material) - def init(self, context): - super(MaterialNode, self).init(context) - self.add_output('NodeSocketShader', 'Material', is_var=True) + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Material', is_var=True) def draw_buttons(self, context, layout): layout.prop_search(self, 'property0', bpy.data, 'materials', icon='NONE', text='') diff --git a/blender/arm/logicnode/material/LN_set_material_image_param.py b/blender/arm/logicnode/material/LN_set_material_image_param.py index 777794c2..686562c1 100644 --- a/blender/arm/logicnode/material/LN_set_material_image_param.py +++ b/blender/arm/logicnode/material/LN_set_material_image_param.py @@ -1,17 +1,42 @@ from arm.logicnode.arm_nodes import * class SetMaterialImageParamNode(ArmLogicTreeNode): - """TO DO.""" + """Set an image value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @input Image: Name of the image. + """ bl_idname = 'LNSetMaterialImageParamNode' bl_label = 'Set Material Image Param' arm_section = 'params' - arm_version = 1 + arm_version = 2 - def init(self, context): - super(SetMaterialImageParamNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Material') - self.add_input('NodeSocketString', 'Node') - self.add_input('NodeSocketString', 'Image') + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('ArmBoolSocket', 'Per Object') + self.add_input('ArmDynamicSocket', 'Material') + self.add_input('ArmStringSocket', 'Node') + self.add_input('ArmStringSocket', 'Image') self.add_output('ArmNodeSocketAction', 'Out') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + return NodeReplacement( + 'LNSetMaterialImageParamNode', self.arm_version, 'LNSetMaterialImageParamNode', 2, + in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0} + ) diff --git a/blender/arm/logicnode/material/LN_set_material_rgb_param.py b/blender/arm/logicnode/material/LN_set_material_rgb_param.py index 34948c9d..607c0ed2 100644 --- a/blender/arm/logicnode/material/LN_set_material_rgb_param.py +++ b/blender/arm/logicnode/material/LN_set_material_rgb_param.py @@ -1,17 +1,42 @@ from arm.logicnode.arm_nodes import * class SetMaterialRgbParamNode(ArmLogicTreeNode): - """TO DO.""" + """Set a color or vector value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @input Color: Color or vector input. + """ bl_idname = 'LNSetMaterialRgbParamNode' bl_label = 'Set Material RGB Param' arm_section = 'params' - arm_version = 1 + arm_version = 2 - def init(self, context): - super(SetMaterialRgbParamNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Material') - self.add_input('NodeSocketString', 'Node') - self.add_input('NodeSocketColor', 'Color') + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('ArmBoolSocket', 'Per Object') + self.add_input('ArmDynamicSocket', 'Material') + self.add_input('ArmStringSocket', 'Node') + self.add_input('ArmColorSocket', 'Color') self.add_output('ArmNodeSocketAction', 'Out') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + return NodeReplacement( + 'LNSetMaterialRgbParamNode', self.arm_version, 'LNSetMaterialRgbParamNode', 2, + in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0} + ) diff --git a/blender/arm/logicnode/material/LN_set_material_value_param.py b/blender/arm/logicnode/material/LN_set_material_value_param.py index 426807d1..e3f2164c 100644 --- a/blender/arm/logicnode/material/LN_set_material_value_param.py +++ b/blender/arm/logicnode/material/LN_set_material_value_param.py @@ -1,17 +1,43 @@ from arm.logicnode.arm_nodes import * class SetMaterialValueParamNode(ArmLogicTreeNode): - """TO DO.""" + """Set a float value material parameter to the specified object. + + @seeNode Get Scene Root + + @input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally. + + @input Per Object: + - `Enabled`: Set material parameter specific to this object. Global parameter will be ignored. + - `Disabled`: Set parameter globally, including this object. + + @input Material: Material whose parameter to be set. + + @input Node: Name of the parameter. + + @input Float: float value. + """ bl_idname = 'LNSetMaterialValueParamNode' bl_label = 'Set Material Value Param' arm_section = 'params' - arm_version = 1 + arm_version = 2 - def init(self, context): - super(SetMaterialValueParamNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Material') - self.add_input('NodeSocketString', 'Node') - self.add_input('NodeSocketFloat', 'Float') + self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('ArmBoolSocket', 'Per Object') + self.add_input('ArmDynamicSocket', 'Material') + self.add_input('ArmStringSocket', 'Node') + self.add_input('ArmFloatSocket', 'Float') self.add_output('ArmNodeSocketAction', 'Out') + + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + + return NodeReplacement( + 'LNSetMaterialValueParamNode', self.arm_version, 'LNSetMaterialValueParamNode', 2, + in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0} + ) diff --git a/blender/arm/logicnode/material/LN_set_object_material_slot.py b/blender/arm/logicnode/material/LN_set_object_material_slot.py index 3806c898..ccfab366 100644 --- a/blender/arm/logicnode/material/LN_set_object_material_slot.py +++ b/blender/arm/logicnode/material/LN_set_object_material_slot.py @@ -6,11 +6,10 @@ class SetMaterialSlotNode(ArmLogicTreeNode): bl_label = 'Set Object Material Slot' arm_version = 1 - def init(self, context): - super(SetMaterialSlotNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Material') - self.add_input('NodeSocketInt', 'Slot') + self.add_input('ArmDynamicSocket', 'Material') + self.add_input('ArmIntSocket', 'Slot') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/math/LN_clamp.py b/blender/arm/logicnode/math/LN_clamp.py index 9205d4d9..257c3802 100644 --- a/blender/arm/logicnode/math/LN_clamp.py +++ b/blender/arm/logicnode/math/LN_clamp.py @@ -9,10 +9,9 @@ class ClampNode(ArmLogicTreeNode): bl_label = 'Clamp' arm_version = 1 - def init(self, context): - super(ClampNode, self).init(context) - self.add_input('NodeSocketFloat', 'Value') - self.add_input('NodeSocketFloat', 'Min') - self.add_input('NodeSocketFloat', 'Max') + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Value') + self.add_input('ArmFloatSocket', 'Min') + self.add_input('ArmFloatSocket', 'Max') - self.add_output('NodeSocketFloat', 'Result') + self.add_output('ArmFloatSocket', 'Result') diff --git a/blender/arm/logicnode/math/LN_compare.py b/blender/arm/logicnode/math/LN_compare.py index 7b1a59ef..c3b86472 100644 --- a/blender/arm/logicnode/math/LN_compare.py +++ b/blender/arm/logicnode/math/LN_compare.py @@ -10,7 +10,8 @@ class CompareNode(ArmLogicTreeNode): bl_idname = 'LNCompareNode' bl_label = 'Compare' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Equal', 'Equal', 'Equal'), ('Almost Equal', 'Almost Equal', 'Almost Equal'), ('Greater', 'Greater', 'Greater'), @@ -22,18 +23,17 @@ class CompareNode(ArmLogicTreeNode): name='', default='Equal', update=remove_extra_inputs) min_inputs = 2 - property1: FloatProperty(name='Tolerance', description='Precision for float compare', default=0.0001) + property1: HaxeFloatProperty('property1', name='Tolerance', description='Precision for float compare', default=0.0001) def __init__(self): super(CompareNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(CompareNode, self).init(context) - self.add_input('NodeSocketShader', 'Value') - self.add_input('NodeSocketShader', 'Value') + def arm_init(self, context): + self.add_input('ArmDynamicSocket', 'Value') + self.add_input('ArmDynamicSocket', 'Value') - self.add_output('NodeSocketBool', 'Bool') + self.add_output('ArmBoolSocket', 'Bool') def draw_buttons(self, context, layout): layout.prop(self, 'property0') @@ -45,6 +45,6 @@ class CompareNode(ArmLogicTreeNode): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op2 = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op2.node_index = str(id(self)) diff --git a/blender/arm/logicnode/math/LN_deg_to_rad.py b/blender/arm/logicnode/math/LN_deg_to_rad.py index 64e63a61..6ee81e61 100644 --- a/blender/arm/logicnode/math/LN_deg_to_rad.py +++ b/blender/arm/logicnode/math/LN_deg_to_rad.py @@ -7,8 +7,7 @@ class DegToRadNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'angle' - def init(self, context): - super(DegToRadNode, self).init(context) - self.add_input('NodeSocketFloat', 'Degrees') + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Degrees') - self.add_output('NodeSocketFloat', 'Radians') + self.add_output('ArmFloatSocket', 'Radians') diff --git a/blender/arm/logicnode/math/LN_map_range.py b/blender/arm/logicnode/math/LN_map_range.py index d1d370b8..0008ce88 100644 --- a/blender/arm/logicnode/math/LN_map_range.py +++ b/blender/arm/logicnode/math/LN_map_range.py @@ -9,12 +9,11 @@ class MapRangeNode(ArmLogicTreeNode): bl_label = 'Map Range' arm_version = 1 - def init(self, context): - super(MapRangeNode, self).init(context) - self.add_input('NodeSocketFloat', 'Value', default_value=1.0) - self.add_input('NodeSocketFloat', 'From Min') - self.add_input('NodeSocketFloat', 'From Max', default_value=1.0) - self.add_input('NodeSocketFloat', 'To Min') - self.add_input('NodeSocketFloat', 'To Max', default_value=1.0) + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Value', default_value=1.0) + self.add_input('ArmFloatSocket', 'From Min') + self.add_input('ArmFloatSocket', 'From Max', default_value=1.0) + self.add_input('ArmFloatSocket', 'To Min') + self.add_input('ArmFloatSocket', 'To Max', default_value=1.0) - self.add_output('NodeSocketFloat', 'Result') + self.add_output('ArmFloatSocket', 'Result') diff --git a/blender/arm/logicnode/math/LN_math.py b/blender/arm/logicnode/math/LN_math.py index 3d8c4796..2271c2dc 100644 --- a/blender/arm/logicnode/math/LN_math.py +++ b/blender/arm/logicnode/math/LN_math.py @@ -51,20 +51,21 @@ class MathNode(ArmLogicTreeNode): # Many arguments: Add, Subtract, Multiply, Divide if (self.get_count_in(select_current) == 0): while (len(self.inputs) < 2): - self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs))) + self.add_input('ArmFloatSocket', 'Value ' + str(len(self.inputs))) # 2 arguments: Max, Min, Power, Arctan2, Modulo, Less Than, Greater Than if (self.get_count_in(select_current) == 2): while (len(self.inputs) > 2): self.inputs.remove(self.inputs.values()[-1]) while (len(self.inputs) < 2): - self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs))) + self.add_input('ArmFloatSocket', 'Value ' + str(len(self.inputs))) # 1 argument: Sine, Cosine, Abs, Tangent, Arcsine, Arccosine, Arctangent, Logarithm, Round, Floor, Ceil, Square Root, Fract, Exponent if (self.get_count_in(select_current) == 1): while (len(self.inputs) > 1): self.inputs.remove(self.inputs.values()[-1]) self['property0'] = value - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Add', 'Add', 'Add'), ('Multiply', 'Multiply', 'Multiply'), ('Sine', 'Sine', 'Sine'), @@ -92,24 +93,19 @@ class MathNode(ArmLogicTreeNode): ('Exponent', 'Exponent', 'Exponent')], name='', default='Add', set=set_enum, get=get_enum) - @property - def property1(self): - return 'true' if self.property1_ else 'false' - - property1_: BoolProperty(name='Clamp', default=False) + property1: HaxeBoolProperty('property1', name='Clamp', default=False) def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(MathNode, self).init(context) - self.add_input('NodeSocketFloat', 'Value 0', default_value=0.0) - self.add_input('NodeSocketFloat', 'Value 1', default_value=0.0) + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Value 0', default_value=0.0) + self.add_input('ArmFloatSocket', 'Value 1', default_value=0.0) - self.add_output('NodeSocketFloat', 'Result') + self.add_output('ArmFloatSocket', 'Result') def draw_buttons(self, context, layout): - layout.prop(self, 'property1_') + layout.prop(self, 'property1') layout.prop(self, 'property0') # Many arguments: Add, Subtract, Multiply, Divide if (self.get_count_in(self.property0) == 0): @@ -117,7 +113,7 @@ class MathNode(ArmLogicTreeNode): column = row.column(align=True) op = column.operator('arm.node_add_input', text='Add Value', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' op.name_format = 'Value {0}' column = row.column(align=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/math/LN_math_expression.py b/blender/arm/logicnode/math/LN_math_expression.py index b2fd3392..0795d6a8 100644 --- a/blender/arm/logicnode/math/LN_math_expression.py +++ b/blender/arm/logicnode/math/LN_math_expression.py @@ -8,15 +8,15 @@ class MathExpressionNode(ArmLogicTreeNode): arm_version = 1 min_inputs = 2 max_inputs = 10 - + @staticmethod def get_variable_name(index): return { - 0: 'a', - 1: 'b', - 2: 'c', - 3: 'd', - 4: 'e', + 0: 'a', + 1: 'b', + 2: 'c', + 3: 'd', + 4: 'e', 5: 'x', 6: 'y', 7: 'h', @@ -27,7 +27,7 @@ class MathExpressionNode(ArmLogicTreeNode): @staticmethod def get_clear_exp(value): return re.sub(r'[\-\+\*\/\(\)\^\%abcdexyhik0123456789. ]', '', value).strip() - + @staticmethod def get_invalid_characters(value): value = value.replace(' ', '') @@ -70,7 +70,7 @@ class MathExpressionNode(ArmLogicTreeNode): return False return True - @staticmethod + @staticmethod def matches(line, opendelim='(', closedelim=')'): stack = [] for m in re.finditer(r'[{}{}]'.format(opendelim, closedelim), line): @@ -92,7 +92,7 @@ class MathExpressionNode(ArmLogicTreeNode): if len(stack) > 0: for pos in stack: yield (False, 0, 0, 0) - + @staticmethod def isPartCorrect(s): if len(s.replace('p', '').replace(' ', '').split()) == 0: @@ -151,21 +151,20 @@ class MathExpressionNode(ArmLogicTreeNode): elif not self.isCorrect(self, value.replace(' ', '')): val_error = True self.set_exp_error(val_error) - + def get_exp(self): return self.get('property0', 'a + b') - property0: StringProperty(name='', description='Expression (operation: +, -, *, /, ^, (, ), %)', set=set_exp, get=get_exp) - property1: BoolProperty(name='Clamp', default=False) + property0: HaxeStringProperty('property0', name='', description='Expression (operation: +, -, *, /, ^, (, ), %)', set=set_exp, get=get_exp) + property1: HaxeBoolProperty('property1', name='Clamp', default=False) def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(MathExpressionNode, self).init(context) - self.add_input('NodeSocketFloat', self.get_variable_name(0), default_value=0.0) - self.add_input('NodeSocketFloat', self.get_variable_name(1), default_value=0.0) - self.add_output('NodeSocketFloat', 'Result') + def arm_init(self, context): + self.add_input('ArmFloatSocket', self.get_variable_name(0), default_value=0.0) + self.add_input('ArmFloatSocket', self.get_variable_name(1), default_value=0.0) + self.add_output('ArmFloatSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property1') @@ -181,10 +180,10 @@ class MathExpressionNode(ArmLogicTreeNode): if len(self.inputs) == 10: column.enabled = False op.node_index = str(id(self)) - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' op.name_format = self.get_variable_name(len(self.inputs)) column = row.column(align=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) op.node_index = str(id(self)) if len(self.inputs) == 2: - column.enabled = False \ No newline at end of file + column.enabled = False diff --git a/blender/arm/logicnode/math/LN_matrix_math.py b/blender/arm/logicnode/math/LN_matrix_math.py index 3a2fc6f4..6a4d200e 100644 --- a/blender/arm/logicnode/math/LN_matrix_math.py +++ b/blender/arm/logicnode/math/LN_matrix_math.py @@ -7,16 +7,16 @@ class MatrixMathNode(ArmLogicTreeNode): arm_section = 'matrix' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Multiply', 'Multiply', 'Multiply')], name='', default='Multiply') - def init(self, context): - super(MatrixMathNode, self).init(context) - self.add_input('NodeSocketShader', 'Matrix 1') - self.add_input('NodeSocketShader', 'Matrix 2') + def arm_init(self, context): + self.add_input('ArmDynamicSocket', 'Matrix 1') + self.add_input('ArmDynamicSocket', 'Matrix 2') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/math/LN_mix.py b/blender/arm/logicnode/math/LN_mix.py index 7924c9c3..7ae648a4 100644 --- a/blender/arm/logicnode/math/LN_mix.py +++ b/blender/arm/logicnode/math/LN_mix.py @@ -5,7 +5,8 @@ class MixNode(ArmLogicTreeNode): bl_idname = 'LNMixNode' bl_label = 'Mix' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Linear', 'Linear', 'Linear'), ('Sine', 'Sine', 'Sine'), ('Quad', 'Quad', 'Quad'), @@ -19,28 +20,24 @@ class MixNode(ArmLogicTreeNode): ('Elastic', 'Elastic', 'Elastic'), ], name='', default='Linear') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('In', 'In', 'In'), ('Out', 'Out', 'Out'), ('InOut', 'InOut', 'InOut'), ], name='', default='Out') - @property - def property2(self): - return 'true' if self.property2_ else 'false' + property2: HaxeBoolProperty('property2', name='Clamp', default=False) - property2_: BoolProperty(name='Clamp', default=False) + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Factor', default_value=0.0) + self.add_input('ArmFloatSocket', 'Value 1', default_value=0.0) + self.add_input('ArmFloatSocket', 'Value 2', default_value=1.0) - def init(self, context): - super(MixNode, self).init(context) - self.add_input('NodeSocketFloat', 'Factor', default_value=0.0) - self.add_input('NodeSocketFloat', 'Value 1', default_value=0.0) - self.add_input('NodeSocketFloat', 'Value 2', default_value=1.0) - - self.add_output('NodeSocketFloat', 'Result') + self.add_output('ArmFloatSocket', 'Result') def draw_buttons(self, context, layout): - layout.prop(self, 'property2_') + layout.prop(self, 'property2') layout.prop(self, 'property0') layout.prop(self, 'property1') diff --git a/blender/arm/logicnode/math/LN_mix_vector.py b/blender/arm/logicnode/math/LN_mix_vector.py index 31c7f587..4349de86 100644 --- a/blender/arm/logicnode/math/LN_mix_vector.py +++ b/blender/arm/logicnode/math/LN_mix_vector.py @@ -7,7 +7,8 @@ class VectorMixNode(ArmLogicTreeNode): arm_section = 'vector' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Linear', 'Linear', 'Linear'), ('Sine', 'Sine', 'Sine'), ('Quad', 'Quad', 'Quad'), @@ -21,29 +22,25 @@ class VectorMixNode(ArmLogicTreeNode): ('Elastic', 'Elastic', 'Elastic'), ], name='', default='Linear') - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items = [('In', 'In', 'In'), ('Out', 'Out', 'Out'), ('InOut', 'InOut', 'InOut'), ], name='', default='Out') - @property - def property2(self): - return 'true' if self.property2_ else 'false' + property2: HaxeBoolProperty('property2', name='Clamp', default=False) - property2_: BoolProperty(name='Clamp', default=False) + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Factor', default_value=0.0) + self.add_input('ArmVectorSocket', 'Vector 1', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmVectorSocket', 'Vector 2', default_value=[1.0, 1.0, 1.0]) - def init(self, context): - super(VectorMixNode, self).init(context) - self.add_input('NodeSocketFloat', 'Factor', default_value=0.0) - self.add_input('NodeSocketVector', 'Vector 1', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketVector', 'Vector 2', default_value=[1.0, 1.0, 1.0]) - - self.add_output('NodeSocketVector', 'Result') + self.add_output('ArmVectorSocket', 'Result') def draw_buttons(self, context, layout): - layout.prop(self, 'property2_') + layout.prop(self, 'property2') layout.prop(self, 'property0') if self.property0 != 'Linear': layout.prop(self, 'property1') diff --git a/blender/arm/logicnode/math/LN_quaternion_math.py b/blender/arm/logicnode/math/LN_quaternion_math.py index ecc681d0..dcf2d164 100644 --- a/blender/arm/logicnode/math/LN_quaternion_math.py +++ b/blender/arm/logicnode/math/LN_quaternion_math.py @@ -9,17 +9,19 @@ class QuaternionMathNode(ArmLogicTreeNode): arm_section = 'quaternions' arm_version = 2 - def ensure_input_socket(self, socket_number, newclass, newname, default_value=None): while len(self.inputs) < socket_number: - self.inputs.new('NodeSocketFloat', 'BOGUS') + self.inputs.new('ArmFloatSocket', 'BOGUS') if len(self.inputs) > socket_number: if len(self.inputs[socket_number].links) == 1: source_socket = self.inputs[socket_number].links[0].from_socket else: source_socket = None - if self.inputs[socket_number].bl_idname == newclass: - default_value = self.inputs[socket_number].default_value + if ( + self.inputs[socket_number].bl_idname == newclass \ + and self.inputs[socket_number].arm_socket_type != 'NONE' + ): + default_value = self.inputs[socket_number].default_value_raw self.inputs.remove(self.inputs[socket_number]) else: source_socket = None @@ -27,7 +29,7 @@ class QuaternionMathNode(ArmLogicTreeNode): self.inputs.new(newclass, newname) if default_value != None: - self.inputs[-1].default_value = default_value + self.inputs[-1].default_value_raw = default_value self.inputs.move(len(self.inputs)-1, socket_number) if source_socket is not None: self.id_data.links.new(source_socket, self.inputs[socket_number]) @@ -35,7 +37,7 @@ class QuaternionMathNode(ArmLogicTreeNode): def ensure_output_socket(self, socket_number, newclass, newname): sink_sockets = [] while len(self.outputs) < socket_number: - self.outputs.new('NodeSocketFloat', 'BOGUS') + self.outputs.new('ArmFloatSocket', 'BOGUS') if len(self.outputs) > socket_number: for link in self.inputs[socket_number].links: sink_sockets.append(link.to_socket) @@ -79,25 +81,24 @@ class QuaternionMathNode(ArmLogicTreeNode): select_current = self.get_enum_id_value(self, 'property0', value) select_prev = self.property0 - if select_current in ('Add','Subtract','Multiply','DotProduct') \ and select_prev in ('Add','Subtract','Multiply','DotProduct'): pass # same as select_current==select_prev for the sockets elif select_prev != select_current: if select_current in ('Add','Subtract','Multiply','DotProduct'): for i in range( max(len(self.inputs)//2 ,2) ): - self.ensure_input_socket(2*i, 'NodeSocketVector', 'Quaternion %d XYZ'%i) - self.ensure_input_socket(2*i+1, 'NodeSocketFloat', 'Quaternion %d W'%i, default_value=1.0) + self.ensure_input_socket(2*i, 'ArmVectorSocket', 'Quaternion %d XYZ'%i) + self.ensure_input_socket(2*i+1, 'ArmFloatSocket', 'Quaternion %d W'%i, default_value=1.0) if len(self.inputs)%1: self.inputs.remove(self.inputs[len(self.inputs)-1]) elif select_current == 'MultiplyFloats': - self.ensure_input_socket(0, 'NodeSocketVector', 'Quaternion XYZ') - self.ensure_input_socket(1, 'NodeSocketFloat', 'Quaternion W', default_value=1.0) + self.ensure_input_socket(0, 'ArmVectorSocket', 'Quaternion XYZ') + self.ensure_input_socket(1, 'ArmFloatSocket', 'Quaternion W', default_value=1.0) for i in range( max(len(self.inputs)-2 ,1) ): - self.ensure_input_socket(i+2, 'NodeSocketFloat', 'Value %d'%i) + self.ensure_input_socket(i+2, 'ArmFloatSocket', 'Value %d'%i) elif select_current in ('Module', 'Normalize'): - self.ensure_input_socket(0, 'NodeSocketVector', 'Quaternion XYZ') - self.ensure_input_socket(1, 'NodeSocketFloat', 'Quaternion W', default_value=1.0) + self.ensure_input_socket(0, 'ArmVectorSocket', 'Quaternion XYZ') + self.ensure_input_socket(1, 'ArmFloatSocket', 'Quaternion W', default_value=1.0) while len(self.inputs)>2: self.inputs.remove(self.inputs[2]) else: @@ -113,7 +114,11 @@ class QuaternionMathNode(ArmLogicTreeNode): self['property0'] = value self['property0_proxy'] = value - property0: EnumProperty( + + # this property swaperoo is kinda janky-looking, but necessary. + # Read more on LN_rotate_object.py + property0: HaxeEnumProperty( + 'property0', items = [('Add', 'Add', 'Add'), ('Subtract', 'Subtract', 'Subtract'), ('DotProduct', 'Dot Product', 'Dot Product'), @@ -133,19 +138,28 @@ class QuaternionMathNode(ArmLogicTreeNode): ('FromEuler', 'DO NOT USE',''), ('GetEuler', 'DO NOT USE','')], name='', default='Add') #, set=set_enum, get=get_enum) + property0_proxy: EnumProperty( + items = [('Add', 'Add', 'Add'), + ('Subtract', 'Subtract', 'Subtract'), + ('DotProduct', 'Dot Product', 'Dot Product'), + ('Multiply', 'Multiply', 'Multiply'), + ('MultiplyFloats', 'Multiply (Floats)', 'Multiply (Floats)'), + ('Module', 'Module', 'Module'), + ('Normalize', 'Normalize', 'Normalize')], + name='', default='Add', set=set_enum, get=get_enum) + def __init__(self): super(QuaternionMathNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(QuaternionMathNode, self).init(context) - self.add_input('NodeSocketVector', 'Quaternion 0 XYZ', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketFloat', 'Quaternion 0 W', default_value=1) - self.add_input('NodeSocketVector', 'Quaternion 1 XYZ', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketFloat', 'Quaternion 1 W', default_value=1) - self.add_output('NodeSocketVector', 'Result XYZ', default_value=[0.0, 0.0, 0.0]) - self.add_output('NodeSocketFloat', 'Result W', default_value=1) + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Quaternion 0 XYZ', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmFloatSocket', 'Quaternion 0 W', default_value=1) + self.add_input('ArmVectorSocket', 'Quaternion 1 XYZ', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmFloatSocket', 'Quaternion 1 W', default_value=1) + self.add_output('ArmVectorSocket', 'Result XYZ', default_value=[0.0, 0.0, 0.0]) + self.add_output('ArmFloatSocket', 'Result W', default_value=1) def draw_buttons(self, context, layout): layout.prop(self, 'property0_proxy') # Operation @@ -160,9 +174,10 @@ class QuaternionMathNode(ArmLogicTreeNode): else: op.name_format = 'Value {0}' if (self.property0 == "MultiplyFloats"): - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' else: - op.socket_type = 'NodeSocketVector;NodeSocketFloat' + op.socket_type = 'ArmVectorSocket;ArmFloatSocket' + column = row.column(align=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) op.node_index = str(id(self)) @@ -246,12 +261,12 @@ class QuaternionMathNode(ArmLogicTreeNode): newself.property0 = self.property0 for in1, in2 in zip(self.inputs, newself.inputs): - if in2.bl_idname == 'ArmNodeSocketRotation': + if in2.bl_idname == 'ArmRotationSocket': in2.default_value_raw = Rotation.convert_to_quaternion( in1.default_value,0, 'EulerAngles','Rad','XZY' ) - elif in1.bl_idname in ('NodeSocketFloat', 'NodeSocketVector'): + elif in1.bl_idname in ('ArmFloatSocket', 'ArmVectorSocket'): in2.default_value = in1.default_value for link in in1.links: node_tree.links.new(link.from_socket, in2) @@ -266,21 +281,21 @@ class QuaternionMathNode(ArmLogicTreeNode): i_in_2 = 0 while i_in_1 < len(self.inputs): in1 = self.inputs[i_in_1] - if in1.bl_idname == 'NodeSocketVector': + if in1.bl_idname == 'ArmVectorSocket': # quaternion input: now two sockets, not one. convnode = node_tree.nodes.new('LNSeparateRotationNode') convnode.property0 = 'Quaternion' ret.append(convnode) if i_in_2 >= len(newself.inputs): - newself.ensure_input_socket(i_in_2, 'NodeSocketVector', 'Quaternion %d XYZ'%(i_in_1)) - newself.ensure_input_socket(i_in_2+1, 'NodeSocketFloat', 'Quaternion %d W'%(i_in_1), 1.0) + newself.ensure_input_socket(i_in_2, 'ArmVectorSocket', 'Quaternion %d XYZ'%(i_in_1)) + newself.ensure_input_socket(i_in_2+1, 'ArmFloatSocket', 'Quaternion %d W'%(i_in_1), 1.0) node_tree.links.new(convnode.outputs[0], newself.inputs[i_in_2]) node_tree.links.new(convnode.outputs[1], newself.inputs[i_in_2+1]) for link in in1.links: node_tree.links.new(link.from_socket, convnode.inputs[0]) i_in_2 +=2 i_in_1 +=1 - elif in1.bl_idname == 'NodeSocketFloat': + elif in1.bl_idname == 'ArmFloatSocket': for link in in1.links: node_tree.links.new(link.from_socket, newself.inputs[i_in_2]) i_in_1 +=1 @@ -362,16 +377,3 @@ class QuaternionMathNode(ArmLogicTreeNode): # note: keep property1, so that it is actually readable for node conversion. property1: BoolProperty(name='DEPRECATED', default=False) - - # this is the version of property0 that is shown in the interface, - # even though the real property0 is the one used elsewhere. - # NOTE FOR FUTURE MAINTAINERS: the value of this proxy property does **not** matter, only the value of property0 does. - property0_proxy: EnumProperty( - items = [('Add', 'Add', 'Add'), - ('Subtract', 'Subtract', 'Subtract'), - ('DotProduct', 'Dot Product', 'Dot Product'), - ('Multiply', 'Multiply', 'Multiply'), - ('MultiplyFloats', 'Multiply (Floats)', 'Multiply (Floats)'), - ('Module', 'Module', 'Module'), - ('Normalize', 'Normalize', 'Normalize')], - name='', default='Add', set=set_enum, get=get_enum) diff --git a/blender/arm/logicnode/math/LN_rad_to_deg.py b/blender/arm/logicnode/math/LN_rad_to_deg.py index 2e13666b..c144ae98 100644 --- a/blender/arm/logicnode/math/LN_rad_to_deg.py +++ b/blender/arm/logicnode/math/LN_rad_to_deg.py @@ -7,8 +7,7 @@ class RadToDegNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'angle' - def init(self, context): - super(RadToDegNode, self).init(context) - self.add_input('NodeSocketFloat', 'Radians') + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Radians') - self.add_output('NodeSocketFloat', 'Degrees') + self.add_output('ArmFloatSocket', 'Degrees') diff --git a/blender/arm/logicnode/math/LN_rotation_math.py b/blender/arm/logicnode/math/LN_rotation_math.py index cbfe92e2..fea8e350 100644 --- a/blender/arm/logicnode/math/LN_rotation_math.py +++ b/blender/arm/logicnode/math/LN_rotation_math.py @@ -25,7 +25,7 @@ class RotationMathNode(ArmLogicTreeNode): def ensure_input_socket(self, socket_number, newclass, newname): while len(self.inputs) < socket_number: - self.inputs.new('NodeSocketFloat', 'BOGUS') + self.inputs.new('ArmFloatSocket', 'BOGUS') if len(self.inputs) > socket_number: if len(self.inputs[socket_number].links) == 1: source_socket = self.inputs[socket_number].links[0].from_socket @@ -44,7 +44,7 @@ class RotationMathNode(ArmLogicTreeNode): def ensure_output_socket(self, socket_number, newclass, newname): sink_sockets = [] while len(self.outputs) < socket_number: - self.outputs.new('NodeSocketFloat', 'BOGUS') + self.outputs.new('ArmFloatSocket', 'BOGUS') if len(self.outputs) > socket_number: for link in self.inputs[socket_number].links: sink_sockets.append(link.to_socket) @@ -61,32 +61,32 @@ class RotationMathNode(ArmLogicTreeNode): # Rotation as argument 0: if self.property0 in ('Inverse','Normalize','Amplify'): - self.ensure_input_socket(0, "ArmNodeSocketRotation", "Rotation") - self.ensure_input_socket(1, "NodeSocketFloat", "Amplification factor") + self.ensure_input_socket(0, "ArmRotationSocket", "Rotation") + self.ensure_input_socket(1, "ArmFloatSocket", "Amplification factor") elif self.property0 in ('Slerp','Lerp','Compose'): - self.ensure_input_socket(0, "ArmNodeSocketRotation", "From") - self.ensure_input_socket(1, "ArmNodeSocketRotation", "To") + self.ensure_input_socket(0, "ArmRotationSocket", "From") + self.ensure_input_socket(1, "ArmRotationSocket", "To") if self.property0 == 'Compose': self.inputs[0].name = 'Outer rotation' self.inputs[1].name = 'Inner rotation' else: - self.ensure_input_socket(2, "NodeSocketFloat", "Interpolation factor") + self.ensure_input_socket(2, "ArmFloatSocket", "Interpolation factor") elif self.property0 == 'FromTo': - self.ensure_input_socket(0, "NodeSocketVector", "From") - self.ensure_input_socket(1, "NodeSocketVector", "To") + self.ensure_input_socket(0, "ArmVectorSocket", "From") + self.ensure_input_socket(1, "ArmVectorSocket", "To") # Rotation as argument 1: if self.property0 in ('Compose','Lerp','Slerp'): - if self.inputs[1].bl_idname != "ArmNodeSocketRotation": - self.replace_input_socket(1, "ArmNodeSocketRotation", "Rotation 2") + if self.inputs[1].bl_idname != "ArmRotationSocket": + self.replace_input_socket(1, "ArmRotationSocket", "Rotation 2") if self.property0 == 'Compose': self.inputs[1].name = "Inner quaternion" # Float as argument 1: if self.property0 == 'Amplify': - if self.inputs[1].bl_idname != 'NodeSocketFloat': - self.replace_input_socket(1, "NodeSocketFloat", "Amplification factor") + if self.inputs[1].bl_idname != 'ArmFloatSocket': + self.replace_input_socket(1, "ArmFloatSocket", "Amplification factor") # Vector as argument 1: #if self.property0 == 'FromRotationMat': # # WHAT?? @@ -96,7 +96,8 @@ class RotationMathNode(ArmLogicTreeNode): self.inputs.remove(self.inputs[len(self.inputs)-1]) - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Compose', 'Compose (multiply)', 'compose (multiply) two rotations. Note that order of the composition matters.'), ('Amplify', 'Amplify (multiply by float)', 'Amplify or diminish the effect of a rotation'), #('Normalize', 'Normalize', 'Normalize'), @@ -111,11 +112,10 @@ class RotationMathNode(ArmLogicTreeNode): #def __init__(self): # array_nodes[str(id(self))] = self - def init(self, context): - super(RotationMathNode, self).init(context) - self.add_input('ArmNodeSocketRotation', 'Outer rotation', default_value=Vector((0.0, 0.0, 0.0, 1.0)) ) - self.add_input('ArmNodeSocketRotation', 'Inner rotation', default_value=Vector((0.0, 0.0, 0.0, 1.0))) - self.add_output('ArmNodeSocketRotation', 'Result') + def arm_init(self, context): + self.add_input('ArmRotationSocket', 'Outer rotation', default_value=(0.0, 0.0, 0.0, 1.0) ) + self.add_input('ArmRotationSocket', 'Inner rotation', default_value=(0.0, 0.0, 0.0, 1.0) ) + self.add_output('ArmRotationSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property0') # Operation diff --git a/blender/arm/logicnode/math/LN_screen_to_world_space.py b/blender/arm/logicnode/math/LN_screen_to_world_space.py index 52b6fa36..ef1864cb 100644 --- a/blender/arm/logicnode/math/LN_screen_to_world_space.py +++ b/blender/arm/logicnode/math/LN_screen_to_world_space.py @@ -10,33 +10,27 @@ class ScreenToWorldSpaceNode(ArmLogicTreeNode): min_outputs = 2 max_outputs = 8 - # Separator - @property - def property0(self): - return True if self.property0_ else False + property0: HaxeBoolProperty('property0', name='Separator Out', default=False) - property0_: BoolProperty(name='Separator Out', default=False) + def arm_init(self, context): + self.add_input('ArmIntSocket', 'Screen X') + self.add_input('ArmIntSocket', 'Screen Y') - def init(self, context): - super(ScreenToWorldSpaceNode, self).init(context) - self.add_input('NodeSocketInt', 'Screen X') - self.add_input('NodeSocketInt', 'Screen Y') - - self.add_output('NodeSocketVector', 'World') - self.add_output('NodeSocketVector', 'Direction') + self.add_output('ArmVectorSocket', 'World') + self.add_output('ArmVectorSocket', 'Direction') def draw_buttons(self, context, layout): - layout.prop(self, 'property0_') # Separator Out - if self.property0_: + layout.prop(self, 'property0') # Separator Out + if self.property0: if len(self.outputs) < self.max_outputs: self.outputs.remove(self.outputs.values()[-1]) # Direction vector - self.add_output('NodeSocketFloat', 'X') # World X - self.add_output('NodeSocketFloat', 'Y') # World Y - self.add_output('NodeSocketFloat', 'Z') # World Z - self.add_output('NodeSocketVector', 'Direction') # Vector - self.add_output('NodeSocketFloat', 'X') # Direction X - self.add_output('NodeSocketFloat', 'Y') # Direction Y - self.add_output('NodeSocketFloat', 'Z') # Direction Z + self.add_output('ArmFloatSocket', 'X') # World X + self.add_output('ArmFloatSocket', 'Y') # World Y + self.add_output('ArmFloatSocket', 'Z') # World Z + self.add_output('ArmVectorSocket', 'Direction') # Vector + self.add_output('ArmFloatSocket', 'X') # Direction X + self.add_output('ArmFloatSocket', 'Y') # Direction Y + self.add_output('ArmFloatSocket', 'Z') # Direction Z else: if len(self.outputs) == self.max_outputs: self.outputs.remove(self.outputs.values()[-1]) # Z @@ -46,4 +40,4 @@ class ScreenToWorldSpaceNode(ArmLogicTreeNode): self.outputs.remove(self.outputs.values()[-1]) # Z self.outputs.remove(self.outputs.values()[-1]) # Y self.outputs.remove(self.outputs.values()[-1]) # X - self.add_output('NodeSocketVector', 'Direction') + self.add_output('ArmVectorSocket', 'Direction') diff --git a/blender/arm/logicnode/math/LN_separate_rgb.py b/blender/arm/logicnode/math/LN_separate_rgb.py index b95c3e32..e2098989 100644 --- a/blender/arm/logicnode/math/LN_separate_rgb.py +++ b/blender/arm/logicnode/math/LN_separate_rgb.py @@ -7,10 +7,9 @@ class SeparateColorNode(ArmLogicTreeNode): arm_section = 'color' arm_version = 1 - def init(self, context): - super(SeparateColorNode, self).init(context) - self.add_input('NodeSocketColor', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) + def arm_init(self, context): + self.add_input('ArmColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0]) - self.add_output('NodeSocketFloat', 'R') - self.add_output('NodeSocketFloat', 'G') - self.add_output('NodeSocketFloat', 'B') + self.add_output('ArmFloatSocket', 'R') + self.add_output('ArmFloatSocket', 'G') + self.add_output('ArmFloatSocket', 'B') diff --git a/blender/arm/logicnode/math/LN_separate_xyz.py b/blender/arm/logicnode/math/LN_separate_xyz.py index d1cc1316..4f7ac735 100644 --- a/blender/arm/logicnode/math/LN_separate_xyz.py +++ b/blender/arm/logicnode/math/LN_separate_xyz.py @@ -7,10 +7,9 @@ class SeparateVectorNode(ArmLogicTreeNode): arm_section = 'vector' arm_version = 1 - def init(self, context): - super(SeparateVectorNode, self).init(context) - self.add_input('NodeSocketVector', 'Vector') + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Vector') - self.add_output('NodeSocketFloat', 'X') - self.add_output('NodeSocketFloat', 'Y') - self.add_output('NodeSocketFloat', 'Z') + self.add_output('ArmFloatSocket', 'X') + self.add_output('ArmFloatSocket', 'Y') + self.add_output('ArmFloatSocket', 'Z') diff --git a/blender/arm/logicnode/math/LN_vector_clamp.py b/blender/arm/logicnode/math/LN_vector_clamp.py index 3a47c025..a61e677d 100644 --- a/blender/arm/logicnode/math/LN_vector_clamp.py +++ b/blender/arm/logicnode/math/LN_vector_clamp.py @@ -7,10 +7,9 @@ class VectorClampToSizeNode(ArmLogicTreeNode): arm_section = 'vector' arm_version = 1 - def init(self, context): - super(VectorClampToSizeNode, self).init(context) - self.add_input('NodeSocketVector', 'Vector In', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketFloat', 'Min') - self.add_input('NodeSocketFloat', 'Max') + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Vector In', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmFloatSocket', 'Min') + self.add_input('ArmFloatSocket', 'Max') - self.add_output('NodeSocketVector', 'Vector Out') + self.add_output('ArmVectorSocket', 'Vector Out') diff --git a/blender/arm/logicnode/math/LN_vector_math.py b/blender/arm/logicnode/math/LN_vector_math.py index 22f9eaa4..4e9e82a5 100644 --- a/blender/arm/logicnode/math/LN_vector_math.py +++ b/blender/arm/logicnode/math/LN_vector_math.py @@ -17,15 +17,15 @@ class VectorMathNode(ArmLogicTreeNode): if value: if (self.property0 == 'Length') or (self.property0 == 'Distance') or (self.property0 == 'Dot Product'): self.outputs.remove(self.outputs.values()[-1]) # Distance/Length/Scalar - self.add_output('NodeSocketFloat', 'X') # Result X - self.add_output('NodeSocketFloat', 'Y') # Result Y - self.add_output('NodeSocketFloat', 'Z') # Result Z + self.add_output('ArmFloatSocket', 'X') # Result X + self.add_output('ArmFloatSocket', 'Y') # Result Y + self.add_output('ArmFloatSocket', 'Z') # Result Z if (self.property0 == 'Length'): - self.add_output('NodeSocketFloat', 'Length') # Length + self.add_output('ArmFloatSocket', 'Length') # Length if (self.property0 == 'Distance'): - self.add_output('NodeSocketFloat', 'Distance') # Distance + self.add_output('ArmFloatSocket', 'Distance') # Distance if (self.property0 == 'Dot Product'): - self.add_output('NodeSocketFloat', 'Scalar') # Scalar + self.add_output('ArmFloatSocket', 'Scalar') # Scalar else: if ((self.property0 == 'Length') or (self.property0 == 'Distance') or (self.property0 == 'Dot Product')) and (len(self.outputs) > 1): self.outputs.remove(self.outputs.values()[-1]) # Distance/Length/Scalar @@ -36,13 +36,13 @@ class VectorMathNode(ArmLogicTreeNode): else: break if (self.property0 == 'Length'): - self.add_output('NodeSocketFloat', 'Length') # Length + self.add_output('ArmFloatSocket', 'Length') # Length if (self.property0 == 'Distance'): - self.add_output('NodeSocketFloat', 'Distance') # Distance + self.add_output('ArmFloatSocket', 'Distance') # Distance if (self.property0 == 'Dot Product'): - self.add_output('NodeSocketFloat', 'Scalar') # Scalar + self.add_output('ArmFloatSocket', 'Scalar') # Scalar - property1: BoolProperty(name='Separator Out', default=False, set=set_bool, get=get_bool) + property1: HaxeBoolProperty('property1', name='Separator Out', default=False, set=set_bool, get=get_bool) @staticmethod def get_enum_id_value(obj, prop_name, value): @@ -80,12 +80,12 @@ class VectorMathNode(ArmLogicTreeNode): while (len(self.inputs) > 1): self.inputs.remove(self.inputs.values()[-1]) if (select_current == "MultiplyFloats"): - self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs))) + self.add_input('ArmFloatSocket', 'Value ' + str(len(self.inputs))) else: while (len(self.inputs) < 2): - self.add_input('NodeSocketVector', 'Value ' + str(len(self.inputs))) + self.add_input('ArmVectorSocket', 'Value ' + str(len(self.inputs))) if (select_current == 'Dot Product'): - self.add_output('NodeSocketFloat', 'Scalar') + self.add_output('ArmFloatSocket', 'Scalar') # 2 arguments: Distance, Reflect if (self.get_count_in(select_current) == 2): count = 2 @@ -94,18 +94,19 @@ class VectorMathNode(ArmLogicTreeNode): while (len(self.inputs) > count): self.inputs.remove(self.inputs.values()[-1]) while (len(self.inputs) < 2): - self.add_input('NodeSocketVector', 'Value ' + str(len(self.inputs))) + self.add_input('ArmVectorSocket', 'Value ' + str(len(self.inputs))) if (select_current == 'Distance'): - self.add_output('NodeSocketFloat', 'Distance') + self.add_output('ArmFloatSocket', 'Distance') # 1 argument: Normalize, Length if (self.get_count_in(select_current) == 1): while (len(self.inputs) > 1): self.inputs.remove(self.inputs.values()[-1]) if (select_current == 'Length'): - self.add_output('NodeSocketFloat', 'Length') + self.add_output('ArmFloatSocket', 'Length') self['property0'] = value - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Add', 'Add', 'Add'), ('Dot Product', 'Dot Product', 'Dot Product'), ('Multiply', 'Multiply', 'Multiply'), @@ -122,12 +123,11 @@ class VectorMathNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(VectorMathNode, self).init(context) - self.add_input('NodeSocketVector', 'Value 0', default_value=[0.0, 0.0, 0.0]) - self.add_input('NodeSocketVector', 'Value 1', default_value=[0.0, 0.0, 0.0]) + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Value 0', default_value=[0.0, 0.0, 0.0]) + self.add_input('ArmVectorSocket', 'Value 1', default_value=[0.0, 0.0, 0.0]) - self.add_output('NodeSocketVector', 'Result') + self.add_output('ArmVectorSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property1') # Separator Out @@ -140,9 +140,9 @@ class VectorMathNode(ArmLogicTreeNode): op.node_index = str(id(self)) op.name_format = 'Value {0}' if (self.property0 == "MultiplyFloats"): - op.socket_type = 'NodeSocketFloat' + op.socket_type = 'ArmFloatSocket' else: - op.socket_type = 'NodeSocketVector' + op.socket_type = 'ArmVectorSocket' column = row.column(align=True) op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True) op.node_index = str(id(self)) diff --git a/blender/arm/logicnode/math/LN_world_to_screen_space.py b/blender/arm/logicnode/math/LN_world_to_screen_space.py index 53b91b54..b3f16b6a 100644 --- a/blender/arm/logicnode/math/LN_world_to_screen_space.py +++ b/blender/arm/logicnode/math/LN_world_to_screen_space.py @@ -7,8 +7,7 @@ class WorldToScreenSpaceNode(ArmLogicTreeNode): arm_section = 'matrix' arm_version = 1 - def init(self, context): - super(WorldToScreenSpaceNode, self).init(context) - self.add_input('NodeSocketVector', 'World') + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'World') - self.add_output('NodeSocketVector', 'Screen') + self.add_output('ArmVectorSocket', 'Screen') diff --git a/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py b/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py index acac1ad3..5bcb5002 100644 --- a/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py +++ b/blender/arm/logicnode/miscellaneous/LN_boolean_to_vector.py @@ -6,13 +6,12 @@ class VectorFromBooleanNode(ArmLogicTreeNode): bl_label = 'Boolean to Vector' arm_version = 1 - def init(self, context): - super(VectorFromBooleanNode, self).init(context) - self.inputs.new('NodeSocketBool', 'X') - self.inputs.new('NodeSocketBool', '-X') - self.inputs.new('NodeSocketBool', 'Y') - self.inputs.new('NodeSocketBool', '-Y') - self.inputs.new('NodeSocketBool', 'Z') - self.inputs.new('NodeSocketBool', '-Z') + def arm_init(self, context): + self.inputs.new('ArmBoolSocket', 'X') + self.inputs.new('ArmBoolSocket', '-X') + self.inputs.new('ArmBoolSocket', 'Y') + self.inputs.new('ArmBoolSocket', '-Y') + self.inputs.new('ArmBoolSocket', 'Z') + self.inputs.new('ArmBoolSocket', '-Z') - self.outputs.new('NodeSocketVector', 'Vector') + self.outputs.new('ArmVectorSocket', 'Vector') diff --git a/blender/arm/logicnode/miscellaneous/LN_call_group.py b/blender/arm/logicnode/miscellaneous/LN_call_group.py index bf483e0d..87e13ec1 100644 --- a/blender/arm/logicnode/miscellaneous/LN_call_group.py +++ b/blender/arm/logicnode/miscellaneous/LN_call_group.py @@ -15,10 +15,9 @@ class CallGroupNode(ArmLogicTreeNode): def property0(self): return arm.utils.safesrc(bpy.data.worlds['Arm'].arm_project_package) + '.node.' + arm.utils.safesrc(self.property0_.name) - property0_: PointerProperty(name='Group', type=bpy.types.NodeTree) + property0_: HaxePointerProperty('property0', name='Group', type=bpy.types.NodeTree) - def init(self, context): - super(CallGroupNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_default_if_null.py b/blender/arm/logicnode/miscellaneous/LN_default_if_null.py index 2437c3a2..e1e5f2db 100644 --- a/blender/arm/logicnode/miscellaneous/LN_default_if_null.py +++ b/blender/arm/logicnode/miscellaneous/LN_default_if_null.py @@ -10,9 +10,8 @@ class DefaultIfNullNode(ArmLogicTreeNode): bl_label = 'Default if Null' arm_version = 1 - def init(self, context): - super(DefaultIfNullNode, self).init(context) - self.inputs.new('NodeSocketShader', 'Value In') - self.inputs.new('NodeSocketShader', 'Default') + def arm_init(self, context): + self.inputs.new('ArmDynamicSocket', 'Value In') + self.inputs.new('ArmDynamicSocket', 'Default') - self.outputs.new('NodeSocketShader', 'Value Out') + self.outputs.new('ArmDynamicSocket', 'Value Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_application_time.py b/blender/arm/logicnode/miscellaneous/LN_get_application_time.py index 602d9856..ae2ba85a 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_application_time.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_application_time.py @@ -6,7 +6,6 @@ class TimeNode(ArmLogicTreeNode): bl_label = 'Get Application Time' arm_version = 1 - def init(self, context): - super(TimeNode, self).init(context) - self.add_output('NodeSocketFloat', 'Time') - self.add_output('NodeSocketFloat', 'Delta') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'Time') + self.add_output('ArmFloatSocket', 'Delta') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py b/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py index 2ea657b0..e6e04394 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_debug_console_settings.py @@ -6,8 +6,7 @@ class GetDebugConsoleSettings(ArmLogicTreeNode): bl_label = 'Get Debug Console Settings' arm_version = 1 - def init(self, context): - super(GetDebugConsoleSettings, self).init(context) - self.add_output('NodeSocketBool', 'Visible') - self.add_output('NodeSocketFloat', 'Scale') - self.add_output('NodeSocketString', 'Position') + def arm_init(self, context): + self.add_output('ArmBoolSocket', 'Visible') + self.add_output('ArmFloatSocket', 'Scale') + self.add_output('ArmStringSocket', 'Position') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py b/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py index 560d053f..411e298c 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_display_resolution.py @@ -10,7 +10,6 @@ class DisplayInfoNode(ArmLogicTreeNode): arm_section = 'screen' arm_version = 1 - def init(self, context): - super(DisplayInfoNode, self).init(context) - self.add_output('NodeSocketInt', 'Width') - self.add_output('NodeSocketInt', 'Height') + def arm_init(self, context): + self.add_output('ArmIntSocket', 'Width') + self.add_output('ArmIntSocket', 'Height') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_fps.py b/blender/arm/logicnode/miscellaneous/LN_get_fps.py index bd2b76ae..4bc298d9 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_fps.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_fps.py @@ -6,6 +6,5 @@ class GetFPSNode(ArmLogicTreeNode): bl_label = 'Get Frames Per Second' arm_version = 1 - def init(self, context): - super(GetFPSNode, self).init(context) - self.add_output('NodeSocketInt', 'Count') + def arm_init(self, context): + self.add_output('ArmIntSocket', 'Count') diff --git a/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py b/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py index 8fd5b504..d2fe52b6 100644 --- a/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py +++ b/blender/arm/logicnode/miscellaneous/LN_get_window_resolution.py @@ -10,7 +10,6 @@ class WindowInfoNode(ArmLogicTreeNode): arm_section = 'screen' arm_version = 1 - def init(self, context): - super(WindowInfoNode, self).init(context) - self.add_output('NodeSocketInt', 'Width') - self.add_output('NodeSocketInt', 'Height') + def arm_init(self, context): + self.add_output('ArmIntSocket', 'Width') + self.add_output('ArmIntSocket', 'Height') diff --git a/blender/arm/logicnode/miscellaneous/LN_group_nodes.py b/blender/arm/logicnode/miscellaneous/LN_group_nodes.py index c8d8cdc9..8b08343b 100644 --- a/blender/arm/logicnode/miscellaneous/LN_group_nodes.py +++ b/blender/arm/logicnode/miscellaneous/LN_group_nodes.py @@ -7,6 +7,5 @@ class GroupOutputNode(ArmLogicTreeNode): arm_section = 'group' arm_version = 1 - def init(self, context): - super(GroupOutputNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') diff --git a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py index a40602e8..4bbd4d12 100644 --- a/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py +++ b/blender/arm/logicnode/miscellaneous/LN_set_debug_console_settings.py @@ -6,17 +6,17 @@ class SetDebugConsoleSettings(ArmLogicTreeNode): bl_label = 'Set Debug Console Settings' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('left', 'Anchor Left', 'Anchor debug console in the top left'), ('center', 'Anchor Center', 'Anchor debug console in the top center'), ('right', 'Anchor Right', 'Anchor the debug console in the top right')], name='', default='right') - def init(self, context): - super(SetDebugConsoleSettings, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Visible') - self.add_input('NodeSocketFloat', 'Scale') + self.add_input('ArmBoolSocket', 'Visible') + self.add_input('ArmFloatSocket', 'Scale') self.inputs[-1].default_value = 1.0 self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py b/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py index aff8c194..77bc3f59 100644 --- a/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py +++ b/blender/arm/logicnode/miscellaneous/LN_set_time_scale.py @@ -6,9 +6,8 @@ class SetTimeScaleNode(ArmLogicTreeNode): bl_label = 'Set Time Scale' arm_version = 1 - def init(self, context): - super(SetTimeScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Scale', default_value=1.0) + self.add_input('ArmFloatSocket', 'Scale', default_value=1.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_sleep.py b/blender/arm/logicnode/miscellaneous/LN_sleep.py index 0d2e4c82..884720e7 100644 --- a/blender/arm/logicnode/miscellaneous/LN_sleep.py +++ b/blender/arm/logicnode/miscellaneous/LN_sleep.py @@ -7,9 +7,8 @@ class SleepNode(ArmLogicTreeNode): bl_label = 'Sleep' arm_version = 1 - def init(self, context): - super(SleepNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Time') + self.add_input('ArmFloatSocket', 'Time') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/miscellaneous/LN_timer.py b/blender/arm/logicnode/miscellaneous/LN_timer.py index d74d7197..e19a36b8 100644 --- a/blender/arm/logicnode/miscellaneous/LN_timer.py +++ b/blender/arm/logicnode/miscellaneous/LN_timer.py @@ -6,21 +6,20 @@ class TimerNode(ArmLogicTreeNode): bl_label = 'Timer' arm_version = 1 - def init(self, context): - super(TimerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'Start') self.add_input('ArmNodeSocketAction', 'Pause') self.add_input('ArmNodeSocketAction', 'Stop') - self.add_input('NodeSocketFloat', 'Duration', default_value=1.0) - self.add_input('NodeSocketInt', 'Repeat') + self.add_input('ArmFloatSocket', 'Duration', default_value=1.0) + self.add_input('ArmIntSocket', 'Repeat') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketAction', 'Done') - self.add_output('NodeSocketBool', 'Running') - self.add_output('NodeSocketInt', 'Time Passed') - self.add_output('NodeSocketInt', 'Time Left') - self.add_output('NodeSocketFloat', 'Progress') - self.add_output('NodeSocketFloat', 'Repetitions') + self.add_output('ArmBoolSocket', 'Running') + self.add_output('ArmIntSocket', 'Time Passed') + self.add_output('ArmIntSocket', 'Time Left') + self.add_output('ArmFloatSocket', 'Progress') + self.add_output('ArmFloatSocket', 'Repetitions') def draw_label(self) -> str: inp_duration = self.inputs['Duration'] diff --git a/blender/arm/logicnode/native/LN_call_haxe_static.py b/blender/arm/logicnode/native/LN_call_haxe_static.py index eb288074..fd7e2c91 100644 --- a/blender/arm/logicnode/native/LN_call_haxe_static.py +++ b/blender/arm/logicnode/native/LN_call_haxe_static.py @@ -10,10 +10,9 @@ class CallHaxeStaticNode(ArmLogicTreeNode): arm_section = 'haxe' arm_version = 1 - def init(self, context): - super(CallHaxeStaticNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Function') + self.add_input('ArmStringSocket', 'Function') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') diff --git a/blender/arm/logicnode/native/LN_detect_mobile_browser.py b/blender/arm/logicnode/native/LN_detect_mobile_browser.py index d49f62cf..4268ab0d 100644 --- a/blender/arm/logicnode/native/LN_detect_mobile_browser.py +++ b/blender/arm/logicnode/native/LN_detect_mobile_browser.py @@ -6,6 +6,5 @@ class DetectMobileBrowserNode(ArmLogicTreeNode): bl_label = 'Detect Mobile Browser' arm_version = 1 - def init(self, context): - super(DetectMobileBrowserNode, self).init(context) - self.add_output('NodeSocketBool', 'Mobile') \ No newline at end of file + def arm_init(self, context): + self.add_output('ArmBoolSocket', 'Mobile') diff --git a/blender/arm/logicnode/native/LN_expression.py b/blender/arm/logicnode/native/LN_expression.py index e216e5b2..843869fd 100644 --- a/blender/arm/logicnode/native/LN_expression.py +++ b/blender/arm/logicnode/native/LN_expression.py @@ -9,14 +9,13 @@ class ExpressionNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'haxe' - property0: StringProperty(name='', default='') + property0: HaxeStringProperty('property0', name='', default='') - def init(self, context): - super(ExpressionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/native/LN_get_haxe_property.py b/blender/arm/logicnode/native/LN_get_haxe_property.py index 20721fc7..2748a873 100644 --- a/blender/arm/logicnode/native/LN_get_haxe_property.py +++ b/blender/arm/logicnode/native/LN_get_haxe_property.py @@ -9,9 +9,8 @@ class GetHaxePropertyNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'haxe' - def init(self, context): - super(GetHaxePropertyNode, self).init(context) - self.add_input('NodeSocketShader', 'Dynamic') - self.add_input('NodeSocketString', 'Property') + def arm_init(self, context): + self.add_input('ArmDynamicSocket', 'Dynamic') + self.add_input('ArmStringSocket', 'Property') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/native/LN_get_system_language.py b/blender/arm/logicnode/native/LN_get_system_language.py index 6989f361..4dcb6cc5 100644 --- a/blender/arm/logicnode/native/LN_get_system_language.py +++ b/blender/arm/logicnode/native/LN_get_system_language.py @@ -7,6 +7,5 @@ class GetSystemLanguage(ArmLogicTreeNode): arm_section = 'Native' arm_version = 1 - def init(self, context): - super(GetSystemLanguage, self).init(context) - self.add_output('NodeSocketString', 'Language') + def arm_init(self, context): + self.add_output('ArmStringSocket', 'Language') diff --git a/blender/arm/logicnode/native/LN_get_system_name.py b/blender/arm/logicnode/native/LN_get_system_name.py index 2f7bf8dc..6e08ac3e 100644 --- a/blender/arm/logicnode/native/LN_get_system_name.py +++ b/blender/arm/logicnode/native/LN_get_system_name.py @@ -8,11 +8,10 @@ class GetSystemName(ArmLogicTreeNode): arm_section = 'Native' arm_version = 1 - def init(self, context): - super(GetSystemName, self).init(context) - self.add_output('NodeSocketString', 'System Name') - self.add_output('NodeSocketBool', 'Windows') - self.add_output('NodeSocketBool', 'Linux') - self.add_output('NodeSocketBool', 'Mac') - self.add_output('NodeSocketBool', 'HTML5') - self.add_output('NodeSocketBool', 'Android') + def arm_init(self, context): + self.add_output('ArmStringSocket', 'System Name') + self.add_output('ArmBoolSocket', 'Windows') + self.add_output('ArmBoolSocket', 'Linux') + self.add_output('ArmBoolSocket', 'Mac') + self.add_output('ArmBoolSocket', 'HTML5') + self.add_output('ArmBoolSocket', 'Android') diff --git a/blender/arm/logicnode/native/LN_loadUrl.py b/blender/arm/logicnode/native/LN_loadUrl.py index c180f456..fbbed12b 100644 --- a/blender/arm/logicnode/native/LN_loadUrl.py +++ b/blender/arm/logicnode/native/LN_loadUrl.py @@ -6,7 +6,6 @@ class LoadUrlNode(ArmLogicTreeNode): bl_label = 'Load URL' arm_version = 1 - def init(self, context): - super(LoadUrlNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'URL') + self.add_input('ArmStringSocket', 'URL') diff --git a/blender/arm/logicnode/native/LN_print.py b/blender/arm/logicnode/native/LN_print.py index b04ea907..8ba303d7 100644 --- a/blender/arm/logicnode/native/LN_print.py +++ b/blender/arm/logicnode/native/LN_print.py @@ -6,9 +6,8 @@ class PrintNode(ArmLogicTreeNode): bl_label = 'Print' arm_version = 1 - def init(self, context): - super(PrintNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'String') + self.add_input('ArmStringSocket', 'String') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_read_file.py b/blender/arm/logicnode/native/LN_read_file.py index e87d152a..dae4805b 100644 --- a/blender/arm/logicnode/native/LN_read_file.py +++ b/blender/arm/logicnode/native/LN_read_file.py @@ -9,11 +9,10 @@ class ReadFileNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(ReadFileNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'File') - self.add_input('NodeSocketBool', 'Use cache', default_value=1) + self.add_input('ArmStringSocket', 'File') + self.add_input('ArmBoolSocket', 'Use cache', default_value=1) self.add_output('ArmNodeSocketAction', 'Loaded') - self.add_output('NodeSocketString', 'String') + self.add_output('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/native/LN_read_json.py b/blender/arm/logicnode/native/LN_read_json.py index 24192ce8..a48081fd 100644 --- a/blender/arm/logicnode/native/LN_read_json.py +++ b/blender/arm/logicnode/native/LN_read_json.py @@ -9,11 +9,10 @@ class ReadJsonNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(ReadJsonNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'File') - self.add_input('NodeSocketBool', 'Use cache', default_value=1) + self.add_input('ArmStringSocket', 'File') + self.add_input('ArmBoolSocket', 'Use cache', default_value=1) self.add_output('ArmNodeSocketAction', 'Loaded') - self.add_output('NodeSocketShader', 'Dynamic') + self.add_output('ArmDynamicSocket', 'Dynamic') diff --git a/blender/arm/logicnode/native/LN_read_storage.py b/blender/arm/logicnode/native/LN_read_storage.py index af9fd7c9..d8bd8bd8 100644 --- a/blender/arm/logicnode/native/LN_read_storage.py +++ b/blender/arm/logicnode/native/LN_read_storage.py @@ -9,9 +9,8 @@ class ReadStorageNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(ReadStorageNode, self).init(context) - self.add_input('NodeSocketString', 'Key') - self.add_input('NodeSocketString', 'Default') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Key') + self.add_input('ArmStringSocket', 'Default') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/native/LN_script.py b/blender/arm/logicnode/native/LN_script.py index d9cf4550..da0fb05d 100644 --- a/blender/arm/logicnode/native/LN_script.py +++ b/blender/arm/logicnode/native/LN_script.py @@ -15,15 +15,14 @@ class ScriptNode(ArmLogicTreeNode): return bpy.data.texts[self.property0_].as_string() if self.property0_ in bpy.data.texts else '' - property0_: StringProperty(name='Text', default='') + property0_: HaxeStringProperty('property0', name='Text', default='') - def init(self, context): - super(ScriptNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketArray', 'Array') self.add_output('ArmNodeSocketAction', 'Out') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') def draw_buttons(self, context, layout): layout.prop_search(self, 'property0_', bpy.data, 'texts', icon='NONE', text='') diff --git a/blender/arm/logicnode/native/LN_set_haxe_property.py b/blender/arm/logicnode/native/LN_set_haxe_property.py index 443123ff..bba925b3 100644 --- a/blender/arm/logicnode/native/LN_set_haxe_property.py +++ b/blender/arm/logicnode/native/LN_set_haxe_property.py @@ -9,11 +9,10 @@ class SetHaxePropertyNode(ArmLogicTreeNode): arm_section = 'haxe' arm_version = 1 - def init(self, context): - super(SetHaxePropertyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Dynamic') - self.add_input('NodeSocketString', 'Property') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Dynamic') + self.add_input('ArmStringSocket', 'Property') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_set_vibrate.py b/blender/arm/logicnode/native/LN_set_vibrate.py index 6136a47e..17ba464c 100644 --- a/blender/arm/logicnode/native/LN_set_vibrate.py +++ b/blender/arm/logicnode/native/LN_set_vibrate.py @@ -8,10 +8,9 @@ class SetVibrateNode(ArmLogicTreeNode): arm_section = 'Native' arm_version = 1 - def init(self, context): - super(SetVibrateNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketInt', 'Milliseconds') + self.add_input('ArmIntSocket', 'Milliseconds') self.inputs[-1].default_value = 100 self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_shutdown.py b/blender/arm/logicnode/native/LN_shutdown.py index 48b8587f..6292eafa 100644 --- a/blender/arm/logicnode/native/LN_shutdown.py +++ b/blender/arm/logicnode/native/LN_shutdown.py @@ -6,8 +6,7 @@ class ShutdownNode(ArmLogicTreeNode): bl_label = 'Shutdown' arm_version = 1 - def init(self, context): - super(ShutdownNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/native/LN_write_file.py b/blender/arm/logicnode/native/LN_write_file.py index 6b04d57d..2f5c4996 100644 --- a/blender/arm/logicnode/native/LN_write_file.py +++ b/blender/arm/logicnode/native/LN_write_file.py @@ -9,8 +9,7 @@ class WriteFileNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(WriteFileNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'File') - self.add_input('NodeSocketString', 'String') + self.add_input('ArmStringSocket', 'File') + self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/native/LN_write_json.py b/blender/arm/logicnode/native/LN_write_json.py index 366fc008..3feff3fa 100644 --- a/blender/arm/logicnode/native/LN_write_json.py +++ b/blender/arm/logicnode/native/LN_write_json.py @@ -9,8 +9,7 @@ class WriteJsonNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(WriteJsonNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'File') - self.add_input('NodeSocketShader', 'Dynamic') + self.add_input('ArmStringSocket', 'File') + self.add_input('ArmDynamicSocket', 'Dynamic') diff --git a/blender/arm/logicnode/native/LN_write_storage.py b/blender/arm/logicnode/native/LN_write_storage.py index b829a2bb..727cbca9 100644 --- a/blender/arm/logicnode/native/LN_write_storage.py +++ b/blender/arm/logicnode/native/LN_write_storage.py @@ -9,10 +9,9 @@ class WriteStorageNode(ArmLogicTreeNode): arm_section = 'file' arm_version = 1 - def init(self, context): - super(WriteStorageNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Key') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmStringSocket', 'Key') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/navmesh/LN_go_to_location.py b/blender/arm/logicnode/navmesh/LN_go_to_location.py index eba3cf97..e60e743f 100644 --- a/blender/arm/logicnode/navmesh/LN_go_to_location.py +++ b/blender/arm/logicnode/navmesh/LN_go_to_location.py @@ -6,11 +6,10 @@ class GoToLocationNode(ArmLogicTreeNode): bl_label = 'Go to Location' arm_version = 1 - def init(self, context): - super(GoToLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Location') + self.add_input('ArmVectorSocket', 'Location') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/navmesh/LN_navigable_location.py b/blender/arm/logicnode/navmesh/LN_navigable_location.py index 5d25dc36..3e8b8f8b 100644 --- a/blender/arm/logicnode/navmesh/LN_navigable_location.py +++ b/blender/arm/logicnode/navmesh/LN_navigable_location.py @@ -6,6 +6,5 @@ class NavigableLocationNode(ArmLogicTreeNode): bl_label = 'Navigable Location' arm_version = 1 - def init(self, context): - super(NavigableLocationNode, self).init(context) - self.add_output('NodeSocketShader', 'Location') + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Location') diff --git a/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py b/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py index c70dfba8..2ae40119 100644 --- a/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py +++ b/blender/arm/logicnode/navmesh/LN_pick_navmesh_location.py @@ -6,9 +6,8 @@ class PickLocationNode(ArmLogicTreeNode): bl_label = 'Pick NavMesh Location' arm_version = 1 - def init(self, context): - super(PickLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'NavMesh') - self.add_input('NodeSocketVector', 'Screen Coords') + self.add_input('ArmVectorSocket', 'Screen Coords') - self.add_output('NodeSocketVector', 'Location') + self.add_output('ArmVectorSocket', 'Location') diff --git a/blender/arm/logicnode/navmesh/LN_stop_agent.py b/blender/arm/logicnode/navmesh/LN_stop_agent.py index d7740fcf..ff49fc9f 100644 --- a/blender/arm/logicnode/navmesh/LN_stop_agent.py +++ b/blender/arm/logicnode/navmesh/LN_stop_agent.py @@ -6,8 +6,7 @@ class StopAgentNode(ArmLogicTreeNode): bl_label = 'Stop Agent' arm_version = 1 - def init(self, context): - super(StopAgentNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_get_distance.py b/blender/arm/logicnode/object/LN_get_distance.py index 0d4b5a3a..5885e890 100644 --- a/blender/arm/logicnode/object/LN_get_distance.py +++ b/blender/arm/logicnode/object/LN_get_distance.py @@ -9,9 +9,8 @@ class GetDistanceNode(ArmLogicTreeNode): bl_label = 'Get Distance' arm_version = 1 - def init(self, context): - super(GetDistanceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketFloat', 'Distance') + self.add_output('ArmFloatSocket', 'Distance') diff --git a/blender/arm/logicnode/object/LN_get_object_by_name.py b/blender/arm/logicnode/object/LN_get_object_by_name.py index fd8c893c..a19bdf06 100644 --- a/blender/arm/logicnode/object/LN_get_object_by_name.py +++ b/blender/arm/logicnode/object/LN_get_object_by_name.py @@ -4,20 +4,13 @@ from arm.logicnode.arm_nodes import * class GetObjectNode(ArmLogicTreeNode): - """Searches for a object that uses the given name and returns it.""" + """Searches for a object that uses the given name in the current active scene and returns it.""" + bl_idname = 'LNGetObjectNode' bl_label = 'Get Object by Name' arm_version = 1 - property0: PointerProperty( - type=bpy.types.Scene, name='Scene', - description='The scene from which to take the object') - - def init(self, context): - super(GetObjectNode, self).init(context) - self.add_input('NodeSocketString', 'Name') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketObject', 'Object') - - def draw_buttons(self, context, layout): - layout.prop_search(self, 'property0', bpy.data, "scenes") diff --git a/blender/arm/logicnode/object/LN_get_object_child.py b/blender/arm/logicnode/object/LN_get_object_child.py index 40fcf310..76409006 100644 --- a/blender/arm/logicnode/object/LN_get_object_child.py +++ b/blender/arm/logicnode/object/LN_get_object_child.py @@ -7,7 +7,8 @@ class GetChildNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('By Name', 'By Name', 'By Name'), ('Contains', 'Contains', 'Contains'), ('Starts With', 'Starts With', 'Starts With'), @@ -15,10 +16,9 @@ class GetChildNode(ArmLogicTreeNode): ], name='', default='By Name') - def init(self, context): - super(GetChildNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Parent') - self.add_input('NodeSocketString', 'Child Name') + self.add_input('ArmStringSocket', 'Child Name') self.add_output('ArmNodeSocketObject', 'Child') diff --git a/blender/arm/logicnode/object/LN_get_object_children.py b/blender/arm/logicnode/object/LN_get_object_children.py index 2ccbcecb..a23bf33f 100644 --- a/blender/arm/logicnode/object/LN_get_object_children.py +++ b/blender/arm/logicnode/object/LN_get_object_children.py @@ -7,8 +7,7 @@ class GetChildrenNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - def init(self, context): - super(GetChildrenNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Parent') self.add_output('ArmNodeSocketArray', 'Children') diff --git a/blender/arm/logicnode/object/LN_get_object_mesh.py b/blender/arm/logicnode/object/LN_get_object_mesh.py index ff9cf047..9339134e 100644 --- a/blender/arm/logicnode/object/LN_get_object_mesh.py +++ b/blender/arm/logicnode/object/LN_get_object_mesh.py @@ -7,8 +7,7 @@ class GetMeshNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(GetMeshNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketShader', 'Mesh') + self.add_output('ArmDynamicSocket', 'Mesh') diff --git a/blender/arm/logicnode/object/LN_get_object_name.py b/blender/arm/logicnode/object/LN_get_object_name.py index 8cd79d98..bb242d32 100644 --- a/blender/arm/logicnode/object/LN_get_object_name.py +++ b/blender/arm/logicnode/object/LN_get_object_name.py @@ -7,8 +7,7 @@ class GetNameNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(GetNameNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketString', 'Name') + self.add_output('ArmStringSocket', 'Name') diff --git a/blender/arm/logicnode/object/LN_get_object_offscreen.py b/blender/arm/logicnode/object/LN_get_object_offscreen.py index 602ce2d0..6bd85f8b 100644 --- a/blender/arm/logicnode/object/LN_get_object_offscreen.py +++ b/blender/arm/logicnode/object/LN_get_object_offscreen.py @@ -7,10 +7,9 @@ class GetObjectOffscreenNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(GetObjectOffscreenNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmNodeSocketObject', 'Object') - self.outputs.new('NodeSocketBool', 'Is Object Offscreen') - self.outputs.new('NodeSocketBool', 'Is Mesh Offscreen') - self.outputs.new('NodeSocketBool', 'Is Shadow Offscreen') + self.outputs.new('ArmBoolSocket', 'Is Object Offscreen') + self.outputs.new('ArmBoolSocket', 'Is Mesh Offscreen') + self.outputs.new('ArmBoolSocket', 'Is Shadow Offscreen') diff --git a/blender/arm/logicnode/object/LN_get_object_parent.py b/blender/arm/logicnode/object/LN_get_object_parent.py index d856d008..5ca239a6 100644 --- a/blender/arm/logicnode/object/LN_get_object_parent.py +++ b/blender/arm/logicnode/object/LN_get_object_parent.py @@ -9,8 +9,7 @@ class GetParentNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - def init(self, context): - super(GetParentNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Child') self.add_output('ArmNodeSocketObject', 'Parent') diff --git a/blender/arm/logicnode/object/LN_get_object_property.py b/blender/arm/logicnode/object/LN_get_object_property.py index 6c2792cf..3332677b 100644 --- a/blender/arm/logicnode/object/LN_get_object_property.py +++ b/blender/arm/logicnode/object/LN_get_object_property.py @@ -9,10 +9,9 @@ class GetPropertyNode(ArmLogicTreeNode): arm_version = 1 arm_section = 'props' - def init(self, context): - super(GetPropertyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Property') + self.add_input('ArmStringSocket', 'Property') - self.add_output('NodeSocketShader', 'Value') - self.add_output('NodeSocketString', 'Property') + self.add_output('ArmDynamicSocket', 'Value') + self.add_output('ArmStringSocket', 'Property') diff --git a/blender/arm/logicnode/object/LN_get_object_visible.py b/blender/arm/logicnode/object/LN_get_object_visible.py index b04eb1b7..e01cbf57 100644 --- a/blender/arm/logicnode/object/LN_get_object_visible.py +++ b/blender/arm/logicnode/object/LN_get_object_visible.py @@ -10,10 +10,9 @@ class GetVisibleNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(GetVisibleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketBool', 'Is Object Visible') - self.add_output('NodeSocketBool', 'Is Mesh Visible') - self.add_output('NodeSocketBool', 'Is Shadow Visible') + self.add_output('ArmBoolSocket', 'Is Object Visible') + self.add_output('ArmBoolSocket', 'Is Mesh Visible') + self.add_output('ArmBoolSocket', 'Is Shadow Visible') diff --git a/blender/arm/logicnode/object/LN_mesh.py b/blender/arm/logicnode/object/LN_mesh.py index 0d4b1f00..3f05dda2 100644 --- a/blender/arm/logicnode/object/LN_mesh.py +++ b/blender/arm/logicnode/object/LN_mesh.py @@ -9,11 +9,10 @@ class MeshNode(ArmLogicTreeNode): bl_label = 'Mesh' arm_version = 1 - property0_get: PointerProperty(name='', type=bpy.types.Mesh) + property0_get: HaxePointerProperty('property0_get', name='', type=bpy.types.Mesh) - def init(self, context): - super(MeshNode, self).init(context) - self.add_output('NodeSocketShader', 'Mesh', is_var=True) + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Mesh', is_var=True) def draw_buttons(self, context, layout): layout.prop_search(self, 'property0_get', bpy.data, 'meshes', icon='NONE', text='') diff --git a/blender/arm/logicnode/object/LN_object.py b/blender/arm/logicnode/object/LN_object.py index 3992bb40..8b9b0636 100644 --- a/blender/arm/logicnode/object/LN_object.py +++ b/blender/arm/logicnode/object/LN_object.py @@ -6,8 +6,7 @@ class ObjectNode(ArmLogicTreeNode): bl_label = 'Object' arm_version = 1 - def init(self, context): - super(ObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object In') self.add_output('ArmNodeSocketObject', 'Object Out', is_var=True) diff --git a/blender/arm/logicnode/object/LN_remove_object.py b/blender/arm/logicnode/object/LN_remove_object.py index 7783da82..78d0f956 100644 --- a/blender/arm/logicnode/object/LN_remove_object.py +++ b/blender/arm/logicnode/object/LN_remove_object.py @@ -6,8 +6,7 @@ class RemoveObjectNode(ArmLogicTreeNode): bl_label = 'Remove Object' arm_version = 1 - def init(self, context): - super(RemoveObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_remove_object_parent.py b/blender/arm/logicnode/object/LN_remove_object_parent.py index ffe60c13..80439051 100644 --- a/blender/arm/logicnode/object/LN_remove_object_parent.py +++ b/blender/arm/logicnode/object/LN_remove_object_parent.py @@ -7,10 +7,9 @@ class ClearParentNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - def init(self, context): - super(ClearParentNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Keep Transform', default_value=True) + self.add_input('ArmBoolSocket', 'Keep Transform', default_value=True) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_self_object.py b/blender/arm/logicnode/object/LN_self_object.py index aa4021ca..0dcbd0d0 100644 --- a/blender/arm/logicnode/object/LN_self_object.py +++ b/blender/arm/logicnode/object/LN_self_object.py @@ -6,6 +6,5 @@ class SelfObjectNode(ArmLogicTreeNode): bl_label = 'Self Object' arm_version = 1 - def init(self, context): - super(SelfObjectNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_set_object_mesh.py b/blender/arm/logicnode/object/LN_set_object_mesh.py index 803b25e1..114e0078 100644 --- a/blender/arm/logicnode/object/LN_set_object_mesh.py +++ b/blender/arm/logicnode/object/LN_set_object_mesh.py @@ -7,10 +7,9 @@ class SetMeshNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(SetMeshNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Mesh') + self.add_input('ArmDynamicSocket', 'Mesh') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_set_object_name.py b/blender/arm/logicnode/object/LN_set_object_name.py index 921675b9..236e25b6 100644 --- a/blender/arm/logicnode/object/LN_set_object_name.py +++ b/blender/arm/logicnode/object/LN_set_object_name.py @@ -7,10 +7,9 @@ class SetNameNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(SetNameNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Name') + self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_set_object_parent.py b/blender/arm/logicnode/object/LN_set_object_parent.py index 30b50d1a..c5e425ee 100644 --- a/blender/arm/logicnode/object/LN_set_object_parent.py +++ b/blender/arm/logicnode/object/LN_set_object_parent.py @@ -9,8 +9,7 @@ class SetParentNode(ArmLogicTreeNode): arm_section = 'relations' arm_version = 1 - def init(self, context): - super(SetParentNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') self.add_input('ArmNodeSocketObject', 'Parent', default_value='Parent') diff --git a/blender/arm/logicnode/object/LN_set_object_property.py b/blender/arm/logicnode/object/LN_set_object_property.py index f5f17e3f..326beb2f 100644 --- a/blender/arm/logicnode/object/LN_set_object_property.py +++ b/blender/arm/logicnode/object/LN_set_object_property.py @@ -14,11 +14,10 @@ class SetPropertyNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): - super(SetPropertyNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Property') - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmStringSocket', 'Property') + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_set_object_visible.py b/blender/arm/logicnode/object/LN_set_object_visible.py index 6fa99efb..09107e9e 100644 --- a/blender/arm/logicnode/object/LN_set_object_visible.py +++ b/blender/arm/logicnode/object/LN_set_object_visible.py @@ -9,19 +9,19 @@ class SetVisibleNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('object', 'Object', 'All object componenets visibility'), ('mesh', 'Mesh', 'Mesh visibility only'), ('shadow', 'Shadow', 'Shadow visibility only'), ], name='', default='object') - def init(self, context): - super(SetVisibleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketBool', 'Visible') - self.add_input('NodeSocketBool', 'Children', default_value=True) + self.add_input('ArmBoolSocket', 'Visible') + self.add_input('ArmBoolSocket', 'Children', default_value=True) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/object/LN_spawn_object.py b/blender/arm/logicnode/object/LN_spawn_object.py index 1288fc3f..de4ff22f 100644 --- a/blender/arm/logicnode/object/LN_spawn_object.py +++ b/blender/arm/logicnode/object/LN_spawn_object.py @@ -1,17 +1,17 @@ from arm.logicnode.arm_nodes import * class SpawnObjectNode(ArmLogicTreeNode): - """Spawns the given object. The spawned object has the same name of its instance, but they are threated as different objects.""" + """Spawns the given object if present in the current active scene. The spawned object has the same name of its instance, but they are treated as different objects.""" + bl_idname = 'LNSpawnObjectNode' bl_label = 'Spawn Object' arm_version = 1 - def init(self, context): - super(SpawnObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Transform') - self.add_input('NodeSocketBool', 'Children', default_value=True) + self.add_input('ArmDynamicSocket', 'Transform') + self.add_input('ArmBoolSocket', 'Children', default_value=True) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/object/LN_spawn_object_by_name.py b/blender/arm/logicnode/object/LN_spawn_object_by_name.py new file mode 100644 index 00000000..3fed2d76 --- /dev/null +++ b/blender/arm/logicnode/object/LN_spawn_object_by_name.py @@ -0,0 +1,24 @@ +from arm.logicnode.arm_nodes import * + +class SpawnObjectByNameNode(ArmLogicTreeNode): + """Spawns an object bearing the given name, even if not present in the active scene""" + bl_idname = 'LNSpawnObjectByNameNode' + bl_label = 'Spawn Object By Name' + arm_version = 1 + + property0: HaxePointerProperty( + 'property0', + type=bpy.types.Scene, name='Scene', + description='The scene from which to take the object') + + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') + self.add_input('ArmStringSocket', 'Name') + self.add_input('ArmDynamicSocket', 'Transform') + self.add_input('ArmBoolSocket', 'Children', default_value=True) + + self.add_output('ArmNodeSocketAction', 'Out') + self.add_output('ArmNodeSocketObject', 'Object') + + def draw_buttons(self, context, layout): + layout.prop_search(self, 'property0', bpy.data, "scenes") diff --git a/blender/arm/logicnode/physics/LN_Add_rigid_body.py b/blender/arm/logicnode/physics/LN_Add_rigid_body.py index 27deaa24..97fa8e80 100644 --- a/blender/arm/logicnode/physics/LN_Add_rigid_body.py +++ b/blender/arm/logicnode/physics/LN_Add_rigid_body.py @@ -1,12 +1,12 @@ from arm.logicnode.arm_nodes import * class AddRigidBodyNode(ArmLogicTreeNode): - """Adds a rigid body to an object if not already present. + """Adds a rigid body to an object if not already present. @option Advanced: Shows optional advanced options for rigid body. @option Shape: Shape of the rigid body including Box, Sphere, Capsule, Cone, Cylinder, Convex Hull and Mesh - + @input Object: Object to which rigid body is added. @input Mass: Mass of the rigid body. Must be > 0. @@ -14,9 +14,9 @@ class AddRigidBodyNode(ArmLogicTreeNode): @input Active: Rigid body actively participates in the physics world and will be affected by collisions @input Animated: Rigid body follows animation and will affect other active non-animated rigid bodies. - - @input Trigger: Rigid body behaves as a trigger and detects collision. However, rigd body does not contribute to or receive collissions. - + + @input Trigger: Rigid body behaves as a trigger and detects collision. However, rigd body does not contribute to or receive collissions. + @input Friction: Surface friction of the rigid body. Minimum value = 0, Preferred max value = 1. @input Bounciness: How elastic is the surface of the rigid body. Minimum value = 0, Preferred max value = 1. @@ -34,7 +34,7 @@ class AddRigidBodyNode(ArmLogicTreeNode): @input Use Deactivation: Deactive this rigid body when below the Linear and Angular velocity threshold. Enable to improve performance. @input Linear Velocity Threshold: Velocity below which decativation occurs if enabled. - + @input Angular Velocity Threshold: Velocity below which decativation occurs if enabled. @input Collision Group: A set of rigid bodies that can interact with each other @@ -43,9 +43,9 @@ class AddRigidBodyNode(ArmLogicTreeNode): @output Rigid body: Object to which rigid body was added. - @output Out: activated after rigid body is added. + @output Out: activated after rigid body is added. """ - + bl_idname = 'LNAddRigidBodyNode' bl_label = 'Add Rigid Body' arm_version = 1 @@ -59,18 +59,16 @@ class AddRigidBodyNode(ArmLogicTreeNode): further down.""" self.update_sockets(context) - @property - def property1(self): - return 'true' if self.property1_ else 'false' - - property1_: BoolProperty( + property1: HaxeBoolProperty( + 'property1', name="Advanced", description="Show advanced options", default=False, update=update_advanced ) - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Box', 'Box', 'Box'), ('Sphere', 'Sphere', 'Sphere'), ('Capsule', 'Capsule', 'Capsule'), @@ -80,18 +78,17 @@ class AddRigidBodyNode(ArmLogicTreeNode): ('Mesh', 'Mesh', 'Mesh')], name='Shape', default='Box') - def init(self, context): - super(AddRigidBodyNode, self).init(context) - + def arm_init(self, context): + self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketFloat', 'Mass', 1.0) - self.add_input('NodeSocketBool', 'Active', True) - self.add_input('NodeSocketBool', 'Animated', False) - self.add_input('NodeSocketBool', 'Trigger', False) - self.add_input('NodeSocketFloat', 'Friction', 0.5) - self.add_input('NodeSocketFloat', 'Bounciness', 0.0) - self.add_input('NodeSocketBool', 'Continuous Collision Detection', False) + self.add_input('ArmFloatSocket', 'Mass', 1.0) + self.add_input('ArmBoolSocket', 'Active', True) + self.add_input('ArmBoolSocket', 'Animated', False) + self.add_input('ArmBoolSocket', 'Trigger', False) + self.add_input('ArmFloatSocket', 'Friction', 0.5) + self.add_input('ArmFloatSocket', 'Bounciness', 0.0) + self.add_input('ArmBoolSocket', 'Continuous Collision Detection', False) self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Rigid body') @@ -110,17 +107,17 @@ class AddRigidBodyNode(ArmLogicTreeNode): # Add dynamic input sockets if self.property1_: - self.add_input('NodeSocketBool', 'Collision Margin', False) - self.add_input('NodeSocketFloat', 'Margin', 0.04) - self.add_input('NodeSocketFloat', 'Linear Damping', 0.04) - self.add_input('NodeSocketFloat', 'Angular Damping', 0.1) - self.add_input('NodeSocketBool', 'Use Deacivation') - self.add_input('NodeSocketFloat', 'Linear Velocity Threshold', 0.4) - self.add_input('NodeSocketFloat', 'Angular Velocity Threshold', 0.5) - self.add_input('NodeSocketInt', 'Collision Group', 1) - self.add_input('NodeSocketInt', 'Collision Mask', 1) + self.add_input('ArmBoolSocket', 'Collision Margin', False) + self.add_input('ArmFloatSocket', 'Margin', 0.04) + self.add_input('ArmFloatSocket', 'Linear Damping', 0.04) + self.add_input('ArmFloatSocket', 'Angular Damping', 0.1) + self.add_input('ArmBoolSocket', 'Use Deacivation') + self.add_input('ArmFloatSocket', 'Linear Velocity Threshold', 0.4) + self.add_input('ArmFloatSocket', 'Angular Velocity Threshold', 0.5) + self.add_input('ArmIntSocket', 'Collision Group', 1) + self.add_input('ArmIntSocket', 'Collision Mask', 1) def draw_buttons(self, context, layout): - layout.prop(self, "property1_") - layout.prop(self, 'property0') \ No newline at end of file + layout.prop(self, "property1") + layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/physics/LN_add_physics_constraint.py b/blender/arm/logicnode/physics/LN_add_physics_constraint.py index 039a536f..192fd804 100644 --- a/blender/arm/logicnode/physics/LN_add_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_add_physics_constraint.py @@ -3,11 +3,11 @@ from arm.logicnode.arm_nodes import * class AddPhysicsConstraintNode(ArmLogicTreeNode): """ Add a physics constraint to constrain two rigid bodies if not already present. - + @option Fixed: No fredom of movement. Relative positions and rotations of rigid bodies are fixed @option Point: Both rigid bodies are constrained at the pivot object. - + @option Hinge: Constrained objects can move only along angular Z axis of the pivot object. @option Slider: Constrained objects can move only along linear X axis of the pivot object. @@ -57,15 +57,15 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): @staticmethod def get_count_in(type_name): return { - 'Fixed': 0, - 'Point': 1, - 'Hinge': 2, + 'Fixed': 0, + 'Point': 1, + 'Hinge': 2, 'Slider': 3, - 'Piston': 4, + 'Piston': 4, 'Generic Spring': 5 }.get(type_name, 0) - def get_enum(self): + def get_enum(self): return self.get('property0', 0) def set_enum(self, value): @@ -91,31 +91,31 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): while (len(self.inputs) > 7): self.inputs.remove(self.inputs.values()[-1]) #Z ang limits - self.add_input('NodeSocketBool', 'Z angle') - self.add_input('NodeSocketFloat', 'Z ang lower', -45.0) - self.add_input('NodeSocketFloat', 'Z ang upper', 45.0) + self.add_input('ArmBoolSocket', 'Z angle') + self.add_input('ArmFloatSocket', 'Z ang lower', -45.0) + self.add_input('ArmFloatSocket', 'Z ang upper', 45.0) #Arguements for type Slider if (self.get_count_in(select_current) == 3): while (len(self.inputs) > 7): self.inputs.remove(self.inputs.values()[-1]) #X lin limits - self.add_input('NodeSocketBool', 'X linear') - self.add_input('NodeSocketFloat', 'X lin lower') - self.add_input('NodeSocketFloat', 'X lin upper') + self.add_input('ArmBoolSocket', 'X linear') + self.add_input('ArmFloatSocket', 'X lin lower') + self.add_input('ArmFloatSocket', 'X lin upper') #Arguements for type Piston if (self.get_count_in(select_current) == 4): while (len(self.inputs) > 7): self.inputs.remove(self.inputs.values()[-1]) #X lin limits - self.add_input('NodeSocketBool', 'X linear') - self.add_input('NodeSocketFloat', 'X lin lower') - self.add_input('NodeSocketFloat', 'X lin upper') + self.add_input('ArmBoolSocket', 'X linear') + self.add_input('ArmFloatSocket', 'X lin lower') + self.add_input('ArmFloatSocket', 'X lin upper') #X ang limits - self.add_input('NodeSocketBool', 'X angle') - self.add_input('NodeSocketFloat', 'X ang lower', -45.0) - self.add_input('NodeSocketFloat', 'X ang upper', 45.0) + self.add_input('ArmBoolSocket', 'X angle') + self.add_input('ArmFloatSocket', 'X ang lower', -45.0) + self.add_input('ArmFloatSocket', 'X ang upper', 45.0) #Arguements for type GenericSpring if (self.get_count_in(select_current) == 5): @@ -124,7 +124,8 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): self['property0'] = value - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Fixed', 'Fixed', 'Fixed'), ('Point', 'Point', 'Point'), ('Hinge', 'Hinge', 'Hinge'), @@ -132,24 +133,23 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): ('Piston', 'Piston', 'Piston'), ('Generic Spring', 'Generic Spring', 'Generic Spring')], name='Type', default='Fixed', set=set_enum, get=get_enum) - + def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(AddPhysicsConstraintNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Pivot Object') self.add_input('ArmNodeSocketObject', 'RB 1') self.add_input('ArmNodeSocketObject', 'RB 2') - self.add_input('NodeSocketBool', 'Disable Collissions') - self.add_input('NodeSocketBool', 'Breakable') - self.add_input('NodeSocketFloat', 'Breaking Threshold') + self.add_input('ArmBoolSocket', 'Disable Collissions') + self.add_input('ArmBoolSocket', 'Breakable') + self.add_input('ArmFloatSocket', 'Breaking Threshold') self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): layout.prop(self, 'property0') - + #GenericSpring: if (self.get_count_in(self.property0) == 5): grid0 = layout.grid_flow(row_major=True, columns=1, align=True) @@ -162,7 +162,7 @@ class AddPhysicsConstraintNode(ArmLogicTreeNode): column = row.column(align=True) op = column.operator('arm.node_add_input', text='Add Constraint', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketShader' + op.socket_type = 'ArmDynamicSocket' op.name_format = 'Constraint {0}'.format(len(self.inputs) - 6) column1 = row.column(align=True) op = column1.operator('arm.node_remove_input', text='', icon='X', emboss=True) diff --git a/blender/arm/logicnode/physics/LN_apply_force.py b/blender/arm/logicnode/physics/LN_apply_force.py index 57c4a9fa..20aba1b1 100644 --- a/blender/arm/logicnode/physics/LN_apply_force.py +++ b/blender/arm/logicnode/physics/LN_apply_force.py @@ -16,11 +16,10 @@ class ApplyForceNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyForceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Force') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Force') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_force_at_location.py b/blender/arm/logicnode/physics/LN_apply_force_at_location.py index 8d83efba..145627f4 100644 --- a/blender/arm/logicnode/physics/LN_apply_force_at_location.py +++ b/blender/arm/logicnode/physics/LN_apply_force_at_location.py @@ -19,13 +19,12 @@ class ApplyForceAtLocationNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyForceAtLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Force') - self.add_input('NodeSocketBool', 'Force On Local Axis') - self.add_input('NodeSocketVector', 'Location') - self.add_input('NodeSocketBool', 'Location On Local Axis') + self.add_input('ArmVectorSocket', 'Force') + self.add_input('ArmBoolSocket', 'Force On Local Axis') + self.add_input('ArmVectorSocket', 'Location') + self.add_input('ArmBoolSocket', 'Location On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_impulse.py b/blender/arm/logicnode/physics/LN_apply_impulse.py index c3ecec3b..68c2006d 100644 --- a/blender/arm/logicnode/physics/LN_apply_impulse.py +++ b/blender/arm/logicnode/physics/LN_apply_impulse.py @@ -16,11 +16,10 @@ class ApplyImpulseNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyImpulseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Impulse') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Impulse') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py b/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py index fbd0c53c..24d55a6d 100644 --- a/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py +++ b/blender/arm/logicnode/physics/LN_apply_impulse_at_location.py @@ -19,13 +19,12 @@ class ApplyImpulseAtLocationNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyImpulseAtLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Impulse') - self.add_input('NodeSocketBool', 'Impulse On Local Axis') - self.add_input('NodeSocketVector', 'Location') - self.add_input('NodeSocketBool', 'Location On Local Axis') + self.add_input('ArmVectorSocket', 'Impulse') + self.add_input('ArmBoolSocket', 'Impulse On Local Axis') + self.add_input('ArmVectorSocket', 'Location') + self.add_input('ArmBoolSocket', 'Location On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_torque.py b/blender/arm/logicnode/physics/LN_apply_torque.py index 88ca157e..70d2633e 100644 --- a/blender/arm/logicnode/physics/LN_apply_torque.py +++ b/blender/arm/logicnode/physics/LN_apply_torque.py @@ -7,11 +7,10 @@ class ApplyTorqueNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyTorqueNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Torque') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Torque') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_apply_torque_impulse.py b/blender/arm/logicnode/physics/LN_apply_torque_impulse.py index f067cf7f..2bf208c2 100644 --- a/blender/arm/logicnode/physics/LN_apply_torque_impulse.py +++ b/blender/arm/logicnode/physics/LN_apply_torque_impulse.py @@ -7,11 +7,10 @@ class ApplyTorqueImpulseNode(ArmLogicTreeNode): arm_section = 'force' arm_version = 1 - def init(self, context): - super(ApplyTorqueImpulseNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Torque') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Torque') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_get_rb_contacts.py b/blender/arm/logicnode/physics/LN_get_rb_contacts.py index fd1cf788..9f63fae5 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_contacts.py +++ b/blender/arm/logicnode/physics/LN_get_rb_contacts.py @@ -11,8 +11,7 @@ class GetContactsNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - def init(self, context): - super(GetContactsNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_output('ArmNodeSocketArray', 'Contacts') diff --git a/blender/arm/logicnode/physics/LN_get_rb_data.py b/blender/arm/logicnode/physics/LN_get_rb_data.py index 90637ff5..76ecb2e2 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_data.py +++ b/blender/arm/logicnode/physics/LN_get_rb_data.py @@ -7,20 +7,20 @@ class GetRigidBodyDataNode(ArmLogicTreeNode): arm_section = 'props' arm_version = 1 - def init(self, context): + def arm_init(self, context): self.inputs.new('ArmNodeSocketObject', 'Object') - self.outputs.new('NodeSocketBool', 'Is RB') - self.outputs.new('NodeSocketInt', 'Collision Group') - self.outputs.new('NodeSocketInt', 'Collision Mask') - self.outputs.new('NodeSocketBool', 'Is Animated') - self.outputs.new('NodeSocketBool', 'Is Static') - self.outputs.new('NodeSocketFloat', 'Angular Damping') - self.outputs.new('NodeSocketFloat', 'Linear Damping') - self.outputs.new('NodeSocketFloat', 'Friction') - self.outputs.new('NodeSocketFloat', 'Mass') - #self.outputs.new('NodeSocketString', 'Collision Shape') - #self.outputs.new('NodeSocketInt', 'Activation State') - #self.outputs.new('NodeSocketBool', 'Is Gravity Enabled') - #self.outputs.new(NodeSocketVector', Angular Factor') - #self.outputs.new('NodeSocketVector', Linear Factor') + self.outputs.new('ArmBoolSocket', 'Is RB') + self.outputs.new('ArmIntSocket', 'Collision Group') + self.outputs.new('ArmIntSocket', 'Collision Mask') + self.outputs.new('ArmBoolSocket', 'Is Animated') + self.outputs.new('ArmBoolSocket', 'Is Static') + self.outputs.new('ArmFloatSocket', 'Angular Damping') + self.outputs.new('ArmFloatSocket', 'Linear Damping') + self.outputs.new('ArmFloatSocket', 'Friction') + self.outputs.new('ArmFloatSocket', 'Mass') + #self.outputs.new('ArmStringSocket', 'Collision Shape') + #self.outputs.new('ArmIntSocket', 'Activation State') + #self.outputs.new('ArmBoolSocket', 'Is Gravity Enabled') + #self.outputs.new(ArmVectorSocket', Angular Factor') + #self.outputs.new('ArmVectorSocket', Linear Factor') diff --git a/blender/arm/logicnode/physics/LN_get_rb_first_contact.py b/blender/arm/logicnode/physics/LN_get_rb_first_contact.py index 9781c184..ea8ea7af 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_first_contact.py +++ b/blender/arm/logicnode/physics/LN_get_rb_first_contact.py @@ -10,8 +10,7 @@ class GetFirstContactNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - def init(self, context): - super(GetFirstContactNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_output('ArmNodeSocketObject', 'First Contact') diff --git a/blender/arm/logicnode/physics/LN_get_rb_velocity.py b/blender/arm/logicnode/physics/LN_get_rb_velocity.py index e4b889f9..18dcadeb 100644 --- a/blender/arm/logicnode/physics/LN_get_rb_velocity.py +++ b/blender/arm/logicnode/physics/LN_get_rb_velocity.py @@ -6,11 +6,10 @@ class GetVelocityNode(ArmLogicTreeNode): bl_label = 'Get RB Velocity' arm_version = 1 - def init(self, context): - super(GetVelocityNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketBool', 'Linear On Local Axis') - self.add_input('NodeSocketBool', 'Angular On Local Axis') + self.add_input('ArmBoolSocket', 'Linear On Local Axis') + self.add_input('ArmBoolSocket', 'Angular On Local Axis') - self.add_output('NodeSocketVector', 'Linear') - self.add_output('NodeSocketVector', 'Angular') + self.add_output('ArmVectorSocket', 'Linear') + self.add_output('ArmVectorSocket', 'Angular') diff --git a/blender/arm/logicnode/physics/LN_get_world_gravity.py b/blender/arm/logicnode/physics/LN_get_world_gravity.py index e91555f5..0fb4aec8 100644 --- a/blender/arm/logicnode/physics/LN_get_world_gravity.py +++ b/blender/arm/logicnode/physics/LN_get_world_gravity.py @@ -9,6 +9,5 @@ class GetGravityNode(ArmLogicTreeNode): bl_label = 'Get World Gravity' arm_version = 1 - def init(self, context): - super(GetGravityNode, self).init(context) - self.add_output('NodeSocketVector', 'World Gravity') + def arm_init(self, context): + self.add_output('ArmVectorSocket', 'World Gravity') diff --git a/blender/arm/logicnode/physics/LN_has_contact.py b/blender/arm/logicnode/physics/LN_has_contact.py index 9a1eb8da..627180e3 100644 --- a/blender/arm/logicnode/physics/LN_has_contact.py +++ b/blender/arm/logicnode/physics/LN_has_contact.py @@ -7,9 +7,8 @@ class HasContactNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - def init(self, context): - super(HasContactNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB 1') self.add_input('ArmNodeSocketObject', 'RB 2') - self.add_output('NodeSocketBool', 'Has Contact') + self.add_output('ArmBoolSocket', 'Has Contact') diff --git a/blender/arm/logicnode/physics/LN_has_contact_array.py b/blender/arm/logicnode/physics/LN_has_contact_array.py index 7fafd3e9..a7a6b759 100644 --- a/blender/arm/logicnode/physics/LN_has_contact_array.py +++ b/blender/arm/logicnode/physics/LN_has_contact_array.py @@ -7,9 +7,8 @@ class HasContactArrayNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - def init(self, context): - super(HasContactArrayNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmNodeSocketArray', 'RBs') - self.add_output('NodeSocketBool', 'Has Contact') + self.add_output('ArmBoolSocket', 'Has Contact') diff --git a/blender/arm/logicnode/physics/LN_on_contact.py b/blender/arm/logicnode/physics/LN_on_contact.py index 7daa4e86..8ca26aab 100644 --- a/blender/arm/logicnode/physics/LN_on_contact.py +++ b/blender/arm/logicnode/physics/LN_on_contact.py @@ -16,14 +16,14 @@ class OnContactNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('begin', 'Begin', 'The contact between the rigid bodies begins'), ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'), ('end', 'End', 'The contact between the rigid bodies ends')], name='', default='begin') - def init(self, context): - super(OnContactNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB 1') self.add_input('ArmNodeSocketObject', 'RB 2') diff --git a/blender/arm/logicnode/physics/LN_on_contact_array.py b/blender/arm/logicnode/physics/LN_on_contact_array.py index 1a300c2d..c39a6ad2 100644 --- a/blender/arm/logicnode/physics/LN_on_contact_array.py +++ b/blender/arm/logicnode/physics/LN_on_contact_array.py @@ -7,14 +7,14 @@ class OnContactArrayNode(ArmLogicTreeNode): arm_section = 'contact' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('begin', 'Begin', 'The contact between the rigid bodies begins'), ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'), ('end', 'End', 'The contact between the rigid bodies ends')], name='', default='begin') - def init(self, context): - super(OnContactArrayNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'RB') self.add_input('ArmNodeSocketArray', 'RBs') diff --git a/blender/arm/logicnode/physics/LN_on_volume_trigger.py b/blender/arm/logicnode/physics/LN_on_volume_trigger.py index 7b796d84..0c4cf2d5 100644 --- a/blender/arm/logicnode/physics/LN_on_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_on_volume_trigger.py @@ -1,7 +1,7 @@ from arm.logicnode.arm_nodes import * class OnVolumeTriggerNode(ArmLogicTreeNode): - """Activates the output when the given rigid body enter, overlap or leave the given trigger. + """Activates the output when the given object enters, overlaps or leaves the bounding box of the given trigger object. (Note: Works even if objects are not Rigid Bodies). @input RB: this object is taken as the entering object @input Trigger: this object is used as the volume trigger @@ -10,14 +10,14 @@ class OnVolumeTriggerNode(ArmLogicTreeNode): bl_label = 'On Volume Trigger' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('begin', 'Begin', 'The contact between the rigid bodies begins'), ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'), ('end', 'End', 'The contact between the rigid bodies ends')], name='', default='begin') - def init(self, context): - super(OnVolumeTriggerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object 1') self.add_input('ArmNodeSocketObject', 'Object 2') diff --git a/blender/arm/logicnode/physics/LN_physics_constraint.py b/blender/arm/logicnode/physics/LN_physics_constraint.py index 457f24ca..f8aafce9 100644 --- a/blender/arm/logicnode/physics/LN_physics_constraint.py +++ b/blender/arm/logicnode/physics/LN_physics_constraint.py @@ -1,5 +1,6 @@ from arm.logicnode.arm_nodes import * + class PhysicsConstraintNode(ArmLogicTreeNode): """ Custom physics constraint to add to `Add Physics Constarint` node. @@ -26,62 +27,48 @@ class PhysicsConstraintNode(ArmLogicTreeNode): def update_spring(self, context): self.update_sockets(context) - property0: EnumProperty( - items = [('Linear', 'Linear', 'Linear'), - ('Angular', 'Angular', 'Angular')], + property0: HaxeEnumProperty( + 'property0', + items=[('Linear', 'Linear', 'Linear'), + ('Angular', 'Angular', 'Angular')], name='Type', default='Linear') - - @property - def property1(self): - if(self.property1_ == 'X'): - return 'X' - if(self.property1_ == 'Y'): - return 'Y' - if(self.property1_ == 'Z'): - return 'Z' - property1_: EnumProperty( - items = [('X', 'X', 'X'), - ('Y', 'Y', 'Y'), - ('Z', 'Z', 'Z')], + property1: HaxeEnumProperty( + 'property1', + items=[('X', 'X', 'X'), + ('Y', 'Y', 'Y'), + ('Z', 'Z', 'Z')], name='Axis', default='X') - @property - def property2(self): - if(self.property2_): - return 'true' if self.property2_ else 'false' - - property2_: BoolProperty( + property2: HaxeBoolProperty( + 'property2', name="Spring", description="Is a spring constraint", default=False, update=update_spring ) - + def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super(PhysicsConstraintNode, self).init(context) - self.add_input('NodeSocketFloat', 'Lower limit') - self.add_input('NodeSocketFloat', 'Upper limit') - self.add_output('NodeSocketShader', 'Constraint') - - def update_sockets(self, context): + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Lower limit') + self.add_input('ArmFloatSocket', 'Upper limit') + self.add_output('ArmDynamicSocket', 'Constraint') - while (len(self.inputs) > 0): + def update_sockets(self, context): + while len(self.inputs) > 0: self.inputs.remove(self.inputs.values()[-1]) # Add dynamic input sockets - if self.property2_: - self.add_input('NodeSocketFloat', 'Stiffness', 10.0) - self.add_input('NodeSocketFloat', 'Damping', 0.5) + if self.property2: + self.add_input('ArmFloatSocket', 'Stiffness', 10.0) + self.add_input('ArmFloatSocket', 'Damping', 0.5) else: - self.add_input('NodeSocketFloat', 'Lower limit') - self.add_input('NodeSocketFloat', 'Upper limit') + self.add_input('ArmFloatSocket', 'Lower limit') + self.add_input('ArmFloatSocket', 'Upper limit') def draw_buttons(self, context, layout): layout.prop(self, 'property0') - layout.prop(self, 'property1_') - layout.prop(self, 'property2_') - + layout.prop(self, 'property1') + layout.prop(self, 'property2') diff --git a/blender/arm/logicnode/physics/LN_pick_rb.py b/blender/arm/logicnode/physics/LN_pick_rb.py index 8497e1f9..25d926b0 100644 --- a/blender/arm/logicnode/physics/LN_pick_rb.py +++ b/blender/arm/logicnode/physics/LN_pick_rb.py @@ -3,14 +3,14 @@ from arm.logicnode.arm_nodes import * class PickObjectNode(ArmLogicTreeNode): """Picks the rigid body in the given location using the screen coordinates (2D). - + @seeNode Mask - - @input Screen Coords: the location at which to pick, in screen + + @input Screen Coords: the location at which to pick, in screen coordinates @input Mask: a bit mask value to specify which objects are considered - + @output RB: the object that was hit @output Hit: the hit position in world coordinates """ @@ -19,10 +19,9 @@ class PickObjectNode(ArmLogicTreeNode): arm_section = 'ray' arm_version = 1 - def init(self, context): - super(PickObjectNode, self).init(context) - self.add_input('NodeSocketVector', 'Screen Coords') - self.add_input('NodeSocketInt', 'Mask', default_value=1) + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Screen Coords') + self.add_input('ArmIntSocket', 'Mask', default_value=1) self.add_output('ArmNodeSocketObject', 'RB') - self.add_output('NodeSocketVector', 'Hit') + self.add_output('ArmVectorSocket', 'Hit') diff --git a/blender/arm/logicnode/physics/LN_ray_cast.py b/blender/arm/logicnode/physics/LN_ray_cast.py index 6792bdb5..1fa4401a 100644 --- a/blender/arm/logicnode/physics/LN_ray_cast.py +++ b/blender/arm/logicnode/physics/LN_ray_cast.py @@ -22,12 +22,11 @@ class RayCastNode(ArmLogicTreeNode): arm_section = 'ray' arm_version = 1 - def init(self, context): - super(RayCastNode, self).init(context) - self.add_input('NodeSocketVector', 'From') - self.add_input('NodeSocketVector', 'To') - self.add_input('NodeSocketInt', 'Mask', default_value=1) + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'From') + self.add_input('ArmVectorSocket', 'To') + self.add_input('ArmIntSocket', 'Mask', default_value=1) self.add_output('ArmNodeSocketObject', 'RB') - self.add_output('NodeSocketVector', 'Hit') - self.add_output('NodeSocketVector', 'Normal') + self.add_output('ArmVectorSocket', 'Hit') + self.add_output('ArmVectorSocket', 'Normal') diff --git a/blender/arm/logicnode/physics/LN_remove_rb.py b/blender/arm/logicnode/physics/LN_remove_rb.py index 158493ae..7c05c68e 100644 --- a/blender/arm/logicnode/physics/LN_remove_rb.py +++ b/blender/arm/logicnode/physics/LN_remove_rb.py @@ -6,8 +6,7 @@ class RemovePhysicsNode (ArmLogicTreeNode): bl_label = 'Remove RB' arm_version = 1 - def init(self, context): - super(RemovePhysicsNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmNodeSocketAction', 'In') self.inputs.new('ArmNodeSocketObject', 'RB') diff --git a/blender/arm/logicnode/physics/LN_set_rb_activation_state.py b/blender/arm/logicnode/physics/LN_set_rb_activation_state.py index 0d193707..63eaec4c 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_activation_state.py +++ b/blender/arm/logicnode/physics/LN_set_rb_activation_state.py @@ -7,7 +7,8 @@ class SetActivationStateNode(ArmLogicTreeNode): bl_icon = 'NONE' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('inactive', 'Inactive', 'The rigid body simulation is deactivated'), ('active', 'Active', 'The rigid body simulation is activated'), ('always active', 'Always Active', 'The rigid body simulation is never deactivated'), @@ -15,8 +16,7 @@ class SetActivationStateNode(ArmLogicTreeNode): ], name='', default='inactive') - def init(self, context): - super(SetActivationStateNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmNodeSocketAction', 'In') self.inputs.new('ArmNodeSocketObject', 'RB') diff --git a/blender/arm/logicnode/physics/LN_set_rb_friction.py b/blender/arm/logicnode/physics/LN_set_rb_friction.py index d2c4e929..7efdcd79 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_friction.py +++ b/blender/arm/logicnode/physics/LN_set_rb_friction.py @@ -7,10 +7,9 @@ class SetFrictionNode (ArmLogicTreeNode): bl_icon = 'NONE' arm_version = 1 - def init(self, context): - super(SetFrictionNode, self).init(context) + def arm_init(self, context): self.inputs.new('ArmNodeSocketAction', 'In') self.inputs.new('ArmNodeSocketObject', 'RB') - self.inputs.new('NodeSocketFloat', 'Friction') + self.inputs.new('ArmFloatSocket', 'Friction') self.outputs.new('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py b/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py index a7109160..27a576a1 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py +++ b/blender/arm/logicnode/physics/LN_set_rb_gravity_enabled.py @@ -6,10 +6,9 @@ class SetGravityEnabledNode(ArmLogicTreeNode): bl_label = 'Set RB Gravity Enabled' arm_version = 1 - def init(self, context): - super(SetGravityEnabledNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketBool', 'Enabled') + self.add_input('ArmBoolSocket', 'Enabled') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_set_rb_velocity.py b/blender/arm/logicnode/physics/LN_set_rb_velocity.py index 024e980c..21980ae8 100644 --- a/blender/arm/logicnode/physics/LN_set_rb_velocity.py +++ b/blender/arm/logicnode/physics/LN_set_rb_velocity.py @@ -6,13 +6,12 @@ class SetVelocityNode(ArmLogicTreeNode): bl_label = 'Set RB Velocity' arm_version = 1 - def init(self, context): - super(SetVelocityNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'RB') - self.add_input('NodeSocketVector', 'Linear') - self.add_input('NodeSocketVector', 'Linear Factor', default_value=[1.0, 1.0, 1.0]) - self.add_input('NodeSocketVector', 'Angular') - self.add_input('NodeSocketVector', 'Angular Factor', default_value=[1.0, 1.0, 1.0]) + self.add_input('ArmVectorSocket', 'Linear') + self.add_input('ArmVectorSocket', 'Linear Factor', default_value=[1.0, 1.0, 1.0]) + self.add_input('ArmVectorSocket', 'Angular') + self.add_input('ArmVectorSocket', 'Angular Factor', default_value=[1.0, 1.0, 1.0]) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_set_world_gravity.py b/blender/arm/logicnode/physics/LN_set_world_gravity.py index c04fc6c4..f8923b29 100644 --- a/blender/arm/logicnode/physics/LN_set_world_gravity.py +++ b/blender/arm/logicnode/physics/LN_set_world_gravity.py @@ -9,9 +9,8 @@ class SetGravityNode(ArmLogicTreeNode): bl_label = 'Set World Gravity' arm_version = 1 - def init(self, context): - super(SetGravityNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketVector', 'Gravity') + self.add_input('ArmVectorSocket', 'Gravity') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/physics/LN_volume_trigger.py b/blender/arm/logicnode/physics/LN_volume_trigger.py index e9ba5bb1..af438c32 100644 --- a/blender/arm/logicnode/physics/LN_volume_trigger.py +++ b/blender/arm/logicnode/physics/LN_volume_trigger.py @@ -12,18 +12,18 @@ class VolumeTriggerNode(ArmLogicTreeNode): arm_section = 'misc' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('begin', 'Begin', 'The contact between the rigid bodies begins'), ('overlap', 'Overlap', 'The contact between the rigid bodies is happening'), ('end', 'End', 'The contact between the rigid bodies ends')], name='', default='begin') - def init(self, context): - super(VolumeTriggerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object 1') self.add_input('ArmNodeSocketObject', 'Object 2') - self.add_output('NodeSocketBool', 'Bool') + self.add_output('ArmBoolSocket', 'Bool') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py index bf1d7faf..f5f61d73 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_global_node.py @@ -7,12 +7,11 @@ class ColorgradingGetGlobalNode(ArmLogicTreeNode): arm_section = 'colorgrading' arm_version = 1 - def init(self, context): - super(ColorgradingGetGlobalNode, self).init(context) - self.add_output('NodeSocketFloat', 'Whitebalance') - self.add_output('NodeSocketVector', 'Tint') - self.add_output('NodeSocketVector', 'Saturation') - self.add_output('NodeSocketVector', 'Contrast') - self.add_output('NodeSocketVector', 'Gamma') - self.add_output('NodeSocketVector', 'Gain') - self.add_output('NodeSocketVector', 'Offset') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'Whitebalance') + self.add_output('ArmVectorSocket', 'Tint') + self.add_output('ArmVectorSocket', 'Saturation') + self.add_output('ArmVectorSocket', 'Contrast') + self.add_output('ArmVectorSocket', 'Gamma') + self.add_output('ArmVectorSocket', 'Gain') + self.add_output('ArmVectorSocket', 'Offset') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py index 598aba4f..c4f16f9f 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_highlight_node.py @@ -7,11 +7,10 @@ class ColorgradingGetHighlightNode(ArmLogicTreeNode): arm_section = 'colorgrading' arm_version = 1 - def init(self, context): - super(ColorgradingGetHighlightNode, self).init(context) - self.add_output('NodeSocketFloat', 'HightlightMin') - self.add_output('NodeSocketVector', 'Saturation') - self.add_output('NodeSocketVector', 'Contrast') - self.add_output('NodeSocketVector', 'Gamma') - self.add_output('NodeSocketVector', 'Gain') - self.add_output('NodeSocketVector', 'Offset') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'HightlightMin') + self.add_output('ArmVectorSocket', 'Saturation') + self.add_output('ArmVectorSocket', 'Contrast') + self.add_output('ArmVectorSocket', 'Gamma') + self.add_output('ArmVectorSocket', 'Gain') + self.add_output('ArmVectorSocket', 'Offset') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py index fde32c60..29bab2c2 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_midtone_node.py @@ -7,10 +7,9 @@ class ColorgradingGetMidtoneNode(ArmLogicTreeNode): arm_section = 'colorgrading' arm_version = 1 - def init(self, context): - super(ColorgradingGetMidtoneNode, self).init(context) - self.add_output('NodeSocketVector', 'Saturation') - self.add_output('NodeSocketVector', 'Contrast') - self.add_output('NodeSocketVector', 'Gamma') - self.add_output('NodeSocketVector', 'Gain') - self.add_output('NodeSocketVector', 'Offset') + def arm_init(self, context): + self.add_output('ArmVectorSocket', 'Saturation') + self.add_output('ArmVectorSocket', 'Contrast') + self.add_output('ArmVectorSocket', 'Gamma') + self.add_output('ArmVectorSocket', 'Gain') + self.add_output('ArmVectorSocket', 'Offset') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py index c4662185..06130071 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_get_shadow_node.py @@ -7,11 +7,10 @@ class ColorgradingGetShadowNode(ArmLogicTreeNode): arm_section = 'colorgrading' arm_version = 1 - def init(self, context): - super(ColorgradingGetShadowNode, self).init(context) - self.add_output('NodeSocketFloat', 'ShadowMax') - self.add_output('NodeSocketVector', 'Saturation') - self.add_output('NodeSocketVector', 'Contrast') - self.add_output('NodeSocketVector', 'Gamma') - self.add_output('NodeSocketVector', 'Gain') - self.add_output('NodeSocketVector', 'Offset') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'ShadowMax') + self.add_output('ArmVectorSocket', 'Saturation') + self.add_output('ArmVectorSocket', 'Contrast') + self.add_output('ArmVectorSocket', 'Gamma') + self.add_output('ArmVectorSocket', 'Gain') + self.add_output('ArmVectorSocket', 'Offset') diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py index 14f4e895..a72ac52a 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_global_node.py @@ -32,37 +32,37 @@ class ColorgradingSetGlobalNode(ArmLogicTreeNode): arm_version = 1 # TODO: RRESET FILE OPTION FOR THE BELOW - property0 : EnumProperty( + property0 : HaxeEnumProperty( + 'property0', items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) - property1 : StringProperty(name="Loaded Data", description="Loaded data - Just ignore", default="") + property1 : HaxeStringProperty('property1', name="Loaded Data", description="Loaded data - Just ignore", default="") filepath : StringProperty(name="Preset File", description="Postprocess colorgrading preset file", default="", subtype="FILE_PATH", update=set_data) def draw_nodes_uniform(self, context): - self.add_input('NodeSocketFloat', 'Whitebalance', default_value=6500.0) - self.add_input('NodeSocketColor', 'Tint', default_value=[1.0, 1.0, 1.0, 1.0]) - self.add_input('NodeSocketFloat', 'Saturation', default_value=1) - self.add_input('NodeSocketFloat', 'Contrast', default_value=1) - self.add_input('NodeSocketFloat', 'Gamma', default_value=1) - self.add_input('NodeSocketFloat', 'Gain', default_value=1) - self.add_input('NodeSocketFloat', 'Offset', default_value=1) + self.add_input('ArmFloatSocket', 'Whitebalance', default_value=6500.0) + self.add_input('ArmColorSocket', 'Tint', default_value=[1.0, 1.0, 1.0, 1.0]) + self.add_input('ArmFloatSocket', 'Saturation', default_value=1) + self.add_input('ArmFloatSocket', 'Contrast', default_value=1) + self.add_input('ArmFloatSocket', 'Gamma', default_value=1) + self.add_input('ArmFloatSocket', 'Gain', default_value=1) + self.add_input('ArmFloatSocket', 'Offset', default_value=1) def draw_nodes_rgb(self, context): - self.add_input('NodeSocketFloat', 'Whitebalance', default_value=6500.0) - self.add_input('NodeSocketVector', 'Tint', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Saturation', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Contrast', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gamma', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gain', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Offset', default_value=[1,1,1]) + self.add_input('ArmFloatSocket', 'Whitebalance', default_value=6500.0) + self.add_input('ArmVectorSocket', 'Tint', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Saturation', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Contrast', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gamma', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gain', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Offset', default_value=[1,1,1]) def draw_nodes_colorwheel(self, context): pass - def init(self, context): - super(ColorgradingSetGlobalNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') self.draw_nodes_uniform(context) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py index 2f34d316..01b23cd1 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_highlight_node.py @@ -32,35 +32,35 @@ class ColorgradingSetHighlightNode(ArmLogicTreeNode): arm_version = 1 # TODO: RRESET FILE OPTION FOR THE BELOW - property0 : EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) - property1 : StringProperty(name="Loaded Data", description="Loaded data - Just ignore", default="") + property1 : HaxeStringProperty('property1', name="Loaded Data", description="Loaded data - Just ignore", default="") filepath : StringProperty(name="Preset File", description="Postprocess colorgrading preset file", default="", subtype="FILE_PATH", update=set_data) def draw_nodes_uniform(self, context): - self.add_input('NodeSocketFloat', 'HighlightMin', default_value=0) - self.add_input('NodeSocketFloat', 'Saturation', default_value=1) - self.add_input('NodeSocketFloat', 'Contrast', default_value=1) - self.add_input('NodeSocketFloat', 'Gamma', default_value=1) - self.add_input('NodeSocketFloat', 'Gain', default_value=1) - self.add_input('NodeSocketFloat', 'Offset', default_value=1) + self.add_input('ArmFloatSocket', 'HighlightMin', default_value=0) + self.add_input('ArmFloatSocket', 'Saturation', default_value=1) + self.add_input('ArmFloatSocket', 'Contrast', default_value=1) + self.add_input('ArmFloatSocket', 'Gamma', default_value=1) + self.add_input('ArmFloatSocket', 'Gain', default_value=1) + self.add_input('ArmFloatSocket', 'Offset', default_value=1) def draw_nodes_rgb(self, context): - self.add_input('NodeSocketFloat', 'HighlightMin', default_value=0) - self.add_input('NodeSocketVector', 'Saturation', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Contrast', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gamma', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gain', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Offset', default_value=[1,1,1]) + self.add_input('ArmFloatSocket', 'HighlightMin', default_value=0) + self.add_input('ArmVectorSocket', 'Saturation', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Contrast', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gamma', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gain', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Offset', default_value=[1,1,1]) def draw_nodes_colorwheel(self, context): pass - def init(self, context): - super(ColorgradingSetHighlightNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') self.draw_nodes_uniform(context) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py index 5b1c31e6..8a5bd19c 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_midtone_node.py @@ -32,34 +32,34 @@ class ColorgradingSetMidtoneNode(ArmLogicTreeNode): arm_version = 1 # TODO: RRESET FILE OPTION FOR THE BELOW - property0 : EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) - property1 : StringProperty(name="Loaded Data", description="Loaded data - Just ignore", default="") + property1 : HaxeStringProperty('property1', name="Loaded Data", description="Loaded data - Just ignore", default="") filepath : StringProperty(name="Preset File", description="Postprocess colorgrading preset file", default="", subtype="FILE_PATH", update=set_data) def draw_nodes_uniform(self, context): - self.add_input('NodeSocketFloat', 'Saturation', default_value=1) - self.add_input('NodeSocketFloat', 'Contrast', default_value=1) - self.add_input('NodeSocketFloat', 'Gamma', default_value=1) - self.add_input('NodeSocketFloat', 'Gain', default_value=1) - self.add_input('NodeSocketFloat', 'Offset', default_value=1) + self.add_input('ArmFloatSocket', 'Saturation', default_value=1) + self.add_input('ArmFloatSocket', 'Contrast', default_value=1) + self.add_input('ArmFloatSocket', 'Gamma', default_value=1) + self.add_input('ArmFloatSocket', 'Gain', default_value=1) + self.add_input('ArmFloatSocket', 'Offset', default_value=1) def draw_nodes_rgb(self, context): - self.add_input('NodeSocketVector', 'Tint', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Saturation', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Contrast', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gamma', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gain', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Offset', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Tint', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Saturation', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Contrast', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gamma', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gain', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Offset', default_value=[1,1,1]) def draw_nodes_colorwheel(self, context): pass - def init(self, context): - super(ColorgradingSetMidtoneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') self.draw_nodes_uniform(context) diff --git a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py index b014cff4..bc05651d 100644 --- a/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py +++ b/blender/arm/logicnode/postprocess/LN_colorgrading_set_shadow_node.py @@ -32,35 +32,35 @@ class ColorgradingSetShadowNode(ArmLogicTreeNode): arm_version = 1 # TODO: RRESET FILE OPTION FOR THE BELOW - property0 : EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('RGB', 'RGB', 'RGB'), ('Uniform', 'Uniform', 'Uniform')], name='Mode', default='Uniform', update=update_node) - property1 : StringProperty(name="Loaded Data", description="Loaded data - Just ignore", default="") + property1 : HaxeStringProperty('property1', name="Loaded Data", description="Loaded data - Just ignore", default="") filepath : StringProperty(name="Preset File", description="Postprocess colorgrading preset file", default="", subtype="FILE_PATH", update=set_data) def draw_nodes_uniform(self, context): - self.add_input('NodeSocketFloat', 'ShadowMax', default_value=1) - self.add_input('NodeSocketFloat', 'Saturation', default_value=1) - self.add_input('NodeSocketFloat', 'Contrast', default_value=1) - self.add_input('NodeSocketFloat', 'Gamma', default_value=1) - self.add_input('NodeSocketFloat', 'Gain', default_value=1) - self.add_input('NodeSocketFloat', 'Offset', default_value=1) + self.add_input('ArmFloatSocket', 'ShadowMax', default_value=1) + self.add_input('ArmFloatSocket', 'Saturation', default_value=1) + self.add_input('ArmFloatSocket', 'Contrast', default_value=1) + self.add_input('ArmFloatSocket', 'Gamma', default_value=1) + self.add_input('ArmFloatSocket', 'Gain', default_value=1) + self.add_input('ArmFloatSocket', 'Offset', default_value=1) def draw_nodes_rgb(self, context): - self.add_input('NodeSocketFloat', 'ShadowMax', default_value=1) - self.add_input('NodeSocketVector', 'Saturation', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Contrast', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gamma', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Gain', default_value=[1,1,1]) - self.add_input('NodeSocketVector', 'Offset', default_value=[1,1,1]) + self.add_input('ArmFloatSocket', 'ShadowMax', default_value=1) + self.add_input('ArmVectorSocket', 'Saturation', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Contrast', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gamma', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Gain', default_value=[1,1,1]) + self.add_input('ArmVectorSocket', 'Offset', default_value=[1,1,1]) def draw_nodes_colorwheel(self, context): pass - def init(self, context): - super(ColorgradingSetShadowNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') self.draw_nodes_uniform(context) diff --git a/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py b/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py index e1774cdb..4dd36c42 100644 --- a/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_bloom_settings.py @@ -6,8 +6,7 @@ class BloomGetNode(ArmLogicTreeNode): bl_label = 'Get Bloom Settings' arm_version = 1 - def init(self, context): - super(BloomGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'Threshold') - self.add_output('NodeSocketFloat', 'Strength') - self.add_output('NodeSocketFloat', 'Radius') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'Threshold') + self.add_output('ArmFloatSocket', 'Strength') + self.add_output('ArmFloatSocket', 'Radius') diff --git a/blender/arm/logicnode/postprocess/LN_get_ca_settings.py b/blender/arm/logicnode/postprocess/LN_get_ca_settings.py index 60a9a429..f4064fb4 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ca_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ca_settings.py @@ -6,7 +6,6 @@ class ChromaticAberrationGetNode(ArmLogicTreeNode): bl_label = 'Get CA Settings' arm_version = 1 - def init(self, context): - super(ChromaticAberrationGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'Strength') - self.add_output('NodeSocketFloat', 'Samples') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'Strength') + self.add_output('ArmFloatSocket', 'Samples') diff --git a/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py b/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py index 33599052..786cee06 100644 --- a/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py +++ b/blender/arm/logicnode/postprocess/LN_get_camera_post_process.py @@ -6,15 +6,14 @@ class CameraGetNode(ArmLogicTreeNode): bl_label = 'Get Camera Post Process' arm_version = 1 - def init(self, context): - super(CameraGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'F-Stop') - self.add_output('NodeSocketFloat', 'Shutter Time') - self.add_output('NodeSocketFloat', 'ISO') - self.add_output('NodeSocketFloat', 'Exposure Compensation') - self.add_output('NodeSocketFloat', 'Fisheye Distortion') - self.add_output('NodeSocketBool', 'Auto Focus') - self.add_output('NodeSocketFloat', 'DOF Distance') - self.add_output('NodeSocketFloat', 'DOF Length') - self.add_output('NodeSocketFloat', 'DOF F-Stop') - self.add_output('NodeSocketFloat', 'Film Grain') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'F-Stop') + self.add_output('ArmFloatSocket', 'Shutter Time') + self.add_output('ArmFloatSocket', 'ISO') + self.add_output('ArmFloatSocket', 'Exposure Compensation') + self.add_output('ArmFloatSocket', 'Fisheye Distortion') + self.add_output('ArmBoolSocket', 'Auto Focus') + self.add_output('ArmFloatSocket', 'DOF Distance') + self.add_output('ArmFloatSocket', 'DOF Length') + self.add_output('ArmFloatSocket', 'DOF F-Stop') + self.add_output('ArmFloatSocket', 'Film Grain') diff --git a/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py b/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py index 6f8aa6a3..8ffa9a3d 100644 --- a/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_lenstexture_settings.py @@ -6,10 +6,9 @@ class LenstextureGetNode(ArmLogicTreeNode): bl_label = 'Get Lenstexture Settings' arm_version = 1 - def init(self, context): - super(LenstextureGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'Center Min Clip') - self.add_output('NodeSocketFloat', 'Center Max Clip') - self.add_output('NodeSocketFloat', 'Luminance Min') - self.add_output('NodeSocketFloat', 'Luminance Max') - self.add_output('NodeSocketFloat', 'Brightness Exponent') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'Center Min Clip') + self.add_output('ArmFloatSocket', 'Center Max Clip') + self.add_output('ArmFloatSocket', 'Luminance Min') + self.add_output('ArmFloatSocket', 'Luminance Max') + self.add_output('ArmFloatSocket', 'Brightness Exponent') diff --git a/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py b/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py index 3aecab2c..d263ae60 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ssao_settings.py @@ -6,8 +6,7 @@ class SSAOGetNode(ArmLogicTreeNode): bl_label = 'Get SSAO Settings' arm_version = 1 - def init(self, context): - super(SSAOGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'Radius') - self.add_output('NodeSocketFloat', 'Strength') - self.add_output('NodeSocketFloat', 'Max Steps') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'Radius') + self.add_output('ArmFloatSocket', 'Strength') + self.add_output('ArmFloatSocket', 'Max Steps') diff --git a/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py b/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py index 782ce30f..684685c9 100644 --- a/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py +++ b/blender/arm/logicnode/postprocess/LN_get_ssr_settings.py @@ -6,10 +6,9 @@ class SSRGetNode(ArmLogicTreeNode): bl_label = 'Get SSR Settings' arm_version = 1 - def init(self, context): - super(SSRGetNode, self).init(context) - self.add_output('NodeSocketFloat', 'SSR Step') - self.add_output('NodeSocketFloat', 'SSR Step Min') - self.add_output('NodeSocketFloat', 'SSR Search') - self.add_output('NodeSocketFloat', 'SSR Falloff') - self.add_output('NodeSocketFloat', 'SSR Jitter') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'SSR Step') + self.add_output('ArmFloatSocket', 'SSR Step Min') + self.add_output('ArmFloatSocket', 'SSR Search') + self.add_output('ArmFloatSocket', 'SSR Falloff') + self.add_output('ArmFloatSocket', 'SSR Jitter') diff --git a/blender/arm/logicnode/postprocess/LN_lenstexture_set.py b/blender/arm/logicnode/postprocess/LN_lenstexture_set.py index 2fe8a79d..53698ccd 100644 --- a/blender/arm/logicnode/postprocess/LN_lenstexture_set.py +++ b/blender/arm/logicnode/postprocess/LN_lenstexture_set.py @@ -6,13 +6,12 @@ class LenstextureSetNode(ArmLogicTreeNode): bl_label = 'Set Lenstexture' arm_version = 1 - def init(self, context): - super(LenstextureSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Center Min Clip', default_value=0.1) - self.add_input('NodeSocketFloat', 'Center Max Clip', default_value=0.5) - self.add_input('NodeSocketFloat', 'Luminance Min', default_value=0.10) - self.add_input('NodeSocketFloat', 'Luminance Max', default_value=2.50) - self.add_input('NodeSocketFloat', 'Brightness Exponent', default_value=2.0) + self.add_input('ArmFloatSocket', 'Center Min Clip', default_value=0.1) + self.add_input('ArmFloatSocket', 'Center Max Clip', default_value=0.5) + self.add_input('ArmFloatSocket', 'Luminance Min', default_value=0.10) + self.add_input('ArmFloatSocket', 'Luminance Max', default_value=2.50) + self.add_input('ArmFloatSocket', 'Brightness Exponent', default_value=2.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py b/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py index ffa85450..7c463480 100644 --- a/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_bloom_settings.py @@ -6,11 +6,10 @@ class BloomSetNode(ArmLogicTreeNode): bl_label = 'Set Bloom Settings' arm_version = 1 - def init(self, context): - super(BloomSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Threshold', default_value=1.00) - self.add_input('NodeSocketFloat', 'Strength', default_value=3.50) - self.add_input('NodeSocketFloat', 'Radius', default_value=3.0) + self.add_input('ArmFloatSocket', 'Threshold', default_value=1.00) + self.add_input('ArmFloatSocket', 'Strength', default_value=3.50) + self.add_input('ArmFloatSocket', 'Radius', default_value=3.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_ca_settings.py b/blender/arm/logicnode/postprocess/LN_set_ca_settings.py index 18b6f0a1..5031cd0a 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ca_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ca_settings.py @@ -6,10 +6,9 @@ class ChromaticAberrationSetNode(ArmLogicTreeNode): bl_label = 'Set CA Settings' arm_version = 1 - def init(self, context): - super(ChromaticAberrationSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Strength', default_value=2.0) - self.add_input('NodeSocketInt', 'Samples', default_value=32) + self.add_input('ArmFloatSocket', 'Strength', default_value=2.0) + self.add_input('ArmIntSocket', 'Samples', default_value=32) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py b/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py index a21d8ddc..e6a59bc4 100644 --- a/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py +++ b/blender/arm/logicnode/postprocess/LN_set_camera_post_process.py @@ -6,19 +6,18 @@ class CameraSetNode(ArmLogicTreeNode): bl_label = 'Set Camera Post Process' arm_version = 1 - def init(self, context): - super(CameraSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'F-stop', default_value=2.0) - self.add_input('NodeSocketFloat', 'Shutter Time', default_value=1.0) - self.add_input('NodeSocketFloat', 'ISO', default_value=100.0) - self.add_input('NodeSocketFloat', 'Exposure Compensation', default_value=0.0) - self.add_input('NodeSocketFloat', 'Fisheye Distortion', default_value=0.01) - self.add_input('NodeSocketBool', 'Auto Focus', default_value=True) - self.add_input('NodeSocketFloat', 'DoF Distance', default_value=10.0) - self.add_input('NodeSocketFloat', 'DoF Length', default_value=160.0) - self.add_input('NodeSocketFloat', 'DoF F-Stop', default_value=128.0) - self.add_input('NodeSocketInt', 'Tonemapper', default_value=0.0) - self.add_input('NodeSocketFloat', 'Film Grain', default_value=2.0) + self.add_input('ArmFloatSocket', 'F-stop', default_value=2.0) + self.add_input('ArmFloatSocket', 'Shutter Time', default_value=1.0) + self.add_input('ArmFloatSocket', 'ISO', default_value=100.0) + self.add_input('ArmFloatSocket', 'Exposure Compensation', default_value=0.0) + self.add_input('ArmFloatSocket', 'Fisheye Distortion', default_value=0.01) + self.add_input('ArmBoolSocket', 'Auto Focus', default_value=True) + self.add_input('ArmFloatSocket', 'DoF Distance', default_value=10.0) + self.add_input('ArmFloatSocket', 'DoF Length', default_value=160.0) + self.add_input('ArmFloatSocket', 'DoF F-Stop', default_value=128.0) + self.add_input('ArmIntSocket', 'Tonemapper', default_value=0.0) + self.add_input('ArmFloatSocket', 'Film Grain', default_value=2.0) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py b/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py index 199d5d6c..e401b955 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ssao_settings.py @@ -6,11 +6,10 @@ class SSAOSetNode(ArmLogicTreeNode): bl_label = 'Set SSAO Settings' arm_version = 1 - def init(self, context): - super(SSAOSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'Radius', default_value=1.0) - self.add_input('NodeSocketFloat', 'Strength', default_value=5.0) - self.add_input('NodeSocketInt', 'Max Steps', default_value=8) + self.add_input('ArmFloatSocket', 'Radius', default_value=1.0) + self.add_input('ArmFloatSocket', 'Strength', default_value=5.0) + self.add_input('ArmIntSocket', 'Max Steps', default_value=8) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py b/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py index 40995378..9e3da739 100644 --- a/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py +++ b/blender/arm/logicnode/postprocess/LN_set_ssr_settings.py @@ -6,13 +6,12 @@ class SSRSetNode(ArmLogicTreeNode): bl_label = 'Set SSR Settings' arm_version = 1 - def init(self, context): - super(SSRSetNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketFloat', 'SSR Step', default_value=0.04) - self.add_input('NodeSocketFloat', 'SSR Step Min', default_value=0.05) - self.add_input('NodeSocketFloat', 'SSR Search', default_value=5.0) - self.add_input('NodeSocketFloat', 'SSR Falloff', default_value=5.0) - self.add_input('NodeSocketFloat', 'SSR Jitter', default_value=0.6) + self.add_input('ArmFloatSocket', 'SSR Step', default_value=0.04) + self.add_input('ArmFloatSocket', 'SSR Step Min', default_value=0.05) + self.add_input('ArmFloatSocket', 'SSR Search', default_value=5.0) + self.add_input('ArmFloatSocket', 'SSR Falloff', default_value=5.0) + self.add_input('ArmFloatSocket', 'SSR Jitter', default_value=0.6) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/random/LN_random_boolean.py b/blender/arm/logicnode/random/LN_random_boolean.py index 8fef83ae..1a2f46b3 100644 --- a/blender/arm/logicnode/random/LN_random_boolean.py +++ b/blender/arm/logicnode/random/LN_random_boolean.py @@ -7,6 +7,5 @@ class RandomBooleanNode(ArmLogicTreeNode): bl_label = 'Random Boolean' arm_version = 1 - def init(self, context): - super(RandomBooleanNode, self).init(context) - self.add_output('NodeSocketBool', 'Bool') + def arm_init(self, context): + self.add_output('ArmBoolSocket', 'Bool') diff --git a/blender/arm/logicnode/random/LN_random_choice.py b/blender/arm/logicnode/random/LN_random_choice.py index ad6b4f7e..7ba4749a 100644 --- a/blender/arm/logicnode/random/LN_random_choice.py +++ b/blender/arm/logicnode/random/LN_random_choice.py @@ -7,8 +7,7 @@ class RandomChoiceNode(ArmLogicTreeNode): bl_label = 'Random Choice' arm_version = 1 - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketArray', 'Array') - self.add_output('NodeSocketShader', 'Value') + self.add_output('ArmDynamicSocket', 'Value') diff --git a/blender/arm/logicnode/random/LN_random_color.py b/blender/arm/logicnode/random/LN_random_color.py index 82ef9bf8..9d142b15 100644 --- a/blender/arm/logicnode/random/LN_random_color.py +++ b/blender/arm/logicnode/random/LN_random_color.py @@ -7,6 +7,5 @@ class RandomColorNode(ArmLogicTreeNode): bl_label = 'Random Color' arm_version = 1 - def init(self, context): - super(RandomColorNode, self).init(context) - self.add_output('NodeSocketColor', 'Color') + def arm_init(self, context): + self.add_output('ArmColorSocket', 'Color') diff --git a/blender/arm/logicnode/random/LN_random_float.py b/blender/arm/logicnode/random/LN_random_float.py index 6303d5e0..7bb08dde 100644 --- a/blender/arm/logicnode/random/LN_random_float.py +++ b/blender/arm/logicnode/random/LN_random_float.py @@ -7,9 +7,8 @@ class RandomFloatNode(ArmLogicTreeNode): bl_label = 'Random Float' arm_version = 1 - def init(self, context): - super(RandomFloatNode, self).init(context) - self.add_input('NodeSocketFloat', 'Min') - self.add_input('NodeSocketFloat', 'Max', default_value=1.0) - # self.add_input('NodeSocketInt', 'Seed') - self.add_output('NodeSocketFloat', 'Float') + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Min') + self.add_input('ArmFloatSocket', 'Max', default_value=1.0) + # self.add_input('ArmIntSocket', 'Seed') + self.add_output('ArmFloatSocket', 'Float') diff --git a/blender/arm/logicnode/random/LN_random_integer.py b/blender/arm/logicnode/random/LN_random_integer.py index dfb3442f..166a2f0a 100644 --- a/blender/arm/logicnode/random/LN_random_integer.py +++ b/blender/arm/logicnode/random/LN_random_integer.py @@ -7,8 +7,7 @@ class RandomIntegerNode(ArmLogicTreeNode): bl_label = 'Random Integer' arm_version = 1 - def init(self, context): - super(RandomIntegerNode, self).init(context) - self.add_input('NodeSocketInt', 'Min') - self.add_input('NodeSocketInt', 'Max', default_value=2) - self.add_output('NodeSocketInt', 'Int') + def arm_init(self, context): + self.add_input('ArmIntSocket', 'Min') + self.add_input('ArmIntSocket', 'Max', default_value=2) + self.add_output('ArmIntSocket', 'Int') diff --git a/blender/arm/logicnode/random/LN_random_output.py b/blender/arm/logicnode/random/LN_random_output.py index fc41b35d..72f249f3 100644 --- a/blender/arm/logicnode/random/LN_random_output.py +++ b/blender/arm/logicnode/random/LN_random_output.py @@ -11,8 +11,7 @@ class RandomOutputNode(ArmLogicTreeNode): def __init__(self): array_nodes[str(id(self))] = self - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') diff --git a/blender/arm/logicnode/random/LN_random_vector.py b/blender/arm/logicnode/random/LN_random_vector.py index 6986a8a0..b49beabe 100644 --- a/blender/arm/logicnode/random/LN_random_vector.py +++ b/blender/arm/logicnode/random/LN_random_vector.py @@ -7,8 +7,7 @@ class RandomVectorNode(ArmLogicTreeNode): bl_label = 'Random Vector' arm_version = 1 - def init(self, context): - super(RandomVectorNode, self).init(context) - self.add_input('NodeSocketVector', 'Min', default_value=[-1.0, -1.0, -1.0]) - self.add_input('NodeSocketVector', 'Max', default_value=[1.0, 1.0, 1.0]) - self.add_output('NodeSocketVector', 'Vector') + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Min', default_value=[-1.0, -1.0, -1.0]) + self.add_input('ArmVectorSocket', 'Max', default_value=[1.0, 1.0, 1.0]) + self.add_output('ArmVectorSocket', 'Vector') diff --git a/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py b/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py index 52022aa4..81511a03 100644 --- a/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_msaa_quality.py @@ -5,7 +5,8 @@ class RpMSAANode(ArmLogicTreeNode): bl_idname = 'LNRpMSAANode' bl_label = 'Set MSAA Quality' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('1', '1', '1'), ('2', '2', '2'), ('4', '4', '4'), @@ -14,8 +15,7 @@ class RpMSAANode(ArmLogicTreeNode): ], name='', default='1') - def init(self, context): - super(RpMSAANode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py b/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py index a93d3f9e..faa883bb 100644 --- a/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_post_process_quality.py @@ -5,7 +5,8 @@ class RpConfigNode(ArmLogicTreeNode): bl_idname = 'LNRpConfigNode' bl_label = 'Set Post Process Quality' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('SSGI', 'SSGI', 'SSGI'), ('SSR', 'SSR', 'SSR'), ('Bloom', 'Bloom', 'Bloom'), @@ -14,10 +15,9 @@ class RpConfigNode(ArmLogicTreeNode): ], name='', default='SSGI') - def init(self, context): - super(RpConfigNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketBool', 'Enable') + self.add_input('ArmBoolSocket', 'Enable') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py b/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py index 676e6994..8afbcd75 100644 --- a/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py +++ b/blender/arm/logicnode/renderpath/LN_set_shader_uniform.py @@ -15,13 +15,14 @@ class SetShaderUniformNode(ArmLogicTreeNode): self.inputs.remove(self.inputs[2]) if self.property0 == 'int': - self.add_input('NodeSocketInt', 'Int') + self.add_input('ArmIntSocket', 'Int') elif self.property0 == 'float': - self.add_input('NodeSocketFloat', 'Float') + self.add_input('ArmFloatSocket', 'Float') elif self.property0 in ('vec2', 'vec3', 'vec4'): - self.add_input('NodeSocketVector', 'Vector') + self.add_input('ArmVectorSocket', 'Vector') - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('int', 'int', 'int'), ('float', 'float', 'float'), ('vec2', 'vec2', 'vec2'), @@ -32,11 +33,10 @@ class SetShaderUniformNode(ArmLogicTreeNode): description="The type of the uniform", update=on_update_uniform_type) - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Uniform Name') - self.add_input('NodeSocketFloat', 'Float') + self.add_input('ArmStringSocket', 'Uniform Name') + self.add_input('ArmFloatSocket', 'Float') self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py b/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py index ba42ac3a..be16096e 100644 --- a/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_shadows_quality.py @@ -5,15 +5,15 @@ class RpShadowQualityNode(ArmLogicTreeNode): bl_idname = 'LNRpShadowQualityNode' bl_label = 'Set Shadows Quality' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('High', 'High', 'High'), ('Medium', 'Medium', 'Medium'), ('Low', 'Low', 'Low') ], name='', default='Medium') - def init(self, context): - super(RpShadowQualityNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py b/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py index f102f21c..7608749a 100644 --- a/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py +++ b/blender/arm/logicnode/renderpath/LN_set_ssaa_quality.py @@ -5,7 +5,8 @@ class RpSuperSampleNode(ArmLogicTreeNode): bl_idname = 'LNRpSuperSampleNode' bl_label = 'Set SSAA Quality' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('1', '1', '1'), ('1.5', '1.5', '1.5'), ('2', '2', '2'), @@ -13,8 +14,7 @@ class RpSuperSampleNode(ArmLogicTreeNode): ], name='', default='1') - def init(self, context): - super(RpSuperSampleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/replacement.py b/blender/arm/logicnode/replacement.py index d062a7bc..4a710e6b 100644 --- a/blender/arm/logicnode/replacement.py +++ b/blender/arm/logicnode/replacement.py @@ -10,6 +10,7 @@ Original author: @niacdoial import os.path import time import traceback +import typing from typing import Dict, List, Optional, Tuple import bpy.props @@ -17,6 +18,15 @@ import bpy.props import arm.log as log import arm.logicnode.arm_nodes as arm_nodes import arm.logicnode.arm_sockets +import arm.node_utils as node_utils + +if arm.is_reload(__name__): + log = arm.reload_module(log) + arm_nodes = arm.reload_module(arm_nodes) + arm.logicnode.arm_sockets = arm.reload_module(arm.logicnode.arm_sockets) + node_utils = arm.reload_module(node_utils) +else: + arm.enable_reload(__name__) # List of errors that occurred during the replacement # Format: (error identifier, node.bl_idname (or None), tree name, exception traceback (optional)) @@ -61,28 +71,27 @@ class NodeReplacement: """ in_socks = {i: i for i in range(len(node.inputs))} out_socks = {i: i for i in range(len(node.outputs))} - props = {} - i = 0 - # finding all the properties fo a node is not possible in a clean way for now. - # so, I'll assume their names start with "property", and list all the node's attributes that fulfill that condition. - # next, to check that those are indeed properties (in the blender sense), we need to check the class's type annotations. - # those annotations are not even instances of bpy.types.Property, but tuples, with the first element being a function accessible at bpy.props.XXXProperty - property_types = [] - for possible_prop_type in dir(bpy.props): - if possible_prop_type.endswith('Property'): - property_types.append(getattr(bpy.props, possible_prop_type)) + # Find all properties for this node + props = {} possible_properties = [] for attrname in dir(node): + # We assume that property names start with 'property' if attrname.startswith('property'): possible_properties.append(attrname) + for attrname in possible_properties: + # Search in type annotations if attrname not in node.__annotations__: continue - if not isinstance(node.__annotations__[attrname], tuple): + + # Properties must be annotated with '_PropertyDeferred', see + # https://developer.blender.org/rB37e6a1995ac7eeabd5b6a56621ad5a850dae4149 + # and https://developer.blender.org/rBc44c611c6d8c6ae071b48efb5fc07168f18cd17e + if not isinstance(node.__annotations__[attrname], bpy.props._PropertyDeferred): continue - if node.__annotations__[attrname][0] in property_types: - props[attrname] = attrname + + props[attrname] = attrname return NodeReplacement( node.bl_idname, node.arm_version, node.bl_idname, type(node).arm_version, @@ -128,15 +137,11 @@ def replace(tree: bpy.types.NodeTree, node: 'ArmLogicTreeNode'): if isinstance(response, arm_nodes.ArmLogicTreeNode): newnode = response # some misc. properties - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select + copy_basic_node_props(from_node=node, to_node=newnode) elif isinstance(response, list): # a list of nodes: for newnode in response: - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select + copy_basic_node_props(from_node=node, to_node=newnode) elif isinstance(response, NodeReplacement): replacement = response @@ -151,9 +156,7 @@ def replace(tree: bpy.types.NodeTree, node: 'ArmLogicTreeNode'): raise LookupError("The provided NodeReplacement doesn't seem to correspond to the node needing replacement") # some misc. properties - newnode.parent = node.parent - newnode.location = node.location - newnode.select = node.select + copy_basic_node_props(from_node=node, to_node=newnode) # now, use the `replacement` to hook up the new node correctly # start by applying defaults @@ -254,7 +257,8 @@ def replace_all(): print(f"A node whose class doesn't exist was found in node tree \"{tree_name}\"", file=reportf) elif error_type == 'update failed': print(f"A node of type {node_class} in tree \"{tree_name}\" failed to be updated, " - f"because there is no (longer?) an update routine for this version of the node.", file=reportf) + f"because there is no (longer?) an update routine for this version of the node. Original exception:" + "\n" + tb + "\n", file=reportf) elif error_type == 'future version': print(f"A node of type {node_class} in tree \"{tree_name}\" seemingly comes from a future version of armory. " f"Please check whether your version of armory is up to date", file=reportf) @@ -273,3 +277,87 @@ def replace_all(): log.error(f'There were errors in the node update procedure, a detailed report has been written to {reportfile}') bpy.ops.arm.show_node_update_errors() + + +def copy_basic_node_props(from_node: bpy.types.Node, to_node: bpy.types.Node): + to_node.parent = from_node.parent + to_node.location = from_node.location + to_node.select = from_node.select + + to_node.arm_logic_id = from_node.arm_logic_id + to_node.arm_watch = from_node.arm_watch + + +def node_compat_sdk2108(): + """SDK 21.08 broke compatibility with older nodes as nodes now use + custom sockets even for Blender's default data types and custom + property "constructors". This allows to listen for events for the + live patch system. + + In order to update older nodes this routine is used. It creates a + full copy of the nodes and replaces all properties and sockets with + their new equivalents. + """ + for tree in bpy.data.node_groups: + if tree.bl_idname == "ArmLogicTreeType": + for node in list(tree.nodes): + # Don't raise exceptions for invalid unregistered nodes, this + # function didn't cause the registration problem if there is one + if not node.__class__.is_registered_node_type(): + continue + + if node.type in ('FRAME', 'REROUTE'): + continue + + newnode = tree.nodes.new(node.__class__.bl_idname) + copy_basic_node_props(from_node=node, to_node=newnode) + + # Also copy the node's version number to _not_ prevent actual node + # replacement after this step + newnode.arm_version = node.arm_version + + # First replace all properties + for prop_name, prop in typing.get_type_hints(node.__class__, {}, {}).items(): + if isinstance(prop, bpy.props._PropertyDeferred): + if hasattr(node, prop_name) and hasattr(newnode, prop_name): + setattr(newnode, prop_name, getattr(node, prop_name)) + + # Replace sockets with new socket types + socket_replacements = { + 'NodeSocketBool': 'ArmBoolSocket', + 'NodeSocketColor': 'ArmColorSocket', + 'NodeSocketFloat': 'ArmFloatSocket', + 'NodeSocketInt': 'ArmIntSocket', + 'NodeSocketShader': 'ArmDynamicSocket', + 'NodeSocketString': 'ArmStringSocket', + 'NodeSocketVector': 'ArmVectorSocket' + } + + # Recreate all sockets + newnode.inputs.clear() + for inp in node.inputs: + inp_idname = inp.bl_idname + inp_idname = socket_replacements.get(inp_idname, inp_idname) + + newinp = newnode.inputs.new(inp_idname, inp.name, identifier=inp.identifier) + + if inp.is_linked: + for link in inp.links: + tree.links.new(link.from_socket, newinp) + else: + node_utils.set_socket_default(newinp, node_utils.get_socket_default(inp)) + + newnode.outputs.clear() + for out in node.outputs: + out_idname = out.bl_idname + out_idname = socket_replacements.get(out_idname, out_idname) + + newout = newnode.outputs.new(out_idname, out.name, identifier=out.identifier) + + if out.is_linked: + for link in out.links: + tree.links.new(newout, link.to_socket) + else: + node_utils.set_socket_default(newout, node_utils.get_socket_default(out)) + + tree.nodes.remove(node) diff --git a/blender/arm/logicnode/scene/LN_collection.py b/blender/arm/logicnode/scene/LN_collection.py index 54442f2f..95d9c5ad 100644 --- a/blender/arm/logicnode/scene/LN_collection.py +++ b/blender/arm/logicnode/scene/LN_collection.py @@ -12,10 +12,9 @@ class GroupNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - property0: PointerProperty(name='', type=bpy.types.Collection) + property0: HaxePointerProperty('property0', name='', type=bpy.types.Collection) - def init(self, context): - super(GroupNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array') def draw_buttons(self, context, layout): diff --git a/blender/arm/logicnode/scene/LN_create_collection.py b/blender/arm/logicnode/scene/LN_create_collection.py index 9c728c21..94e98a01 100644 --- a/blender/arm/logicnode/scene/LN_create_collection.py +++ b/blender/arm/logicnode/scene/LN_create_collection.py @@ -7,9 +7,8 @@ class CreateCollectionNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - def init(self, context): - super(CreateCollectionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Collection') + self.add_input('ArmStringSocket', 'Collection') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/scene/LN_get_collection.py b/blender/arm/logicnode/scene/LN_get_collection.py index 14fb5d7a..74a0f05d 100644 --- a/blender/arm/logicnode/scene/LN_get_collection.py +++ b/blender/arm/logicnode/scene/LN_get_collection.py @@ -10,8 +10,7 @@ class GetGroupNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - def init(self, context): - super(GetGroupNode, self).init(context) - self.add_input('NodeSocketString', 'Name') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Name') self.add_output('ArmNodeSocketArray', 'Objects') diff --git a/blender/arm/logicnode/scene/LN_get_scene_active.py b/blender/arm/logicnode/scene/LN_get_scene_active.py index a263b428..f99c72c0 100644 --- a/blender/arm/logicnode/scene/LN_get_scene_active.py +++ b/blender/arm/logicnode/scene/LN_get_scene_active.py @@ -6,6 +6,5 @@ class ActiveSceneNode(ArmLogicTreeNode): bl_label = 'Get Scene Active' arm_version = 1 - def init(self, context): - super(ActiveSceneNode, self).init(context) - self.add_output('NodeSocketShader', 'Scene') + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Scene') diff --git a/blender/arm/logicnode/scene/LN_get_scene_root.py b/blender/arm/logicnode/scene/LN_get_scene_root.py index 658cbfa2..2dc4e428 100644 --- a/blender/arm/logicnode/scene/LN_get_scene_root.py +++ b/blender/arm/logicnode/scene/LN_get_scene_root.py @@ -6,6 +6,5 @@ class SceneRootNode(ArmLogicTreeNode): bl_label = 'Get Scene Root' arm_version = 1 - def init(self, context): - super(SceneRootNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/scene/LN_global_object.py b/blender/arm/logicnode/scene/LN_global_object.py index 015da8ca..ca001743 100644 --- a/blender/arm/logicnode/scene/LN_global_object.py +++ b/blender/arm/logicnode/scene/LN_global_object.py @@ -7,6 +7,5 @@ class GlobalObjectNode(ArmLogicTreeNode): bl_label = 'Global Object' arm_version = 1 - def init(self, context): - super(GlobalObjectNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketObject', 'Object') diff --git a/blender/arm/logicnode/scene/LN_remove_collection.py b/blender/arm/logicnode/scene/LN_remove_collection.py index b7a40ce9..813c13d6 100644 --- a/blender/arm/logicnode/scene/LN_remove_collection.py +++ b/blender/arm/logicnode/scene/LN_remove_collection.py @@ -7,9 +7,8 @@ class RemoveGroupNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - def init(self, context): - super(RemoveGroupNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketString', 'Collection') + self.add_input('ArmStringSocket', 'Collection') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/scene/LN_remove_scene_active.py b/blender/arm/logicnode/scene/LN_remove_scene_active.py index 815b941a..4fd2ac66 100644 --- a/blender/arm/logicnode/scene/LN_remove_scene_active.py +++ b/blender/arm/logicnode/scene/LN_remove_scene_active.py @@ -6,8 +6,7 @@ class RemoveActiveSceneNode(ArmLogicTreeNode): bl_label = 'Remove Scene Active' arm_version = 1 - def init(self, context): - super(RemoveActiveSceneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/scene/LN_scene.py b/blender/arm/logicnode/scene/LN_scene.py index 29f68205..c4849838 100644 --- a/blender/arm/logicnode/scene/LN_scene.py +++ b/blender/arm/logicnode/scene/LN_scene.py @@ -9,11 +9,10 @@ class SceneNode(ArmLogicTreeNode): bl_label = 'Scene' arm_version = 1 - property0_get: PointerProperty(name='', type=bpy.types.Scene) + property0_get: HaxePointerProperty('property0_get', name='', type=bpy.types.Scene) - def init(self, context): - super(SceneNode, self).init(context) - self.add_output('NodeSocketShader', 'Scene') + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Scene') def draw_buttons(self, context, layout): layout.prop_search(self, 'property0_get', bpy.data, 'scenes', icon='NONE', text='') diff --git a/blender/arm/logicnode/scene/LN_set_scene_active.py b/blender/arm/logicnode/scene/LN_set_scene_active.py index 4a231d83..95138c85 100644 --- a/blender/arm/logicnode/scene/LN_set_scene_active.py +++ b/blender/arm/logicnode/scene/LN_set_scene_active.py @@ -6,10 +6,9 @@ class SetSceneNode(ArmLogicTreeNode): bl_label = 'Set Scene Active' arm_version = 1 - def init(self, context): - super(SetSceneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Scene') + self.add_input('ArmDynamicSocket', 'Scene') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Root') diff --git a/blender/arm/logicnode/scene/LN_spawn_collection.py b/blender/arm/logicnode/scene/LN_spawn_collection.py index b8d6caaf..c03ed012 100644 --- a/blender/arm/logicnode/scene/LN_spawn_collection.py +++ b/blender/arm/logicnode/scene/LN_spawn_collection.py @@ -28,12 +28,11 @@ class SpawnCollectionNode(ArmLogicTreeNode): arm_section = 'collection' arm_version = 1 - property0: PointerProperty(name='Collection', type=bpy.types.Collection) + property0: HaxePointerProperty('property0', name='Collection', type=bpy.types.Collection) - def init(self, context): - super(SpawnCollectionNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketArray', 'Top-Level Objects') diff --git a/blender/arm/logicnode/scene/LN_spawn_scene.py b/blender/arm/logicnode/scene/LN_spawn_scene.py index d9d42a2b..e3df2adb 100644 --- a/blender/arm/logicnode/scene/LN_spawn_scene.py +++ b/blender/arm/logicnode/scene/LN_spawn_scene.py @@ -6,11 +6,10 @@ class SpawnSceneNode(ArmLogicTreeNode): bl_label = 'Spawn Scene' arm_version = 1 - def init(self, context): - super(SpawnSceneNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Scene') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Scene') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') self.add_output('ArmNodeSocketObject', 'Root') diff --git a/blender/arm/logicnode/sound/LN_pause_speaker.py b/blender/arm/logicnode/sound/LN_pause_speaker.py index b211a62c..14fb5852 100644 --- a/blender/arm/logicnode/sound/LN_pause_speaker.py +++ b/blender/arm/logicnode/sound/LN_pause_speaker.py @@ -11,8 +11,7 @@ class PauseSpeakerNode(ArmLogicTreeNode): bl_label = 'Pause Speaker' arm_version = 1 - def init(self, context): - super(PauseSpeakerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Speaker') diff --git a/blender/arm/logicnode/sound/LN_play_sound.py b/blender/arm/logicnode/sound/LN_play_sound.py index f87838ea..47e622d4 100644 --- a/blender/arm/logicnode/sound/LN_play_sound.py +++ b/blender/arm/logicnode/sound/LN_play_sound.py @@ -31,32 +31,36 @@ class PlaySoundNode(ArmLogicTreeNode): bl_width_default = 200 arm_version = 1 - property0: PointerProperty(name='', type=bpy.types.Sound) - property1: BoolProperty( + property0: HaxePointerProperty('property0', name='', type=bpy.types.Sound) + property1: HaxeBoolProperty( + 'property1', name='Loop', description='Play the sound in a loop', default=False) - property2: BoolProperty( + property2: HaxeBoolProperty( + 'property2', name='Retrigger', description='Play the sound from the beginning every time', default=False) - property3: BoolProperty( + property3: HaxeBoolProperty( + 'property3', name='Use Custom Sample Rate', description='If enabled, override the default sample rate', default=False) - property4: IntProperty( + property4: HaxeIntProperty( + 'property4', name='Sample Rate', description='Set the sample rate used to play this sound', default=44100, min=0) - property5: BoolProperty( + property5: HaxeBoolProperty( + 'property5', name='Stream', description='Stream the sound from disk', default=False ) - def init(self, context): - super(PlaySoundNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'Play') self.add_input('ArmNodeSocketAction', 'Pause') self.add_input('ArmNodeSocketAction', 'Stop') diff --git a/blender/arm/logicnode/sound/LN_play_speaker.py b/blender/arm/logicnode/sound/LN_play_speaker.py index b91446b9..d4fdeb84 100644 --- a/blender/arm/logicnode/sound/LN_play_speaker.py +++ b/blender/arm/logicnode/sound/LN_play_speaker.py @@ -11,8 +11,7 @@ class PlaySpeakerNode(ArmLogicTreeNode): bl_label = 'Play Speaker' arm_version = 1 - def init(self, context): - super(PlaySpeakerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Speaker') diff --git a/blender/arm/logicnode/sound/LN_stop_speaker.py b/blender/arm/logicnode/sound/LN_stop_speaker.py index 9b5ba8bc..4940133d 100644 --- a/blender/arm/logicnode/sound/LN_stop_speaker.py +++ b/blender/arm/logicnode/sound/LN_stop_speaker.py @@ -11,8 +11,7 @@ class StopSpeakerNode(ArmLogicTreeNode): bl_label = 'Stop Speaker' arm_version = 1 - def init(self, context): - super(StopSpeakerNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Speaker') diff --git a/blender/arm/logicnode/string/LN_concatenate_string.py b/blender/arm/logicnode/string/LN_concatenate_string.py index 381957c0..3467ddee 100644 --- a/blender/arm/logicnode/string/LN_concatenate_string.py +++ b/blender/arm/logicnode/string/LN_concatenate_string.py @@ -10,16 +10,15 @@ class ConcatenateStringNode(ArmLogicTreeNode): super(ConcatenateStringNode, self).__init__() array_nodes[str(id(self))] = self - def init(self, context): - super(ConcatenateStringNode, self).init(context) - self.add_input('NodeSocketString', 'Input 0') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'Input 0') - self.add_output('NodeSocketString', 'String') + self.add_output('ArmStringSocket', 'String') def draw_buttons(self, context, layout): row = layout.row(align=True) op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True) op.node_index = str(id(self)) - op.socket_type = 'NodeSocketString' + op.socket_type = 'ArmStringSocket' op = row.operator('arm.node_remove_input', text='', icon='X', emboss=True) op.node_index = str(id(self)) diff --git a/blender/arm/logicnode/string/LN_parse_float.py b/blender/arm/logicnode/string/LN_parse_float.py index 2cb6b8cb..5d14bc34 100644 --- a/blender/arm/logicnode/string/LN_parse_float.py +++ b/blender/arm/logicnode/string/LN_parse_float.py @@ -7,8 +7,7 @@ class ParseFloatNode(ArmLogicTreeNode): arm_section = 'parse' arm_version = 1 - def init(self, context): - super(ParseFloatNode, self).init(context) - self.add_output('NodeSocketFloat', 'Float') + def arm_init(self, context): + self.add_output('ArmFloatSocket', 'Float') - self.add_input('NodeSocketString', 'String') + self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/string/LN_split_string.py b/blender/arm/logicnode/string/LN_split_string.py index 793716eb..3b7478aa 100644 --- a/blender/arm/logicnode/string/LN_split_string.py +++ b/blender/arm/logicnode/string/LN_split_string.py @@ -6,9 +6,8 @@ class SplitStringNode(ArmLogicTreeNode): bl_label = 'Split String' arm_version = 1 - def init(self, context): - super(SplitStringNode, self).init(context) + def arm_init(self, context): self.add_output('ArmNodeSocketArray', 'Array') - self.add_input('NodeSocketString', 'String') - self.add_input('NodeSocketString', 'Split') + self.add_input('ArmStringSocket', 'String') + self.add_input('ArmStringSocket', 'Split') diff --git a/blender/arm/logicnode/string/LN_string.py b/blender/arm/logicnode/string/LN_string.py index bc37a715..d7f4d6c1 100644 --- a/blender/arm/logicnode/string/LN_string.py +++ b/blender/arm/logicnode/string/LN_string.py @@ -6,8 +6,7 @@ class StringNode(ArmLogicTreeNode): bl_label = 'String' arm_version = 1 - def init(self, context): - super(StringNode, self).init(context) - self.add_input('NodeSocketString', 'String In') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'String In') - self.add_output('NodeSocketString', 'String Out', is_var=True) + self.add_output('ArmStringSocket', 'String Out', is_var=True) diff --git a/blender/arm/logicnode/string/LN_string_case.py b/blender/arm/logicnode/string/LN_string_case.py index 30f8f276..22619e41 100644 --- a/blender/arm/logicnode/string/LN_string_case.py +++ b/blender/arm/logicnode/string/LN_string_case.py @@ -5,17 +5,17 @@ class CaseStringNode(ArmLogicTreeNode): bl_idname = 'LNCaseStringNode' bl_label = 'String Case' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Upper Case', 'Upper Case', 'Upper Case'), ('Lower Case', 'Lower Case', 'Lower Case'), ], name='', default='Upper Case') - def init(self, context): - super(CaseStringNode, self).init(context) - self.add_input('NodeSocketString', 'String In') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'String In') - self.add_output('NodeSocketString', 'String Out') + self.add_output('ArmStringSocket', 'String Out') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/string/LN_string_contains.py b/blender/arm/logicnode/string/LN_string_contains.py index 1f99961a..f9811baf 100644 --- a/blender/arm/logicnode/string/LN_string_contains.py +++ b/blender/arm/logicnode/string/LN_string_contains.py @@ -5,19 +5,19 @@ class ContainsStringNode(ArmLogicTreeNode): bl_idname = 'LNContainsStringNode' bl_label = 'String Contains' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Contains', 'Contains', 'Contains'), ('Starts With', 'Starts With', 'Starts With'), ('Ends With', 'Ends With', 'Ends With'), ], name='', default='Contains') - def init(self, context): - super(ContainsStringNode, self).init(context) - self.add_input('NodeSocketString', 'String') - self.add_input('NodeSocketString', 'Find') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'String') + self.add_input('ArmStringSocket', 'Find') - self.add_output('NodeSocketBool', 'Contains') + self.add_output('ArmBoolSocket', 'Contains') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/string/LN_string_length.py b/blender/arm/logicnode/string/LN_string_length.py index 2e4f249d..5edf39cb 100644 --- a/blender/arm/logicnode/string/LN_string_length.py +++ b/blender/arm/logicnode/string/LN_string_length.py @@ -6,8 +6,7 @@ class LengthStringNode(ArmLogicTreeNode): bl_label = 'String Length' arm_version = 1 - def init(self, context): - super(LengthStringNode, self).init(context) - self.add_output('NodeSocketInt', 'Length') + def arm_init(self, context): + self.add_output('ArmIntSocket', 'Length') - self.add_input('NodeSocketString', 'String') + self.add_input('ArmStringSocket', 'String') diff --git a/blender/arm/logicnode/string/LN_sub_string.py b/blender/arm/logicnode/string/LN_sub_string.py index 60c65213..647d0e64 100644 --- a/blender/arm/logicnode/string/LN_sub_string.py +++ b/blender/arm/logicnode/string/LN_sub_string.py @@ -6,10 +6,9 @@ class SubStringNode(ArmLogicTreeNode): bl_label = 'Sub String' arm_version = 1 - def init(self, context): - super(SubStringNode, self).init(context) - self.add_input('NodeSocketString', 'String In') - self.add_input('NodeSocketInt', 'Start') - self.add_input('NodeSocketInt', 'End') + def arm_init(self, context): + self.add_input('ArmStringSocket', 'String In') + self.add_input('ArmIntSocket', 'Start') + self.add_input('ArmIntSocket', 'End') - self.add_output('NodeSocketString', 'String Out') + self.add_output('ArmStringSocket', 'String Out') diff --git a/blender/arm/logicnode/trait/LN_add_trait_to_object.py b/blender/arm/logicnode/trait/LN_add_trait_to_object.py index 04a22b31..aac4a5f3 100644 --- a/blender/arm/logicnode/trait/LN_add_trait_to_object.py +++ b/blender/arm/logicnode/trait/LN_add_trait_to_object.py @@ -6,10 +6,9 @@ class AddTraitNode(ArmLogicTreeNode): bl_label = 'Add Trait to Object' arm_version = 1 - def init(self, context): - super(AddTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/trait/LN_get_object_trait.py b/blender/arm/logicnode/trait/LN_get_object_trait.py index 86c9dd15..b0890ab2 100644 --- a/blender/arm/logicnode/trait/LN_get_object_trait.py +++ b/blender/arm/logicnode/trait/LN_get_object_trait.py @@ -7,9 +7,8 @@ class GetTraitNode(ArmLogicTreeNode): bl_label = 'Get Object Trait' arm_version = 1 - def init(self, context): - super(GetTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketString', 'Name') + self.add_input('ArmStringSocket', 'Name') - self.add_output('NodeSocketShader', 'Trait') + self.add_output('ArmDynamicSocket', 'Trait') diff --git a/blender/arm/logicnode/trait/LN_get_object_traits.py b/blender/arm/logicnode/trait/LN_get_object_traits.py index ab80f3ce..7fd1dfae 100644 --- a/blender/arm/logicnode/trait/LN_get_object_traits.py +++ b/blender/arm/logicnode/trait/LN_get_object_traits.py @@ -6,8 +6,7 @@ class GetObjectTraitsNode(ArmLogicTreeNode): bl_label = 'Get Object Traits' arm_version = 1 - def init(self, context): - super(GetObjectTraitsNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') self.add_output('ArmNodeSocketArray', 'Traits') diff --git a/blender/arm/logicnode/trait/LN_get_trait_name.py b/blender/arm/logicnode/trait/LN_get_trait_name.py index 49143b6b..9a34fd70 100644 --- a/blender/arm/logicnode/trait/LN_get_trait_name.py +++ b/blender/arm/logicnode/trait/LN_get_trait_name.py @@ -6,9 +6,8 @@ class GetTraitNameNode(ArmLogicTreeNode): bl_label = 'Get Trait Name' arm_version = 1 - def init(self, context): - super(GetTraitNameNode, self).init(context) - self.add_input('NodeSocketShader', 'Trait') + def arm_init(self, context): + self.add_input('ArmDynamicSocket', 'Trait') - self.add_output('NodeSocketString', 'Name') - self.add_output('NodeSocketString', 'Class Type') + self.add_output('ArmStringSocket', 'Name') + self.add_output('ArmStringSocket', 'Class Type') diff --git a/blender/arm/logicnode/trait/LN_get_trait_paused.py b/blender/arm/logicnode/trait/LN_get_trait_paused.py index 2150523d..12f1ac0d 100644 --- a/blender/arm/logicnode/trait/LN_get_trait_paused.py +++ b/blender/arm/logicnode/trait/LN_get_trait_paused.py @@ -6,8 +6,7 @@ class GetTraitPausedNode(ArmLogicTreeNode): bl_label = 'Get Trait Paused' arm_version = 1 - def init(self, context): - super(GetTraitPausedNode, self).init(context) - self.add_input('NodeSocketShader', 'Trait') + def arm_init(self, context): + self.add_input('ArmDynamicSocket', 'Trait') - self.add_output('NodeSocketBool', 'Is Paused') + self.add_output('ArmBoolSocket', 'Is Paused') diff --git a/blender/arm/logicnode/trait/LN_remove_trait.py b/blender/arm/logicnode/trait/LN_remove_trait.py index 07a41f16..7e81f12c 100644 --- a/blender/arm/logicnode/trait/LN_remove_trait.py +++ b/blender/arm/logicnode/trait/LN_remove_trait.py @@ -6,9 +6,8 @@ class RemoveTraitNode(ArmLogicTreeNode): bl_label = 'Remove Trait' arm_version = 1 - def init(self, context): - super(RemoveTraitNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait') + self.add_input('ArmDynamicSocket', 'Trait') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/trait/LN_self_trait.py b/blender/arm/logicnode/trait/LN_self_trait.py index dc06e0c4..41b09676 100644 --- a/blender/arm/logicnode/trait/LN_self_trait.py +++ b/blender/arm/logicnode/trait/LN_self_trait.py @@ -6,6 +6,5 @@ class SelfTraitNode(ArmLogicTreeNode): bl_label = 'Self Trait' arm_version = 1 - def init(self, context): - super(SelfTraitNode, self).init(context) - self.add_output('NodeSocketShader', 'Trait') + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Trait') diff --git a/blender/arm/logicnode/trait/LN_set_trait_paused.py b/blender/arm/logicnode/trait/LN_set_trait_paused.py index 81ada9c4..739692dd 100644 --- a/blender/arm/logicnode/trait/LN_set_trait_paused.py +++ b/blender/arm/logicnode/trait/LN_set_trait_paused.py @@ -6,10 +6,9 @@ class SetTraitPausedNode(ArmLogicTreeNode): bl_label = 'Set Trait Paused' arm_version = 1 - def init(self, context): - super(SetTraitPausedNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Trait') - self.add_input('NodeSocketBool', 'Paused') + self.add_input('ArmDynamicSocket', 'Trait') + self.add_input('ArmBoolSocket', 'Paused') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/trait/LN_trait.py b/blender/arm/logicnode/trait/LN_trait.py index bcec41c0..bc1b85ef 100644 --- a/blender/arm/logicnode/trait/LN_trait.py +++ b/blender/arm/logicnode/trait/LN_trait.py @@ -8,11 +8,10 @@ class TraitNode(ArmLogicTreeNode): bl_label = 'Trait' arm_version = 1 - property0: StringProperty(name='', default='') + property0: HaxeStringProperty('property0', name='', default='') - def init(self, context): - super(TraitNode, self).init(context) - self.add_output('NodeSocketShader', 'Trait', is_var=True) + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Trait', is_var=True) def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/transform/LN_append_transform.py b/blender/arm/logicnode/transform/LN_append_transform.py index a0def018..3a85088e 100644 --- a/blender/arm/logicnode/transform/LN_append_transform.py +++ b/blender/arm/logicnode/transform/LN_append_transform.py @@ -6,10 +6,9 @@ class AppendTransformNode(ArmLogicTreeNode): bl_label = 'Append Transform' arm_version = 1 - def init(self, context): - super(AppendTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_get_object_location.py b/blender/arm/logicnode/transform/LN_get_object_location.py index c708ba9a..d4af86b4 100644 --- a/blender/arm/logicnode/transform/LN_get_object_location.py +++ b/blender/arm/logicnode/transform/LN_get_object_location.py @@ -1,14 +1,26 @@ from arm.logicnode.arm_nodes import * class GetLocationNode(ArmLogicTreeNode): - """Returns the current location of the given object in world coordinates.""" + """Get the location of the given object in world coordinates. + + @input Parent Relative: If enabled, transforms the world coordinates into object parent local coordinates + + @seeNode Set Object Location + @seeNode World Vector to Local Space + @seeNode Vector to Object Orientation + """ bl_idname = 'LNGetLocationNode' bl_label = 'Get Object Location' arm_section = 'location' - arm_version = 1 + arm_version = 2 - def init(self, context): - super(GetLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') + self.add_input('ArmBoolSocket', 'Parent Relative') - self.add_output('NodeSocketVector', 'Location') + self.add_output('ArmVectorSocket', 'Location') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + return NodeReplacement.Identity(self) diff --git a/blender/arm/logicnode/transform/LN_get_object_rotation.py b/blender/arm/logicnode/transform/LN_get_object_rotation.py index 86cce2f4..067211cf 100644 --- a/blender/arm/logicnode/transform/LN_get_object_rotation.py +++ b/blender/arm/logicnode/transform/LN_get_object_rotation.py @@ -7,15 +7,16 @@ class GetRotationNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 2 - def init(self, context): - super(GetRotationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('ArmNodeSocketRotation', 'Rotation') + self.add_output('ArmRotationSocket', 'Rotation') + def draw_buttons(self, context, layout): layout.prop(self, 'property0') - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Local', 'Local', 'Local'), ('Global', 'Global', 'Global')], name='', default='Local') diff --git a/blender/arm/logicnode/transform/LN_get_object_scale.py b/blender/arm/logicnode/transform/LN_get_object_scale.py index 60f21e65..67d5d5df 100644 --- a/blender/arm/logicnode/transform/LN_get_object_scale.py +++ b/blender/arm/logicnode/transform/LN_get_object_scale.py @@ -7,8 +7,7 @@ class GetScaleNode(ArmLogicTreeNode): arm_section = 'scale' arm_version = 1 - def init(self, context): - super(GetScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketVector', 'Scale') + self.add_output('ArmVectorSocket', 'Scale') diff --git a/blender/arm/logicnode/transform/LN_get_object_transform.py b/blender/arm/logicnode/transform/LN_get_object_transform.py index 1477d899..347dc81b 100644 --- a/blender/arm/logicnode/transform/LN_get_object_transform.py +++ b/blender/arm/logicnode/transform/LN_get_object_transform.py @@ -8,8 +8,7 @@ class GetTransformNode(ArmLogicTreeNode): bl_label = 'Get Object Transform' arm_version = 1 - def init(self, context): - super(GetTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketShader', 'Transform') + self.add_output('ArmDynamicSocket', 'Transform') diff --git a/blender/arm/logicnode/transform/LN_get_world_orientation.py b/blender/arm/logicnode/transform/LN_get_world_orientation.py index 3ba71b5b..9e9b7de4 100644 --- a/blender/arm/logicnode/transform/LN_get_world_orientation.py +++ b/blender/arm/logicnode/transform/LN_get_world_orientation.py @@ -7,17 +7,17 @@ class GetWorldNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 1 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Right', 'Right', 'The object right (X) direction'), ('Look', 'Look', 'The object look (Y) direction'), ('Up', 'Up', 'The object up (Z) direction')], name='', default='Look') - def init(self, context): - super(GetWorldNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_output('NodeSocketVector', 'Vector') + self.add_output('ArmVectorSocket', 'Vector') def draw_buttons(self, context, layout): layout.prop(self, 'property0') diff --git a/blender/arm/logicnode/transform/LN_look_at.py b/blender/arm/logicnode/transform/LN_look_at.py index 669d2f8a..79a3a02d 100644 --- a/blender/arm/logicnode/transform/LN_look_at.py +++ b/blender/arm/logicnode/transform/LN_look_at.py @@ -7,7 +7,8 @@ class LookAtNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 2 - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('X', ' X', 'X'), ('-X', '-X', '-X'), ('Y', ' Y', 'Y'), @@ -16,18 +17,17 @@ class LookAtNode(ArmLogicTreeNode): ('-Z', '-Z', '-Z')], name='With', default='Z') - def init(self, context): - super(LookAtNode, self).init(context) - self.add_input('NodeSocketVector', 'From Location') - self.add_input('NodeSocketVector', 'To Location') + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'From Location') + self.add_input('ArmVectorSocket', 'To Location') - self.add_output('ArmNodeSocketRotation', 'Rotation') + self.add_output('ArmRotationSocket', 'Rotation') + def draw_buttons(self, context, layout): layout.prop(self, 'property0') - def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): raise LookupError() diff --git a/blender/arm/logicnode/transform/LN_rotate_object.py b/blender/arm/logicnode/transform/LN_rotate_object.py index b43cafc0..aab97549 100644 --- a/blender/arm/logicnode/transform/LN_rotate_object.py +++ b/blender/arm/logicnode/transform/LN_rotate_object.py @@ -8,21 +8,46 @@ class RotateObjectNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 2 - def init(self, context): - super().init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('ArmNodeSocketRotation', 'Rotation') + self.add_input('ArmRotationSocket', 'Rotation') self.add_output('ArmNodeSocketAction', 'Out') def draw_buttons(self, context, layout): - layout.prop(self, 'property0') + layout.prop(self, 'property0_proxy') - property0: EnumProperty( + + # this property swaperoo is kinda janky-looking, but listen out: + # - when you reload an old file, the properties of loaded nodes can be mangled if the node class drops the property or the specific value within the property. + # -> to fix this, 'property0' needs to contain the old values so that node replacement can be done decently. + # - "but", I hear you ask, "why not make property0 a simple blender property, and create a property0v2 HaxeProperty to be bound to the haxe-time property0?" + # -> well, at the time of writing, a HaxeProperty's prop_name is only used for livepatching, not at initial setup, so a freshly-compiled game would get completely borked properties. + # solution: have a property0 HaxeProperty contain every possible value, and have a property0_proxy Property be in the UI. + + # NOTE FOR FUTURE MAINTAINERS: the value of the proxy property does **not** matter, only the value of property0 does. When eventually editing this class, you can safely drop the values in the proxy property, and *only* the proxy property. + + def on_proxyproperty_update(self, context=None): + self.property0 = self.property0_proxy + + property0_proxy: EnumProperty( items = [('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'), ('Global', 'Global/Parent F.O.R.', 'Frame of reference oriented with the object\'s parent or the world')], + name='', default='Local', + update = on_proxyproperty_update + ) + property0: HaxeEnumProperty( + 'property0', + items=[('Euler Angles', 'NODE REPLACEMENT ONLY', ''), + ('Angle Axies (Radians)', 'NODE REPLACEMENT ONLY', ''), + ('Angle Axies (Degrees)', 'NODE REPLACEMENT ONLY', ''), + ('Quaternion', 'NODE REPLACEMENT ONLY', ''), + ('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'), + ('Global', 'Global/Parent F.O.R.', + 'Frame of reference oriented with the object\'s parent or the world') + ], name='', default='Local') @@ -31,6 +56,7 @@ class RotateObjectNode(ArmLogicTreeNode): + def get_replacement_node(self, node_tree: bpy.types.NodeTree): if self.arm_version not in (0, 1): raise LookupError() diff --git a/blender/arm/logicnode/transform/LN_separate_rotation.py b/blender/arm/logicnode/transform/LN_separate_rotation.py index 9f1cfbde..e751d9d2 100644 --- a/blender/arm/logicnode/transform/LN_separate_rotation.py +++ b/blender/arm/logicnode/transform/LN_separate_rotation.py @@ -7,12 +7,11 @@ class SeparateRotationNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 1 - def init(self, context): - super(SeparateRotationNode, self).init(context) - self.add_input('ArmNodeSocketRotation', 'Angle') + def arm_init(self, context): + self.add_input('ArmRotationSocket', 'Angle') - self.add_output('NodeSocketVector', 'Euler Angles / Vector XYZ') - self.add_output('NodeSocketFloat', 'Angle / W') + self.add_output('ArmVectorSocket', 'Euler Angles / Vector XYZ') + self.add_output('ArmFloatSocket', 'Angle / W') def on_property_update(self, context): @@ -37,18 +36,21 @@ class SeparateRotationNode(ArmLogicTreeNode): if self.property0=='EulerAngles': coll.prop(self, 'property2') - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('EulerAngles', 'Euler Angles', 'Euler Angles'), ('AxisAngle', 'Axis/Angle', 'Axis/Angle'), ('Quaternion', 'Quaternion', 'Quaternion')], name='', default='EulerAngles', update=on_property_update) - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items=[('Deg', 'Degrees', 'Degrees'), ('Rad', 'Radians', 'Radians')], name='', default='Rad') - property2: EnumProperty( + property2: HaxeEnumProperty( + 'property2', items=[('XYZ','XYZ','XYZ'), ('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'), ('YXZ','YXZ','YXZ'), diff --git a/blender/arm/logicnode/transform/LN_separate_transform.py b/blender/arm/logicnode/transform/LN_separate_transform.py index 0a96747b..2770841a 100644 --- a/blender/arm/logicnode/transform/LN_separate_transform.py +++ b/blender/arm/logicnode/transform/LN_separate_transform.py @@ -6,13 +6,12 @@ class SeparateTransformNode(ArmLogicTreeNode): bl_label = 'Separate Transform' arm_version = 2 - def init(self, context): - super(SeparateTransformNode, self).init(context) - self.add_input('NodeSocketShader', 'Transform') + def arm_init(self, context): + self.add_input('ArmDynamicSocket', 'Transform') - self.add_output('NodeSocketVector', 'Location') - self.add_output('ArmNodeSocketRotation', 'Rotation') - self.add_output('NodeSocketVector', 'Scale') + self.add_output('ArmVectorSocket', 'Location') + self.add_output('ArmRotationSocket', 'Rotation') + self.add_output('ArmVectorSocket', 'Scale') diff --git a/blender/arm/logicnode/transform/LN_set_object_location.py b/blender/arm/logicnode/transform/LN_set_object_location.py index 48afdffb..5c9e4ac9 100644 --- a/blender/arm/logicnode/transform/LN_set_object_location.py +++ b/blender/arm/logicnode/transform/LN_set_object_location.py @@ -1,16 +1,28 @@ from arm.logicnode.arm_nodes import * class SetLocationNode(ArmLogicTreeNode): - """Sets the location of the given object.""" + """Set the location of the given object in world coordinates. + + @input Parent Relative: If enabled, transforms the world coordinates into object parent local coordinates + + @seeNode Get Object Location + @seeNode World Vector to Local Space + @seeNode Vector to Object Orientation + """ bl_idname = 'LNSetLocationNode' bl_label = 'Set Object Location' arm_section = 'location' - arm_version = 1 + arm_version = 2 - def init(self, context): - super(SetLocationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Location') + self.add_input('ArmVectorSocket', 'Location') + self.add_input('ArmBoolSocket', 'Parent Relative') self.add_output('ArmNodeSocketAction', 'Out') + + def get_replacement_node(self, node_tree: bpy.types.NodeTree): + if self.arm_version not in (0, 1): + raise LookupError() + return NodeReplacement.Identity(self) diff --git a/blender/arm/logicnode/transform/LN_set_object_rotation.py b/blender/arm/logicnode/transform/LN_set_object_rotation.py index 1223cdf8..2e2af8f5 100644 --- a/blender/arm/logicnode/transform/LN_set_object_rotation.py +++ b/blender/arm/logicnode/transform/LN_set_object_rotation.py @@ -9,11 +9,10 @@ class SetRotationNode(ArmLogicTreeNode): arm_version = 2 - def init(self, context): - super(SetRotationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('ArmNodeSocketRotation', 'Rotation') + self.add_input('ArmRotationSocket', 'Rotation') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_set_object_scale.py b/blender/arm/logicnode/transform/LN_set_object_scale.py index e9580801..7f7221d9 100644 --- a/blender/arm/logicnode/transform/LN_set_object_scale.py +++ b/blender/arm/logicnode/transform/LN_set_object_scale.py @@ -7,10 +7,9 @@ class SetScaleNode(ArmLogicTreeNode): arm_section = 'scale' arm_version = 1 - def init(self, context): - super(SetScaleNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Scale', default_value=[1.0, 1.0, 1.0]) + self.add_input('ArmVectorSocket', 'Scale', default_value=[1.0, 1.0, 1.0]) self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_set_object_transform.py b/blender/arm/logicnode/transform/LN_set_object_transform.py index 8d965d3e..1a139551 100644 --- a/blender/arm/logicnode/transform/LN_set_object_transform.py +++ b/blender/arm/logicnode/transform/LN_set_object_transform.py @@ -6,10 +6,9 @@ class SetTransformNode(ArmLogicTreeNode): bl_label = 'Set Object Transform' arm_version = 1 - def init(self, context): - super(SetTransformNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketShader', 'Transform') + self.add_input('ArmDynamicSocket', 'Transform') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_test_rotation.py b/blender/arm/logicnode/transform/LN_test_rotation.py deleted file mode 100644 index 5b66910d..00000000 --- a/blender/arm/logicnode/transform/LN_test_rotation.py +++ /dev/null @@ -1,12 +0,0 @@ -from arm.logicnode.arm_nodes import * - -class TestRotationNode(ArmLogicTreeNode): - """TO DO.""" - bl_idname = 'LNTestRotationNode' - bl_label = 'TEST NODE DO NOT USE' - arm_section = 'quaternions' - arm_version = 1 - - def init(self, context): - super(TestRotationNode, self).init(context) - self.add_input('ArmNodeSocketRotation', 'taste') diff --git a/blender/arm/logicnode/transform/LN_transform_math.py b/blender/arm/logicnode/transform/LN_transform_math.py index 282a9439..bc5a90a2 100644 --- a/blender/arm/logicnode/transform/LN_transform_math.py +++ b/blender/arm/logicnode/transform/LN_transform_math.py @@ -6,9 +6,8 @@ class TransformMathNode(ArmLogicTreeNode): bl_label = 'Transform Math' arm_version = 1 - def init(self, context): - super(TransformMathNode, self).init(context) - self.add_input('NodeSocketShader', 'Transform 1') - self.add_input('NodeSocketShader', 'Transform 2') + def arm_init(self, context): + self.add_input('ArmDynamicSocket', 'Transform 1') + self.add_input('ArmDynamicSocket', 'Transform 2') - self.add_output('NodeSocketShader', 'Result') + self.add_output('ArmDynamicSocket', 'Result') diff --git a/blender/arm/logicnode/transform/LN_transform_to_vector.py b/blender/arm/logicnode/transform/LN_transform_to_vector.py index bb70185c..222f8abb 100644 --- a/blender/arm/logicnode/transform/LN_transform_to_vector.py +++ b/blender/arm/logicnode/transform/LN_transform_to_vector.py @@ -6,13 +6,12 @@ class VectorFromTransformNode(ArmLogicTreeNode): bl_label = 'Transform to Vector' arm_version = 1 - def init(self, context): - super(VectorFromTransformNode, self).init(context) - self.add_input('NodeSocketShader', 'Transform') + def arm_init(self, context): + self.add_input('ArmDynamicSocket', 'Transform') - self.add_output('NodeSocketVector', 'Vector') - self.add_output('NodeSocketVector', 'Quaternion XYZ') - self.add_output('NodeSocketFloat', 'Quaternion W') + self.add_output('ArmVectorSocket', 'Vector') + self.add_output('ArmVectorSocket', 'Quaternion XYZ') + self.add_output('ArmFloatSocket', 'Quaternion W') def on_property_update(self, context): """called by the EnumProperty, used to update the node socket labels""" @@ -31,7 +30,8 @@ class VectorFromTransformNode(ArmLogicTreeNode): def draw_buttons(self, context, layout): layout.prop(self, 'property0') - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('Right', 'Right', 'The transform right (X) direction'), ('Look', 'Look', 'The transform look (Y) direction'), ('Up', 'Up', 'The transform up (Z) direction'), diff --git a/blender/arm/logicnode/transform/LN_translate_object.py b/blender/arm/logicnode/transform/LN_translate_object.py index 048043be..d5964666 100644 --- a/blender/arm/logicnode/transform/LN_translate_object.py +++ b/blender/arm/logicnode/transform/LN_translate_object.py @@ -7,11 +7,10 @@ class TranslateObjectNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(TranslateObjectNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'Vector') - self.add_input('NodeSocketBool', 'On Local Axis') + self.add_input('ArmVectorSocket', 'Vector') + self.add_input('ArmBoolSocket', 'On Local Axis') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_translate_on_local_axis.py b/blender/arm/logicnode/transform/LN_translate_on_local_axis.py index 41c1af5c..08192fe6 100644 --- a/blender/arm/logicnode/transform/LN_translate_on_local_axis.py +++ b/blender/arm/logicnode/transform/LN_translate_on_local_axis.py @@ -8,12 +8,11 @@ class TranslateOnLocalAxisNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(TranslateOnLocalAxisNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketFloat', 'Speed') - self.add_input('NodeSocketInt', 'Forward/Up/Right') - self.add_input('NodeSocketBool', 'Inverse') + self.add_input('ArmFloatSocket', 'Speed') + self.add_input('ArmIntSocket', 'Forward/Up/Right') + self.add_input('ArmBoolSocket', 'Inverse') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py b/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py index cb9ceaf9..c80fb367 100644 --- a/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py +++ b/blender/arm/logicnode/transform/LN_vector_to_object_orientation.py @@ -1,10 +1,9 @@ from arm.logicnode.arm_nodes import * class VectorToObjectOrientationNode(ArmLogicTreeNode): - """Converts the given world vector to a vector oriented by the given object. - The object scale is taken in count. + """Transform world coordinates into object oriented coordinates (in other words: apply object rotation to it). - @seeNode World Vector To Object Space + @seeNode World Vector to Object Space @seeNode Get World Orientation @seeNode Vector From Transform """ @@ -13,9 +12,8 @@ class VectorToObjectOrientationNode(ArmLogicTreeNode): arm_section = 'rotation' arm_version = 1 - def init(self, context): - super(VectorToObjectOrientationNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'World') + self.add_input('ArmVectorSocket', 'World') - self.add_output('NodeSocketVector', 'Oriented') + self.add_output('ArmVectorSocket', 'Oriented') diff --git a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py index 61497f81..5bc8442c 100644 --- a/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py +++ b/blender/arm/logicnode/transform/LN_world_vector_to_local_space.py @@ -1,10 +1,9 @@ from arm.logicnode.arm_nodes import * class WorldVectorToLocalSpaceNode(ArmLogicTreeNode): - """Converts the given world vector to a object space vector. - The object scale is taken in count. + """Transform world coordinates into object local coordinates. - @seeNode Vector To Object Orientation + @seeNode Vector to Object Orientation @seeNode Get World Orientation @seeNode Vector From Transform """ @@ -13,9 +12,8 @@ class WorldVectorToLocalSpaceNode(ArmLogicTreeNode): arm_section = 'location' arm_version = 1 - def init(self, context): - super(WorldVectorToLocalSpaceNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketObject', 'Object') - self.add_input('NodeSocketVector', 'World') + self.add_input('ArmVectorSocket', 'World') - self.add_output('NodeSocketVector', 'Local') + self.add_output('ArmVectorSocket', 'Local') diff --git a/blender/arm/logicnode/variable/LN_boolean.py b/blender/arm/logicnode/variable/LN_boolean.py index 08d597d1..f1769d8f 100644 --- a/blender/arm/logicnode/variable/LN_boolean.py +++ b/blender/arm/logicnode/variable/LN_boolean.py @@ -7,8 +7,7 @@ class BooleanNode(ArmLogicTreeNode): bl_label = 'Boolean' arm_version = 1 - def init(self, context): - super(BooleanNode, self).init(context) - self.add_input('NodeSocketBool', 'Bool In') + def arm_init(self, context): + self.add_input('ArmBoolSocket', 'Bool In') - self.add_output('NodeSocketBool', 'Bool Out', is_var=True) + self.add_output('ArmBoolSocket', 'Bool Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_color.py b/blender/arm/logicnode/variable/LN_color.py index 1723f052..92ff7140 100644 --- a/blender/arm/logicnode/variable/LN_color.py +++ b/blender/arm/logicnode/variable/LN_color.py @@ -6,8 +6,7 @@ class ColorNode(ArmLogicTreeNode): bl_label = 'Color' arm_version = 1 - def init(self, context): - super(ColorNode, self).init(context) - self.add_input('NodeSocketColor', 'Color In', default_value=[1.0, 1.0, 1.0, 1.0]) + def arm_init(self, context): + self.add_input('ArmColorSocket', 'Color In', default_value=[1.0, 1.0, 1.0, 1.0]) - self.add_output('NodeSocketColor', 'Color Out', is_var=True) + self.add_output('ArmColorSocket', 'Color Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_dynamic.py b/blender/arm/logicnode/variable/LN_dynamic.py index 495d23da..e1465c99 100644 --- a/blender/arm/logicnode/variable/LN_dynamic.py +++ b/blender/arm/logicnode/variable/LN_dynamic.py @@ -6,6 +6,5 @@ class DynamicNode(ArmLogicTreeNode): bl_label = 'Dynamic' arm_version = 1 - def init(self, context): - super(DynamicNode, self).init(context) - self.add_output('NodeSocketShader', 'Dynamic', is_var=True) + def arm_init(self, context): + self.add_output('ArmDynamicSocket', 'Dynamic', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_float.py b/blender/arm/logicnode/variable/LN_float.py index a5c0f290..abd2caae 100644 --- a/blender/arm/logicnode/variable/LN_float.py +++ b/blender/arm/logicnode/variable/LN_float.py @@ -9,7 +9,6 @@ class FloatNode(ArmLogicTreeNode): bl_label = 'Float' arm_version = 1 - def init(self, context): - super(FloatNode, self).init(context) - self.add_input('NodeSocketFloat', 'Float In') - self.add_output('NodeSocketFloat', 'Float Out', is_var=True) + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'Float In') + self.add_output('ArmFloatSocket', 'Float Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_integer.py b/blender/arm/logicnode/variable/LN_integer.py index f4f022f4..89dd5d54 100644 --- a/blender/arm/logicnode/variable/LN_integer.py +++ b/blender/arm/logicnode/variable/LN_integer.py @@ -6,7 +6,6 @@ class IntegerNode(ArmLogicTreeNode): bl_label = 'Integer' arm_version = 1 - def init(self, context): - super(IntegerNode, self).init(context) - self.add_input('NodeSocketInt', 'Int In') - self.add_output('NodeSocketInt', 'Int Out', is_var=True) + def arm_init(self, context): + self.add_input('ArmIntSocket', 'Int In') + self.add_output('ArmIntSocket', 'Int Out', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_mask.py b/blender/arm/logicnode/variable/LN_mask.py index 002c2472..4be96e2a 100644 --- a/blender/arm/logicnode/variable/LN_mask.py +++ b/blender/arm/logicnode/variable/LN_mask.py @@ -6,10 +6,9 @@ class MaskNode(ArmLogicTreeNode): bl_label = 'Mask' arm_version = 1 - def init(self, context): - super(MaskNode, self).init(context) + def arm_init(self, context): for i in range(1, 21): label = 'Group {:02d}'.format(i) - self.inputs.new('NodeSocketBool', label) + self.inputs.new('ArmBoolSocket', label) - self.add_output('NodeSocketInt', 'Mask', is_var=True) + self.add_output('ArmIntSocket', 'Mask', is_var=True) diff --git a/blender/arm/logicnode/variable/LN_rotation.py b/blender/arm/logicnode/variable/LN_rotation.py index a5ee0a2d..5eb7880d 100644 --- a/blender/arm/logicnode/variable/LN_rotation.py +++ b/blender/arm/logicnode/variable/LN_rotation.py @@ -8,12 +8,11 @@ class RotationNode(ArmLogicTreeNode): #arm_section = 'rotation' arm_version = 1 - def init(self, context): - super(RotationNode, self).init(context) - self.add_input('NodeSocketVector', 'Euler Angles / Vector XYZ') - self.add_input('NodeSocketFloat', 'Angle / W') + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Euler Angles / Vector XYZ') + self.add_input('ArmFloatSocket', 'Angle / W') - self.add_output('ArmNodeSocketRotation', 'Out', is_var=True) + self.add_output('ArmRotationSocket', 'Out', is_var=True) def on_property_update(self, context): """called by the EnumProperty, used to update the node socket labels""" @@ -37,18 +36,21 @@ class RotationNode(ArmLogicTreeNode): if self.property0=='EulerAngles': coll.prop(self, 'property2') - property0: EnumProperty( + property0: HaxeEnumProperty( + 'property0', items = [('EulerAngles', 'Euler Angles', 'Euler Angles'), ('AxisAngle', 'Axis/Angle', 'Axis/Angle'), ('Quaternion', 'Quaternion', 'Quaternion')], name='', default='EulerAngles', update=on_property_update) - property1: EnumProperty( + property1: HaxeEnumProperty( + 'property1', items=[('Deg', 'Degrees', 'Degrees'), ('Rad', 'Radians', 'Radians')], name='', default='Rad') - property2: EnumProperty( + property2: HaxeEnumProperty( + 'property2', items=[('XYZ','XYZ','XYZ'), ('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'), ('YXZ','YXZ','YXZ'), diff --git a/blender/arm/logicnode/variable/LN_set_variable.py b/blender/arm/logicnode/variable/LN_set_variable.py index d9ed2985..411aca6a 100644 --- a/blender/arm/logicnode/variable/LN_set_variable.py +++ b/blender/arm/logicnode/variable/LN_set_variable.py @@ -13,10 +13,9 @@ class SetVariableNode(ArmLogicTreeNode): arm_section = 'set' arm_version = 1 - def init(self, context): - super(SetVariableNode, self).init(context) + def arm_init(self, context): self.add_input('ArmNodeSocketAction', 'In') - self.add_input('NodeSocketShader', 'Variable', is_var=True) - self.add_input('NodeSocketShader', 'Value') + self.add_input('ArmDynamicSocket', 'Variable', is_var=True) + self.add_input('ArmDynamicSocket', 'Value') self.add_output('ArmNodeSocketAction', 'Out') diff --git a/blender/arm/logicnode/variable/LN_transform.py b/blender/arm/logicnode/variable/LN_transform.py index a4302b97..180b879f 100644 --- a/blender/arm/logicnode/variable/LN_transform.py +++ b/blender/arm/logicnode/variable/LN_transform.py @@ -6,13 +6,11 @@ class TransformNode(ArmLogicTreeNode): bl_label = 'Transform' arm_version = 2 - def init(self, context): - super(TransformNode, self).init(context) - self.add_input('NodeSocketVector', 'Location') - self.add_input('ArmNodeSocketRotation', 'Rotation') - self.add_input('NodeSocketVector', 'Scale', default_value=[1.0, 1.0, 1.0]) - - self.add_output('NodeSocketShader', 'Transform', is_var=True) + def arm_init(self, context): + self.add_input('ArmVectorSocket', 'Location') + self.add_input('ArmRotationSocket', 'Rotation') + self.add_input('ArmVectorSocket', 'Scale', default_value=[1.0, 1.0, 1.0]) + self.add_output('ArmDynamicSocket', 'Transform', is_var=True) def get_replacement_node(self, node_tree: bpy.types.NodeTree): diff --git a/blender/arm/logicnode/variable/LN_vector.py b/blender/arm/logicnode/variable/LN_vector.py index 630f2a9f..19cf02e7 100644 --- a/blender/arm/logicnode/variable/LN_vector.py +++ b/blender/arm/logicnode/variable/LN_vector.py @@ -6,10 +6,9 @@ class VectorNode(ArmLogicTreeNode): bl_label = 'Vector' arm_version = 1 - def init(self, context): - super(VectorNode, self).init(context) - self.add_input('NodeSocketFloat', 'X') - self.add_input('NodeSocketFloat', 'Y') - self.add_input('NodeSocketFloat', 'Z') + def arm_init(self, context): + self.add_input('ArmFloatSocket', 'X') + self.add_input('ArmFloatSocket', 'Y') + self.add_input('ArmFloatSocket', 'Z') - self.add_output('NodeSocketVector', 'Vector', is_var=True) + self.add_output('ArmVectorSocket', 'Vector', is_var=True) diff --git a/blender/arm/make.py b/blender/arm/make.py index c00fcc52..b21770aa 100755 --- a/blender/arm/make.py +++ b/blender/arm/make.py @@ -1,15 +1,16 @@ +import errno import glob import json import os +from queue import Queue +import shlex import shutil -import time import stat import subprocess import threading +import time +from typing import Callable import webbrowser -import shlex -import errno -import math import bpy @@ -17,6 +18,7 @@ import arm.assets as assets from arm.exporter import ArmoryExporter import arm.lib.make_datas import arm.lib.server +import arm.live_patch as live_patch import arm.log as log import arm.make_logic as make_logic import arm.make_renderpath as make_renderpath @@ -25,18 +27,62 @@ import arm.make_world as make_world import arm.utils import arm.write_data as write_data +if arm.is_reload(__name__): + assets = arm.reload_module(assets) + arm.exporter = arm.reload_module(arm.exporter) + from arm.exporter import ArmoryExporter + arm.lib.make_datas = arm.reload_module(arm.lib.make_datas) + arm.lib.server = arm.reload_module(arm.lib.server) + live_patch = arm.reload_module(live_patch) + log = arm.reload_module(log) + make_logic = arm.reload_module(make_logic) + make_renderpath = arm.reload_module(make_renderpath) + state = arm.reload_module(state) + make_world = arm.reload_module(make_world) + arm.utils = arm.reload_module(arm.utils) + write_data = arm.reload_module(write_data) +else: + arm.enable_reload(__name__) + scripts_mtime = 0 # Monitor source changes profile_time = 0 -def run_proc(cmd, done): - def fn(p, done): - p.wait() - if done != None: +# Queue of threads and their done callbacks. Item format: [thread, done] +thread_callback_queue = Queue(maxsize=0) + + +def run_proc(cmd, done: Callable) -> subprocess.Popen: + """Creates a subprocess with the given command and returns it. + + If Blender is not running in background mode, a thread is spawned + that waits until the subprocess has finished executing to not freeze + the UI, otherwise (in background mode) execution is blocked until + the subprocess has finished. + + If `done` is not `None`, it is called afterwards in the main thread. + """ + use_thread = not bpy.app.background + + def wait_for_proc(proc: subprocess.Popen): + proc.wait() + + if use_thread: + # Put the done callback into the callback queue so that it + # can be received by a polling function in the main thread + thread_callback_queue.put([threading.current_thread(), done], block=True) + else: done() + p = subprocess.Popen(cmd) - threading.Thread(target=fn, args=(p, done)).start() + + if use_thread: + threading.Thread(target=wait_for_proc, args=(p,)).start() + else: + wait_for_proc(p) + return p + def compile_shader_pass(res, raw_shaders_path, shader_name, defs, make_variants): os.chdir(raw_shaders_path + '/' + shader_name) @@ -211,15 +257,14 @@ def export_data(fp, sdk_path): resx, resy = arm.utils.get_render_resolution(arm.utils.get_active_scene()) if wrd.arm_write_config: write_data.write_config(resx, resy) - + # Change project version (Build, Publish) if (not state.is_play) and (wrd.arm_project_version_autoinc): wrd.arm_project_version = arm.utils.arm.utils.change_version_project(wrd.arm_project_version) # Write khafile.js enable_dce = state.is_publish and wrd.arm_dce - import_logic = not state.is_publish and arm.utils.logic_editor_space() != None - write_data.write_khafilejs(state.is_play, export_physics, export_navigation, export_ui, state.is_publish, enable_dce, ArmoryExporter.import_traits, import_logic) + write_data.write_khafilejs(state.is_play, export_physics, export_navigation, export_ui, state.is_publish, enable_dce, ArmoryExporter.import_traits) # Write Main.hx - depends on write_khafilejs for writing number of assets scene_name = arm.utils.get_project_scene_name() @@ -338,7 +383,7 @@ def build(target, is_play=False, is_publish=False, is_export=False): if arm.utils.get_save_on_build(): bpy.ops.wm.save_mainfile() - log.clear(clear_warnings=True) + log.clear(clear_warnings=True, clear_errors=True) # Set camera in active scene active_scene = arm.utils.get_active_scene() @@ -397,9 +442,11 @@ def build(target, is_play=False, is_publish=False, is_export=False): shutil.copy(fn, arm.utils.build_dir() + dest + os.path.basename(fn)) def play_done(): + """Called if the player was stopped/terminated.""" state.proc_play = None state.redraw_ui = True log.clear() + live_patch.stop() def assets_done(): if state.proc_build == None: @@ -433,7 +480,7 @@ def compilation_server_done(): def build_done(): print('Finished in {:0.3f}s'.format(time.time() - profile_time)) if log.num_warnings > 0: - log.print_warn(f'{log.num_warnings} warnings occurred during compilation') + log.print_warn(f'{log.num_warnings} warning{"s" if log.num_warnings > 1 else ""} occurred during compilation') if state.proc_build is None: return result = state.proc_build.poll() @@ -445,40 +492,6 @@ def build_done(): else: log.error('Build failed, check console') -def patch(): - if state.proc_build != None: - return - assets.invalidate_enabled = False - fp = arm.utils.get_fp() - os.chdir(fp) - asset_path = arm.utils.get_fp_build() + '/compiled/Assets/' + arm.utils.safestr(bpy.context.scene.name) + '.arm' - ArmoryExporter.export_scene(bpy.context, asset_path, scene=bpy.context.scene) - if not os.path.isdir(arm.utils.build_dir() + '/compiled/Shaders/std'): - raw_shaders_path = arm.utils.get_sdk_path() + '/armory/Shaders/' - shutil.copytree(raw_shaders_path + 'std', arm.utils.build_dir() + '/compiled/Shaders/std') - node_path = arm.utils.get_node_path() - khamake_path = arm.utils.get_khamake_path() - - cmd = [node_path, khamake_path, 'krom'] - cmd.extend(('--shaderversion', '330', '--parallelAssetConversion', '4', - '--to', arm.utils.build_dir() + '/debug', '--nohaxe', '--noproject')) - - assets.invalidate_enabled = True - state.proc_build = run_proc(cmd, patch_done) - -def patch_done(): - js = 'iron.Scene.patch();' - write_patch(js) - state.proc_build = None - -patch_id = 0 - -def write_patch(js): - global patch_id - with open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w') as f: - patch_id += 1 - f.write(str(patch_id) + '\n') - f.write(js) def runtime_to_target(): wrd = bpy.data.worlds['Arm'] @@ -545,6 +558,7 @@ def build_success(): webbrowser.open(html5_app_path) elif wrd.arm_runtime == 'Krom': if wrd.arm_live_patch: + live_patch.start() open(arm.utils.get_fp_build() + '/debug/krom/krom.patch', 'w').close() krom_location, krom_path = arm.utils.krom_paths() os.chdir(krom_location) @@ -654,12 +668,12 @@ def build_success(): state.proc_publish_build = run_proc(cmd, done_gradlew_build) else: print('\nBuilding APK Warning: ANDROID_SDK_ROOT is not specified in environment variables and "Android SDK Path" setting is not specified in preferences: \n- If you specify an environment variable ANDROID_SDK_ROOT, then you need to restart Blender;\n- If you specify the setting "Android SDK Path" in the preferences, then repeat operation "Publish"') - + # HTML5 After Publish if target_name.startswith('html5'): if len(arm.utils.get_html5_copy_path()) > 0 and (wrd.arm_project_html5_copy): project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version) - dst = os.path.join(arm.utils.get_html5_copy_path(), project_name) + dst = os.path.join(arm.utils.get_html5_copy_path(), project_name) if os.path.exists(dst): shutil.rmtree(dst) try: @@ -673,10 +687,10 @@ def build_success(): link_html5_app = arm.utils.get_link_web_server() +'/'+ project_name print("Running a browser with a link " + link_html5_app) webbrowser.open(link_html5_app) - + # Windows After Publish if target_name.startswith('windows'): - list_vs = [] + list_vs = [] err = '' # Print message project_name = arm.utils.safesrc(wrd.arm_project_name +'-'+ wrd.arm_project_version) @@ -707,18 +721,18 @@ def build_success(): for vs in list_vs: print('- ' + vs[1] + ' (version ' + vs[3] +')') return - # Current VS + # Current VS vs_path = '' for vs in list_vs: if vs[0] == wrd.arm_project_win_list_vs: vs_path = vs[2] break - # Open in Visual Studio + # Open in Visual Studio if int(wrd.arm_project_win_build) == 1: cmd = os.path.join('start "' + vs_path, 'Common7', 'IDE', 'devenv.exe" "' + os.path.join(project_path, project_name + '.sln"')) subprocess.Popen(cmd, shell=True) # Compile - if int(wrd.arm_project_win_build) > 1: + if int(wrd.arm_project_win_build) > 1: bits = '64' if wrd.arm_project_win_build_arch == 'x64' else '32' # vcvars cmd = os.path.join(vs_path, 'VC', 'Auxiliary', 'Build', 'vcvars' + bits + '.bat') @@ -792,7 +806,7 @@ def done_vs_vars(): # MSBuild wrd = bpy.data.worlds['Arm'] list_vs, err = arm.utils.get_list_installed_vs(True, True, True) - # Current VS + # Current VS vs_path = '' vs_name = '' for vs in list_vs: @@ -851,7 +865,7 @@ def done_vs_build(): os.chdir(res_path) # set work folder subprocess.Popen(cmd, shell=True) # Open Build Directory - if wrd.arm_project_win_build_open: + if wrd.arm_project_win_build_open: arm.utils.open_folder(path) state.redraw_ui = True else: diff --git a/blender/arm/make_logic.py b/blender/arm/make_logic.py index cbec6f34..3ac1c0a2 100755 --- a/blender/arm/make_logic.py +++ b/blender/arm/make_logic.py @@ -5,22 +5,34 @@ import bpy from arm.exporter import ArmoryExporter import arm.log +import arm.node_utils import arm.utils +if arm.is_reload(__name__): + arm.exporter = arm.reload_module(arm.exporter) + from arm.exporter import ArmoryExporter + arm.log = arm.reload_module(arm.log) + arm.node_utils = arm.reload_module(arm.node_utils) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + parsed_nodes = [] parsed_ids = dict() # Sharing node data function_nodes = dict() function_node_outputs = dict() group_name = '' -def get_logic_trees(): + +def get_logic_trees() -> list['arm.nodes_logic.ArmLogicTree']: ar = [] for node_group in bpy.data.node_groups: if node_group.bl_idname == 'ArmLogicTreeType': - node_group.use_fake_user = True # Keep fake references for now + node_group.use_fake_user = True # Keep fake references for now ar.append(node_group) return ar + # Generating node sources def build(): os.chdir(arm.utils.get_fp()) @@ -34,7 +46,7 @@ def build(): for tree in trees: build_node_tree(tree) -def build_node_tree(node_group): +def build_node_tree(node_group: 'arm.nodes_logic.ArmLogicTree'): global parsed_nodes global parsed_ids global function_nodes @@ -48,12 +60,8 @@ def build_node_tree(node_group): pack_path = arm.utils.safestr(bpy.data.worlds['Arm'].arm_project_package) path = 'Sources/' + pack_path.replace('.', '/') + '/node/' - group_name = arm.utils.safesrc(node_group.name[0].upper() + node_group.name[1:]) - - if group_name != node_group.name: - arm.log.warn('Logic node tree and generated trait names differ! Node' - f' tree: "{node_group.name}", trait: "{group_name}"') + group_name = arm.node_utils.get_export_tree_name(node_group, do_warn=True) file = path + group_name + '.hx' # Import referenced node group @@ -65,17 +73,29 @@ def build_node_tree(node_group): if node_group.arm_cached and os.path.isfile(file): return + wrd = bpy.data.worlds['Arm'] + with open(file, 'w', encoding="utf-8") as f: f.write('package ' + pack_path + '.node;\n\n') + f.write('@:access(armory.logicnode.LogicNode)') f.write('@:keep class ' + group_name + ' extends armory.logicnode.LogicTree {\n\n') f.write('\tvar functionNodes:Map;\n\n') f.write('\tvar functionOutputNodes:Map;\n\n') f.write('\tpublic function new() {\n') f.write('\t\tsuper();\n') - if bpy.data.worlds['Arm'].arm_debug_console: + if wrd.arm_debug_console: f.write('\t\tname = "' + group_name + '";\n') f.write('\t\tthis.functionNodes = new Map();\n') f.write('\t\tthis.functionOutputNodes = new Map();\n') + if arm.utils.is_livepatch_enabled(): + # Store a reference to this trait instance in Logictree.nodeTrees + f.write('\t\tvar nodeTrees = armory.logicnode.LogicTree.nodeTrees;\n') + f.write(f'\t\tif (nodeTrees.exists("{group_name}")) ' + '{\n') + f.write(f'\t\t\tnodeTrees["{group_name}"].push(this);\n') + f.write('\t\t} else {\n') + f.write(f'\t\t\tnodeTrees["{group_name}"] = cast [this];\n') + f.write('\t\t}\n') + f.write('\t\tnotifyOnRemove(() -> { nodeTrees.remove("' + group_name + '"); });\n') f.write('\t\tnotifyOnAdd(add);\n') f.write('\t}\n\n') f.write('\toverride public function add() {\n') @@ -109,6 +129,8 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: global parsed_nodes global parsed_ids + use_live_patch = arm.utils.is_livepatch_enabled() + if node.type == 'REROUTE': if len(node.inputs) > 0 and len(node.inputs[0].links) > 0: return build_node(node.inputs[0].links[0].from_node, f) @@ -116,7 +138,7 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: return None # Get node name - name = '_' + arm.utils.safesrc(node.name) + name = arm.node_utils.get_export_node_name(node) # Link nodes using IDs if node.arm_logic_id != '': @@ -143,35 +165,28 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: # Index function output name by corresponding function name function_node_outputs[node.function_name] = name + wrd = bpy.data.worlds['Arm'] + # Watch in debug console - if node.arm_watch and bpy.data.worlds['Arm'].arm_debug_console: + if node.arm_watch and wrd.arm_debug_console: f.write('\t\t' + name + '.name = "' + name[1:] + '";\n') f.write('\t\t' + name + '.watch(true);\n') + elif use_live_patch: + f.write('\t\t' + name + '.name = "' + name[1:] + '";\n') + f.write(f'\t\tthis.nodes["{name[1:]}"] = {name};\n') + # Properties - for i in range(0, 10): - prop_name = 'property' + str(i) + '_get' - prop_found = hasattr(node, prop_name) - if not prop_found: - prop_name = 'property' + str(i) - prop_found = hasattr(node, prop_name) - if prop_found: - prop = getattr(node, prop_name) - if isinstance(prop, str): - prop = '"' + str(prop) + '"' - elif isinstance(prop, bool): - prop = str(prop).lower() - elif hasattr(prop, 'name'): # PointerProperty - prop = '"' + str(prop.name) + '"' - else: - if prop is None: - prop = 'null' - else: - prop = str(prop) - f.write('\t\t' + name + '.property' + str(i) + ' = ' + prop + ';\n') + for prop_py_name, prop_hx_name in arm.node_utils.get_haxe_property_names(node): + prop = arm.node_utils.haxe_format_prop_value(node, prop_py_name) + f.write('\t\t' + name + '.' + prop_hx_name + ' = ' + prop + ';\n') + + # Avoid unnecessary input/output array resizes + f.write(f'\t\t{name}.preallocInputs({len(node.inputs)});\n') + f.write(f'\t\t{name}.preallocOutputs({len(node.outputs)});\n') # Create inputs - for inp in node.inputs: + for idx, inp in enumerate(node.inputs): # True if the input is connected to a unlinked reroute # somewhere down the reroute line unconnected = False @@ -199,34 +214,40 @@ def build_node(node: bpy.types.Node, f: TextIO) -> Optional[str]: for i in range(0, len(n.outputs)): if n.outputs[i] == socket: inp_from = i + from_type = socket.arm_socket_type break # Not linked -> create node with default values else: inp_name = build_default_node(inp) inp_from = 0 + from_type = inp.arm_socket_type # The input is linked to a reroute, but the reroute is unlinked if unconnected: inp_name = build_default_node(inp) inp_from = 0 + from_type = inp.arm_socket_type # Add input - f.write('\t\t' + name + '.addInput(' + inp_name + ', ' + str(inp_from) + ');\n') + f.write(f'\t\t{"var __link = " if use_live_patch else ""}armory.logicnode.LogicNode.addLink({inp_name}, {name}, {inp_from}, {idx});\n') + if use_live_patch: + to_type = inp.arm_socket_type + f.write(f'\t\t__link.fromType = "{from_type}";\n') + f.write(f'\t\t__link.toType = "{to_type}";\n') + f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(inp.get_default_value())};\n') # Create outputs - for out in node.outputs: - if out.is_linked: - out_name = '' - for node in collect_nodes_from_output(out, f): - out_name += '[' if len(out_name) == 0 else ', ' - out_name += node - out_name += ']' - # Not linked - create node with default values - else: - out_name = '[' + build_default_node(out) + ']' - # Add outputs - f.write('\t\t' + name + '.addOutputs(' + out_name + ');\n') + for idx, out in enumerate(node.outputs): + # Linked outputs are already handled after iterating over inputs + # above, so only unconnected outputs are handled here + if not out.is_linked: + f.write(f'\t\t{"var __link = " if use_live_patch else ""}armory.logicnode.LogicNode.addLink({name}, {build_default_node(out)}, {idx}, 0);\n') + if use_live_patch: + out_type = out.arm_socket_type + f.write(f'\t\t__link.fromType = "{out_type}";\n') + f.write(f'\t\t__link.toType = "{out_type}";\n') + f.write(f'\t\t__link.toValue = {arm.node_utils.haxe_format_socket_val(out.get_default_value())};\n') return name @@ -270,42 +291,33 @@ def get_root_nodes(node_group): def build_default_node(inp: bpy.types.NodeSocket): """Creates a new node to give a not connected input socket a value""" - is_custom_socket = isinstance(inp, arm.logicnode.arm_sockets.ArmCustomSocket) if is_custom_socket: # ArmCustomSockets need to implement get_default_value() default_value = inp.get_default_value() - if isinstance(default_value, str): - default_value = '"{:s}"'.format(default_value.replace('"', '\\"') ) inp_type = inp.arm_socket_type # any custom socket's `type` is "VALUE". might as well have valuable type information for custom nodes as well. else: if hasattr(inp, 'default_value'): default_value = inp.default_value else: default_value = None - if isinstance(default_value, str): - default_value = '"{:s}"'.format(default_value.replace('"', '\\"') ) inp_type = inp.type - # Don't write 'None' into the Haxe code - if default_value is None: - default_value = 'null' + default_value = arm.node_utils.haxe_format_socket_val(default_value, array_outer_brackets=False) if inp_type == 'VECTOR': - return f'new armory.logicnode.VectorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})' + return f'new armory.logicnode.VectorNode(this, {default_value})' elif inp_type == 'ROTATION': # a rotation is internally represented as a quaternion. - return f'new armory.logicnode.RotationNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]}, {default_value[3]})' - elif inp_type == 'RGBA': - return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]}, {default_value[3]})' - elif inp_type == 'RGB': - return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})' + return f'new armory.logicnode.RotationNode(this, {default_value})' + elif inp_type in ('RGB', 'RGBA'): + return f'new armory.logicnode.ColorNode(this, {default_value})' elif inp_type == 'VALUE': return f'new armory.logicnode.FloatNode(this, {default_value})' elif inp_type == 'INT': return f'new armory.logicnode.IntegerNode(this, {default_value})' elif inp_type == 'BOOLEAN': - return f'new armory.logicnode.BooleanNode(this, {str(default_value).lower()})' + return f'new armory.logicnode.BooleanNode(this, {default_value})' elif inp_type == 'STRING': return f'new armory.logicnode.StringNode(this, {default_value})' elif inp_type == 'NONE': diff --git a/blender/arm/make_renderpath.py b/blender/arm/make_renderpath.py index 051ec4dc..d5c7958b 100755 --- a/blender/arm/make_renderpath.py +++ b/blender/arm/make_renderpath.py @@ -1,9 +1,19 @@ import bpy + +import arm.api import arm.assets as assets -import arm.utils import arm.log as log import arm.make_state as state -import arm.api +import arm.utils + +if arm.is_reload(__name__): + arm.api = arm.reload_module(arm.api) + assets = arm.reload_module(assets) + log = arm.reload_module(log) + state = arm.reload_module(state) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) callback = None @@ -373,8 +383,11 @@ def build(): if obj.type == "MESH": for slot in obj.material_slots: mat = slot.material - if mat.arm_ignore_irradiance: - ignoreIrr = True + + if mat: #Check if not NoneType + + if mat.arm_ignore_irradiance: + ignoreIrr = True if ignoreIrr: wrd.world_defs += '_IgnoreIrr' diff --git a/blender/arm/make_state.py b/blender/arm/make_state.py index b83b8726..289e3834 100644 --- a/blender/arm/make_state.py +++ b/blender/arm/make_state.py @@ -1,15 +1,20 @@ -redraw_ui = False -target = 'krom' -last_target = 'krom' -export_gapi = '' -last_resx = 0 -last_resy = 0 -last_scene = '' -last_world_defs = '' -proc_play = None -proc_build = None -proc_publish_build = None -mod_scripts = [] -is_export = False -is_play = False -is_publish = False +import arm + +if not arm.is_reload(__name__): + arm.enable_reload(__name__) + + redraw_ui = False + target = 'krom' + last_target = 'krom' + export_gapi = '' + last_resx = 0 + last_resy = 0 + last_scene = '' + last_world_defs = '' + proc_play = None + proc_build = None + proc_publish_build = None + mod_scripts = [] + is_export = False + is_play = False + is_publish = False diff --git a/blender/arm/make_world.py b/blender/arm/make_world.py index 8c3dcedd..1c8c3e4b 100755 --- a/blender/arm/make_world.py +++ b/blender/arm/make_world.py @@ -12,22 +12,79 @@ import arm.node_utils as node_utils import arm.utils import arm.write_probes as write_probes +if arm.is_reload(__name__): + arm.assets = arm.reload_module(arm.assets) + arm.log = arm.reload_module(arm.log) + arm.material = arm.reload_module(arm.material) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState, ParserContext + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import ShaderContext, Shader + cycles = arm.reload_module(cycles) + node_utils = arm.reload_module(node_utils) + arm.utils = arm.reload_module(arm.utils) + write_probes = arm.reload_module(write_probes) +else: + arm.enable_reload(__name__) + callback = None shader_datas = [] def build(): + """Builds world shaders for all exported worlds.""" global shader_datas - bpy.data.worlds['Arm'].world_defs = '' + wrd = bpy.data.worlds['Arm'] + rpdat = arm.utils.get_rp() + + mobile_mat = rpdat.arm_material_model == 'Mobile' or rpdat.arm_material_model == 'Solid' + envpath = os.path.join(arm.utils.get_fp_build(), 'compiled', 'Assets', 'envmaps') + + wrd.world_defs = '' worlds = [] shader_datas = [] - for scene in bpy.data.scenes: - # Only export worlds from enabled scenes - if scene.arm_export and scene.world is not None and scene.world not in worlds: - worlds.append(scene.world) - create_world_shaders(scene.world) + with write_probes.setup_envmap_render(): + + for scene in bpy.data.scenes: + world = scene.world + + # Only export worlds from enabled scenes and only once per world + if scene.arm_export and world is not None and world not in worlds: + worlds.append(world) + + world.arm_envtex_name = '' + create_world_shaders(world) + + if rpdat.arm_irradiance: + # Plain background color + if '_EnvCol' in world.world_defs: + world_name = arm.utils.safestr(world.name) + # Irradiance json file name + world.arm_envtex_name = world_name + world.arm_envtex_irr_name = world_name + write_probes.write_color_irradiance(world_name, world.arm_envtex_color) + + # Render world to envmap for (ir)radiance, if no + # other probes are exported + elif world.arm_envtex_name == '': + write_probes.render_envmap(envpath, world) + + filename = f'env_{arm.utils.safesrc(world.name)}' + image_file = f'{filename}.jpg' + image_filepath = os.path.join(envpath, image_file) + + world.arm_envtex_name = image_file + world.arm_envtex_irr_name = os.path.basename(image_filepath).rsplit('.', 1)[0] + + write_radiance = rpdat.arm_radiance and not mobile_mat + mip_count = write_probes.write_probes(image_filepath, True, world.arm_envtex_num_mips, write_radiance) + world.arm_envtex_num_mips = mip_count + + if write_radiance: + # Set world def, everything else is handled by write_probes() + wrd.world_defs += '_Rad' def create_world_shaders(world: bpy.types.World): @@ -131,14 +188,7 @@ def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: Sha col = world.color world.arm_envtex_color = [col[0], col[1], col[2], 1.0] world.arm_envtex_strength = 1.0 - - # Irradiance/Radiance: clear to color if no texture or sky is provided - if rpdat.arm_irradiance or rpdat.arm_irradiance: - if '_EnvSky' not in world.world_defs and '_EnvTex' not in world.world_defs and '_EnvImg' not in world.world_defs: - # Irradiance json file name - world.arm_envtex_name = world_name - world.arm_envtex_irr_name = world_name - write_probes.write_color_irradiance(world_name, world.arm_envtex_color) + world.world_defs += '_EnvCol' # Clouds enabled if rpdat.arm_clouds and world.arm_use_clouds: @@ -279,7 +329,12 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader): func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n' if '_EnvSky' in world.world_defs: - func_cloud_radiance += '\tvec3 sun_dir = hosekSunDirection;\n' + # Nishita sky + if 'vec3 sunDir' in frag.uniforms: + func_cloud_radiance += '\tvec3 sun_dir = sunDir;\n' + # Hosek + else: + func_cloud_radiance += '\tvec3 sun_dir = hosekSunDirection;\n' else: func_cloud_radiance += '\tvec3 sun_dir = vec3(0, 0, -1);\n' func_cloud_radiance += '''\tconst int steps = 8; @@ -293,7 +348,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader): }''' frag.add_function(func_cloud_radiance) - frag.add_function('''vec3 traceClouds(vec3 sky, vec3 dir) { + func_trace_clouds = '''vec3 traceClouds(vec3 sky, vec3 dir) { \tconst float step_size = 0.5 / float(cloudsSteps); \tfloat T = 1.0; \tfloat C = 0.0; @@ -312,6 +367,17 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader): \t\t} \t\tuv += (dir.xy / dir.z) * step_size * cloudsUpper; \t} +''' -\treturn vec3(C) + sky * T; -}''') + if world.arm_darken_clouds: + func_trace_clouds += '\t// Darken clouds when the sun is low\n' + + # Nishita sky + if 'vec3 sunDir' in frag.uniforms: + func_trace_clouds += '\tC *= smoothstep(-0.02, 0.25, sunDir.z);\n' + # Hosek + else: + func_trace_clouds += '\tC *= smoothstep(0.04, 0.32, hosekSunDirection.z);\n' + + func_trace_clouds += '\treturn vec3(C) + sky * T;\n}' + frag.add_function(func_trace_clouds) diff --git a/blender/arm/material/__init__.py b/blender/arm/material/__init__.py index e69de29b..661b7767 100755 --- a/blender/arm/material/__init__.py +++ b/blender/arm/material/__init__.py @@ -0,0 +1 @@ +import arm diff --git a/blender/arm/material/arm_nodes/custom_particle_node.py b/blender/arm/material/arm_nodes/custom_particle_node.py index 8c9d9f63..e09c78e8 100644 --- a/blender/arm/material/arm_nodes/custom_particle_node.py +++ b/blender/arm/material/arm_nodes/custom_particle_node.py @@ -5,6 +5,17 @@ from arm.material.arm_nodes.arm_nodes import add_node from arm.material.shader import Shader from arm.material.cycles import * +if arm.is_reload(__name__): + import arm + arm.material.arm_nodes.arm_nodes = arm.reload_module(arm.material.arm_nodes.arm_nodes) + from arm.material.arm_nodes.arm_nodes import add_node + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader + arm.material.cycles = arm.reload_module(arm.material.cycles) + from arm.material.cycles import * +else: + arm.enable_reload(__name__) + class CustomParticleNode(Node): """Input data for paricles.""" @@ -174,7 +185,7 @@ class CustomParticleNode(Node): if self.posZ: vertshdr.write(f'spos.z += {pos}.z;') - + vertshdr.write('wposition = vec4(W * spos).xyz;') diff --git a/blender/arm/material/arm_nodes/shader_data_node.py b/blender/arm/material/arm_nodes/shader_data_node.py index 8ab0aaa6..f4ff6a73 100644 --- a/blender/arm/material/arm_nodes/shader_data_node.py +++ b/blender/arm/material/arm_nodes/shader_data_node.py @@ -1,9 +1,18 @@ from bpy.props import * from bpy.types import Node +import arm from arm.material.arm_nodes.arm_nodes import add_node from arm.material.shader import Shader +if arm.is_reload(__name__): + arm.material.arm_nodes.arm_nodes = arm.reload_module(arm.material.arm_nodes.arm_nodes) + from arm.material.arm_nodes.arm_nodes import add_node + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader +else: + arm.enable_reload(__name__) + class ShaderDataNode(Node): """Allows access to shader data such as uniforms and inputs.""" diff --git a/blender/arm/material/cycles.py b/blender/arm/material/cycles.py index 71cf6f84..91c1c47b 100644 --- a/blender/arm/material/cycles.py +++ b/blender/arm/material/cycles.py @@ -30,6 +30,23 @@ from arm.material.parser_state import ParserState, ParserContext from arm.material.shader import Shader, ShaderContext, floatstr, vec3str import arm.utils +if arm.is_reload(__name__): + arm.assets = arm.reload_module(arm.assets) + log = arm.reload_module(log) + arm.make_state = arm.reload_module(arm.make_state) + c_functions = arm.reload_module(c_functions) + arm.material.cycles_nodes = arm.reload_module(arm.material.cycles_nodes) + from arm.material.cycles_nodes import * + mat_state = arm.reload_module(mat_state) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState, ParserContext + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader, ShaderContext, floatstr, vec3str + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + # Particle info export particle_info: Dict[str, bool] = {} @@ -204,6 +221,8 @@ def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[st 'BSDF_VELVET': nodes_shader.parse_bsdfvelvet, } + state.reset_outs() + if node.type in node_parser_funcs: node_parser_funcs[node.type](node, socket, state) @@ -331,6 +350,7 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str: 'COMBXYZ': nodes_converter.parse_combxyz, 'VECT_MATH': nodes_converter.parse_vectormath, 'DISPLACEMENT': nodes_vector.parse_displacement, + 'VECTOR_ROTATE': nodes_vector.parse_vectorrotate, } if node.type in node_parser_funcs: @@ -433,6 +453,7 @@ def parse_value(node, socket): 'SEPRGB': nodes_converter.parse_seprgb, 'SEPXYZ': nodes_converter.parse_sepxyz, 'VECT_MATH': nodes_converter.parse_vectormath, + 'MAP_RANGE': nodes_converter.parse_maprange, } if node.type in node_parser_funcs: @@ -556,7 +577,7 @@ def to_uniform(inp: bpy.types.NodeSocket): def store_var_name(node: bpy.types.Node): return node_name(node.name) + '_store' -def texture_store(node, tex, tex_name, to_linear=False, tex_link=None): +def texture_store(node, tex, tex_name, to_linear=False, tex_link=None, default_value=None, is_arm_mat_param=None): curshader = state.curshader tex_store = store_var_name(node) @@ -565,7 +586,7 @@ def texture_store(node, tex, tex_name, to_linear=False, tex_link=None): state.parsed.add(tex_store) mat_bind_texture(tex) state.con.add_elem('tex', 'short2norm') - curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link) + curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link, default_value=default_value, is_arm_mat_param=is_arm_mat_param) triplanar = node.projection == 'BOX' if node.inputs[0].is_linked: uv_name = parse_vector_input(node.inputs[0]) @@ -640,8 +661,47 @@ def to_vec1(v): return str(v) +def to_vec2(v): + return f'vec2({v[0]}, {v[1]})' + + def to_vec3(v): - return 'vec3({0}, {1}, {2})'.format(v[0], v[1], v[2]) + return f'vec3({v[0]}, {v[1]}, {v[2]})' + + +def cast_value(val: str, from_type: str, to_type: str) -> str: + """Casts a value that is already parsed in a glsl string to another + value in a string. + + vec2 types are not supported (not used in the node editor) and there + is no cast towards int types. If casting from vec3 to vec4, the w + coordinate/alpha channel is filled with a 1. + + If this function is called with invalid parameters, a TypeError is + raised. + """ + if from_type == to_type: + return val + + if from_type in ('int', 'float'): + if to_type in ('int', 'float'): + return val + elif to_type in ('vec2', 'vec3', 'vec4'): + return f'{to_type}({val})' + + elif from_type == 'vec3': + if to_type == 'float': + return rgb_to_bw(val) + elif to_type == 'vec4': + return f'vec4({val}, 1.0)' + + elif from_type == 'vec4': + if to_type == 'float': + return rgb_to_bw(val) + elif to_type == 'vec3': + return f'{val}.xyz' + + raise TypeError("Invalid type cast in shader!") def rgb_to_bw(res_var: vec3str) -> floatstr: diff --git a/blender/arm/material/cycles_functions.py b/blender/arm/material/cycles_functions.py index a133d2f8..1bd3e340 100644 --- a/blender/arm/material/cycles_functions.py +++ b/blender/arm/material/cycles_functions.py @@ -332,3 +332,186 @@ vec3 wrap(const vec3 value, const vec3 max, const vec3 min) { \t wrap(value.z, max.z, min.z)); } """ + +str_blackbody = """ +vec3 blackbody(const float temperature){ + + vec3 rgb = vec3(0.0, 0.0, 0.0); + + vec3 r = vec3(0.0, 0.0, 0.0); + vec3 g = vec3(0.0, 0.0, 0.0); + vec3 b = vec3(0.0, 0.0, 0.0); + + float t_inv = float(1.0 / temperature); + + if (temperature >= 12000.0) { + + rgb = vec3(0.826270103, 0.994478524, 1.56626022); + + } else if(temperature < 965.0) { + + rgb = vec3(4.70366907, 0.0, 0.0); + + } else { + + if (temperature >= 6365.0) { + vec3 r = vec3(3.78765709e+03, 9.36026367e-06, 3.98995841e-01); + vec3 g = vec3(-5.00279505e+02, -4.59745390e-06, 1.09090465e+00); + vec4 b = vec4(6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 3315.0) { + vec3 r = vec3(4.60124770e+03, 2.89727618e-05, 1.48001316e-01); + vec3 g = vec3(-1.18134453e+03, -2.18913373e-05, 1.30656109e+00); + vec4 b = vec4(-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1902.0) { + vec3 r = vec3(4.66849800e+03, 2.85655028e-05, 1.29075375e-01); + vec3 g = vec3(-1.42546105e+03, -4.01730887e-05, 1.44002695e+00); + vec4 b = vec4(-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1449.0) { + vec3 r = vec3(4.10671449e+03, -8.61949938e-05, 6.41423749e-01); + vec3 g = vec3(-1.22075471e+03, 2.56245413e-05, 1.20753416e+00); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else if (temperature >= 1167.0) { + vec3 r = vec3(3.37763626e+03, -4.34581697e-04, 1.64843306e+00); + vec3 g = vec3(-1.00402363e+03, 1.29189794e-04, 9.08181524e-01); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } else { + vec3 r = vec3(2.52432244e+03, -1.06185848e-03, 3.11067539e+00); + vec3 g = vec3(-7.50343014e+02, 3.15679613e-04, 4.73464526e-01); + vec4 b = vec4(0.0, 0.0, 0.0, 0.0); + + rgb = vec3(r.r * t_inv + r.g * temperature + r.b, g.r * t_inv + g.g * temperature + g.b, ((b.r * temperature + b.g) * temperature + b.b) * temperature + b.a ); + + } + } + + return rgb; + +} +""" + +# Adapted from https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/source/blender/gpu/shaders/material/gpu_shader_material_map_range.glsl +str_map_range_linear = """ +float map_range_linear(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) { + if (fromMax != fromMin) { + return float(toMin + ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_stepped = """ +float map_range_stepped(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax, const float steps) { + if (fromMax != fromMin) { + float factor = (value - fromMin) / (fromMax - fromMin); + factor = (steps > 0.0) ? floor(factor * (steps + 1.0)) / steps : 0.0; + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_smoothstep = """ +float map_range_smoothstep(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) +{ + if (fromMax != fromMin) { + float factor = (fromMin > fromMax) ? 1.0 - smoothstep(fromMax, fromMin, value) : + smoothstep(fromMin, fromMax, value); + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_map_range_smootherstep = """ +float safe_divide(float a, float b) +{ + return (b != 0.0) ? a / b : 0.0; +} + +float smootherstep(float edge0, float edge1, float x) +{ + x = clamp(safe_divide((x - edge0), (edge1 - edge0)), 0.0, 1.0); + return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); +} + +float map_range_smootherstep(const float value, const float fromMin, const float fromMax, const float toMin, const float toMax) { + if (fromMax != fromMin) { + float factor = (fromMin > fromMax) ? 1.0 - smootherstep(fromMax, fromMin, value) : + smootherstep(fromMin, fromMax, value); + return float(toMin + factor * (toMax - toMin)); + } + else { + return float(0.0); + } +} +""" + +str_rotate_around_axis = """ +vec3 rotate_around_axis(const vec3 p, const vec3 axis, const float angle) +{ + float costheta = cos(angle); + float sintheta = sin(angle); + vec3 r; + + r.x = ((costheta + (1.0 - costheta) * axis.x * axis.x) * p.x) + + (((1.0 - costheta) * axis.x * axis.y - axis.z * sintheta) * p.y) + + (((1.0 - costheta) * axis.x * axis.z + axis.y * sintheta) * p.z); + + r.y = (((1.0 - costheta) * axis.x * axis.y + axis.z * sintheta) * p.x) + + ((costheta + (1.0 - costheta) * axis.y * axis.y) * p.y) + + (((1.0 - costheta) * axis.y * axis.z - axis.x * sintheta) * p.z); + + r.z = (((1.0 - costheta) * axis.x * axis.z - axis.y * sintheta) * p.x) + + (((1.0 - costheta) * axis.y * axis.z + axis.x * sintheta) * p.y) + + ((costheta + (1.0 - costheta) * axis.z * axis.z) * p.z); + + return r; +} +""" + +str_euler_to_mat3 = """ +mat3 euler_to_mat3(vec3 euler) +{ + float cx = cos(euler.x); + float cy = cos(euler.y); + float cz = cos(euler.z); + float sx = sin(euler.x); + float sy = sin(euler.y); + float sz = sin(euler.z); + + mat3 mat; + mat[0][0] = cy * cz; + mat[0][1] = cy * sz; + mat[0][2] = -sy; + + mat[1][0] = sy * sx * cz - cx * sz; + mat[1][1] = sy * sx * sz + cx * cz; + mat[1][2] = cy * sx; + + mat[2][0] = sy * cx * cz + sx * sz; + mat[2][1] = sy * cx * sz - sx * cz; + mat[2][2] = cy * cx; + return mat; +} +""" \ No newline at end of file diff --git a/blender/arm/material/cycles_nodes/nodes_color.py b/blender/arm/material/cycles_nodes/nodes_color.py index 1b7dff08..97048d5f 100644 --- a/blender/arm/material/cycles_nodes/nodes_color.py +++ b/blender/arm/material/cycles_nodes/nodes_color.py @@ -1,11 +1,23 @@ import bpy +import arm import arm.log as log import arm.material.cycles as c import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str +if arm.is_reload(__name__): + log = arm.reload_module(log) + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str +else: + arm.enable_reload(__name__) + def parse_brightcontrast(node: bpy.types.ShaderNodeBrightContrast, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: out_col = c.parse_vector_input(node.inputs[0]) diff --git a/blender/arm/material/cycles_nodes/nodes_converter.py b/blender/arm/material/cycles_nodes/nodes_converter.py index b87e9f16..553fee50 100644 --- a/blender/arm/material/cycles_nodes/nodes_converter.py +++ b/blender/arm/material/cycles_nodes/nodes_converter.py @@ -2,79 +2,62 @@ from typing import Union import bpy +import arm import arm.log as log import arm.material.cycles as c import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str +if arm.is_reload(__name__): + log = arm.reload_module(log) + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str +else: + arm.enable_reload(__name__) + + +def parse_maprange(node: bpy.types.ShaderNodeMapRange, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: + + interp = node.interpolation_type + + value: str = c.parse_value_input(node.inputs[0]) if node.inputs[0].is_linked else c.to_vec1(node.inputs[0].default_value) + fromMin = c.parse_value_input(node.inputs[1]) + fromMax = c.parse_value_input(node.inputs[2]) + toMin = c.parse_value_input(node.inputs[3]) + toMax = c.parse_value_input(node.inputs[4]) + + if interp == "LINEAR": + + state.curshader.add_function(c_functions.str_map_range_linear) + return f'map_range_linear({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' + + elif interp == "STEPPED": + + steps = float(c.parse_value_input(node.inputs[5])) + state.curshader.add_function(c_functions.str_map_range_stepped) + return f'map_range_stepped({value}, {fromMin}, {fromMax}, {toMin}, {toMax}, {steps})' + + elif interp == "SMOOTHSTEP": + + state.curshader.add_function(c_functions.str_map_range_smoothstep) + return f'map_range_smoothstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' + + elif interp == "SMOOTHERSTEP": + + state.curshader.add_function(c_functions.str_map_range_smootherstep) + return f'map_range_smootherstep({value}, {fromMin}, {fromMax}, {toMin}, {toMax})' def parse_blackbody(node: bpy.types.ShaderNodeBlackbody, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: - t = float(c.parse_value_input(node.inputs[0])) - rgb = [0, 0, 0] - blackbody_table_r = [ - [2.52432244e+03, -1.06185848e-03, 3.11067539e+00], - [3.37763626e+03, -4.34581697e-04, 1.64843306e+00], - [4.10671449e+03, -8.61949938e-05, 6.41423749e-01], - [4.66849800e+03, 2.85655028e-05, 1.29075375e-01], - [4.60124770e+03, 2.89727618e-05, 1.48001316e-01], - [3.78765709e+03, 9.36026367e-06, 3.98995841e-01] - ] - blackbody_table_g = [ - [-7.50343014e+02, 3.15679613e-04, 4.73464526e-01], - [-1.00402363e+03, 1.29189794e-04, 9.08181524e-01], - [-1.22075471e+03, 2.56245413e-05, 1.20753416e+00], - [-1.42546105e+03, -4.01730887e-05, 1.44002695e+00], - [-1.18134453e+03, -2.18913373e-05, 1.30656109e+00], - [-5.00279505e+02, -4.59745390e-06, 1.09090465e+00] - ] - blackbody_table_b = [ - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [-2.02524603e-11, 1.79435860e-07, -2.60561875e-04, -1.41761141e-02], - [-2.22463426e-13, -1.55078698e-08, 3.81675160e-04, -7.30646033e-01], - [6.72595954e-13, -2.73059993e-08, 4.24068546e-04, -7.52204323e-01] - ] - - if t >= 12000: - rgb[0] = 0.826270103 - rgb[1] = 0.994478524 - rgb[2] = 1.56626022 - - elif t < 965.0: - rgb[0] = 4.70366907 - rgb[1] = 0.0 - rgb[2] = 0.0 - - else: - if t >= 6365.0: - i = 5 - elif t >= 3315.0: - i = 4 - elif t >= 1902.0: - i = 3 - elif t >= 1449.0: - i = 2 - elif t >= 1167.0: - i = 1 - else: - i = 0 - - r = blackbody_table_r[i] - g = blackbody_table_g[i] - b = blackbody_table_b[i] - - t_inv = 1.0 / t - - rgb[0] = r[0] * t_inv + r[1] * t + r[2] - rgb[1] = g[0] * t_inv + g[1] * t + g[2] - rgb[2] = ((b[0] * t + b[1]) * t + b[2]) * t + b[3] - - # Pass constant - return c.to_vec3([rgb[0], rgb[1], rgb[2]]) + t = c.parse_value_input(node.inputs[0]) + state.curshader.add_function(c_functions.str_blackbody) + return f'blackbody({t})' def parse_clamp(node: bpy.types.ShaderNodeClamp, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: value = c.parse_value_input(node.inputs['Value']) @@ -262,14 +245,21 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = '({0} * {1})'.format(val1, val2) elif op == 'DIVIDE': out_val = '({0} / {1})'.format(val1, val2) + elif op == 'MULTIPLY_ADD': + val3 = c.parse_value_input(node.inputs[2]) + out_val = '({0} * {1} + {2})'.format(val1, val2, val3) elif op == 'POWER': out_val = 'pow({0}, {1})'.format(val1, val2) elif op == 'LOGARITHM': out_val = 'log({0})'.format(val1) elif op == 'SQRT': out_val = 'sqrt({0})'.format(val1) + elif op == 'INVERSE_SQRT': + out_val = 'inversesqrt({0})'.format(val1) elif op == 'ABSOLUTE': out_val = 'abs({0})'.format(val1) + elif op == 'EXPONENT': + out_val = 'exp({0})'.format(val1) elif op == 'MINIMUM': out_val = 'min({0}, {1})'.format(val1, val2) elif op == 'MAXIMUM': @@ -278,6 +268,17 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'float({0} < {1})'.format(val1, val2) elif op == 'GREATER_THAN': out_val = 'float({0} > {1})'.format(val1, val2) + elif op == 'SIGN': + out_val = 'sign({0})'.format(val1) + elif op == 'COMPARE': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float((abs({0} - {1}) <= max({2}, 1e-5)) ? 1.0 : 0.0)'.format(val1, val2, val3) + elif op == 'SMOOTH_MIN': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float(float({2} != 0.0 ? min({0},{1}) - (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * (max({2} - abs({0} - {1}), 0.0) / {2}) * {2} * (1.0 / 6.0) : min({0}, {1})))'.format(val1, val2, val3) + elif op == 'SMOOTH_MAX': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float(0-(float({2} != 0.0 ? min(-{0},-{1}) - (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * (max({2} - abs(-{0} - (-{1})), 0.0) / {2}) * {2} * (1.0 / 6.0) : min(-{0}, (-{1})))))'.format(val1, val2, val3) elif op == 'ROUND': # out_val = 'round({0})'.format(val1) out_val = 'floor({0} + 0.5)'.format(val1) @@ -285,11 +286,20 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'floor({0})'.format(val1) elif op == 'CEIL': out_val = 'ceil({0})'.format(val1) + elif op == 'TRUNC': + out_val = 'trunc({0})'.format(val1) elif op == 'FRACT': out_val = 'fract({0})'.format(val1) elif op == 'MODULO': # out_val = 'float({0} % {1})'.format(val1, val2) out_val = 'mod({0}, {1})'.format(val1, val2) + elif op == 'WRAP': + val3 = c.parse_value_input(node.inputs[2]) + out_val = 'float((({1}-{2}) != 0.0) ? {0} - (({1}-{2}) * floor(({0} - {2}) / ({1}-{2}))) : {2})'.format(val1, val2, val3) + elif op == 'SNAP': + out_val = 'floor(({1} != 0.0) ? {0} / {1} : 0.0) * {1}'.format(val1, val2) + elif op == 'PINGPONG': + out_val = 'float(({1} != 0.0) ? abs(fract(({0} - {1}) / ({1} * 2.0)) * {1} * 2.0 - {1}) : 0.0)'.format(val1, val2) elif op == 'SINE': out_val = 'sin({0})'.format(val1) elif op == 'COSINE': @@ -304,6 +314,16 @@ def parse_math(node: bpy.types.ShaderNodeMath, out_socket: bpy.types.NodeSocket, out_val = 'atan({0})'.format(val1) elif op == 'ARCTAN2': out_val = 'atan({0}, {1})'.format(val1, val2) + elif op == 'SINH': + out_val = 'sinh({0})'.format(val1) + elif op == 'COSH': + out_val = 'cosh({0})'.format(val1) + elif op == 'TANH': + out_val = 'tanh({0})'.format(val1) + elif op == 'RADIANS': + out_val = 'radians({0})'.format(val1) + elif op == 'DEGREES': + out_val = 'degrees({0})'.format(val1) if node.use_clamp: return 'clamp({0}, 0.0, 1.0)'.format(out_val) diff --git a/blender/arm/material/cycles_nodes/nodes_input.py b/blender/arm/material/cycles_nodes/nodes_input.py index 60d86799..6a8b1aca 100644 --- a/blender/arm/material/cycles_nodes/nodes_input.py +++ b/blender/arm/material/cycles_nodes/nodes_input.py @@ -1,6 +1,8 @@ -import bpy from typing import Union +import bpy +import mathutils + import arm.log as log import arm.material.cycles as c import arm.material.cycles_functions as c_functions @@ -8,50 +10,107 @@ from arm.material.parser_state import ParserState, ParserContext from arm.material.shader import floatstr, vec3str import arm.utils +if arm.is_reload(__name__): + log = arm.reload_module(log) + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState, ParserContext + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: - # Color - if out_socket == node.outputs[0]: - # Vertex colors only for now - state.con.add_elem('col', 'short4norm') - return 'vcolor' + out_type = 'float' if out_socket.type == 'VALUE' else 'vec3' - # Vector - elif out_socket == node.outputs[1]: - # UV maps only for now - state.con.add_elem('tex', 'short2norm') + if node.attribute_name == 'time': + state.curshader.add_uniform('float time', link='_time') + + if out_socket == node.outputs[3]: + return '1.0' + return c.cast_value('time', from_type='float', to_type=out_type) + + # UV maps (higher priority) and vertex colors + if node.attribute_type == 'GEOMETRY': + + # Alpha output. Armory doesn't support vertex colors with alpha + # values yet and UV maps don't have an alpha channel + if out_socket == node.outputs[3]: + return '1.0' + + # UV maps mat = c.mat_get_material() mat_users = c.mat_get_material_users() if mat_users is not None and mat in mat_users: mat_user = mat_users[mat][0] - # No UV layers for Curve + # Curves don't have uv layers, so check that first if hasattr(mat_user.data, 'uv_layers'): lays = mat_user.data.uv_layers + # First UV map referenced + if len(lays) > 0 and node.attribute_name == lays[0].name: + state.con.add_elem('tex', 'short2norm') + return c.cast_value('vec3(texCoord.x, 1.0 - texCoord.y, 0.0)', from_type='vec3', to_type=out_type) + # Second UV map referenced - if len(lays) > 1 and node.attribute_name == lays[1].name: + elif len(lays) > 1 and node.attribute_name == lays[1].name: state.con.add_elem('tex1', 'short2norm') - return 'vec3(texCoord1.x, 1.0 - texCoord1.y, 0.0)' + return c.cast_value('vec3(texCoord1.x, 1.0 - texCoord1.y, 0.0)', from_type='vec3', to_type=out_type) - return 'vec3(texCoord.x, 1.0 - texCoord.y, 0.0)' + # Vertex colors + # TODO: support multiple vertex color sets + state.con.add_elem('col', 'short4norm') + return c.cast_value('vcolor', from_type='vec3', to_type=out_type) - # Fac - else: - if node.attribute_name == 'time': - state.curshader.add_uniform('float time', link='_time') - return 'time' + # Check object properties + # see https://developer.blender.org/rB6fdcca8de6 for reference + mat = c.mat_get_material() + mat_users = c.mat_get_material_users() + if mat_users is not None and mat in mat_users: + # Use first material user for now... + mat_user = mat_users[mat][0] - # Return 0.0 till drivers are implemented - else: - return '0.0' + val = None + # Custom properties first + if node.attribute_name in mat_user: + val = mat_user[node.attribute_name] + # Blender properties + elif hasattr(mat_user, node.attribute_name): + val = getattr(mat_user, node.attribute_name) + + if val is not None: + if isinstance(val, float): + return c.cast_value(str(val), from_type='float', to_type=out_type) + elif isinstance(val, int): + return c.cast_value(str(val), from_type='int', to_type=out_type) + elif isinstance(val, mathutils.Vector) and len(val) <= 4: + out = val.to_4d() + + if out_socket == node.outputs[3]: + return c.to_vec1(out[3]) + return c.cast_value(c.to_vec3(out), from_type='vec3', to_type=out_type) + + # Default values, attribute name did not match + if out_socket == node.outputs[3]: + return '1.0' + return c.cast_value('0.0', from_type='float', to_type=out_type) def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: if node.arm_material_param: nn = 'param_' + c.node_name(node.name) - state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}') + v = out_socket.default_value + value = [] + value.append(float(v[0])) + value.append(float(v[1])) + value.append(float(v[2])) + is_arm_mat_param = True + state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}', default_value = value, is_arm_mat_param = is_arm_mat_param) return nn else: return c.to_vec3(out_socket.default_value) @@ -310,7 +369,9 @@ def parse_lightpath(node: bpy.types.ShaderNodeLightPath, out_socket: bpy.types.N def parse_value(node: bpy.types.ShaderNodeValue, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr: if node.arm_material_param: nn = 'param_' + c.node_name(node.name) - state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name)) + value = c.to_vec1(node.outputs[0].default_value) + is_arm_mat_param = True + state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name), default_value=value, is_arm_mat_param=is_arm_mat_param) return nn else: return c.to_vec1(node.outputs[0].default_value) diff --git a/blender/arm/material/cycles_nodes/nodes_shader.py b/blender/arm/material/cycles_nodes/nodes_shader.py index d6114454..734674a1 100644 --- a/blender/arm/material/cycles_nodes/nodes_shader.py +++ b/blender/arm/material/cycles_nodes/nodes_shader.py @@ -1,9 +1,17 @@ import bpy from bpy.types import NodeSocket +import arm import arm.material.cycles as c from arm.material.parser_state import ParserState +if arm.is_reload(__name__): + c = arm.reload_module(c) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState +else: + arm.enable_reload(__name__) + def parse_mixshader(node: bpy.types.ShaderNodeMixShader, out_socket: NodeSocket, state: ParserState) -> None: prefix = '' if node.inputs[0].is_linked else 'const ' @@ -41,7 +49,7 @@ def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket, def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: - c.write_normal(node.inputs[19]) + c.write_normal(node.inputs[20]) state.out_basecol = c.parse_vector_input(node.inputs[0]) # subsurface = c.parse_vector_input(node.inputs[1]) # subsurface_radius = c.parse_vector_input(node.inputs[2]) @@ -62,11 +70,12 @@ def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: N if node.inputs[17].is_linked or node.inputs[17].default_value[0] != 0.0: state.out_emission = '({0}.x)'.format(c.parse_vector_input(node.inputs[17])) state.emission_found = True - # clearcoar_normal = c.parse_vector_input(node.inputs[20]) - # tangent = c.parse_vector_input(node.inputs[21]) + # clearcoar_normal = c.parse_vector_input(node.inputs[21]) + # tangent = c.parse_vector_input(node.inputs[22]) if state.parse_opacity: - if len(node.inputs) > 20: - state.out_opacity = c.parse_value_input(node.inputs[18]) + if len(node.inputs) > 21: + state.out_opacity = c.parse_value_input(node.inputs[19]) + def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None: if state.parse_surface: diff --git a/blender/arm/material/cycles_nodes/nodes_texture.py b/blender/arm/material/cycles_nodes/nodes_texture.py index b9f57daf..d79f360a 100644 --- a/blender/arm/material/cycles_nodes/nodes_texture.py +++ b/blender/arm/material/cycles_nodes/nodes_texture.py @@ -1,3 +1,4 @@ +import math import os from typing import Union @@ -5,13 +6,27 @@ import bpy import arm.assets as assets import arm.log as log -import arm.material.cycles_functions as c_functions import arm.material.cycles as c +import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState, ParserContext from arm.material.shader import floatstr, vec3str import arm.utils import arm.write_probes as write_probes +if arm.is_reload(__name__): + assets = arm.reload_module(assets) + log = arm.reload_module(log) + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState, ParserContext + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str + arm.utils = arm.reload_module(arm.utils) + write_probes = arm.reload_module(write_probes) +else: + arm.enable_reload(__name__) + def parse_tex_brick(node: bpy.types.ShaderNodeTexBrick, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: state.curshader.add_function(c_functions.str_tex_brick) @@ -113,15 +128,22 @@ def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.No tex_name = c.node_name(node.name) tex = c.make_texture(node, tex_name) - tex_link = node.name if node.arm_material_param else None + tex_link = None + tex_default_file = None + is_arm_mat_param = None + if node.arm_material_param: + tex_link = node.name + is_arm_mat_param = True if tex is not None: state.curshader.write_textures += 1 + if node.arm_material_param and tex['file'] is not None: + tex_default_file = tex['file'] if use_color_out: to_linear = node.image is not None and node.image.colorspace_settings.name == 'sRGB' - res = f'{c.texture_store(node, tex, tex_name, to_linear, tex_link=tex_link)}.rgb' + res = f'{c.texture_store(node, tex, tex_name, to_linear, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.rgb' else: - res = f'{c.texture_store(node, tex, tex_name, tex_link=tex_link)}.a' + res = f'{c.texture_store(node, tex, tex_name, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.a' state.curshader.write_textures -= 1 return res @@ -132,8 +154,8 @@ def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.No 'file': '' } if use_color_out: - return '{0}.rgb'.format(c.texture_store(node, tex, tex_name, to_linear=False, tex_link=tex_link)) - return '{0}.a'.format(c.texture_store(node, tex, tex_name, to_linear=True, tex_link=tex_link)) + return '{0}.rgb'.format(c.texture_store(node, tex, tex_name, to_linear=False, tex_link=tex_link, is_arm_mat_param=is_arm_mat_param)) + return '{0}.a'.format(c.texture_store(node, tex, tex_name, to_linear=True, tex_link=tex_link, is_arm_mat_param=is_arm_mat_param)) # Pink color for missing texture else: @@ -293,13 +315,29 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo # Pass through return c.to_vec3([0.0, 0.0, 0.0]) + state.world.world_defs += '_EnvSky' + + if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE': + if node.sky_type == 'PREETHAM': + log.info('Info: Preetham sky model is not supported, using Hosek Wilkie sky model instead') + + return parse_sky_hosekwilkie(node, state) + + elif node.sky_type == 'NISHITA': + return parse_sky_nishita(node, state) + + else: + log.error(f'Unsupported sky model: {node.sky_type}!') + return c.to_vec3([0.0, 0.0, 0.0]) + + +def parse_sky_hosekwilkie(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str: world = state.world curshader = state.curshader # Match to cycles world.arm_envtex_strength *= 0.1 - world.world_defs += '_EnvSky' assets.add_khafile_def('arm_hosek') curshader.add_uniform('vec3 A', link="_hosekA") curshader.add_uniform('vec3 B', link="_hosekB") @@ -312,10 +350,10 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo curshader.add_uniform('vec3 I', link="_hosekI") curshader.add_uniform('vec3 Z', link="_hosekZ") curshader.add_uniform('vec3 hosekSunDirection', link="_hosekSunDirection") - curshader.add_function('''vec3 hosekWilkie(float cos_theta, float gamma, float cos_gamma) { + curshader.add_function("""vec3 hosekWilkie(float cos_theta, float gamma, float cos_gamma) { \tvec3 chi = (1 + cos_gamma * cos_gamma) / pow(1 + H * H - 2 * cos_gamma * H, vec3(1.5)); \treturn (1 + A * exp(B / (cos_theta + 0.01))) * (C + D * exp(E * gamma) + F * (cos_gamma * cos_gamma) + G * chi + I * sqrt(cos_theta)); -}''') +}""") world.arm_envtex_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]] world.arm_envtex_turbidity = node.turbidity @@ -353,6 +391,40 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo return 'Z * hosekWilkie(cos_theta, gamma_val, cos_gamma) * envmapStrength;' +def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str: + curshader = state.curshader + curshader.add_include('std/sky.glsl') + curshader.add_uniform('vec3 sunDir', link='_sunDirection') + curshader.add_uniform('sampler2D nishitaLUT', link='_nishitaLUT', included=True, + tex_addr_u='clamp', tex_addr_v='clamp') + curshader.add_uniform('vec2 nishitaDensity', link='_nishitaDensity', included=True) + + planet_radius = 6360e3 # Earth radius used in Blender + ray_origin_z = planet_radius + node.altitude + + state.world.arm_nishita_density = [node.air_density, node.dust_density, node.ozone_density] + + sun = '' + if node.sun_disc: + # The sun size is calculated relative in terms of the distance + # between the sun position and the sky dome normal at every + # pixel (see sun_disk() in sky.glsl). + # + # An isosceles triangle is created with the camera at the + # opposite side of the base with node.sun_size being the vertex + # angle from which the base angle theta is calculated. Iron's + # skydome geometry roughly resembles a unit sphere, so the leg + # size is set to 1. The base size is the doubled normal-relative + # target size. + + # sun_size is already in radians despite being degrees in the UI + theta = 0.5 * (math.pi - node.sun_size) + size = math.cos(theta) + sun = f'* sun_disk(n, sunDir, {size}, {node.sun_intensity})' + + return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}){sun}' + + def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: if state.context == ParserContext.OBJECT: log.warn('Environment Texture node is not supported for object node trees, using default value') diff --git a/blender/arm/material/cycles_nodes/nodes_vector.py b/blender/arm/material/cycles_nodes/nodes_vector.py index 5e35c7de..ab72a240 100644 --- a/blender/arm/material/cycles_nodes/nodes_vector.py +++ b/blender/arm/material/cycles_nodes/nodes_vector.py @@ -3,10 +3,22 @@ from typing import Union import bpy from mathutils import Euler, Vector +import arm import arm.material.cycles as c +import arm.material.cycles_functions as c_functions from arm.material.parser_state import ParserState from arm.material.shader import floatstr, vec3str +if arm.is_reload(__name__): + c = arm.reload_module(c) + c_functions = arm.reload_module(c_functions) + arm.material.parser_state = arm.reload_module(arm.material.parser_state) + from arm.material.parser_state import ParserState + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import floatstr, vec3str +else: + arm.enable_reload(__name__) + def parse_curvevec(node: bpy.types.ShaderNodeVectorCurve, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: fac = c.parse_value_input(node.inputs[0]) @@ -141,3 +153,33 @@ def parse_displacement(node: bpy.types.ShaderNodeDisplacement, out_socket: bpy.t scale = c.parse_value_input(node.inputs[2]) nor = c.parse_vector_input(node.inputs[3]) return f'(vec3({height}) * {scale})' + +def parse_vectorrotate(node: bpy.types.ShaderNodeVectorRotate, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str: + + type = node.rotation_type + input_vector: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[0]) + input_center: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[1]) + input_axis: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[2]) + input_angle: bpy.types.NodeSocket = c.parse_value_input(node.inputs[3]) + input_rotation: bpy.types.NodeSocket = c.parse_vector_input(node.inputs[4]) + + if node.invert: + input_invert = "0" + else: + input_invert = "1" + + state.curshader.add_function(c_functions.str_rotate_around_axis) + + if type == 'AXIS_ANGLE': + return f'vec3( (length({input_axis}) != 0.0) ? rotate_around_axis({input_vector} - {input_center}, normalize({input_axis}), {input_angle} * {input_invert}) + {input_center} : {input_vector} )' + elif type == 'X_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(1.0, 0.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'Y_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 1.0, 0.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'Z_AXIS': + return f'vec3( rotate_around_axis({input_vector} - {input_center}, vec3(0.0, 0.0, 1.0), {input_angle} * {input_invert}) + {input_center} )' + elif type == 'EULER_XYZ': + state.curshader.add_function(c_functions.str_euler_to_mat3) + return f'vec3( mat3(({input_invert} < 0.0) ? transpose(euler_to_mat3({input_rotation})) : euler_to_mat3({input_rotation})) * ({input_vector} - {input_center}) + {input_center})' + + return f'(vec3(1.0, 0.0, 0.0))' diff --git a/blender/arm/material/make.py b/blender/arm/material/make.py index 13a79d33..079b9d84 100755 --- a/blender/arm/material/make.py +++ b/blender/arm/material/make.py @@ -10,6 +10,15 @@ import arm.material.mat_batch as mat_batch import arm.node_utils import arm.utils +if arm.is_reload(__name__): + cycles = arm.reload_module(cycles) + make_shader = arm.reload_module(make_shader) + mat_batch = arm.reload_module(mat_batch) + arm.node_utils = arm.reload_module(arm.node_utils) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + def glsl_value(val): if str(type(val)) == "": diff --git a/blender/arm/material/make_attrib.py b/blender/arm/material/make_attrib.py index 1d01e99b..b792bad3 100644 --- a/blender/arm/material/make_attrib.py +++ b/blender/arm/material/make_attrib.py @@ -1,11 +1,27 @@ +from typing import Optional + import arm.material.cycles as cycles import arm.material.mat_state as mat_state import arm.material.make_skin as make_skin import arm.material.make_particle as make_particle import arm.material.make_inst as make_inst -import arm.material.shader as shader +import arm.material.make_tess as make_tess +from arm.material.shader import Shader, ShaderContext import arm.utils +if arm.is_reload(__name__): + cycles = arm.reload_module(cycles) + mat_state = arm.reload_module(mat_state) + make_skin = arm.reload_module(make_skin) + make_particle = arm.reload_module(make_particle) + make_inst = arm.reload_module(make_inst) + make_tess = arm.reload_module(make_tess) + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader, ShaderContext + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + def write_vertpos(vert): billboard = mat_state.material.arm_billboard @@ -33,17 +49,45 @@ def write_vertpos(vert): vert.write('gl_Position = WVP * spos;') -def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=False, write_nor=True): - prep = '' - if declare: - prep = 'vec3 ' +def write_norpos(con_mesh: ShaderContext, vert: Shader, declare=False, write_nor=True): is_bone = con_mesh.is_elem('bone') if is_bone: make_skin.skin_pos(vert) if write_nor: + prep = 'vec3 ' if declare else '' if is_bone: make_skin.skin_nor(vert, prep) else: vert.write_attrib(prep + 'wnormal = normalize(N * vec3(nor.xy, pos.w));') if con_mesh.is_elem('ipos'): make_inst.inst_pos(con_mesh, vert) + + +def write_tex_coords(con_mesh: ShaderContext, vert: Shader, frag: Shader, tese: Optional[Shader]): + rpdat = arm.utils.get_rp() + + if con_mesh.is_elem('tex'): + vert.add_out('vec2 texCoord') + vert.add_uniform('float texUnpack', link='_texUnpack') + if mat_state.material.arm_tilesheet_flag: + if mat_state.material.arm_particle_flag and rpdat.arm_particles == 'On': + make_particle.write_tilesheet(vert) + else: + vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset') + vert.write_attrib('texCoord = tex * texUnpack + tilesheetOffset;') + else: + vert.write_attrib('texCoord = tex * texUnpack;') + + if tese is not None: + tese.write_pre = True + make_tess.interpolate(tese, 'texCoord', 2, declare_out=frag.contains('texCoord')) + tese.write_pre = False + + if con_mesh.is_elem('tex1'): + vert.add_out('vec2 texCoord1') + vert.add_uniform('float texUnpack', link='_texUnpack') + vert.write_attrib('texCoord1 = tex1 * texUnpack;') + if tese is not None: + tese.write_pre = True + make_tess.interpolate(tese, 'texCoord1', 2, declare_out=frag.contains('texCoord1')) + tese.write_pre = False diff --git a/blender/arm/material/make_cluster.py b/blender/arm/material/make_cluster.py index 34589d42..e3d104f1 100644 --- a/blender/arm/material/make_cluster.py +++ b/blender/arm/material/make_cluster.py @@ -4,12 +4,12 @@ def write(vert, frag): wrd = bpy.data.worlds['Arm'] is_shadows = '_ShadowMap' in wrd.world_defs is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs - is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs + is_single_atlas = '_SingleAtlas' in wrd.world_defs frag.add_include_front('std/clusters.glsl') frag.add_uniform('vec2 cameraProj', link='_cameraPlaneProj') frag.add_uniform('vec2 cameraPlane', link='_cameraPlane') - frag.add_uniform('vec4 lightsArray[maxLights * 2]', link='_lightsArray') + frag.add_uniform('vec4 lightsArray[maxLights * 3]', link='_lightsArray') frag.add_uniform('sampler2D clustersData', link='_clustersData') if is_shadows: frag.add_uniform('bool receiveShadow') @@ -17,6 +17,8 @@ def write(vert, frag): if is_shadows_atlas: if not is_single_atlas: frag.add_uniform('sampler2DShadow shadowMapAtlasPoint', included=True) + else: + frag.add_uniform('sampler2DShadow shadowMapAtlas', top=True) frag.add_uniform('vec4 pointLightDataArray[maxLightsCluster]', link='_pointLightsAtlasArray', included=True) else: frag.add_uniform('samplerCubeShadow shadowMapPoint[4]', included=True) @@ -36,9 +38,12 @@ def write(vert, frag): frag.write('int numSpots = int(texelFetch(clustersData, ivec2(clusterI, 1 + maxLightsCluster), 0).r * 255);') frag.write('int numPoints = numLights - numSpots;') if is_shadows: - if is_shadows_atlas and not is_single_atlas: - frag.add_uniform(f'sampler2DShadow shadowMapAtlasSpot', included=True) - elif not is_shadows_atlas: + if is_shadows_atlas: + if not is_single_atlas: + frag.add_uniform('sampler2DShadow shadowMapAtlasSpot', included=True) + else: + frag.add_uniform('sampler2DShadow shadowMapAtlas', top=True) + else: frag.add_uniform('sampler2DShadow shadowMapSpot[4]', included=True) # FIXME: type is actually mat4, but otherwise it will not be set as floats when writing the shaders' json files frag.add_uniform('vec4 LWVPSpotArray[maxLightsCluster]', link='_biasLightWorldViewProjectionMatrixSpotArray', included=True) @@ -51,19 +56,19 @@ def write(vert, frag): frag.write(' n,') frag.write(' vVec,') frag.write(' dotNV,') - frag.write(' lightsArray[li * 2].xyz,') # lp - frag.write(' lightsArray[li * 2 + 1].xyz,') # lightCol + frag.write(' lightsArray[li * 3].xyz,') # lp + frag.write(' lightsArray[li * 3 + 1].xyz,') # lightCol frag.write(' albedo,') frag.write(' roughness,') frag.write(' specular,') frag.write(' f0') if is_shadows: - frag.write(' , li, lightsArray[li * 2].w, receiveShadow') # bias + frag.write('\t, li, lightsArray[li * 3 + 2].x, lightsArray[li * 3 + 2].z != 0.0') # bias if '_Spot' in wrd.world_defs: - frag.write(' , lightsArray[li * 2 + 1].w != 0.0') - frag.write(' , lightsArray[li * 2 + 1].w') # cutoff - frag.write(' , lightsArraySpot[li].w') # cutoff - exponent - frag.write(' , lightsArraySpot[li].xyz') # spotDir + frag.write('\t, lightsArray[li * 3 + 2].y != 0.0') + frag.write('\t, lightsArray[li * 3 + 2].y') # cutoff + frag.write('\t, lightsArraySpot[li].w') # cutoff - exponent + frag.write('\t, lightsArraySpot[li].xyz') # spotDir if '_VoxelShadow' in wrd.world_defs and '_VoxelAOvar' in wrd.world_defs: frag.write(' , voxels, voxpos') frag.write(');') diff --git a/blender/arm/material/make_decal.py b/blender/arm/material/make_decal.py index 32e896e9..0be96f75 100644 --- a/blender/arm/material/make_decal.py +++ b/blender/arm/material/make_decal.py @@ -1,10 +1,19 @@ import bpy + import arm.material.cycles as cycles import arm.material.mat_state as mat_state -import arm.material.mat_utils as mat_utils import arm.material.make_finalize as make_finalize import arm.utils +if arm.is_reload(__name__): + cycles = arm.reload_module(cycles) + mat_state = arm.reload_module(mat_state) + make_finalize = arm.reload_module(make_finalize) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + def make(context_id): wrd = bpy.data.worlds['Arm'] diff --git a/blender/arm/material/make_depth.py b/blender/arm/material/make_depth.py index 03ae858d..75ec922c 100644 --- a/blender/arm/material/make_depth.py +++ b/blender/arm/material/make_depth.py @@ -1,4 +1,5 @@ import bpy + import arm.material.cycles as cycles import arm.material.mat_state as mat_state import arm.material.mat_utils as mat_utils @@ -10,6 +11,21 @@ import arm.material.make_finalize as make_finalize import arm.assets as assets import arm.utils +if arm.is_reload(__name__): + cycles = arm.reload_module(cycles) + mat_state = arm.reload_module(mat_state) + mat_utils = arm.reload_module(mat_utils) + make_skin = arm.reload_module(make_skin) + make_inst = arm.reload_module(make_inst) + make_tess = arm.reload_module(make_tess) + make_particle = arm.reload_module(make_particle) + make_finalize = arm.reload_module(make_finalize) + assets = arm.reload_module(assets) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + def make(context_id, rpasses, shadowmap=False): is_disp = mat_utils.disp_linked(mat_state.output_node) @@ -33,8 +49,6 @@ def make(context_id, rpasses, shadowmap=False): parse_custom_particle = (cycles.node_by_name(mat_state.nodes, 'ArmCustomParticleNode') is not None) if parse_opacity: - frag.write('vec3 n;') # Discard at compile time - frag.write('float dotNV;') frag.write('float opacity;') if con_depth.is_elem('bone'): diff --git a/blender/arm/material/make_finalize.py b/blender/arm/material/make_finalize.py index 1eca9455..7525b517 100644 --- a/blender/arm/material/make_finalize.py +++ b/blender/arm/material/make_finalize.py @@ -1,7 +1,18 @@ import bpy -import arm.material.make_tess as make_tess -def make(con_mesh): +import arm +import arm.material.make_tess as make_tess +from arm.material.shader import ShaderContext + +if arm.is_reload(__name__): + make_tess = arm.reload_module(make_tess) + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import ShaderContext +else: + arm.enable_reload(__name__) + + +def make(con_mesh: ShaderContext): vert = con_mesh.vert frag = con_mesh.frag geom = con_mesh.geom @@ -13,9 +24,26 @@ def make(con_mesh): if frag.contains('dotNV') and not frag.contains('float dotNV'): frag.write_init('float dotNV = max(dot(n, vVec), 0.0);') + # n is not always defined yet (in some shadowmap shaders e.g.) + if not frag.contains('vec3 n'): + vert.add_out('vec3 wnormal') + vert.add_uniform('mat3 N', '_normalMatrix') + vert.write_attrib('wnormal = normalize(N * vec3(nor.xy, pos.w));') + frag.write_attrib('vec3 n = normalize(wnormal);') + + # If not yet added, add nor vertex data + vertex_elems = con_mesh.data['vertex_elements'] + has_normals = False + for elem in vertex_elems: + if elem['name'] == 'nor': + has_normals = True + break + if not has_normals: + vertex_elems.append({'name': 'nor', 'data': 'short2norm'}) + write_wpos = False if frag.contains('vVec') and not frag.contains('vec3 vVec'): - if tese != None: + if tese is not None: tese.add_out('vec3 eyeDir') tese.add_uniform('vec3 eye', '_cameraPosition') tese.write('eyeDir = eye - wposition;') @@ -31,7 +59,7 @@ def make(con_mesh): export_wpos = False if frag.contains('wposition') and not frag.contains('vec3 wposition'): export_wpos = True - if tese != None: + if tese is not None: export_wpos = True if vert.contains('wposition'): write_wpos = True @@ -50,7 +78,7 @@ def make(con_mesh): vert.add_uniform('float posUnpack', link='_posUnpack') vert.write_attrib('mposition = spos.xyz * posUnpack;') - if tese != None: + if tese is not None: if frag_mpos: make_tess.interpolate(tese, 'mposition', 3, declare_out=True) elif tese.contains('mposition') and not tese.contains('vec3 mposition'): @@ -72,7 +100,7 @@ def make(con_mesh): vert.write_attrib('if (dim.y == 0) bposition.y = 0;') vert.write_attrib('if (dim.x == 0) bposition.x = 0;') - if tese != None: + if tese is not None: if frag_bpos: make_tess.interpolate(tese, 'bposition', 3, declare_out=True) elif tese.contains('bposition') and not tese.contains('vec3 bposition'): @@ -93,7 +121,7 @@ def make(con_mesh): vert.write('wtangent = normalize(N * tang.xyz);') vert.write_pre = False - if tese != None: + if tese is not None: if frag_wtan: make_tess.interpolate(tese, 'wtangent', 3, declare_out=True) elif tese.contains('wtangent') and not tese.contains('vec3 wtangent'): diff --git a/blender/arm/material/make_mesh.py b/blender/arm/material/make_mesh.py index c64ec408..e654cf1e 100644 --- a/blender/arm/material/make_mesh.py +++ b/blender/arm/material/make_mesh.py @@ -1,4 +1,5 @@ import bpy + import arm.assets as assets import arm.material.mat_state as mat_state import arm.material.mat_utils as mat_utils @@ -10,6 +11,20 @@ import arm.material.make_finalize as make_finalize import arm.material.make_attrib as make_attrib import arm.utils +if arm.is_reload(__name__): + assets = arm.reload_module(assets) + mat_state = arm.reload_module(mat_state) + mat_utils = arm.reload_module(mat_utils) + cycles = arm.reload_module(cycles) + make_tess = arm.reload_module(make_tess) + make_particle = arm.reload_module(make_particle) + make_cluster = arm.reload_module(make_cluster) + make_finalize = arm.reload_module(make_finalize) + make_attrib = arm.reload_module(make_attrib) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + is_displacement = False write_material_attribs = None write_material_attribs_post = None @@ -125,34 +140,14 @@ def make_base(con_mesh, parse_opacity): if write_material_attribs_post != None: write_material_attribs_post(con_mesh, frag) + vert.add_out('vec3 wnormal') + make_attrib.write_norpos(con_mesh, vert) + frag.write_attrib('vec3 n = normalize(wnormal);') + if not is_displacement and not vattr_written: make_attrib.write_vertpos(vert) - if con_mesh.is_elem('tex'): - vert.add_out('vec2 texCoord') - vert.add_uniform('float texUnpack', link='_texUnpack') - if mat_state.material.arm_tilesheet_flag: - if mat_state.material.arm_particle_flag and rpdat.arm_particles == 'On': - make_particle.write_tilesheet(vert) - else: - vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset') - vert.write_attrib('texCoord = tex * texUnpack + tilesheetOffset;') - else: - vert.write_attrib('texCoord = tex * texUnpack;') - - if tese is not None: - tese.write_pre = True - make_tess.interpolate(tese, 'texCoord', 2, declare_out=frag.contains('texCoord')) - tese.write_pre = False - - if con_mesh.is_elem('tex1'): - vert.add_out('vec2 texCoord1') - vert.add_uniform('float texUnpack', link='_texUnpack') - vert.write_attrib('texCoord1 = tex1 * texUnpack;') - if tese is not None: - tese.write_pre = True - make_tess.interpolate(tese, 'texCoord1', 2, declare_out=frag.contains('texCoord1')) - tese.write_pre = False + make_attrib.write_tex_coords(con_mesh, vert, frag, tese) if con_mesh.is_elem('col'): vert.add_out('vec3 vcolor') @@ -162,10 +157,6 @@ def make_base(con_mesh, parse_opacity): make_tess.interpolate(tese, 'vcolor', 3, declare_out=frag.contains('vcolor')) tese.write_pre = False - vert.add_out('vec3 wnormal') - make_attrib.write_norpos(con_mesh, vert) - frag.write_attrib('vec3 n = normalize(wnormal);') - if con_mesh.is_elem('tang'): if tese is not None: tese.add_out('mat3 TBN') @@ -296,8 +287,6 @@ def make_forward_mobile(con_mesh): vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);') frag.ins = vert.outs - make_attrib.write_vertpos(vert) - frag.add_include('compiled.inc') frag.write('vec3 basecol;') frag.write('float roughness;') @@ -320,14 +309,7 @@ def make_forward_mobile(con_mesh): opac = mat_state.material.arm_discard_opacity frag.write('if (opacity < {0}) discard;'.format(opac)) - if con_mesh.is_elem('tex'): - vert.add_out('vec2 texCoord') - vert.add_uniform('float texUnpack', link='_texUnpack') - if mat_state.material.arm_tilesheet_flag: - vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset') - vert.write('texCoord = tex * texUnpack + tilesheetOffset;') - else: - vert.write('texCoord = tex * texUnpack;') + make_attrib.write_tex_coords(con_mesh, vert, frag, tese) if con_mesh.is_elem('col'): vert.add_out('vec3 vcolor') @@ -344,6 +326,8 @@ def make_forward_mobile(con_mesh): make_attrib.write_norpos(con_mesh, vert) frag.write_attrib('vec3 n = normalize(wnormal);') + make_attrib.write_vertpos(vert) + frag.add_include('std/math.glsl') frag.add_include('std/brdf.glsl') @@ -358,9 +342,9 @@ def make_forward_mobile(con_mesh): is_shadows = '_ShadowMap' in wrd.world_defs is_shadows_atlas = '_ShadowMapAtlas' in wrd.world_defs - is_single_atlas = is_shadows_atlas and '_SingleAtlas' in wrd.world_defs shadowmap_sun = 'shadowMap' if is_shadows_atlas: + is_single_atlas = '_SingleAtlas' in wrd.world_defs shadowmap_sun = 'shadowMapAtlasSun' if not is_single_atlas else 'shadowMapAtlas' frag.add_uniform('vec2 smSizeUniform', '_shadowMapSize', included=True) frag.write('vec3 direct = vec3(0.0);') @@ -372,7 +356,7 @@ def make_forward_mobile(con_mesh): frag.write('float sdotNL = max(dot(n, sunDir), 0.0);') if is_shadows: vert.add_out('vec4 lightPosition') - vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix') + vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrixSun') vert.write('lightPosition = LWVP * spos;') frag.add_uniform('bool receiveShadow') frag.add_uniform(f'sampler2DShadow {shadowmap_sun}') @@ -474,8 +458,6 @@ def make_forward_solid(con_mesh): vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);') frag.ins = vert.outs - make_attrib.write_vertpos(vert) - frag.add_include('compiled.inc') frag.write('vec3 basecol;') frag.write('float roughness;') @@ -512,6 +494,7 @@ def make_forward_solid(con_mesh): vert.write('vcolor = col.rgb;') make_attrib.write_norpos(con_mesh, vert, write_nor=False) + make_attrib.write_vertpos(vert) frag.add_out('vec4 fragColor') if blend and parse_opacity: @@ -575,7 +558,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): arm_discard = mat_state.material.arm_discard make_base(con_mesh, parse_opacity=(parse_opacity or arm_discard)) - + blend = mat_state.material.arm_blending vert = con_mesh.vert @@ -694,7 +677,7 @@ def make_forward_base(con_mesh, parse_opacity=False, transluc_pass=False): vert.write('lightPosition = LVP * vec4(wposition, 1.0);') else: vert.add_out('vec4 lightPosition') - vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrix') + vert.add_uniform('mat4 LWVP', '_biasLightWorldViewProjectionMatrixSun') vert.write('lightPosition = LWVP * spos;') frag.write('vec3 lPos = lightPosition.xyz / lightPosition.w;') frag.write('const vec2 smSize = shadowmapSize;') diff --git a/blender/arm/material/make_overlay.py b/blender/arm/material/make_overlay.py index 4d13c412..679967f1 100644 --- a/blender/arm/material/make_overlay.py +++ b/blender/arm/material/make_overlay.py @@ -1,8 +1,18 @@ -import arm.material.cycles as cycles -import arm.material.mat_state as mat_state +import arm +import arm.material.make_finalize as make_finalize import arm.material.make_mesh as make_mesh +import arm.material.mat_state as mat_state import arm.material.mat_utils as mat_utils +if arm.is_reload(__name__): + make_finalize = arm.reload_module(make_finalize) + make_mesh = arm.reload_module(make_mesh) + mat_state = arm.reload_module(mat_state) + mat_utils = arm.reload_module(mat_utils) +else: + arm.enable_reload(__name__) + + def make(context_id): con = { 'name': context_id, 'depth_write': True, 'compare_mode': 'less', 'cull_mode': 'clockwise' } mat = mat_state.material @@ -14,14 +24,14 @@ def make(context_id): con['alpha_blend_source'] = mat.arm_blending_source_alpha con['alpha_blend_destination'] = mat.arm_blending_destination_alpha con['alpha_blend_operation'] = mat.arm_blending_operation_alpha - + con_overlay = mat_state.data.add_context(con) arm_discard = mat.arm_discard is_transluc = mat_utils.is_transluc(mat) parse_opacity = (blend and is_transluc) or arm_discard make_mesh.make_base(con_overlay, parse_opacity=parse_opacity) - + frag = con_overlay.frag if arm_discard: @@ -36,4 +46,6 @@ def make(context_id): frag.write('fragColor.rgb = pow(fragColor.rgb, vec3(1.0 / 2.2));') + make_finalize.make(con_overlay) + return con_overlay diff --git a/blender/arm/material/make_particle.py b/blender/arm/material/make_particle.py index 2c9a71e2..8c71e136 100644 --- a/blender/arm/material/make_particle.py +++ b/blender/arm/material/make_particle.py @@ -1,7 +1,13 @@ - import arm.utils import arm.material.mat_state as mat_state +if arm.is_reload(__name__): + arm.utils = arm.reload_module(arm.utils) + mat_state = arm.reload_module(mat_state) +else: + arm.enable_reload(__name__) + + def write(vert, particle_info=None, shadowmap=False): # Outs @@ -17,7 +23,7 @@ def write(vert, particle_info=None, shadowmap=False): str_tex_hash = "float fhash(float n) { return fract(sin(n) * 43758.5453); }\n" vert.add_function(str_tex_hash) - + prep = 'float ' if out_age: prep = '' @@ -45,7 +51,7 @@ def write(vert, particle_info=None, shadowmap=False): vert.write('}') # vert.write('p_age /= 2;') # Match - + # object_align_factor / 2 + gxyz prep = 'vec3 ' if out_velocity: diff --git a/blender/arm/material/make_shader.py b/blender/arm/material/make_shader.py index 93b40391..8e95fa5c 100644 --- a/blender/arm/material/make_shader.py +++ b/blender/arm/material/make_shader.py @@ -22,6 +22,26 @@ import arm.material.mat_utils as mat_utils from arm.material.shader import Shader, ShaderContext, ShaderData import arm.utils +if arm.is_reload(__name__): + arm.api = arm.reload_module(arm.api) + assets = arm.reload_module(assets) + arm.exporter = arm.reload_module(arm.exporter) + log = arm.reload_module(log) + cycles = arm.reload_module(cycles) + make_decal = arm.reload_module(make_decal) + make_depth = arm.reload_module(make_depth) + make_mesh = arm.reload_module(make_mesh) + make_overlay = arm.reload_module(make_overlay) + make_transluc = arm.reload_module(make_transluc) + make_voxel = arm.reload_module(make_voxel) + mat_state = arm.reload_module(mat_state) + mat_utils = arm.reload_module(mat_utils) + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader, ShaderContext, ShaderData + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + rpass_hook = None diff --git a/blender/arm/material/make_skin.py b/blender/arm/material/make_skin.py index d6ea4177..aaaff3ce 100644 --- a/blender/arm/material/make_skin.py +++ b/blender/arm/material/make_skin.py @@ -1,5 +1,11 @@ import arm.utils +if arm.is_reload(__name__): + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + def skin_pos(vert): vert.add_include('compiled.inc') @@ -15,6 +21,7 @@ def skin_pos(vert): vert.write_attrib('spos.xyz += 2.0 * (skinA.w * skinB.xyz - skinB.w * skinA.xyz + cross(skinA.xyz, skinB.xyz)); // Translate') vert.write_attrib('spos.xyz /= posUnpack;') + def skin_nor(vert, prep): rpdat = arm.utils.get_rp() vert.write_attrib(prep + 'wnormal = normalize(N * (vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w))));') diff --git a/blender/arm/material/make_transluc.py b/blender/arm/material/make_transluc.py index 4257b53e..69757345 100644 --- a/blender/arm/material/make_transluc.py +++ b/blender/arm/material/make_transluc.py @@ -1,10 +1,22 @@ import bpy + +import arm import arm.material.cycles as cycles import arm.material.mat_state as mat_state import arm.material.make_mesh as make_mesh import arm.material.make_finalize as make_finalize import arm.assets as assets +if arm.is_reload(__name__): + cycles = arm.reload_module(cycles) + mat_state = arm.reload_module(mat_state) + make_mesh = arm.reload_module(make_mesh) + make_finalize = arm.reload_module(make_finalize) + assets = arm.reload_module(assets) +else: + arm.enable_reload(__name__) + + def make(context_id): con_transluc = mat_state.data.add_context({ 'name': context_id, 'depth_write': False, 'compare_mode': 'less', 'cull_mode': 'clockwise', \ 'blend_source': 'blend_one', 'blend_destination': 'blend_one', 'blend_operation': 'add', \ @@ -26,7 +38,7 @@ def make(context_id): if '_VoxelAOvar' in wrd.world_defs: frag.write('indirect *= 0.25;') frag.write('vec4 premultipliedReflect = vec4(vec3(direct + indirect * 0.5) * opacity, opacity);') - + frag.write('float w = clamp(pow(min(1.0, premultipliedReflect.a * 10.0) + 0.01, 3.0) * 1e8 * pow(1.0 - (gl_FragCoord.z) * 0.9, 3.0), 1e-2, 3e3);') frag.write('fragColor[0] = vec4(premultipliedReflect.rgb * w, premultipliedReflect.a);') frag.write('fragColor[1] = vec4(premultipliedReflect.a * w, 0.0, 0.0, 1.0);') diff --git a/blender/arm/material/make_voxel.py b/blender/arm/material/make_voxel.py index d86fac29..5fd84d1e 100644 --- a/blender/arm/material/make_voxel.py +++ b/blender/arm/material/make_voxel.py @@ -1,11 +1,16 @@ import bpy + import arm.utils import arm.assets as assets -import arm.material.cycles as cycles import arm.material.mat_state as mat_state -import arm.material.mat_utils as mat_utils -import arm.material.make_particle as make_particle -import arm.make_state as state + +if arm.is_reload(__name__): + arm.utils = arm.reload_module(arm.utils) + assets = arm.reload_module(assets) + mat_state = arm.reload_module(mat_state) +else: + arm.enable_reload(__name__) + def make(context_id): rpdat = arm.utils.get_rp() @@ -108,7 +113,7 @@ def make_ao(context_id): if rpdat.arm_voxelgi_revoxelize and rpdat.arm_voxelgi_camera: vert.add_uniform('vec3 eyeSnap', '_cameraPositionSnap') vert.write('voxpositionGeom = (vec3(W * vec4(pos.xyz, 1.0)) - eyeSnap) / voxelgiHalfExtents;') - else: + else: vert.write('voxpositionGeom = vec3(W * vec4(pos.xyz, 1.0)) / voxelgiHalfExtents;') geom.add_out('vec3 voxposition') diff --git a/blender/arm/material/mat_batch.py b/blender/arm/material/mat_batch.py index dc86eefe..1412a8bc 100644 --- a/blender/arm/material/mat_batch.py +++ b/blender/arm/material/mat_batch.py @@ -1,8 +1,15 @@ -import bpy +import arm import arm.material.cycles as cycles import arm.material.make_shader as make_shader import arm.material.mat_state as mat_state +if arm.is_reload(__name__): + cycles = arm.reload_module(cycles) + make_shader = arm.reload_module(make_shader) + mat_state = arm.reload_module(mat_state) +else: + arm.enable_reload(__name__) + # TODO: handle groups # TODO: handle cached shaders @@ -21,7 +28,7 @@ def traverse_tree(node, sign): def get_signature(mat): nodes = mat.node_tree.nodes output_node = cycles.node_by_type(nodes, 'OUTPUT_MATERIAL') - + if output_node != None: sign = traverse_tree(output_node, '') # Append flags @@ -40,7 +47,7 @@ def traverse_tree2(node, ar): def get_sorted(mat): nodes = mat.node_tree.nodes output_node = cycles.node_by_type(nodes, 'OUTPUT_MATERIAL') - + if output_node != None: ar = [] traverse_tree2(output_node, ar) diff --git a/blender/arm/material/mat_utils.py b/blender/arm/material/mat_utils.py index 9d6eb1f0..7403f5dd 100644 --- a/blender/arm/material/mat_utils.py +++ b/blender/arm/material/mat_utils.py @@ -1,9 +1,18 @@ import bpy + import arm.utils import arm.make_state as make_state import arm.material.cycles as cycles import arm.log as log +if arm.is_reload(__name__): + arm.utils = arm.reload_module(arm.utils) + make_state = arm.reload_module(make_state) + cycles = arm.reload_module(cycles) + log = arm.reload_module(log) +else: + arm.enable_reload(__name__) + add_mesh_contexts = [] def disp_linked(output_node): @@ -42,7 +51,7 @@ def get_rpasses(material): ar.append('voxel') if rpdat.rp_renderer == 'Forward' and rpdat.rp_depthprepass and not material.arm_blending and not material.arm_particle_flag: ar.append('depth') - + if material.arm_cast_shadow and rpdat.rp_shadows and ('mesh' in ar): ar.append('shadowmap') @@ -73,7 +82,8 @@ def is_transluc_type(node): node.type == 'BSDF_TRANSPARENT' or \ node.type == 'BSDF_TRANSLUCENT' or \ (node.type == 'GROUP' and node.node_tree.name.startswith('Armory PBR') and (node.inputs[1].is_linked or node.inputs[1].default_value != 1.0)) or \ - (node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 20 and (node.inputs[18].is_linked or node.inputs[18].default_value != 1.0)): + (node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 20 and (node.inputs[18].is_linked or node.inputs[18].default_value != 1.0)) or \ + (node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 21 and (node.inputs[19].is_linked or node.inputs[19].default_value != 1.0)): return True return False diff --git a/blender/arm/material/parser_state.py b/blender/arm/material/parser_state.py index bcbb59c8..77b01941 100644 --- a/blender/arm/material/parser_state.py +++ b/blender/arm/material/parser_state.py @@ -3,8 +3,15 @@ from typing import List, Set, Tuple, Union, Optional import bpy +import arm from arm.material.shader import Shader, ShaderContext, vec3str, floatstr +if arm.is_reload(__name__): + arm.material.shader = arm.reload_module(arm.material.shader) + from arm.material.shader import Shader, ShaderContext, vec3str, floatstr +else: + arm.enable_reload(__name__) + class ParserContext(Enum): """Describes which kind of node tree is parsed.""" @@ -64,6 +71,16 @@ class ParserState: self.out_opacity: floatstr = '1.0' self.out_emission: floatstr = '0.0' + def reset_outs(self): + """Reset the shader output values to their default values.""" + self.out_basecol = 'vec3(0.8)' + self.out_roughness = '0.0' + self.out_metallic = '0.0' + self.out_occlusion = '1.0' + self.out_specular = '1.0' + self.out_opacity = '1.0' + self.out_emission = '0.0' + def get_outs(self) -> Tuple[vec3str, floatstr, floatstr, floatstr, floatstr, floatstr, floatstr]: """Return the shader output values as a tuple.""" return (self.out_basecol, self.out_roughness, self.out_metallic, self.out_occlusion, self.out_specular, diff --git a/blender/arm/material/shader.py b/blender/arm/material/shader.py index 48539e96..ada7534e 100644 --- a/blender/arm/material/shader.py +++ b/blender/arm/material/shader.py @@ -1,5 +1,10 @@ import arm.utils +if arm.is_reload(__name__): + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + # Type aliases for type hints to make it easier to see which kind of # shader data type is stored in a string floatstr = str @@ -109,26 +114,51 @@ class ShaderContext: def get(self): return self.data - def add_constant(self, ctype, name, link=None): + def add_constant(self, ctype, name, link=None, default_value=None, is_arm_mat_param=None): for c in self.constants: if c['name'] == name: return - c = { 'name': name, 'type': ctype } - if link != None: + c = { 'name': name, 'type': ctype} + if link is not None: c['link'] = link + if default_value is not None: + if ctype == 'float': + c['float'] = default_value + if ctype == 'vec3': + c['vec3'] = default_value + if is_arm_mat_param is not None: + c['is_arm_parameter'] = 'true' self.constants.append(c) - def add_texture_unit(self, ctype, name, link=None, is_image=None): + def add_texture_unit(self, name, link=None, is_image=None, + addr_u=None, addr_v=None, + filter_min=None, filter_mag=None, mipmap_filter=None, + default_value=None, is_arm_mat_param=None): for c in self.tunits: if c['name'] == name: return - c = { 'name': name } - if link != None: + c = {'name': name} + if link is not None: c['link'] = link - if is_image != None: + if is_image is not None: c['is_image'] = is_image + if addr_u is not None: + c['addressing_u'] = addr_u + if addr_v is not None: + c['addressing_v'] = addr_v + if filter_min is not None: + c['filter_min'] = filter_min + if filter_mag is not None: + c['filter_mag'] = filter_mag + if mipmap_filter is not None: + c['mipmap_filter'] = mipmap_filter + if default_value is not None: + c['default_image_file'] = default_value + if is_arm_mat_param is not None: + c['is_arm_parameter'] = 'true' + self.tunits.append(c) def make_vert(self, custom_name: str = None): @@ -222,7 +252,10 @@ class Shader: if s not in self.outs: self.outs.append(s) - def add_uniform(self, s, link=None, included=False, top=False): + def add_uniform(self, s, link=None, included=False, top=False, + tex_addr_u=None, tex_addr_v=None, + tex_filter_min=None, tex_filter_mag=None, + tex_mipmap_filter=None, default_value=None, is_arm_mat_param=None): ar = s.split(' ') # layout(RGBA8) image3D voxels utype = ar[-2] @@ -233,9 +266,16 @@ class Shader: # Add individual units - mySamplers[0], mySamplers[1] for i in range(int(uname[-2])): uname_array = uname[:-2] + str(i) + ']' - self.context.add_texture_unit(utype, uname_array, link=link, is_image=is_image) + self.context.add_texture_unit( + uname_array, link, is_image, + tex_addr_u, tex_addr_v, + tex_filter_min, tex_filter_mag, tex_mipmap_filter) else: - self.context.add_texture_unit(utype, uname, link=link, is_image=is_image) + self.context.add_texture_unit( + uname, link, is_image, + tex_addr_u, tex_addr_v, + tex_filter_min, tex_filter_mag, tex_mipmap_filter, + default_value=default_value, is_arm_mat_param=is_arm_mat_param) else: # Prefer vec4[] for d3d to avoid padding if ar[0] == 'float' and '[' in ar[1]: @@ -244,13 +284,12 @@ class Shader: elif ar[0] == 'vec4' and '[' in ar[1]: ar[0] = 'floats' ar[1] = ar[1].split('[', 1)[0] - self.context.add_constant(ar[0], ar[1], link=link) + self.context.add_constant(ar[0], ar[1], link=link, default_value=default_value, is_arm_mat_param=is_arm_mat_param) if top: if not included and s not in self.uniforms_top: self.uniforms_top.append(s) - else: - if not included and s not in self.uniforms: - self.uniforms.append(s) + elif not included and s not in self.uniforms: + self.uniforms.append(s) def add_const(self, type_str: str, name: str, value_str: str, array_size: int = 0): """ diff --git a/blender/arm/node_utils.py b/blender/arm/node_utils.py index e8808f49..00c2e4c1 100755 --- a/blender/arm/node_utils.py +++ b/blender/arm/node_utils.py @@ -1,8 +1,22 @@ -from typing import Type +import collections +from typing import Any, Generator, Type, Union import bpy +import mathutils +from bpy.types import NodeSocket, NodeInputs, NodeOutputs from nodeitems_utils import NodeItem +import arm.log +import arm.logicnode.arm_sockets +import arm.utils + +if arm.is_reload(__name__): + arm.log = arm.reload_module(arm.log) + arm.logicnode.arm_sockets = arm.reload_module(arm.logicnode.arm_sockets) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + def find_node_by_link(node_group, to_node, inp): for link in node_group.links: @@ -46,6 +60,125 @@ def get_output_node(node_group, from_node, output_index): return link.to_node +def get_socket_index(sockets: Union[NodeInputs, NodeOutputs], socket: NodeSocket) -> int: + """Find the socket index in the given node input or output + collection, return -1 if not found. + """ + for i in range(0, len(sockets)): + if sockets[i] == socket: + return i + return -1 + + +def get_socket_default(socket: NodeSocket) -> Any: + """Get the socket's default value, or `None` if it doesn't exist.""" + if isinstance(socket, arm.logicnode.arm_sockets.ArmCustomSocket): + if socket.arm_socket_type != 'NONE': + return socket.default_value_raw + + # Shader-type sockets don't have a default value + elif socket.type != 'SHADER': + return socket.default_value + + return None + + +def set_socket_default(socket: NodeSocket, value: Any): + """Set the socket's default value if it exists.""" + if isinstance(socket, arm.logicnode.arm_sockets.ArmCustomSocket): + if socket.arm_socket_type != 'NONE': + socket.default_value_raw = value + + # Shader-type sockets don't have a default value + elif socket.type != 'SHADER': + socket.default_value = value + + +def get_export_tree_name(tree: bpy.types.NodeTree, do_warn=False) -> str: + """Return the name of the given node tree that's used in the + exported Haxe code. + + If `do_warn` is true, a warning is displayed if the export name + differs from the actual tree name. + """ + export_name = arm.utils.safesrc(tree.name[0].upper() + tree.name[1:]) + + if export_name != tree.name: + arm.log.warn('Logic node tree and generated trait names differ! Node' + f' tree: "{tree.name}", trait: "{export_name}"') + + return export_name + + +def get_export_node_name(node: bpy.types.Node) -> str: + """Return the name of the given node that's used in the exported + Haxe code. + """ + return '_' + arm.utils.safesrc(node.name) + + +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 + a counterpart in the node's Haxe class. + """ + for i in range(0, 10): + prop_name = f'property{i}_get' + prop_found = hasattr(node, prop_name) + if not prop_found: + prop_name = f'property{i}' + prop_found = hasattr(node, prop_name) + if prop_found: + # Haxe properties are called property0 - property9 even if + # their Python equivalent can end with '_get', so yield + # both names + yield prop_name, f'property{i}' + + +def haxe_format_socket_val(socket_val: Any, array_outer_brackets=True) -> str: + """Formats a socket value to be valid Haxe syntax. + + If `array_outer_brackets` is false, no square brackets are put + around array values. + + Make sure that elements of sequence types are not yet in Haxe + syntax, otherwise they are strings and get additional quotes! + """ + if isinstance(socket_val, bool): + socket_val = str(socket_val).lower() + + elif isinstance(socket_val, str): + socket_val = '"{:s}"'.format(socket_val.replace('"', '\\"')) + + elif isinstance(socket_val, (collections.Sequence, bpy.types.bpy_prop_array, mathutils.Color, mathutils.Euler, mathutils.Vector)): + socket_val = ','.join(haxe_format_socket_val(v, array_outer_brackets=True) for v in socket_val) + if array_outer_brackets: + socket_val = f'[{socket_val}]' + + elif socket_val is None: + # Don't write 'None' into the Haxe code + socket_val = 'null' + + return str(socket_val) + + +def haxe_format_prop_value(node: bpy.types.Node, prop_name: str) -> str: + """Formats a property value to be valid Haxe syntax.""" + prop_value = getattr(node, prop_name) + if isinstance(prop_value, str): + prop_value = '"' + str(prop_value) + '"' + elif isinstance(prop_value, bool): + prop_value = str(prop_value).lower() + elif hasattr(prop_value, 'name'): # PointerProperty + prop_value = '"' + str(prop_value.name) + '"' + else: + if prop_value is None: + prop_value = 'null' + else: + prop_value = str(prop_value) + + return prop_value + + def nodetype_to_nodeitem(node_type: Type[bpy.types.Node]) -> NodeItem: """Create a NodeItem from a given node class.""" # Internal node types seem to have no bl_idname attribute diff --git a/blender/arm/nodes_logic.py b/blender/arm/nodes_logic.py index 3a44971f..d6ac713b 100755 --- a/blender/arm/nodes_logic.py +++ b/blender/arm/nodes_logic.py @@ -8,8 +8,19 @@ import arm.logicnode.arm_nodes as arm_nodes import arm.logicnode.replacement import arm.logicnode import arm.props_traits +import arm.ui_icons as ui_icons import arm.utils +if arm.is_reload(__name__): + arm_nodes = arm.reload_module(arm_nodes) + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + arm.logicnode = arm.reload_module(arm.logicnode) + arm.props_traits = arm.reload_module(arm.props_traits) + ui_icons = arm.reload_module(ui_icons) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + registered_nodes = [] registered_categories = [] @@ -58,13 +69,14 @@ class ARM_OT_AddNodeOverride(bpy.types.Operator): bl_idname = "arm.add_node_override" bl_label = "Add Node" bl_property = "type" + bl_options = {'INTERNAL'} type: StringProperty(name="NodeItem type") use_transform: BoolProperty(name="Use Transform") def invoke(self, context, event): bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.type, use_transform=self.use_transform) - return {"FINISHED"} + return {'FINISHED'} @classmethod def description(cls, context, properties): @@ -173,7 +185,7 @@ class ARM_PT_LogicNodePanel(bpy.types.Panel): layout.operator('arm.open_node_documentation', icon='HELP') column = layout.column(align=True) column.operator('arm.open_node_python_source', icon='FILE_SCRIPT') - column.operator('arm.open_node_haxe_source', icon_value=arm.props_traits.icons_dict['haxe'].icon_id) + column.operator('arm.open_node_haxe_source', icon_value=ui_icons.get_id("haxe")) class ArmOpenNodeHaxeSource(bpy.types.Operator): @@ -261,7 +273,7 @@ class ARM_PT_Variables(bpy.types.Panel): setN.ntype = ID class ARMAddVarNode(bpy.types.Operator): - '''Add a linked node of that Variable''' + """Add a linked node of that Variable""" bl_idname = 'arm.add_var_node' bl_label = 'Add Get' bl_options = {'GRAB_CURSOR', 'BLOCKING'} @@ -296,7 +308,7 @@ class ARMAddVarNode(bpy.types.Operator): return({'FINISHED'}) class ARMAddSetVarNode(bpy.types.Operator): - '''Add a node to set this Variable''' + """Add a node to set this Variable""" bl_idname = 'arm.add_setvar_node' bl_label = 'Add Set' bl_options = {'GRAB_CURSOR', 'BLOCKING'} @@ -361,6 +373,7 @@ class ReplaceNodesOperator(bpy.types.Operator): def register(): + arm.logicnode.arm_nodes.register() arm.logicnode.arm_sockets.register() bpy.utils.register_class(ArmLogicTree) @@ -401,3 +414,4 @@ def unregister(): bpy.utils.register_class(ARM_MT_NodeAddOverride.overridden_menu) arm.logicnode.arm_sockets.unregister() + arm.logicnode.arm_nodes.unregister() diff --git a/blender/arm/nodes_material.py b/blender/arm/nodes_material.py index ef9a8f1e..fd6750c6 100644 --- a/blender/arm/nodes_material.py +++ b/blender/arm/nodes_material.py @@ -2,11 +2,19 @@ import bpy import nodeitems_utils from nodeitems_utils import NodeCategory +import arm import arm.material.arm_nodes.arm_nodes as arm_nodes # Import all nodes so that they register. Do not remove this import # even if it looks unused from arm.material.arm_nodes import * +if arm.is_reload(__name__): + arm_nodes = arm.reload_module(arm_nodes) + arm.material.arm_nodes = arm.reload_module(arm.material.arm_nodes) + from arm.material.arm_nodes import * +else: + arm.enable_reload(__name__) + registered_nodes = [] diff --git a/blender/arm/profiler.py b/blender/arm/profiler.py index 87f7a0a2..3c657981 100644 --- a/blender/arm/profiler.py +++ b/blender/arm/profiler.py @@ -2,9 +2,16 @@ import cProfile import os import pstats +import arm import arm.log as log import arm.utils as utils +if arm.is_reload(__name__): + log = arm.reload_module(log) + utils = arm.reload_module(utils) +else: + arm.enable_reload(__name__) + class Profile: """Context manager for profiling the enclosed code when the given condition is true. diff --git a/blender/arm/props.py b/blender/arm/props.py index 47398aa3..7d71f07c 100755 --- a/blender/arm/props.py +++ b/blender/arm/props.py @@ -10,8 +10,18 @@ import arm.nodes_logic import arm.proxy import arm.utils +if arm.is_reload(__name__): + assets = arm.reload_module(assets) + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + arm.make = arm.reload_module(arm.make) + arm.nodes_logic = arm.reload_module(arm.nodes_logic) + arm.proxy = arm.reload_module(arm.proxy) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + # Armory version -arm_version = '2021.2' +arm_version = '2021.8' arm_commit = '$Id$' def get_project_html5_copy(self): @@ -178,7 +188,7 @@ def init_properties(): ('ErrorsOnly', 'Errors Only', 'Show only errors')], name="Compile Log Parameter", update=assets.invalidate_compiler_cache, default="Summary") - bpy.types.World.arm_project_win_build_cpu = IntProperty(name="Count CPU", description="Specifies the maximum number of concurrent processes to use when building", default=1, min=1, max=multiprocessing.cpu_count()) + bpy.types.World.arm_project_win_build_cpu = IntProperty(name="CPU Count", description="Specifies the maximum number of concurrent processes to use when building", default=1, min=1, max=multiprocessing.cpu_count()) bpy.types.World.arm_project_win_build_open = BoolProperty(name="Open Build Directory", description="Open the build directory after successfully assemble", default=False) bpy.types.World.arm_project_icon = StringProperty(name="Icon (PNG)", description="Exported project icon, must be a PNG image", default="", subtype="FILE_PATH", update=assets.invalidate_compiler_cache) @@ -212,11 +222,11 @@ def init_properties(): bpy.types.World.arm_khafile = PointerProperty(name="Khafile", description="Source appended to khafile.js", update=assets.invalidate_compiler_cache, type=bpy.types.Text) bpy.types.World.arm_texture_quality = FloatProperty(name="Texture Quality", default=1.0, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache) bpy.types.World.arm_sound_quality = FloatProperty(name="Sound Quality", default=0.9, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache) - bpy.types.World.arm_minimize = BoolProperty(name="Minimize Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data) + bpy.types.World.arm_minimize = BoolProperty(name="Binary Scene Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data) bpy.types.World.arm_minify_js = BoolProperty(name="Minify JS", description="Minimize JavaScript output when publishing", default=True) bpy.types.World.arm_optimize_data = BoolProperty(name="Optimize Data", description="Export more efficient geometry and shader data, prolongs build times", default=True, update=assets.invalidate_compiled_data) bpy.types.World.arm_deinterleaved_buffers = BoolProperty(name="Deinterleaved Buffers", description="Use deinterleaved vertex buffers", default=False, update=assets.invalidate_compiler_cache) - bpy.types.World.arm_export_tangents = BoolProperty(name="Export Tangents", description="Precompute tangents for normal mapping, otherwise computed in shader", default=True, update=assets.invalidate_compiled_data) + bpy.types.World.arm_export_tangents = BoolProperty(name="Precompute Tangents", description="Precompute tangents for normal mapping, otherwise computed in shader", default=True, update=assets.invalidate_compiled_data) bpy.types.World.arm_batch_meshes = BoolProperty(name="Batch Meshes", description="Group meshes by materials to speed up rendering", default=False, update=assets.invalidate_compiler_cache) bpy.types.World.arm_batch_materials = BoolProperty(name="Batch Materials", description="Marge similar materials into single pipeline state", default=False, update=assets.invalidate_shader_cache) bpy.types.World.arm_stream_scene = BoolProperty(name="Stream Scene", description="Stream scene content", default=False, update=assets.invalidate_compiler_cache) @@ -280,6 +290,13 @@ def init_properties(): bpy.types.Object.arm_rb_trigger = BoolProperty(name="Trigger", description="Disable contact response", default=False) bpy.types.Object.arm_rb_deactivation_time = FloatProperty(name="Deactivation Time", description="Delay putting rigid body into sleep", default=0.0) bpy.types.Object.arm_rb_ccd = BoolProperty(name="Continuous Collision Detection", description="Improve collision for fast moving objects", default=False) + bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty( + name="Collision Collections Filter Mask", + description="Collision collections rigid body interacts with", + default=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False), + size=20, + subtype='LAYER') + bpy.types.Object.arm_relative_physics_constraint = BoolProperty(name="Relative Physics Constraint", description="Add physics constraint relative to the parent object or collection when spawned", default=False) bpy.types.Object.arm_animation_enabled = BoolProperty(name="Animation", description="Enable skinning & timeline animation", default=True) bpy.types.Object.arm_tilesheet = StringProperty(name="Tilesheet", description="Set tilesheet animation", default='') bpy.types.Object.arm_tilesheet_action = StringProperty(name="Tilesheet Action", description="Set startup action", default='') @@ -326,6 +343,7 @@ def init_properties(): bpy.types.World.arm_envtex_sun_direction = FloatVectorProperty(name="Sun Direction", size=3, default=[0,0,0]) bpy.types.World.arm_envtex_turbidity = FloatProperty(name="Turbidity", default=1.0) bpy.types.World.arm_envtex_ground_albedo = FloatProperty(name="Ground Albedo", default=0.0) + bpy.types.World.arm_nishita_density = FloatVectorProperty(name="Nishita Density", size=3, default=[1, 1, 1]) bpy.types.Material.arm_cast_shadow = BoolProperty(name="Cast Shadow", default=True) bpy.types.Material.arm_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True) bpy.types.Material.arm_overlay = BoolProperty(name="Overlay", default=False) @@ -392,7 +410,7 @@ def init_properties(): ('destination_color', 'Destination Color', 'Destination Color'), ('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'), ('inverse_destination_color', 'Inverse Destination Color', 'Inverse Destination Color')], - name='Source', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache) + name='Source (Alpha)', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache) bpy.types.Material.arm_blending_destination_alpha = EnumProperty( items=[('blend_one', 'One', 'One'), ('blend_zero', 'Zero', 'Zero'), @@ -404,14 +422,14 @@ def init_properties(): ('destination_color', 'Destination Color', 'Destination Color'), ('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'), ('inverse_destination_color', 'Inverse Destination Color', 'Inverse Destination Color')], - name='Destination', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache) + name='Destination (Alpha)', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache) bpy.types.Material.arm_blending_operation_alpha = EnumProperty( items=[('add', 'Add', 'Add'), ('subtract', 'Subtract', 'Subtract'), ('reverse_subtract', 'Reverse Subtract', 'Reverse Subtract'), ('min', 'Min', 'Min'), ('max', 'Max', 'Max')], - name='Operation', default='add', description='Blending operation', update=assets.invalidate_shader_cache) + name='Operation (Alpha)', default='add', description='Blending operation', update=assets.invalidate_shader_cache) # For scene bpy.types.Scene.arm_export = BoolProperty(name="Export", description="Export scene data", default=True) bpy.types.Scene.arm_terrain_textures = StringProperty(name="Textures", description="Set root folder for terrain assets", default="//Bundled/", subtype="DIR_PATH") @@ -436,6 +454,10 @@ def init_properties(): bpy.types.World.compo_defs = StringProperty(name="Compositor Shader Defs", default='') bpy.types.World.arm_use_clouds = BoolProperty(name="Clouds", default=False, update=assets.invalidate_shader_cache) + bpy.types.World.arm_darken_clouds = BoolProperty( + name="Darken Clouds at Night", + description="Darkens the clouds when the sun is low. This setting is for artistic purposes and is not physically correct", + default=False, update=assets.invalidate_shader_cache) bpy.types.World.arm_clouds_lower = FloatProperty(name="Lower", default=1.0, min=0.1, max=10.0, update=assets.invalidate_shader_cache) bpy.types.World.arm_clouds_upper = FloatProperty(name="Upper", default=1.0, min=0.1, max=10.0, update=assets.invalidate_shader_cache) bpy.types.World.arm_clouds_wind = FloatVectorProperty(name="Wind", default=[1.0, 0.0], size=2, update=assets.invalidate_shader_cache) @@ -481,18 +503,25 @@ def init_properties_on_load(): def update_armory_world(): global arm_version wrd = bpy.data.worlds['Arm'] + # Outdated project - if bpy.data.filepath != '' and (wrd.arm_version != arm_version or wrd.arm_commit != arm_commit): # Call on project load only - # This allows for seamless migration from ealier versions of Armory - for rp in wrd.arm_rplist: # TODO: deprecated + file_version = tuple(map(int, wrd.arm_version.split('.'))) + sdk_version = tuple(map(int, arm_version.split('.'))) + if bpy.data.filepath != '' and (file_version < sdk_version or wrd.arm_commit != arm_commit): + # This allows for seamless migration from earlier versions of Armory + for rp in wrd.arm_rplist: # TODO: deprecated if rp.rp_gi != 'Off': rp.rp_gi = 'Off' rp.rp_voxelao = True - # Replace deprecated nodes + if file_version < (2021, 8): + # There were breaking changes in SDK 2021.08, use a special + # update routine first before regularly replacing nodes + arm.logicnode.replacement.node_compat_sdk2108() + arm.logicnode.replacement.replace_all() - print('Project updated to sdk v' + arm_version + ' (' + arm_commit + ')') + print(f'Project updated to SDK v{arm_version}({arm_commit})') wrd.arm_version = arm_version wrd.arm_commit = arm_commit arm.make.clean() diff --git a/blender/arm/props_bake.py b/blender/arm/props_bake.py index 25bff93a..50af8499 100644 --- a/blender/arm/props_bake.py +++ b/blender/arm/props_bake.py @@ -1,9 +1,18 @@ -import arm.utils -import arm.assets import bpy from bpy.types import Menu, Panel, UIList from bpy.props import * -from arm.lightmapper import operators, properties, utility, keymap + +from arm.lightmapper import operators, properties, utility + +import arm.assets +import arm.utils + +if arm.is_reload(__name__): + arm.assets = arm.reload_module(arm.assets) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + class ArmBakeListItem(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object, description="The object to bake") @@ -166,7 +175,7 @@ class ArmBakeButton(bpy.types.Operator): img_node.image = img img_node.select = True nodes.active = img_node - + obs = bpy.context.view_layer.objects # Unwrap @@ -361,7 +370,6 @@ def register(): operators.register() properties.register() - keymap.register() def unregister(): bpy.utils.unregister_class(ArmBakeListItem) @@ -381,4 +389,3 @@ def unregister(): operators.unregister() properties.unregister() - keymap.unregister() \ No newline at end of file diff --git a/blender/arm/props_collision_filter_mask.py b/blender/arm/props_collision_filter_mask.py index d5b52910..f14c25aa 100644 --- a/blender/arm/props_collision_filter_mask.py +++ b/blender/arm/props_collision_filter_mask.py @@ -1,31 +1,38 @@ import bpy -from bpy.props import * -from bpy.types import Panel + class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel): - bl_label = "Armory Collision Filter Mask" + bl_label = "Collections Filter Mask" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "physics" - + bl_parent_id = "ARM_PT_PhysicsPropsPanel" + + @classmethod + def poll(self, context): + obj = context.object + if obj is None: + return False + return obj.rigid_body is not None def draw(self, context): layout = self.layout - layout.use_property_split = True + layout.use_property_split = False layout.use_property_decorate = False - obj = bpy.context.object - if obj == None: - return - if obj.rigid_body != None: - layout.prop(obj, 'arm_rb_collision_filter_mask') + obj = context.object + layout.prop(obj, 'arm_rb_collision_filter_mask', text="", expand=True) + col_mask = '' + for b in obj.arm_rb_collision_filter_mask: + col_mask = ('1' if b else '0') + col_mask + col = layout.column() + row = col.row() + row.alignment = 'RIGHT' + row.label(text=f'Integer Mask Value: {str(int(col_mask, 2))}') + def register(): bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel) - bpy.types.Object.arm_rb_collision_filter_mask = bpy.props.BoolVectorProperty( - name="Collision Filter Mask", - default=(True, False, False,False,False,False, False, False,False,False,False, False, False,False,False,False, False, False,False,False), - size=20, - subtype='LAYER') + def unregister(): bpy.utils.unregister_class(ARM_PT_RbCollisionFilterMaskPanel) diff --git a/blender/arm/props_exporter.py b/blender/arm/props_exporter.py index 2b19a543..2cbbbfdb 100644 --- a/blender/arm/props_exporter.py +++ b/blender/arm/props_exporter.py @@ -1,14 +1,23 @@ import os import shutil -import arm.assets as assets -import arm.utils -import bpy import stat import subprocess import webbrowser + +import bpy from bpy.types import Menu, Panel, UIList from bpy.props import * +import arm.assets as assets +import arm.utils + +if arm.is_reload(__name__): + assets = arm.reload_module(assets) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + def remove_readonly(func, path, excinfo): os.chmod(path, stat.S_IWRITE) func(path) @@ -358,7 +367,7 @@ class ArmExporterSpecialsMenu(bpy.types.Menu): layout.operator("arm.exporter_gpuprofile") class ArmoryExporterOpenFolderButton(bpy.types.Operator): - '''Open published folder''' + """Open published folder""" bl_idname = 'arm.exporter_open_folder' bl_label = 'Open Folder' @@ -417,7 +426,7 @@ def register(): bpy.types.World.arm_exporterlist = CollectionProperty(type=ArmExporterListItem) bpy.types.World.arm_exporterlist_index = IntProperty(name="Index for my_list", default=0) bpy.types.World.arm_exporter_android_permission_list = CollectionProperty(type=ArmExporterAndroidPermissionListItem) - bpy.types.World.arm_exporter_android_permission_list_index = IntProperty(name="Index for my_list", default=0) + bpy.types.World.arm_exporter_android_permission_list_index = IntProperty(name="Index for my_list", default=0) bpy.types.World.arm_exporter_android_abi_list = CollectionProperty(type=ArmExporterAndroidAbiListItem) bpy.types.World.arm_exporter_android_abi_list_index = IntProperty(name="Index for my_list", default=0) diff --git a/blender/arm/props_lod.py b/blender/arm/props_lod.py index ef1b2e86..2ad67bf2 100755 --- a/blender/arm/props_lod.py +++ b/blender/arm/props_lod.py @@ -1,5 +1,4 @@ import bpy -from bpy.types import Menu, Panel, UIList from bpy.props import * def update_size_prop(self, context): @@ -34,29 +33,29 @@ class ArmLodListItem(bpy.types.PropertyGroup): class ARM_UL_LodList(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - # We could write some code to decide which icon to use here... - custom_icon = 'OBJECT_DATAMODE' + layout.use_property_split = False - # Make sure your code supports all 3 layout types if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.prop(item, "enabled_prop") + row = layout.row() + row.separator(factor=0.1) + row.prop(item, "enabled_prop") name = item.name if name == '': name = 'None' - row = layout.row() - row.label(text=name, icon=custom_icon) + row.label(text=name, icon='OBJECT_DATAMODE') col = row.column() col.alignment = 'RIGHT' col.label(text="{:.2f}".format(item.screen_size_prop)) elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - layout.label(text="", icon = custom_icon) + layout.label(text="", icon='OBJECT_DATAMODE') class ArmLodListNewItem(bpy.types.Operator): # Add a new item to the list bl_idname = "arm_lodlist.new_item" bl_label = "Add a new item" + bl_options = {'UNDO'} def execute(self, context): mdata = bpy.context.object.data @@ -69,10 +68,13 @@ class ArmLodListDeleteItem(bpy.types.Operator): # Delete the selected item from the list bl_idname = "arm_lodlist.delete_item" bl_label = "Deletes an item" + bl_options = {'INTERNAL', 'UNDO'} @classmethod - def poll(self, context): + def poll(cls, context): """ Enable if there's something in the list """ + if bpy.context.object is None: + return False mdata = bpy.context.object.data return len(mdata.arm_lodlist) > 0 @@ -98,6 +100,7 @@ class ArmLodListMoveItem(bpy.types.Operator): # Move an item in the list bl_idname = "arm_lodlist.move_item" bl_label = "Move an item in the list" + bl_options = {'INTERNAL', 'UNDO'} direction: EnumProperty( items=( ('UP', 'Up', ""), diff --git a/blender/arm/props_renderpath.py b/blender/arm/props_renderpath.py index 246c505c..6196f4f3 100644 --- a/blender/arm/props_renderpath.py +++ b/blender/arm/props_renderpath.py @@ -4,6 +4,43 @@ from bpy.props import * import arm.assets as assets import arm.utils +if arm.is_reload(__name__): + assets = arm.reload_module(assets) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + +atlas_sizes = [ ('256', '256', '256'), + ('512', '512', '512'), + ('1024', '1024', '1024'), + ('2048', '2048', '2048'), + ('4096', '4096', '4096'), + ('8192', '8192', '8192'), + ('16384', '16384', '16384'), + ('32768', '32768', '32768') ] + +def atlas_sizes_from_min(min_size: int) -> list: + """ Create an enum list of atlas sizes from a minimal size """ + sizes = [] + for i in range(len(atlas_sizes)): + if int(atlas_sizes[i][0]) > min_size: + sizes.append(atlas_sizes[i]) + return sizes + +def update_spot_sun_atlas_size_options(scene: bpy.types.Scene, context: bpy.types.Context) -> list: + wrd = bpy.data.worlds['Arm'] + if len(wrd.arm_rplist) <= wrd.arm_rplist_index: + return [] + rpdat = wrd.arm_rplist[wrd.arm_rplist_index] + return atlas_sizes_from_min(int(rpdat.rp_shadowmap_cascade)) + +def update_point_atlas_size_options(scene: bpy.types.Scene, context: bpy.types.Context) -> list: + wrd = bpy.data.worlds['Arm'] + if len(wrd.arm_rplist) <= wrd.arm_rplist_index: + return [] + rpdat = wrd.arm_rplist[wrd.arm_rplist_index] + return atlas_sizes_from_min(int(rpdat.rp_shadowmap_cube) * 2) + def update_preset(self, context): rpdat = arm.utils.get_rp() if self.rp_preset == 'Desktop': @@ -271,33 +308,17 @@ class ArmRPListItem(bpy.types.PropertyGroup): ('8', '8', '8'),], name="LOD Subdivisions", description="Number of subdivisions of the default tile size for LOD", default='2', update=update_renderpath) rp_shadowmap_atlas_max_size_point: EnumProperty( - items=[('1024', '1024', '1024'), - ('2048', '2048', '2048'), - ('4096', '4096', '4096'), - ('8192', '8192', '8192'), - ('16384', '16384', '16384')], - name="Max Atlas Texture Size Points", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + items=update_point_atlas_size_options, + name="Max Atlas Texture Size Points", description="Sets the limit of the size of the texture.", update=update_renderpath) rp_shadowmap_atlas_max_size_spot: EnumProperty( - items=[('1024', '1024', '1024'), - ('2048', '2048', '2048'), - ('4096', '4096', '4096'), - ('8192', '8192', '8192'), - ('16384', '16384', '16384')], - name="Max Atlas Texture Size Spots", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + items=update_spot_sun_atlas_size_options, + name="Max Atlas Texture Size Spots", description="Sets the limit of the size of the texture.", update=update_renderpath) rp_shadowmap_atlas_max_size_sun: EnumProperty( - items=[('1024', '1024', '1024'), - ('2048', '2048', '2048'), - ('4096', '4096', '4096'), - ('8192', '8192', '8192'), - ('16384', '16384', '16384')], - name="Max Atlas Texture Size Sun", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + items=update_spot_sun_atlas_size_options, + name="Max Atlas Texture Size Sun", description="Sets the limit of the size of the texture.", update=update_renderpath) rp_shadowmap_atlas_max_size: EnumProperty( - items=[('1024', '1024', '1024'), - ('2048', '2048', '2048'), - ('4096', '4096', '4096'), - ('8192', '8192', '8192'), - ('16384', '16384', '16384')], - name="Max Atlas Texture Size", description="Sets the limit of the size of the texture.", default='8192', update=update_renderpath) + items=update_spot_sun_atlas_size_options, + name="Max Atlas Texture Size", description="Sets the limit of the size of the texture.", update=update_renderpath) rp_shadowmap_cube: EnumProperty( items=[('256', '256', '256'), ('512', '512', '512'), diff --git a/blender/arm/props_tilesheet.py b/blender/arm/props_tilesheet.py index eeea1b4f..1f7d53b6 100644 --- a/blender/arm/props_tilesheet.py +++ b/blender/arm/props_tilesheet.py @@ -1,30 +1,26 @@ -import shutil import bpy -import os -import json -from bpy.types import Menu, Panel, UIList from bpy.props import * class ArmTilesheetActionListItem(bpy.types.PropertyGroup): name: StringProperty( - name="Name", - description="A name for this item", - default="Untitled") + name="Name", + description="A name for this item", + default="Untitled") start_prop: IntProperty( - name="Start", - description="A name for this item", - default=0) + name="Start", + description="A name for this item", + default=0) end_prop: IntProperty( - name="End", - description="A name for this item", - default=0) + name="End", + description="A name for this item", + default=0) loop_prop: BoolProperty( - name="Loop", - description="A name for this item", - default=True) + name="Loop", + description="A name for this item", + default=True) class ARM_UL_TilesheetActionList(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): @@ -52,14 +48,16 @@ class ArmTilesheetActionListNewItem(bpy.types.Operator): return{'FINISHED'} class ArmTilesheetActionListDeleteItem(bpy.types.Operator): - # Delete the selected item from the list + """Delete the selected item from the list""" bl_idname = "arm_tilesheetactionlist.delete_item" bl_label = "Deletes an item" @classmethod def poll(self, context): - """ Enable if there's something in the list """ + """Enable if there's something in the list""" wrd = bpy.data.worlds['Arm'] + if len(wrd.arm_tilesheetlist) == 0: + return False trait = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index] return len(trait.arm_tilesheetactionlist) > 0 @@ -78,18 +76,23 @@ class ArmTilesheetActionListDeleteItem(bpy.types.Operator): return{'FINISHED'} class ArmTilesheetActionListMoveItem(bpy.types.Operator): - # Move an item in the list + """Move an item in the list""" bl_idname = "arm_tilesheetactionlist.move_item" bl_label = "Move an item in the list" + bl_options = {'INTERNAL'} + direction: EnumProperty( - items=( - ('UP', 'Up', ""), - ('DOWN', 'Down', ""),)) + items=( + ('UP', 'Up', ""), + ('DOWN', 'Down', "") + )) @classmethod def poll(self, context): - """ Enable if there's something in the list. """ + """Enable if there's something in the list""" wrd = bpy.data.worlds['Arm'] + if len(wrd.arm_tilesheetlist) == 0: + return False trait = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index] return len(trait.arm_tilesheetactionlist) > 0 @@ -129,27 +132,27 @@ class ArmTilesheetActionListMoveItem(bpy.types.Operator): class ArmTilesheetListItem(bpy.types.PropertyGroup): name: StringProperty( - name="Name", - description="A name for this item", - default="Untitled") + name="Name", + description="A name for this item", + default="Untitled") tilesx_prop: IntProperty( - name="Tiles X", - description="A name for this item", - default=0) + name="Tiles X", + description="A name for this item", + default=0) tilesy_prop: IntProperty( - name="Tiles Y", - description="A name for this item", - default=0) + name="Tiles Y", + description="A name for this item", + default=0) framerate_prop: FloatProperty( - name="Frame Rate", - description="A name for this item", - default=4.0) + name="Frame Rate", + description="A name for this item", + default=4.0) arm_tilesheetactionlist: CollectionProperty(type=ArmTilesheetActionListItem) - arm_tilesheetactionlist_index: IntProperty(name="Index for my_list", default=0) + arm_tilesheetactionlist_index: IntProperty(name="Index for arm_tilesheetactionlist", default=0) class ARM_UL_TilesheetList(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): @@ -162,10 +165,10 @@ class ARM_UL_TilesheetList(bpy.types.UIList): elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - layout.label(text="", icon = custom_icon) + layout.label(text="", icon=custom_icon) class ArmTilesheetListNewItem(bpy.types.Operator): - # Add a new item to the list + """Add a new item to the list""" bl_idname = "arm_tilesheetlist.new_item" bl_label = "Add a new item" @@ -176,7 +179,7 @@ class ArmTilesheetListNewItem(bpy.types.Operator): return{'FINISHED'} class ArmTilesheetListDeleteItem(bpy.types.Operator): - # Delete the selected item from the list + """Delete the selected item from the list""" bl_idname = "arm_tilesheetlist.delete_item" bl_label = "Deletes an item" @@ -200,13 +203,16 @@ class ArmTilesheetListDeleteItem(bpy.types.Operator): return{'FINISHED'} class ArmTilesheetListMoveItem(bpy.types.Operator): - # Move an item in the list + """Move an item in the list""" bl_idname = "arm_tilesheetlist.move_item" bl_label = "Move an item in the list" + bl_options = {'INTERNAL'} + direction: EnumProperty( - items=( - ('UP', 'Up', ""), - ('DOWN', 'Down', ""),)) + items=( + ('UP', 'Up', ""), + ('DOWN', 'Down', "") + )) @classmethod def poll(self, context): @@ -247,7 +253,6 @@ class ArmTilesheetListMoveItem(bpy.types.Operator): return{'FINISHED'} def register(): - bpy.utils.register_class(ArmTilesheetActionListItem) bpy.utils.register_class(ARM_UL_TilesheetActionList) bpy.utils.register_class(ArmTilesheetActionListNewItem) @@ -261,7 +266,7 @@ def register(): bpy.utils.register_class(ArmTilesheetListMoveItem) bpy.types.World.arm_tilesheetlist = CollectionProperty(type=ArmTilesheetListItem) - bpy.types.World.arm_tilesheetlist_index = IntProperty(name="Index for my_list", default=0) + bpy.types.World.arm_tilesheetlist_index = IntProperty(name="Index for arm_tilesheetlist", default=0) def unregister(): bpy.utils.unregister_class(ArmTilesheetListItem) diff --git a/blender/arm/props_traits.py b/blender/arm/props_traits.py index a11beade..7f873440 100755 --- a/blender/arm/props_traits.py +++ b/blender/arm/props_traits.py @@ -2,18 +2,47 @@ import json import os import shutil import subprocess +from typing import Union import webbrowser from bpy.types import NodeTree +from bpy.props import * import bpy.utils.previews import arm.make as make from arm.props_traits_props import * import arm.proxy as proxy +import arm.ui_icons as ui_icons import arm.utils import arm.write_data as write_data -icons_dict: bpy.utils.previews.ImagePreviewCollection +if arm.is_reload(__name__): + arm.make = arm.reload_module(arm.make) + arm.props_traits_props = arm.reload_module(arm.props_traits_props) + from arm.props_traits_props import * + proxy = arm.reload_module(proxy) + ui_icons = arm.reload_module(ui_icons) + arm.utils = arm.reload_module(arm.utils) + arm.write_data = arm.reload_module(arm.write_data) +else: + arm.enable_reload(__name__) + +ICON_HAXE = ui_icons.get_id('haxe') +ICON_NODES = 'NODETREE' +ICON_CANVAS = 'NODE_COMPOSITING' +ICON_BUNDLED = ui_icons.get_id('bundle') +ICON_WASM = ui_icons.get_id('wasm') + +# Pay attention to the ID number parameter for backward compatibility! +# This is important if the enum is reordered or the string identifier +# is changed as the number is what's stored in the blend file +PROP_TYPES_ENUM = [ + ('Haxe Script', 'Haxe', 'Haxe script', ICON_HAXE, 0), + ('Logic Nodes', 'Nodes', 'Logic nodes (visual scripting)', ICON_NODES, 4), + ('UI Canvas', 'UI', 'User interface', ICON_CANVAS, 2), + ('Bundled Script', 'Bundled', 'Premade script with common functionality', ICON_BUNDLED, 3), + ('WebAssembly', 'Wasm', 'WebAssembly', ICON_WASM, 1) +] def trigger_recompile(self, context): @@ -61,78 +90,81 @@ def update_trait_group(self, context): pass class ArmTraitListItem(bpy.types.PropertyGroup): + def poll_node_trees(self, tree: NodeTree): + """Ensure that only logic node trees show up as node traits""" + return tree.bl_idname == 'ArmLogicTreeType' + name: StringProperty(name="Name", description="A name for this item", default="") enabled_prop: BoolProperty(name="", description="A name for this item", default=True, update=trigger_recompile) is_object: BoolProperty(name="", default=True) fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False) - type_prop: EnumProperty( - items = [('Haxe Script', 'Haxe', 'Haxe Script'), - ('WebAssembly', 'Wasm', 'WebAssembly'), - ('UI Canvas', 'UI', 'UI Canvas'), - ('Bundled Script', 'Bundled', 'Bundled Script'), - ('Logic Nodes', 'Nodes', 'Logic Nodes') - ], - name = "Type") + 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) 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) + node_tree_prop: PointerProperty(type=NodeTree, update=update_trait_group, poll=poll_node_trees) arm_traitpropslist: CollectionProperty(type=ArmTraitPropListItem) arm_traitpropslist_index: IntProperty(name="Index for my_list", default=0) arm_traitpropswarnings: CollectionProperty(type=ArmTraitPropWarning) class ARM_UL_TraitList(bpy.types.UIList): + """List of traits.""" def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + layout.use_property_split = False + custom_icon = "NONE" custom_icon_value = 0 if item.type_prop == "Haxe Script": - custom_icon_value = icons_dict["haxe"].icon_id + custom_icon_value = ICON_HAXE elif item.type_prop == "WebAssembly": - custom_icon_value = icons_dict["wasm"].icon_id + custom_icon_value = ICON_WASM elif item.type_prop == "UI Canvas": - custom_icon = "OBJECT_DATAMODE" + custom_icon = "NODE_COMPOSITING" elif item.type_prop == "Bundled Script": - custom_icon_value = icons_dict["bundle"].icon_id + custom_icon_value = ICON_BUNDLED elif item.type_prop == "Logic Nodes": custom_icon = 'NODETREE' - # Make sure your code supports all 3 layout types if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.prop(item, "enabled_prop") + row = layout.row() + row.separator(factor=0.1) + row.prop(item, "enabled_prop") # Display " " for props without a name to right-align the # fake_user button - layout.label(text=item.name if item.name != "" else " ", icon=custom_icon, icon_value=custom_icon_value) + row.label(text=item.name if item.name != "" else " ", icon=custom_icon, icon_value=custom_icon_value) elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' layout.label(text="", icon=custom_icon, icon_value=custom_icon_value) - layout.prop(item, "fake_user", text="", icon="FAKE_USER_ON" if item.fake_user else "FAKE_USER_OFF") + row = layout.row(align=True) + row.prop(item, "fake_user", text="", icon="FAKE_USER_ON" if item.fake_user else "FAKE_USER_OFF") class ArmTraitListNewItem(bpy.types.Operator): bl_idname = "arm_traitlist.new_item" - bl_label = "New Trait Item" + bl_label = "Add Trait" bl_description = "Add a new trait item to the list" - is_object: BoolProperty(name="Object Trait", description="Whether this is an object or scene trait", default=False) - type_prop: EnumProperty( - items = [('Haxe Script', 'Haxe', 'Haxe Script'), - ('WebAssembly', 'Wasm', 'WebAssembly'), - ('UI Canvas', 'UI', 'UI Canvas'), - ('Bundled Script', 'Bundled', 'Bundled Script'), - ('Logic Nodes', 'Nodes', 'Logic Nodes') - ], - name = "Type") + is_object: BoolProperty(name="Is Object Trait", description="Whether this trait belongs to an object or a scene", default=False) + type_prop: EnumProperty(name="Type", items=PROP_TYPES_ENUM) + + # Show more options when invoked from the operator search menu + invoked_by_search: BoolProperty(name="", default=True) def invoke(self, context, event): wm = context.window_manager - return wm.invoke_props_dialog(self) + return wm.invoke_props_dialog(self, width=400) def draw(self, context): layout = self.layout - # Todo: show is_object property when called from operator search menu - # layout.prop(self, "is_object") - layout.prop(self, "type_prop", expand=True) + + if self.invoked_by_search: + row = layout.row() + row.prop(self, "is_object") + + row = layout.row() + row.scale_y = 1.3 + row.prop(self, "type_prop", expand=True) def execute(self, context): if self.is_object: @@ -149,7 +181,7 @@ class ArmTraitListNewItem(bpy.types.Operator): class ArmTraitListDeleteItem(bpy.types.Operator): """Delete the selected item from the list""" bl_idname = "arm_traitlist.delete_item" - bl_label = "Deletes an item" + bl_label = "Remove Trait" bl_options = {'INTERNAL'} is_object: BoolProperty(name="", description="A name for this item", default=False) @@ -582,8 +614,6 @@ class ArmNewCanvasDialog(bpy.types.Operator): self.canvas_name = self.canvas_name.replace(' ', '') write_data.write_canvasjson(self.canvas_name) arm.utils.fetch_script_names() - # Todo: create new trait item when called from operator search - # menu, then remove 'INTERNAL' from bl_options item = obj.arm_traitlist[obj.arm_traitlist_index] item.canvas_name_prop = self.canvas_name return {'FINISHED'} @@ -635,11 +665,8 @@ class ARM_PT_TraitPanel(bpy.types.Panel): bl_context = "object" def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False obj = bpy.context.object - draw_traits(layout, obj, is_object=True) + draw_traits_panel(self.layout, obj, is_object=True) class ARM_PT_SceneTraitPanel(bpy.types.Panel): bl_label = "Armory Scene Traits" @@ -648,11 +675,8 @@ class ARM_PT_SceneTraitPanel(bpy.types.Panel): bl_context = "scene" def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False obj = bpy.context.scene - draw_traits(layout, obj, is_object=False) + draw_traits_panel(self.layout, obj, is_object=False) class ARM_OT_CopyTraitsFromActive(bpy.types.Operator): bl_label = 'Copy Traits from Active Object' @@ -728,21 +752,28 @@ class ARM_OT_CopyTraitsFromActive(bpy.types.Operator): return {'INTERFACE'} -def draw_traits(layout, obj, is_object): - rows = 2 + +def draw_traits_panel(layout: bpy.types.UILayout, obj: Union[bpy.types.Object, bpy.types.Scene], + is_object: bool) -> None: + layout.use_property_split = True + layout.use_property_decorate = False + + # Make the list bigger when there are a few traits + num_rows = 2 if len(obj.arm_traitlist) > 1: - rows = 4 + num_rows = 4 row = layout.row() - row.template_list("ARM_UL_TraitList", "The_List", obj, "arm_traitlist", obj, "arm_traitlist_index", rows=rows) + row.template_list("ARM_UL_TraitList", "The_List", obj, "arm_traitlist", obj, "arm_traitlist_index", rows=num_rows) col = row.column(align=True) op = col.operator("arm_traitlist.new_item", icon='ADD', text="") + op.invoked_by_search = False op.is_object = is_object if is_object: - op = col.operator("arm_traitlist.delete_item", icon='REMOVE', text="")#.all = False + op = col.operator("arm_traitlist.delete_item", icon='REMOVE', text="") else: - op = col.operator("arm_traitlist.delete_item_scene", icon='REMOVE', text="")#.all = False + op = col.operator("arm_traitlist.delete_item_scene", icon='REMOVE', text="") op.is_object = is_object if len(obj.arm_traitlist) > 1: @@ -754,35 +785,29 @@ def draw_traits(layout, obj, is_object): op.direction = 'DOWN' op.is_object = is_object + # Draw trait specific content if obj.arm_traitlist_index >= 0 and len(obj.arm_traitlist) > 0: item = obj.arm_traitlist[obj.arm_traitlist_index] + row = layout.row(align=True) + row.alignment = 'EXPAND' + row.scale_y = 1.2 + if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script': if item.type_prop == 'Haxe Script': - row = layout.row(align=True) - row.alignment = 'EXPAND' + row.operator("arm.new_script", icon="FILE_NEW").is_object = is_object column = row.column(align=True) - column.alignment = 'EXPAND' - if item.class_name_prop == '': - column.enabled = False - op = column.operator("arm.edit_script", icon="FILE_SCRIPT") - op.is_object = is_object - op = row.operator("arm.new_script") - op.is_object = is_object - op = row.operator("arm.refresh_scripts", text="Refresh") - else: # Bundled + column.enabled = item.class_name_prop != '' + column.operator("arm.edit_script", icon="FILE_SCRIPT").is_object = is_object + + # Bundled scripts + else: if item.class_name_prop == 'NavMesh': - row = layout.row(align=True) - row.alignment = 'EXPAND' - op = layout.operator("arm.generate_navmesh") - row = layout.row(align=True) - row.alignment = 'EXPAND' - column = row.column(align=True) - column.alignment = 'EXPAND' - if not item.class_name_prop == 'NavMesh': - op = column.operator("arm.edit_bundled_script", icon="FILE_SCRIPT") - op.is_object = is_object - op = row.operator("arm.refresh_scripts", text="Refresh") + row.operator("arm.generate_navmesh", icon="UV_VERTEXSEL") + else: + row.operator("arm.edit_bundled_script", icon="FILE_SCRIPT").is_object = is_object + + row.operator("arm.refresh_scripts", text="Refresh", icon="FILE_REFRESH") # Default props item.name = item.class_name_prop @@ -797,102 +822,72 @@ def draw_traits(layout, obj, is_object): elif item.type_prop == 'WebAssembly': item.name = item.webassembly_prop + + row.operator("arm.new_wasm", icon="FILE_NEW") + row.operator("arm.refresh_scripts", text="Refresh", icon="FILE_REFRESH") + row = layout.row() row.prop_search(item, "webassembly_prop", bpy.data.worlds['Arm'], "arm_wasm_list", text="Module") - row = layout.row(align=True) - row.alignment = 'EXPAND' - column = row.column(align=True) - column.alignment = 'EXPAND' - if item.class_name_prop == '': - column.enabled = False - # op = column.operator("arm.edit_script", icon="FILE_SCRIPT") - # op.is_object = is_object - op = row.operator("arm.new_wasm") - # op.is_object = is_object - op = row.operator("arm.refresh_scripts", text="Refresh") elif item.type_prop == 'UI Canvas': item.name = item.canvas_name_prop - row = layout.row(align=True) - row.alignment = 'EXPAND' + row.operator("arm.new_canvas", icon="FILE_NEW").is_object = is_object column = row.column(align=True) - column.alignment = 'EXPAND' - if item.canvas_name_prop == '': - column.enabled = False - op = column.operator("arm.edit_canvas", icon="FILE_SCRIPT") - op.is_object = is_object - op = row.operator("arm.new_canvas") - op.is_object = is_object - op = row.operator("arm.refresh_canvas_list", text="Refresh") + column.enabled = item.canvas_name_prop != '' + column.operator("arm.edit_canvas", icon="NODE_COMPOSITING").is_object = is_object + row.operator("arm.refresh_canvas_list", text="Refresh", icon="FILE_REFRESH") row = layout.row() row.prop_search(item, "canvas_name_prop", bpy.data.worlds['Arm'], "arm_canvas_list", text="Canvas") elif item.type_prop == 'Logic Nodes': - # Row for buttons - row = layout.row(align=True) - row.alignment = 'EXPAND' - # New - column = row.column(align=True) - column.alignment = 'EXPAND' - op = column.operator("arm.new_treenode", text="New Node Tree", icon="ADD") - op.is_object = is_object - # At least one check is active Logic Node Editor - is_check_logic_node_editor = False - context_screen = bpy.context.screen - # Loop for all spaces - if context_screen is not None: - areas = context_screen.areas + # Check if there is at least one active Logic Node Editor + is_editor_active = False + if bpy.context.screen is not None: + areas = bpy.context.screen.areas for area in areas: for space in area.spaces: if space.type == 'NODE_EDITOR': if space.tree_type == 'ArmLogicTreeType' and space.node_tree is not None: - is_check_logic_node_editor = True + is_editor_active = True break - if is_check_logic_node_editor: + if is_editor_active: break - # Edit - column = row.column(align=True) - column.alignment = 'EXPAND' - if item.node_tree_prop is None: - column.enabled = False - else: - column.enabled = is_check_logic_node_editor - op = column.operator("arm.edit_treenode", text="Edit Node Tree", icon="NODETREE") - op.is_object = is_object - # Get from Node Tree Editor - column = row.column(align=True) - column.alignment = 'EXPAND' - if item is None: - column.enabled = False - else: - column.enabled = is_check_logic_node_editor - op = column.operator("arm.get_treenode", text="From Node Editor", icon="IMPORT") - op.is_object = is_object - # Row for search + row.operator("arm.new_treenode", text="New Tree", icon="ADD").is_object = is_object + + column = row.column(align=True) + column.enabled = is_editor_active and item.node_tree_prop is not None + column.operator("arm.edit_treenode", text="Edit Tree", icon="NODETREE").is_object = is_object + + column = row.column(align=True) + column.enabled = is_editor_active and item is not None + column.operator("arm.get_treenode", text="From Editor", icon="IMPORT").is_object = is_object + row = layout.row() row.prop_search(item, "node_tree_prop", bpy.data, "node_groups", text="Tree") + # ===================== + # Draw trait properties if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script': - # Props + if item.arm_traitpropslist: layout.label(text="Trait Properties:") if item.arm_traitpropswarnings: box = layout.box() box.label(text=f"Warnings ({len(item.arm_traitpropswarnings)}):", icon="ERROR") + col = box.column(align=True) for warning in item.arm_traitpropswarnings: - box.label(text=warning.warning) + col.label(text=f'"{warning.propName}": {warning.warning}') - propsrow = layout.row() propsrows = max(len(item.arm_traitpropslist), 6) row = layout.row() row.template_list("ARM_UL_PropList", "The_List", item, "arm_traitpropslist", item, "arm_traitpropslist_index", rows=propsrows) + def register(): - global icons_dict bpy.utils.register_class(ArmTraitListItem) bpy.utils.register_class(ARM_UL_TraitList) bpy.utils.register_class(ArmTraitListNewItem) @@ -920,14 +915,8 @@ def register(): bpy.types.Scene.arm_traitlist = CollectionProperty(type=ArmTraitListItem) bpy.types.Scene.arm_traitlist_index = IntProperty(name="Index for arm_traitlist", default=0) - icons_dict = bpy.utils.previews.new() - icons_dir = os.path.join(os.path.dirname(__file__), "custom_icons") - icons_dict.load("haxe", os.path.join(icons_dir, "haxe.png"), 'IMAGE') - icons_dict.load("wasm", os.path.join(icons_dir, "wasm.png"), 'IMAGE') - icons_dict.load("bundle", os.path.join(icons_dir, "bundle.png"), 'IMAGE') def unregister(): - global icons_dict bpy.utils.unregister_class(ARM_OT_CopyTraitsFromActive) bpy.utils.unregister_class(ArmTraitListItem) bpy.utils.unregister_class(ARM_UL_TraitList) @@ -949,4 +938,3 @@ def unregister(): bpy.utils.unregister_class(ArmRefreshCanvasListButton) bpy.utils.unregister_class(ARM_PT_TraitPanel) bpy.utils.unregister_class(ARM_PT_SceneTraitPanel) - bpy.utils.previews.remove(icons_dict) diff --git a/blender/arm/props_traits_props.py b/blender/arm/props_traits_props.py index ea9dc23d..b65072cf 100644 --- a/blender/arm/props_traits_props.py +++ b/blender/arm/props_traits_props.py @@ -1,6 +1,8 @@ import bpy from bpy.props import * +__all__ = ['ArmTraitPropWarning', 'ArmTraitPropListItem', 'ARM_UL_PropList'] + PROP_TYPE_ICONS = { "String": "SORTALPHA", "Int": "CHECKBOX_DEHLT", @@ -32,6 +34,7 @@ def filter_objects(item, b_object): class ArmTraitPropWarning(bpy.types.PropertyGroup): + propName: StringProperty(name="Property Name") warning: StringProperty(name="Warning") diff --git a/blender/arm/props_ui.py b/blender/arm/props_ui.py index 582ee734..b7b90276 100644 --- a/blender/arm/props_ui.py +++ b/blender/arm/props_ui.py @@ -5,8 +5,11 @@ import shutil import bpy from bpy.props import * +from arm.lightmapper.panels import scene + import arm.api import arm.assets as assets +from arm.exporter import ArmoryExporter import arm.log as log import arm.logicnode.replacement import arm.make as make @@ -16,14 +19,32 @@ import arm.props_properties import arm.props_traits import arm.nodes_logic import arm.proxy +import arm.ui_icons as ui_icons import arm.utils -from arm.lightmapper.utility import icon -from arm.lightmapper.properties.denoiser import oidn, optix -import importlib -# Menu in object region +if arm.is_reload(__name__): + arm.api = arm.reload_module(arm.api) + assets = arm.reload_module(assets) + arm.exporter = arm.reload_module(arm.exporter) + from arm.exporter import ArmoryExporter + log = arm.reload_module(log) + arm.logicnode.replacement = arm.reload_module(arm.logicnode.replacement) + make = arm.reload_module(make) + state = arm.reload_module(state) + props = arm.reload_module(props) + arm.props_properties = arm.reload_module(arm.props_properties) + arm.props_traits = arm.reload_module(arm.props_traits) + arm.nodes_logic = arm.reload_module(arm.nodes_logic) + arm.proxy = arm.reload_module(arm.proxy) + ui_icons = arm.reload_module(ui_icons) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + + class ARM_PT_ObjectPropsPanel(bpy.types.Panel): + """Menu in object region.""" bl_label = "Armory Props" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -38,12 +59,13 @@ class ARM_PT_ObjectPropsPanel(bpy.types.Panel): if obj == None: return - layout.prop(obj, 'arm_export') + col = layout.column() + col.prop(obj, 'arm_export') if not obj.arm_export: return - layout.prop(obj, 'arm_spawn') - layout.prop(obj, 'arm_mobile') - layout.prop(obj, 'arm_animation_enabled') + col.prop(obj, 'arm_spawn') + col.prop(obj, 'arm_mobile') + col.prop(obj, 'arm_animation_enabled') if obj.type == 'MESH': layout.prop(obj, 'arm_instanced') @@ -71,9 +93,18 @@ class ARM_PT_ObjectPropsPanel(bpy.types.Panel): if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: row = layout.row() - row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_resolution") + row.prop(obj.TLM_ObjectProperties, "tlm_use_default_channel") + + if not obj.TLM_ObjectProperties.tlm_use_default_channel: + + row = layout.row() + row.prop_search(obj.TLM_ObjectProperties, "tlm_uv_channel", obj.data, "uv_layers", text='UV Channel') + row = layout.row() - row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_unwrap_mode") + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_resolution") + if obj.TLM_ObjectProperties.tlm_use_default_channel: + row = layout.row() + row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_unwrap_mode") row = layout.row() if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": @@ -91,7 +122,6 @@ class ARM_PT_ObjectPropsPanel(bpy.types.Panel): row.prop(obj.TLM_ObjectProperties, "tlm_postpack_object") row = layout.row() - if obj.TLM_ObjectProperties.tlm_postpack_object and obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA": if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0: row = layout.row() @@ -180,15 +210,30 @@ class ARM_PT_PhysicsPropsPanel(bpy.types.Panel): if obj == None: return - if obj.rigid_body != None: + rb = obj.rigid_body + if rb is not None: + col = layout.column() + row = col.row() + row.alignment = 'RIGHT' + + rb_type = 'Dynamic' + if ArmoryExporter.rigid_body_static(rb): + rb_type = 'Static' + if rb.kinematic: + rb_type = 'Kinematic' + row.label(text=(f'Rigid Body Export Type: {rb_type}'), icon='AUTO') + layout.prop(obj, 'arm_rb_linear_factor') layout.prop(obj, 'arm_rb_angular_factor') layout.prop(obj, 'arm_rb_trigger') layout.prop(obj, 'arm_rb_ccd') - if obj.soft_body != None: + if obj.soft_body is not None: layout.prop(obj, 'arm_soft_body_margin') + if obj.rigid_body_constraint is not None: + layout.prop(obj, 'arm_relative_physics_constraint') + # Menu in data region class ARM_PT_DataPropsPanel(bpy.types.Panel): bl_label = "Armory Props" @@ -243,6 +288,7 @@ class ARM_PT_WorldPropsPanel(bpy.types.Panel): layout.prop(world, 'arm_use_clouds') col = layout.column(align=True) col.enabled = world.arm_use_clouds + col.prop(world, 'arm_darken_clouds') col.prop(world, 'arm_clouds_lower') col.prop(world, 'arm_clouds_upper') col.prop(world, 'arm_clouds_precipitation') @@ -269,7 +315,7 @@ class ARM_PT_ScenePropsPanel(bpy.types.Panel): row.prop(scene, 'arm_export') class InvalidateCacheButton(bpy.types.Operator): - '''Delete cached mesh data''' + """Delete cached mesh data""" bl_idname = "arm.invalidate_cache" bl_label = "Invalidate Cache" @@ -278,7 +324,7 @@ class InvalidateCacheButton(bpy.types.Operator): return{'FINISHED'} class InvalidateMaterialCacheButton(bpy.types.Operator): - '''Delete cached material data''' + """Delete cached material data""" bl_idname = "arm.invalidate_material_cache" bl_label = "Invalidate Cache" @@ -511,7 +557,7 @@ class ARM_PT_MaterialBlendingPropsPanel(bpy.types.Panel): bl_parent_id = "ARM_PT_MaterialPropsPanel" def draw_header(self, context): - if context.material == None: + if context.material is None: return self.layout.prop(context.material, 'arm_blending', text="") @@ -520,16 +566,18 @@ class ARM_PT_MaterialBlendingPropsPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False mat = bpy.context.material - if mat == None: + if mat is None: return flow = layout.grid_flow() flow.enabled = mat.arm_blending - col = flow.column() + col = flow.column(align=True) col.prop(mat, 'arm_blending_source') col.prop(mat, 'arm_blending_destination') col.prop(mat, 'arm_blending_operation') - col = flow.column() + flow.separator() + + col = flow.column(align=True) col.prop(mat, 'arm_blending_source_alpha') col.prop(mat, 'arm_blending_destination_alpha') col.prop(mat, 'arm_blending_operation_alpha') @@ -545,22 +593,40 @@ class ARM_PT_ArmoryPlayerPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] + row = layout.row(align=True) row.alignment = 'EXPAND' + row.scale_y = 1.3 if state.proc_play is None and state.proc_build is None: row.operator("arm.play", icon="PLAY") else: row.operator("arm.stop", icon="MESH_PLANE") - row.operator("arm.clean_menu") - layout.prop(wrd, 'arm_runtime') - layout.prop(wrd, 'arm_play_camera') - layout.prop(wrd, 'arm_play_scene') + row.operator("arm.clean_menu", icon="BRUSH_DATA") + + box = layout.box() + box.prop(wrd, 'arm_runtime') + box.prop(wrd, 'arm_play_camera') + box.prop(wrd, 'arm_play_scene') if log.num_warnings > 0: box = layout.box() + box.alert = True + + col = box.column(align=True) + warnings = 'warnings' if log.num_warnings > 1 else 'warning' + col.label(text=f'{log.num_warnings} {warnings} occurred during compilation!', icon='ERROR') + # Blank icon to achieve the same indentation as the line before + # prevent showing "open console" twice: + if log.num_errors == 0: + col.label(text='Please open the console to get more information.', icon='BLANK1') + + if log.num_errors > 0: + box = layout.box() + box.alert = True # Less spacing between lines col = box.column(align=True) - col.label(text=f'{log.num_warnings} warnings occurred during compilation!', icon='ERROR') + errors = 'errors' if log.num_errors > 1 else 'error' + col.label(text=f'{log.num_errors} {errors} occurred during compilation!', icon='CANCEL') # Blank icon to achieve the same indentation as the line before col.label(text='Please open the console to get more information.', icon='BLANK1') @@ -576,12 +642,13 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] + row = layout.row(align=True) row.alignment = 'EXPAND' - row.operator("arm.build_project") + row.scale_y = 1.3 + row.operator("arm.build_project", icon="MOD_BUILD") # row.operator("arm.patch_project") row.operator("arm.publish_project", icon="EXPORT") - row.enabled = wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0 rows = 2 if len(wrd.arm_exporterlist) > 1: @@ -614,59 +681,78 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel): box.prop_search(item, 'arm_project_scene', bpy.data, 'scenes', text='Scene') layout.separator() - col = layout.column() + col = layout.column(align=True) col.prop(wrd, 'arm_project_name') col.prop(wrd, 'arm_project_package') col.prop(wrd, 'arm_project_bundle') + + col = layout.column(align=True) col.prop(wrd, 'arm_project_version') col.prop(wrd, 'arm_project_version_autoinc') + + col = layout.column() col.prop(wrd, 'arm_project_icon') + + col = layout.column(heading='Code Output') col.prop(wrd, 'arm_dce') col.prop(wrd, 'arm_compiler_inline') col.prop(wrd, 'arm_minify_js') + + col = layout.column(heading='Data') + col.prop(wrd, 'arm_minimize') col.prop(wrd, 'arm_optimize_data') col.prop(wrd, 'arm_asset_compression') col.prop(wrd, 'arm_single_data_file') -class ARM_PT_ArmoryExporterAndroidSettingsPanel(bpy.types.Panel): - bl_label = "Android Settings" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - bl_options = { 'HIDE_HEADER' } - bl_parent_id = "ARM_PT_ArmoryExporterPanel" + +class ExporterTargetSettingsMixin: + """Mixin for common exporter setting subpanel functionality. + + Panels that inherit from this mixin need to have a arm_target + variable for polling.""" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = 'render' + bl_parent_id = 'ARM_PT_ArmoryExporterPanel' + + # Override this in sub classes + arm_panel = '' @classmethod def poll(cls, context): wrd = bpy.data.worlds['Arm'] if (len(wrd.arm_exporterlist) > 0) and (wrd.arm_exporterlist_index >= 0): item = wrd.arm_exporterlist[wrd.arm_exporterlist_index] - return item.arm_project_target == 'android-hl' - else: - return False + return item.arm_project_target == cls.arm_target + return False + + def draw_header(self, context): + self.layout.label(text='', icon='SETTINGS') + + +class ARM_PT_ArmoryExporterAndroidSettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel): + bl_label = "Android Settings" + arm_target = 'android-hl' # See ExporterTargetSettingsMixin def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] - # Options - layout.label(text='Android Settings', icon='SETTINGS') - row = layout.row() - row.prop(wrd, 'arm_winorient') - row = layout.row() - row.prop(wrd, 'arm_project_android_sdk_compile') - row = layout.row() - row.prop(wrd, 'arm_project_android_sdk_min') - row = layout.row() - row.prop(wrd, 'arm_project_android_sdk_target') + + col = layout.column() + col.prop(wrd, 'arm_winorient') + col.prop(wrd, 'arm_project_android_sdk_compile') + col.prop(wrd, 'arm_project_android_sdk_min') + col.prop(wrd, 'arm_project_android_sdk_target') + class ARM_PT_ArmoryExporterAndroidPermissionsPanel(bpy.types.Panel): bl_label = "Permissions" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" - bl_options = { 'DEFAULT_CLOSED' } + bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryExporterAndroidSettingsPanel" def draw(self, context): @@ -724,7 +810,7 @@ class ARM_PT_ArmoryExporterAndroidBuildAPKPanel(bpy.types.Panel): bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "render" - bl_options = { 'DEFAULT_CLOSED'} + bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ArmoryExporterAndroidSettingsPanel" def draw(self, context): @@ -732,109 +818,84 @@ class ARM_PT_ArmoryExporterAndroidBuildAPKPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] - row = layout.row() - row.prop(wrd, 'arm_project_android_build_apk') path = arm.utils.get_android_sdk_root_path() + + col = layout.column() + + row = col.row() row.enabled = len(path) > 0 - row = layout.row() - row.prop(wrd, 'arm_project_android_rename_apk') + row.prop(wrd, 'arm_project_android_build_apk') + + row = col.row() row.enabled = wrd.arm_project_android_build_apk - row = layout.row() + row.prop(wrd, 'arm_project_android_rename_apk') + row = col.row() + row.enabled = wrd.arm_project_android_build_apk and len(arm.utils.get_android_apk_copy_path()) > 0 row.prop(wrd, 'arm_project_android_copy_apk') - row.enabled = (wrd.arm_project_android_build_apk) and (len(arm.utils.get_android_apk_copy_path()) > 0) - row = layout.row() + + row = col.row(align=True) row.prop(wrd, 'arm_project_android_list_avd') - col = row.column(align=True) - col.operator('arm.update_list_android_emulator', text='', icon='FILE_REFRESH') - col.enabled = len(path) > 0 - col = row.column(align=True) - col.operator('arm.run_android_emulator', text='', icon='PLAY') - col.enabled = len(path) > 0 and len(arm.utils.get_android_emulator_name()) > 0 - row = layout.row() - row.prop(wrd, 'arm_project_android_run_avd') + sub = row.column(align=True) + sub.enabled = len(path) > 0 + sub.operator('arm.update_list_android_emulator', text='', icon='FILE_REFRESH') + sub = row.column(align=True) + sub.enabled = len(path) > 0 and len(arm.utils.get_android_emulator_name()) > 0 + sub.operator('arm.run_android_emulator', text='', icon='PLAY') + + row = col.row() row.enabled = arm.utils.get_project_android_build_apk() and len(arm.utils.get_android_emulator_name()) > 0 + row.prop(wrd, 'arm_project_android_run_avd') -class ARM_PT_ArmoryExporterHTML5SettingsPanel(bpy.types.Panel): + +class ARM_PT_ArmoryExporterHTML5SettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel): bl_label = "HTML5 Settings" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - bl_options = { 'HIDE_HEADER' } - bl_parent_id = "ARM_PT_ArmoryExporterPanel" - - @classmethod - def poll(cls, context): - wrd = bpy.data.worlds['Arm'] - if (len(wrd.arm_exporterlist) > 0) and (wrd.arm_exporterlist_index >= 0): - item = wrd.arm_exporterlist[wrd.arm_exporterlist_index] - return item.arm_project_target == 'html5' - else: - return False + arm_target = 'html5' # See ExporterTargetSettingsMixin def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] - # Options - layout.label(text='HTML5 Settings', icon='SETTINGS') - row = layout.row() - row.prop(wrd, 'arm_project_html5_popupmenu_in_browser') - row = layout.row() - row.prop(wrd, 'arm_project_html5_copy') + + col = layout.column() + col.prop(wrd, 'arm_project_html5_popupmenu_in_browser') + row = col.row() row.enabled = len(arm.utils.get_html5_copy_path()) > 0 - row = layout.row() + row.prop(wrd, 'arm_project_html5_copy') + row = col.row() + row.enabled = len(arm.utils.get_html5_copy_path()) > 0 and wrd.arm_project_html5_copy and len(arm.utils.get_link_web_server()) > 0 row.prop(wrd, 'arm_project_html5_start_browser') - row.enabled = (len(arm.utils.get_html5_copy_path()) > 0) and (wrd.arm_project_html5_copy) and (len(arm.utils.get_link_web_server()) > 0) -class ARM_PT_ArmoryExporterWindowsSettingsPanel(bpy.types.Panel): + +class ARM_PT_ArmoryExporterWindowsSettingsPanel(ExporterTargetSettingsMixin, bpy.types.Panel): bl_label = "Windows Settings" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - bl_options = { 'HIDE_HEADER' } - bl_parent_id = "ARM_PT_ArmoryExporterPanel" - - @classmethod - def poll(cls, context): - wrd = bpy.data.worlds['Arm'] - if (len(wrd.arm_exporterlist) > 0) and (wrd.arm_exporterlist_index >= 0): - item = wrd.arm_exporterlist[wrd.arm_exporterlist_index] - return item.arm_project_target == 'windows-hl' - else: - return False + arm_target = 'windows-hl' # See ExporterTargetSettingsMixin def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] - # Options - layout.label(text='Windows Settings', icon='SETTINGS') - row = layout.row() + + col = layout.column() + row = col.row(align=True) row.prop(wrd, 'arm_project_win_list_vs') - col = row.column(align=True) - col.operator('arm.update_list_installed_vs', text='', icon='FILE_REFRESH') - col.enabled = arm.utils.get_os_is_windows() - row = layout.row() - row.prop(wrd, 'arm_project_win_build') + sub = row.column(align=True) + sub.enabled = arm.utils.get_os_is_windows() + sub.operator('arm.update_list_installed_vs', text='', icon='FILE_REFRESH') + + row = col.row() row.enabled = arm.utils.get_os_is_windows() - is_enable = arm.utils.get_os_is_windows() and wrd.arm_project_win_build != '0' and wrd.arm_project_win_build != '1' - row = layout.row() - row.prop(wrd, 'arm_project_win_build_mode') - row.enabled = is_enable - row = layout.row() - row.prop(wrd, 'arm_project_win_build_arch') - row.enabled = is_enable - row = layout.row() - row.prop(wrd, 'arm_project_win_build_log') - row.enabled = is_enable - row = layout.row() - row.prop(wrd, 'arm_project_win_build_cpu') - row.enabled = is_enable - row = layout.row() - row.prop(wrd, 'arm_project_win_build_open') - row.enabled = is_enable + row.prop(wrd, 'arm_project_win_build', text='After Publish') + layout.separator() + + col = layout.column() + col.enabled = arm.utils.get_os_is_windows() and wrd.arm_project_win_build != '0' and wrd.arm_project_win_build != '1' + col.prop(wrd, 'arm_project_win_build_mode') + col.prop(wrd, 'arm_project_win_build_arch') + col.prop(wrd, 'arm_project_win_build_log') + col.prop(wrd, 'arm_project_win_build_cpu') + col.prop(wrd, 'arm_project_win_build_open') class ARM_PT_ArmoryProjectPanel(bpy.types.Panel): bl_label = "Armory Project" @@ -863,19 +924,26 @@ class ARM_PT_ProjectFlagsPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] - layout.prop(wrd, 'arm_verbose_output') - layout.prop(wrd, 'arm_cache_build') - layout.prop(wrd, 'arm_live_patch') - layout.prop(wrd, 'arm_stream_scene') - layout.prop(wrd, 'arm_batch_meshes') - layout.prop(wrd, 'arm_batch_materials') - layout.prop(wrd, 'arm_write_config') - layout.prop(wrd, 'arm_minimize') - layout.prop(wrd, 'arm_deinterleaved_buffers') - layout.prop(wrd, 'arm_export_tangents') - layout.prop(wrd, 'arm_loadscreen') - layout.prop(wrd, 'arm_texture_quality') - layout.prop(wrd, 'arm_sound_quality') + + col = layout.column(heading='Debug') + col.prop(wrd, 'arm_verbose_output') + col.prop(wrd, 'arm_cache_build') + + col = layout.column(heading='Runtime') + col.prop(wrd, 'arm_live_patch') + col.prop(wrd, 'arm_stream_scene') + col.prop(wrd, 'arm_loadscreen') + col.prop(wrd, 'arm_write_config') + + col = layout.column(heading='Renderer') + col.prop(wrd, 'arm_batch_meshes') + col.prop(wrd, 'arm_batch_materials') + col.prop(wrd, 'arm_deinterleaved_buffers') + col.prop(wrd, 'arm_export_tangents') + + col = layout.column(heading='Quality') + col.prop(wrd, 'arm_texture_quality') + col.prop(wrd, 'arm_sound_quality') class ARM_PT_ProjectFlagsDebugConsolePanel(bpy.types.Panel): bl_label = "Debug Console" @@ -885,23 +953,20 @@ class ARM_PT_ProjectFlagsDebugConsolePanel(bpy.types.Panel): bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "ARM_PT_ProjectFlagsPanel" + def draw_header(self, context): + wrd = bpy.data.worlds['Arm'] + self.layout.prop(wrd, 'arm_debug_console', text='') + def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] - row = layout.row() - row.enabled = wrd.arm_ui != 'Disabled' - row.prop(wrd, 'arm_debug_console') - row = layout.row() - row.enabled = wrd.arm_debug_console - row.prop(wrd, 'arm_debug_console_position') - row = layout.row() - row.enabled = wrd.arm_debug_console - row.prop(wrd, 'arm_debug_console_scale') - row = layout.row() - row.enabled = wrd.arm_debug_console - row.prop(wrd, 'arm_debug_console_visible') + col = layout.column() + col.enabled = wrd.arm_debug_console + col.prop(wrd, 'arm_debug_console_position') + col.prop(wrd, 'arm_debug_console_scale') + col.prop(wrd, 'arm_debug_console_visible') class ARM_PT_ProjectWindowPanel(bpy.types.Panel): bl_label = "Window" @@ -917,11 +982,15 @@ class ARM_PT_ProjectWindowPanel(bpy.types.Panel): layout.use_property_decorate = False wrd = bpy.data.worlds['Arm'] layout.prop(wrd, 'arm_winmode') - layout.prop(wrd, 'arm_winresize') - col = layout.column() - col.enabled = wrd.arm_winresize - col.prop(wrd, 'arm_winmaximize') - layout.prop(wrd, 'arm_winminimize') + + col = layout.column(align=True) + col.prop(wrd, 'arm_winresize') + sub = col.column() + sub.enabled = wrd.arm_winresize + sub.prop(wrd, 'arm_winmaximize') + col.enabled = True + col.prop(wrd, 'arm_winminimize') + layout.prop(wrd, 'arm_vsync') class ARM_PT_ProjectModulesPanel(bpy.types.Panel): @@ -971,7 +1040,7 @@ class ArmoryPlayButton(bpy.types.Operator): # Compare version Blender and Armory (major, minor) if not arm.utils.compare_version_blender_arm(): - self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.83 LTS.') + self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} @@ -1003,14 +1072,19 @@ class ArmoryStopButton(bpy.types.Operator): return{'FINISHED'} class ArmoryBuildProjectButton(bpy.types.Operator): - '''Build and compile project''' + """Build and compile project""" bl_idname = 'arm.build_project' bl_label = 'Build' + @classmethod + def poll(cls, context): + wrd = bpy.data.worlds['Arm'] + return wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0 + def execute(self, context): # Compare version Blender and Armory (major, minor) if not arm.utils.compare_version_blender_arm(): - self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.83 LTS.') + self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} @@ -1043,14 +1117,19 @@ class ArmoryBuildProjectButton(bpy.types.Operator): return{'FINISHED'} class ArmoryPublishProjectButton(bpy.types.Operator): - '''Build project ready for publishing''' + """Build project ready for publishing.""" bl_idname = 'arm.publish_project' bl_label = 'Publish' + @classmethod + def poll(cls, context): + wrd = bpy.data.worlds['Arm'] + return wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0 + def execute(self, context): # Compare version Blender and Armory (major, minor) if not arm.utils.compare_version_blender_arm(): - self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.83 LTS.') + self.report({'INFO'}, 'For Armory to work correctly, you need Blender 2.93 LTS.') if not arm.utils.check_saved(self): return {"CANCELLED"} @@ -1242,17 +1321,23 @@ class ARM_PT_RenderPathRendererPanel(bpy.types.Panel): layout.prop(rpdat, 'arm_tess_shadows_outer') layout.prop(rpdat, 'arm_particles') - layout.prop(rpdat, 'arm_skin') - row = layout.row() - row.enabled = rpdat.arm_skin == 'On' - row.prop(rpdat, 'arm_skin_max_bones_auto') - row = layout.row() + layout.separator(factor=0.1) + + col = layout.column() + col.prop(rpdat, 'arm_skin') + col = col.column() + col.enabled = rpdat.arm_skin == 'On' + col.prop(rpdat, 'arm_skin_max_bones_auto') + row = col.row() row.enabled = not rpdat.arm_skin_max_bones_auto row.prop(rpdat, 'arm_skin_max_bones') - layout.prop(rpdat, "rp_hdr") - layout.prop(rpdat, "rp_stereo") - layout.prop(rpdat, 'arm_culling') - layout.prop(rpdat, 'rp_pp') + layout.separator(factor=0.1) + + col = layout.column() + col.prop(rpdat, "rp_hdr") + col.prop(rpdat, "rp_stereo") + col.prop(rpdat, 'arm_culling') + col.prop(rpdat, 'rp_pp') class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): bl_label = "Shadows" @@ -1276,6 +1361,20 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): max = max / 2 return l + def tiles_per_light_type(self, rpdat: arm.props_renderpath.ArmRPListItem, light_type: str) -> int: + if light_type == 'point': + return 6 + elif light_type == 'spot': + return 1 + else: + return int(rpdat.rp_shadowmap_cascades) + + def lights_number_atlas(self, rpdat: arm.props_renderpath.ArmRPListItem, atlas_size: int, shadowmap_size: int, light_type: str) -> int: + '''Compute number lights that could fit in an atlas''' + lights = atlas_size / shadowmap_size + lights *= lights / self.tiles_per_light_type(rpdat, light_type) + return int(lights) + def draw(self, context): layout = self.layout layout.use_property_split = True @@ -1286,7 +1385,9 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.enabled = rpdat.rp_shadows - layout.prop(rpdat, 'rp_shadowmap_cube') + col = layout.column() + col.enabled = not rpdat.rp_shadowmap_atlas_single_map or not rpdat.rp_shadowmap_atlas + col.prop(rpdat, 'rp_shadowmap_cube') layout.prop(rpdat, 'rp_shadowmap_cascade') layout.prop(rpdat, 'rp_shadowmap_cascades') col = layout.column() @@ -1311,39 +1412,88 @@ class ARM_PT_RenderPathShadowsPanel(bpy.types.Panel): colatlas_lod_info = colatlas_lod.row() colatlas_lod_info.alignment = 'RIGHT' subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cascade), int(rpdat.rp_shadowmap_atlas_lod_subdivisions)) - subdiv_text = "Subdivisions: " + ', '.join(map(str, subdivs_list)) + subdiv_text = "Subdivisions for spot lights: " + ', '.join(map(str, subdivs_list)) colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH") + if not rpdat.rp_shadowmap_atlas_single_map: + colatlas_lod_info = colatlas_lod.row() + colatlas_lod_info.alignment = 'RIGHT' + subdivs_list = self.compute_subdivs(int(rpdat.rp_shadowmap_cube), int(rpdat.rp_shadowmap_atlas_lod_subdivisions)) + subdiv_text = "Subdivisions for point lights: " + ', '.join(map(str, subdivs_list)) + colatlas_lod_info.label(text=subdiv_text, icon="IMAGE_ZDEPTH") + + size_warning = int(rpdat.rp_shadowmap_cascade) > 2048 or int(rpdat.rp_shadowmap_cube) > 2048 + colatlas.prop(rpdat, 'rp_shadowmap_atlas_single_map') # show size for single texture if rpdat.rp_shadowmap_atlas_single_map: colatlas_single = colatlas.column() colatlas_single.prop(rpdat, 'rp_shadowmap_atlas_max_size') - if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size): - print(rpdat.rp_shadowmap_atlas_max_size) - colatlas_warning = colatlas_single.row() - colatlas_warning.alignment = 'RIGHT' - colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + if rpdat.rp_shadowmap_atlas_max_size != '': + atlas_size = int(rpdat.rp_shadowmap_atlas_max_size) + shadowmap_size = int(rpdat.rp_shadowmap_cascade) + + if shadowmap_size > 2048: + size_warning = True + + point_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'point') + spot_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'spot') + dir_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'sun') + + col = colatlas_single.row() + col.alignment = 'RIGHT' + col.label(text=f'Enough space for { point_lights } point lights or { spot_lights } spot lights or { dir_lights } directional lights.') else: # show size for all types colatlas_mixed = colatlas.column() colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_spot') - if int(rpdat.rp_shadowmap_cascade) > int(rpdat.rp_shadowmap_atlas_max_size_spot): - colatlas_warning = colatlas_mixed.row() - colatlas_warning.alignment = 'RIGHT' - colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_spot} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + + if rpdat.rp_shadowmap_atlas_max_size_spot != '': + atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_spot) + shadowmap_size = int(rpdat.rp_shadowmap_cascade) + spot_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'spot') + + if shadowmap_size > 2048: + size_warning = True + + col = colatlas_mixed.row() + col.alignment = 'RIGHT' + col.label(text=f'Enough space for {spot_lights} spot lights.') colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_point') - if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_point): - colatlas_warning = colatlas_mixed.row() - colatlas_warning.alignment = 'RIGHT' - colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_point} is too small for the shadowmap size: {rpdat.rp_shadowmap_cube}', icon="ERROR") + + if rpdat.rp_shadowmap_atlas_max_size_point != '': + atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_point) + shadowmap_size = int(rpdat.rp_shadowmap_cube) + point_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'point') + + if shadowmap_size > 2048: + size_warning = True + + col = colatlas_mixed.row() + col.alignment = 'RIGHT' + col.label(text=f'Enough space for {point_lights} point lights.') colatlas_mixed.prop(rpdat, 'rp_shadowmap_atlas_max_size_sun') - if int(rpdat.rp_shadowmap_cascade) >= int(rpdat.rp_shadowmap_atlas_max_size_sun): - colatlas_warning = colatlas_mixed.row() - colatlas_warning.alignment = 'RIGHT' - colatlas_warning.label(text=f'Warning: {rpdat.rp_shadowmap_atlas_max_size_sun} is too small for the shadowmap size: {rpdat.rp_shadowmap_cascade}', icon="ERROR") + + if rpdat.rp_shadowmap_atlas_max_size_sun != '': + atlas_size = int(rpdat.rp_shadowmap_atlas_max_size_sun) + shadowmap_size = int(rpdat.rp_shadowmap_cascade) + dir_lights = self.lights_number_atlas(rpdat, atlas_size, shadowmap_size, 'sun') + + if shadowmap_size > 2048: + size_warning = True + + col = colatlas_mixed.row() + col.alignment = 'RIGHT' + col.label(text=f'Enough space for {dir_lights} directional lights.') + + # show warning when user picks a size higher than 2048 (arbitrary number). + if size_warning: + col = layout.column() + row = col.row() + row.alignment = 'RIGHT' + row.label(text='Warning: Game will crash if texture size is higher than max texture size allowed by target.', icon='ERROR') class ARM_PT_RenderPathVoxelsPanel(bpy.types.Panel): bl_label = "Voxel AO" @@ -1404,16 +1554,22 @@ class ARM_PT_RenderPathWorldPanel(bpy.types.Panel): rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.prop(rpdat, "rp_background") - layout.prop(rpdat, 'arm_irradiance') + col = layout.column() - col.enabled = rpdat.arm_irradiance - col.prop(rpdat, 'arm_radiance') + col.prop(rpdat, 'arm_irradiance') colb = col.column() - colb.enabled = rpdat.arm_radiance - colb.prop(rpdat, 'arm_radiance_size') + colb.enabled = rpdat.arm_irradiance + colb.prop(rpdat, 'arm_radiance') + sub = colb.row() + sub.enabled = rpdat.arm_radiance + sub.prop(rpdat, 'arm_radiance_size') + layout.separator() + layout.prop(rpdat, 'arm_clouds') - layout.prop(rpdat, "rp_water") + col = layout.column(align=True) + col.prop(rpdat, "rp_water") + col = col.column(align=True) col.enabled = rpdat.rp_water col.prop(rpdat, 'arm_water_level') col.prop(rpdat, 'arm_water_density') @@ -1449,27 +1605,35 @@ class ARM_PT_RenderPathPostProcessPanel(bpy.types.Panel): rpdat = wrd.arm_rplist[wrd.arm_rplist_index] layout.enabled = rpdat.rp_render_to_texture - row = layout.row() - row.prop(rpdat, "rp_antialiasing") - layout.prop(rpdat, "rp_supersampling") - layout.prop(rpdat, 'arm_rp_resolution') + col = layout.column() + col.prop(rpdat, "rp_antialiasing") + col.prop(rpdat, "rp_supersampling") + + col = layout.column() + col.prop(rpdat, 'arm_rp_resolution') if rpdat.arm_rp_resolution == 'Custom': - layout.prop(rpdat, 'arm_rp_resolution_size') - layout.prop(rpdat, 'arm_rp_resolution_filter') - layout.prop(rpdat, 'rp_dynres') + col.prop(rpdat, 'arm_rp_resolution_size') + col.prop(rpdat, 'arm_rp_resolution_filter') + col.prop(rpdat, 'rp_dynres') layout.separator() - row = layout.row() - row.prop(rpdat, "rp_ssgi") + col = layout.column() - col.enabled = rpdat.rp_ssgi != 'Off' - col.prop(rpdat, 'arm_ssgi_half_res') - col.prop(rpdat, 'arm_ssgi_rays') - col.prop(rpdat, 'arm_ssgi_radius') - col.prop(rpdat, 'arm_ssgi_strength') - col.prop(rpdat, 'arm_ssgi_max_steps') + col.prop(rpdat, "rp_ssgi") + sub = col.column() + sub.enabled = rpdat.rp_ssgi != 'Off' + sub.prop(rpdat, 'arm_ssgi_half_res') + sub.prop(rpdat, 'arm_ssgi_rays') + sub.prop(rpdat, 'arm_ssgi_radius') + sub.prop(rpdat, 'arm_ssgi_strength') + sub.prop(rpdat, 'arm_ssgi_max_steps') + layout.separator(factor=0.5) + + layout.prop(rpdat, 'arm_micro_shadowing') layout.separator() - layout.prop(rpdat, "rp_ssr") + col = layout.column() + col.prop(rpdat, "rp_ssr") + col = col.column() col.enabled = rpdat.rp_ssr col.prop(rpdat, 'arm_ssr_half_res') col.prop(rpdat, 'arm_ssr_ray_step') @@ -1478,33 +1642,42 @@ class ARM_PT_RenderPathPostProcessPanel(bpy.types.Panel): col.prop(rpdat, 'arm_ssr_falloff_exp') col.prop(rpdat, 'arm_ssr_jitter') layout.separator() - layout.prop(rpdat, 'arm_ssrs') + col = layout.column() + col.prop(rpdat, 'arm_ssrs') + col = col.column() col.enabled = rpdat.arm_ssrs col.prop(rpdat, 'arm_ssrs_ray_step') - layout.prop(rpdat, 'arm_micro_shadowing') layout.separator() - layout.prop(rpdat, "rp_bloom") + col = layout.column() + col.prop(rpdat, "rp_bloom") + col = col.column() col.enabled = rpdat.rp_bloom col.prop(rpdat, 'arm_bloom_threshold') col.prop(rpdat, 'arm_bloom_strength') col.prop(rpdat, 'arm_bloom_radius') layout.separator() - layout.prop(rpdat, "rp_motionblur") + col = layout.column() + col.prop(rpdat, "rp_motionblur") + col = col.column() col.enabled = rpdat.rp_motionblur != 'Off' col.prop(rpdat, 'arm_motion_blur_intensity') layout.separator() - layout.prop(rpdat, "rp_volumetriclight") + col = layout.column() + col.prop(rpdat, "rp_volumetriclight") + col = col.column() col.enabled = rpdat.rp_volumetriclight col.prop(rpdat, 'arm_volumetric_light_air_color') col.prop(rpdat, 'arm_volumetric_light_air_turbidity') col.prop(rpdat, 'arm_volumetric_light_steps') layout.separator() - layout.prop(rpdat, "rp_chromatic_aberration") + col = layout.column() + col.prop(rpdat, "rp_chromatic_aberration") + col = col.column() col.enabled = rpdat.rp_chromatic_aberration col.prop(rpdat, 'arm_chromatic_aberration_type') col.prop(rpdat, 'arm_chromatic_aberration_strength') @@ -1537,45 +1710,51 @@ class ARM_PT_RenderPathCompositorPanel(bpy.types.Panel): layout.enabled = rpdat.rp_compositornodes layout.prop(rpdat, 'arm_tonemap') - layout.prop(rpdat, 'arm_letterbox') + layout.separator() + col = layout.column() - col.enabled = rpdat.arm_letterbox - col.prop(rpdat, 'arm_letterbox_size') - layout.prop(rpdat, 'arm_sharpen') + draw_conditional_prop(col, 'Letterbox', rpdat, 'arm_letterbox', 'arm_letterbox_size') + draw_conditional_prop(col, 'Sharpen', rpdat, 'arm_sharpen', 'arm_sharpen_strength') + draw_conditional_prop(col, 'Vignette', rpdat, 'arm_vignette', 'arm_vignette_strength') + draw_conditional_prop(col, 'Film Grain', rpdat, 'arm_grain', 'arm_grain_strength') + layout.separator() + col = layout.column() - col.enabled = rpdat.arm_sharpen - col.prop(rpdat, 'arm_sharpen_strength') - layout.prop(rpdat, 'arm_fisheye') - layout.prop(rpdat, 'arm_vignette') - col = layout.column() - col.enabled = rpdat.arm_vignette - col.prop(rpdat, 'arm_vignette_strength') - layout.prop(rpdat, 'arm_lensflare') - layout.prop(rpdat, 'arm_grain') - col = layout.column() - col.enabled = rpdat.arm_grain - col.prop(rpdat, 'arm_grain_strength') - layout.prop(rpdat, 'arm_fog') - col = layout.column(align=True) + col.prop(rpdat, 'arm_fog') + col = col.column(align=True) col.enabled = rpdat.arm_fog col.prop(rpdat, 'arm_fog_color') col.prop(rpdat, 'arm_fog_amounta') col.prop(rpdat, 'arm_fog_amountb') layout.separator() - layout.prop(rpdat, "rp_autoexposure") + col = layout.column() - col.enabled = rpdat.rp_autoexposure - col.prop(rpdat, 'arm_autoexposure_strength', text='Strength') - col.prop(rpdat, 'arm_autoexposure_speed', text='Speed') - layout.prop(rpdat, 'arm_lens_texture') + col.prop(rpdat, "rp_autoexposure") + sub = col.column(align=True) + sub.enabled = rpdat.rp_autoexposure + sub.prop(rpdat, 'arm_autoexposure_strength', text='Strength') + sub.prop(rpdat, 'arm_autoexposure_speed', text='Speed') + layout.separator() + + col = layout.column() + col.prop(rpdat, 'arm_lensflare') + col.prop(rpdat, 'arm_fisheye') + layout.separator() + + col = layout.column() + col.prop(rpdat, 'arm_lens_texture') if rpdat.arm_lens_texture != "": - layout.prop(rpdat, 'arm_lens_texture_masking') + col.prop(rpdat, 'arm_lens_texture_masking') if rpdat.arm_lens_texture_masking: - layout.prop(rpdat, 'arm_lens_texture_masking_centerMinClip') - layout.prop(rpdat, 'arm_lens_texture_masking_centerMaxClip') - layout.prop(rpdat, 'arm_lens_texture_masking_luminanceMin') - layout.prop(rpdat, 'arm_lens_texture_masking_luminanceMax') - layout.prop(rpdat, 'arm_lens_texture_masking_brightnessExp') + sub = col.column(align=True) + sub.prop(rpdat, 'arm_lens_texture_masking_centerMinClip') + sub.prop(rpdat, 'arm_lens_texture_masking_centerMaxClip') + sub = col.column(align=True) + sub.prop(rpdat, 'arm_lens_texture_masking_luminanceMin') + sub.prop(rpdat, 'arm_lens_texture_masking_luminanceMax') + col.prop(rpdat, 'arm_lens_texture_masking_brightnessExp') + layout.separator() + layout.prop(rpdat, 'arm_lut_texture') class ARM_PT_BakePanel(bpy.types.Panel): @@ -1630,425 +1809,8 @@ class ARM_PT_BakePanel(bpy.types.Panel): layout.prop(item, "res_x") layout.prop(item, "res_y") - else: - - scene = context.scene - sceneProperties = scene.TLM_SceneProperties - row = layout.row(align=True) - - row = layout.row(align=True) - - #We list LuxCoreRender as available, by default we assume Cycles exists - row.prop(sceneProperties, "tlm_lightmap_engine") - - if sceneProperties.tlm_lightmap_engine == "Cycles": - - #CYCLES SETTINGS HERE - engineProperties = scene.TLM_EngineProperties - - row = layout.row(align=True) - row.label(text="General Settings") - row = layout.row(align=True) - row.operator("tlm.build_lightmaps") - row = layout.row(align=True) - row.operator("tlm.clean_lightmaps") - row = layout.row(align=True) - row.operator("tlm.explore_lightmaps") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_apply_on_unwrap") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_headless") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_alert_on_finish") - - if sceneProperties.tlm_alert_on_finish: - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_alert_sound") - - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_verbose") - #row = layout.row(align=True) - #row.prop(sceneProperties, "tlm_compile_statistics") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_override_bg_color") - if sceneProperties.tlm_override_bg_color: - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_override_color") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_reset_uv") - - row = layout.row(align=True) - try: - if bpy.context.scene["TLM_Buildstat"] is not None: - row.label(text="Last build completed in: " + str(bpy.context.scene["TLM_Buildstat"][0])) - except: - pass - - row = layout.row(align=True) - row.label(text="Cycles Settings") - - row = layout.row(align=True) - row.prop(engineProperties, "tlm_mode") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_quality") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_resolution_scale") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_bake_mode") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_lighting_mode") - - if scene.TLM_EngineProperties.tlm_bake_mode == "Background": - row = layout.row(align=True) - row.label(text="Warning! Background mode is currently unstable", icon_value=2) - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_network_render") - if sceneProperties.tlm_network_render: - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_network_paths") - #row = layout.row(align=True) - #row.prop(sceneProperties, "tlm_network_dir") - row = layout.row(align=True) - row = layout.row(align=True) - row.prop(engineProperties, "tlm_caching_mode") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_directional_mode") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_lightmap_savedir") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_dilation_margin") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_exposure_multiplier") - row = layout.row(align=True) - row.prop(engineProperties, "tlm_setting_supersample") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_metallic_clamp") - - elif sceneProperties.tlm_lightmap_engine == "LuxCoreRender": - - #LUXCORE SETTINGS HERE - luxcore_available = False - - #Look for Luxcorerender in the renderengine classes - for engine in bpy.types.RenderEngine.__subclasses__(): - if engine.bl_idname == "LUXCORE": - luxcore_available = True - break - - row = layout.row(align=True) - if not luxcore_available: - row.label(text="Please install BlendLuxCore.") - else: - row.label(text="LuxCoreRender not yet available.") - - elif sceneProperties.tlm_lightmap_engine == "OctaneRender": - - #LUXCORE SETTINGS HERE - octane_available = False - - row = layout.row(align=True) - row.label(text="Octane Render not yet available.") - - - ################## - #DENOISE SETTINGS! - row = layout.row(align=True) - row.label(text="Denoise Settings") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_denoise_use") - row = layout.row(align=True) - - if sceneProperties.tlm_denoise_use: - row.prop(sceneProperties, "tlm_denoise_engine", expand=True) - row = layout.row(align=True) - - if sceneProperties.tlm_denoise_engine == "Integrated": - row.label(text="No options for Integrated.") - elif sceneProperties.tlm_denoise_engine == "OIDN": - denoiseProperties = scene.TLM_OIDNEngineProperties - row.prop(denoiseProperties, "tlm_oidn_path") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_oidn_verbose") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_oidn_threads") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_oidn_maxmem") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_oidn_affinity") - # row = layout.row(align=True) - # row.prop(denoiseProperties, "tlm_denoise_ao") - elif sceneProperties.tlm_denoise_engine == "Optix": - denoiseProperties = scene.TLM_OptixEngineProperties - row.prop(denoiseProperties, "tlm_optix_path") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_optix_verbose") - row = layout.row(align=True) - row.prop(denoiseProperties, "tlm_optix_maxmem") - - - ################## - #FILTERING SETTINGS! - row = layout.row(align=True) - row.label(text="Filtering Settings") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_filtering_use") - row = layout.row(align=True) - - if sceneProperties.tlm_filtering_use: - - if sceneProperties.tlm_filtering_engine == "OpenCV": - - cv2 = importlib.util.find_spec("cv2") - - if cv2 is None: - row = layout.row(align=True) - row.label(text="OpenCV is not installed. Install it below.") - row = layout.row(align=True) - row.label(text="It is recommended to install as administrator.") - row = layout.row(align=True) - row.operator("tlm.install_opencv_lightmaps") - else: - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_mode") - row = layout.row(align=True) - if scene.TLM_SceneProperties.tlm_filtering_mode == "Gaussian": - row.prop(scene.TLM_SceneProperties, "tlm_filtering_gaussian_strength") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") - elif scene.TLM_SceneProperties.tlm_filtering_mode == "Box": - row.prop(scene.TLM_SceneProperties, "tlm_filtering_box_strength") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") - - elif scene.TLM_SceneProperties.tlm_filtering_mode == "Bilateral": - row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_diameter") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_color_deviation") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_coordinate_deviation") - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") - else: - row.prop(scene.TLM_SceneProperties, "tlm_filtering_median_kernel", expand=True) - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations") - else: - row = layout.row(align=True) - row.prop(scene.TLM_SceneProperties, "tlm_numpy_filtering_mode") - - - ################## - #ENCODING SETTINGS! - row = layout.row(align=True) - row.label(text="Encoding Settings") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_encoding_use") - row = layout.row(align=True) - - if sceneProperties.tlm_encoding_use: - - - if scene.TLM_EngineProperties.tlm_bake_mode == "Background": - row.label(text="Encoding options disabled in background mode") - row = layout.row(align=True) - - row.prop(sceneProperties, "tlm_encoding_device", expand=True) - row = layout.row(align=True) - - if sceneProperties.tlm_encoding_device == "CPU": - row.prop(sceneProperties, "tlm_encoding_mode_a", expand=True) - else: - row.prop(sceneProperties, "tlm_encoding_mode_b", expand=True) - - if sceneProperties.tlm_encoding_device == "CPU": - if sceneProperties.tlm_encoding_mode_a == "RGBM": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_encoding_range") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_decoder_setup") - if sceneProperties.tlm_encoding_mode_a == "RGBD": - pass - if sceneProperties.tlm_encoding_mode_a == "HDR": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_format") - else: - - if sceneProperties.tlm_encoding_mode_b == "RGBM": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_encoding_range") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_decoder_setup") - - if sceneProperties.tlm_encoding_mode_b == "LogLuv" and sceneProperties.tlm_encoding_device == "GPU": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_decoder_setup") - if sceneProperties.tlm_encoding_mode_b == "HDR": - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_format") - - ################## - #SELECTION OPERATORS! - - row = layout.row(align=True) - row.label(text="Selection Operators") - row = layout.row(align=True) - - row = layout.row(align=True) - row.operator("tlm.enable_selection") - row = layout.row(align=True) - row.operator("tlm.disable_selection") - row = layout.row(align=True) - row.prop(sceneProperties, "tlm_override_object_settings") - - if sceneProperties.tlm_override_object_settings: - - row = layout.row(align=True) - row = layout.row() - row.prop(sceneProperties, "tlm_mesh_lightmap_unwrap_mode") - row = layout.row() - - if sceneProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - - if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0: - row = layout.row() - item = scene.TLM_AtlasList[scene.TLM_AtlasListItem] - row.prop_search(sceneProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group') - else: - row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") - - else: - row = layout.row() - row.prop(sceneProperties, "tlm_postpack_object") - row = layout.row() - - if sceneProperties.tlm_postpack_object and sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA": - if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0: - row = layout.row() - item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem] - row.prop_search(sceneProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group') - row = layout.row() - - else: - row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.") - row = layout.row() - - if sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA": - row.prop(sceneProperties, "tlm_mesh_lightmap_resolution") - row = layout.row() - row.prop(sceneProperties, "tlm_mesh_unwrap_margin") - - row = layout.row(align=True) - row.operator("tlm.remove_uv_selection") - row = layout.row(align=True) - row.operator("tlm.select_lightmapped_objects") - row = layout.row(align=True) - - ################## - #Additional settings - row = layout.row(align=True) - row.label(text="Additional options") - sceneProperties = scene.TLM_SceneProperties - atlasListItem = scene.TLM_AtlasListItem - atlasList = scene.TLM_AtlasList - postatlasListItem = scene.TLM_PostAtlasListItem - postatlasList = scene.TLM_PostAtlasList - - layout.label(text="Atlas Groups") - row = layout.row() - row.prop(sceneProperties, "tlm_atlas_mode", expand=True) - - if sceneProperties.tlm_atlas_mode == "Prepack": - - rows = 2 - if len(atlasList) > 1: - rows = 4 - row = layout.row() - row.template_list("TLM_UL_AtlasList", "Atlas List", scene, "TLM_AtlasList", scene, "TLM_AtlasListItem", rows=rows) - col = row.column(align=True) - col.operator("tlm_atlaslist.new_item", icon='ADD', text="") - col.operator("tlm_atlaslist.delete_item", icon='REMOVE', text="") - #col.menu("ARM_MT_BakeListSpecials", icon='DOWNARROW_HLT', text="") - - # if len(scene.TLM_AtlasList) > 1: - # col.separator() - # op = col.operator("arm_bakelist.move_item", icon='TRIA_UP', text="") - # op.direction = 'UP' - # op = col.operator("arm_bakelist.move_item", icon='TRIA_DOWN', text="") - # op.direction = 'DOWN' - - if atlasListItem >= 0 and len(atlasList) > 0: - item = atlasList[atlasListItem] - #layout.prop_search(item, "obj", bpy.data, "objects", text="Object") - #layout.prop(item, "res_x") - layout.prop(item, "tlm_atlas_lightmap_unwrap_mode") - layout.prop(item, "tlm_atlas_lightmap_resolution") - layout.prop(item, "tlm_atlas_unwrap_margin") - - amount = 0 - - for obj in bpy.data.objects: - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": - if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name: - amount = amount + 1 - - layout.label(text="Objects: " + str(amount)) - - # layout.use_property_split = True - # layout.use_property_decorate = False - # layout.label(text="Enable for selection") - # layout.label(text="Disable for selection") - # layout.label(text="Something...") - - else: - - layout.label(text="Postpacking is unstable.") - rows = 2 - if len(atlasList) > 1: - rows = 4 - row = layout.row() - row.template_list("TLM_UL_PostAtlasList", "PostList", scene, "TLM_PostAtlasList", scene, "TLM_PostAtlasListItem", rows=rows) - col = row.column(align=True) - col.operator("tlm_postatlaslist.new_item", icon='ADD', text="") - col.operator("tlm_postatlaslist.delete_item", icon='REMOVE', text="") - - if postatlasListItem >= 0 and len(postatlasList) > 0: - item = postatlasList[postatlasListItem] - layout.prop(item, "tlm_atlas_lightmap_resolution") - - #Below list object counter - amount = 0 - utilized = 0 - atlasUsedArea = 0 - atlasSize = item.tlm_atlas_lightmap_resolution - - for obj in bpy.data.objects: - if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: - if obj.TLM_ObjectProperties.tlm_postpack_object: - if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name: - amount = amount + 1 - - atlasUsedArea += int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) ** 2 - - row = layout.row() - row.prop(item, "tlm_atlas_repack_on_cleanup") - - #TODO SET A CHECK FOR THIS! ADD A CV2 CHECK TO UTILITY! - cv2 = True - - if cv2: - row = layout.row() - row.prop(item, "tlm_atlas_dilation") - layout.label(text="Objects: " + str(amount)) - - utilized = atlasUsedArea / (int(atlasSize) ** 2) - layout.label(text="Utilized: " + str(utilized * 100) + "%") - - if (utilized * 100) > 100: - layout.label(text="Warning! Overflow not yet supported") - class ArmGenLodButton(bpy.types.Operator): - '''Automatically generate LoD levels''' + """Automatically generate LoD levels.""" bl_idname = 'arm.generate_lod' bl_label = 'Auto Generate' @@ -2178,7 +1940,7 @@ class ArmGenTerrainButton(bpy.types.Operator): node.location = (-200, -200) node.inputs[0].default_value = 5.0 links.new(nodes['Bump'].inputs[2], nodes['_TerrainHeight'].outputs[0]) - links.new(nodes['Principled BSDF'].inputs[17], nodes['Bump'].outputs[0]) + links.new(nodes['Principled BSDF'].inputs[20], nodes['Bump'].outputs[0]) # Create sectors root_obj = bpy.data.objects.new("Terrain", None) @@ -2312,20 +2074,27 @@ class ARM_PT_ProxyPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False layout.operator("arm.make_proxy") + obj = bpy.context.object - if obj != None and obj.proxy != None: - layout.label(text="Sync") - layout.prop(obj, "arm_proxy_sync_loc") - layout.prop(obj, "arm_proxy_sync_rot") - layout.prop(obj, "arm_proxy_sync_scale") - layout.prop(obj, "arm_proxy_sync_materials") - layout.prop(obj, "arm_proxy_sync_modifiers") - layout.prop(obj, "arm_proxy_sync_traits") - row = layout.row() + if obj is not None and obj.proxy is not None: + col = layout.column(heading="Sync") + col.prop(obj, "arm_proxy_sync_loc") + col.prop(obj, "arm_proxy_sync_rot") + col.prop(obj, "arm_proxy_sync_scale") + col.separator() + + col.prop(obj, "arm_proxy_sync_materials") + col.prop(obj, "arm_proxy_sync_modifiers") + col.separator() + + col.prop(obj, "arm_proxy_sync_traits") + row = col.row() row.enabled = obj.arm_proxy_sync_traits row.prop(obj, "arm_proxy_sync_trait_props") - layout.operator("arm.proxy_toggle_all") - layout.operator("arm.proxy_apply_all") + + row = layout.row(align=True) + row.operator("arm.proxy_toggle_all") + row.operator("arm.proxy_apply_all") class ArmMakeProxyButton(bpy.types.Operator): '''Create proxy from linked object''' @@ -2437,7 +2206,7 @@ class ARM_OT_ShowFileVersionInfo(bpy.types.Operator): bl_idname = 'arm.show_old_file_version_info' bl_description = ('Displays an info panel that warns about opening a file' 'which was created in a previous version of Armory') - # bl_options = {'INTERNAL'} + bl_options = {'INTERNAL'} wrd = None @@ -2726,7 +2495,7 @@ def draw_custom_node_menu(self, context): layout = self.layout layout.separator() layout.operator("arm.open_node_documentation", text="Show documentation for this node", icon='HELP') - layout.operator("arm.open_node_haxe_source", text="Open .hx source in the browser", icon_value=arm.props_traits.icons_dict['haxe'].icon_id) + layout.operator("arm.open_node_haxe_source", text="Open .hx source in the browser", icon_value=ui_icons.get_id("haxe")) layout.operator("arm.open_node_python_source", text="Open .py source in the browser", icon='FILE_SCRIPT') elif context.space_data.tree_type == 'ShaderNodeTree': @@ -2736,6 +2505,18 @@ def draw_custom_node_menu(self, context): layout.prop(context.active_node, 'arm_material_param', text='Armory: Material Parameter') +def draw_conditional_prop(layout: bpy.types.UILayout, heading: str, data: bpy.types.AnyType, prop_condition: str, prop_value: str) -> None: + """Draws a property row with a checkbox that enables a value field. + The function fails when prop_condition is not a boolean property. + """ + col = layout.column(heading=heading) + row = col.row() + row.prop(data, prop_condition, text='') + sub = row.row() + sub.enabled = getattr(data, prop_condition) + sub.prop(data, prop_value, expand=True) + + def register(): bpy.utils.register_class(ARM_PT_ObjectPropsPanel) bpy.utils.register_class(ARM_PT_ModifiersPropsPanel) @@ -2801,6 +2582,13 @@ def register(): bpy.utils.register_class(ArmoryUpdateListAndroidEmulatorRunButton) bpy.utils.register_class(ArmoryUpdateListInstalledVSButton) + bpy.utils.register_class(scene.TLM_PT_Settings) + bpy.utils.register_class(scene.TLM_PT_Denoise) + bpy.utils.register_class(scene.TLM_PT_Filtering) + bpy.utils.register_class(scene.TLM_PT_Encoding) + bpy.utils.register_class(scene.TLM_PT_Utility) + bpy.utils.register_class(scene.TLM_PT_Additional) + bpy.types.VIEW3D_HT_header.append(draw_view3d_header) bpy.types.VIEW3D_MT_object.append(draw_view3d_object_menu) bpy.types.NODE_MT_context_menu.append(draw_custom_node_menu) @@ -2874,3 +2662,10 @@ def unregister(): bpy.utils.unregister_class(ArmSyncProxyButton) bpy.utils.unregister_class(ArmPrintTraitsButton) bpy.utils.unregister_class(ARM_PT_MaterialNodePanel) + + bpy.utils.unregister_class(scene.TLM_PT_Settings) + bpy.utils.unregister_class(scene.TLM_PT_Denoise) + bpy.utils.unregister_class(scene.TLM_PT_Filtering) + bpy.utils.unregister_class(scene.TLM_PT_Encoding) + bpy.utils.unregister_class(scene.TLM_PT_Utility) + bpy.utils.unregister_class(scene.TLM_PT_Additional) diff --git a/blender/arm/ui_icons.py b/blender/arm/ui_icons.py new file mode 100644 index 00000000..38f6b922 --- /dev/null +++ b/blender/arm/ui_icons.py @@ -0,0 +1,55 @@ +""" +Blender user interface icon handling. +""" +import os.path +from typing import Optional + +import bpy.utils.previews + +import arm + +if arm.is_reload(__name__): + # _unload_icons is not available in the module scope yet + def __unload(): + _unload_icons() + + # Refresh icons after reload + __unload() +else: + arm.enable_reload(__name__) + + +__all__ = ["get_id"] + +_icons_dict: Optional[bpy.utils.previews.ImagePreviewCollection] = None +"""Dictionary of all loaded icons, or `None` if not loaded""" + +_icons_dir = os.path.join(os.path.dirname(__file__), "custom_icons") +"""Directory of the icon files""" + + +def _load_icons(): + """(Re)loads all icons.""" + global _icons_dict + + _unload_icons() + + _icons_dict = bpy.utils.previews.new() + _icons_dict.load("bundle", os.path.join(_icons_dir, "bundle.png"), 'IMAGE', force_reload=True) + _icons_dict.load("haxe", os.path.join(_icons_dir, "haxe.png"), 'IMAGE', force_reload=True) + _icons_dict.load("wasm", os.path.join(_icons_dir, "wasm.png"), 'IMAGE', force_reload=True) + + +def _unload_icons(): + """Unloads all icons.""" + global _icons_dict + if _icons_dict is not None: + bpy.utils.previews.remove(_icons_dict) + _icons_dict = None + + +def get_id(identifier: str) -> int: + """Returns the icon ID from the given identifier.""" + if _icons_dict is None: + _load_icons() + return _icons_dict[identifier].icon_id diff --git a/blender/arm/utils.py b/blender/arm/utils.py index 82ff4f9f..c23100f0 100755 --- a/blender/arm/utils.py +++ b/blender/arm/utils.py @@ -1,13 +1,14 @@ +from enum import Enum, unique import glob import json +import locale import os import platform import re -import subprocess -from typing import Any -import webbrowser import shlex -import locale +import subprocess +from typing import Any, Dict, List, Optional, Tuple +import webbrowser import numpy as np @@ -18,7 +19,17 @@ from arm.lib.lz4 import LZ4 import arm.log as log import arm.make_state as state import arm.props_renderpath -from enum import Enum, unique + +if arm.is_reload(__name__): + arm.lib.armpack = arm.reload_module(arm.lib.armpack) + arm.lib.lz4 = arm.reload_module(arm.lib.lz4) + from arm.lib.lz4 import LZ4 + log = arm.reload_module(log) + state = arm.reload_module(state) + arm.props_renderpath = arm.reload_module(arm.props_renderpath) +else: + arm.enable_reload(__name__) + class NumpyEncoder(json.JSONEncoder): def default(self, obj): @@ -78,6 +89,15 @@ def convert_image(image, path, file_format='JPEG'): ren.image_settings.file_format = orig_file_format ren.image_settings.color_mode = orig_color_mode + +def is_livepatch_enabled(): + """Returns whether live patch is enabled and can be used.""" + wrd = bpy.data.worlds['Arm'] + # If the game is published, the target is krom-[OS] and not krom, + # so there is no live patch when publishing + return wrd.arm_live_patch and state.target == 'krom' + + def blend_name(): return bpy.path.basename(bpy.context.blend_data.filepath).rsplit('.', 1)[0] @@ -91,7 +111,17 @@ def get_fp(): else: s = bpy.data.filepath.split(os.path.sep) s.pop() - return os.path.sep.join(s) + s = os.path.sep.join(s) + if get_os_is_windows() and len(s) == 2 and s[1] == ':': + # If the project is located at a drive root (C:/ for example), + # then s = "C:". If joined later with another path, no path + # separator is added by default because C:some_path is valid + # Windows path syntax (some_path is then relative to the CWD on the + # C drive). We prevent this by manually adding the path separator + # in these cases. Please refer to the Python doc of os.path.join() + # for more details. + s += os.path.sep + return s def get_fp_build(): return os.path.join(get_fp(), build_dir()) @@ -292,116 +322,97 @@ def fetch_bundled_script_names(): for file in glob.glob('*.hx'): wrd.arm_bundled_scripts_list.add().name = file.rsplit('.', 1)[0] + script_props = {} script_props_defaults = {} -script_warnings = {} -def fetch_script_props(file): - with open(file, encoding="utf-8") as f: - name = file.rsplit('.', 1)[0] - if 'Sources' in name: - name = name[name.index('Sources') + 8:] - if '/' in name: - name = name.replace('/', '.') - if '\\' in file: - name = name.replace('\\', '.') +script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of (identifier, warning message) - script_props[name] = [] - script_props_defaults[name] = [] - script_warnings[name] = [] +# See https://regex101.com/r/bbrCzN/8 +RX_MODIFIERS = r'(?P(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers +RX_IDENTIFIER = r'(?P[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules +RX_TYPE = r'(?:\s*:\s*(?P[_a-z]+[\._a-z0-9]*))?' # Optional type annotation +RX_VALUE = r'(?:\s*=\s*(?P(?:\".*\")|(?:[^;]+)|))?' # Optional default value - lines = f.read().splitlines() +PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}(?Pvar|final)\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};' +PROP_REGEX = re.compile(PROP_REGEX_RAW, re.IGNORECASE) +def fetch_script_props(filename: str): + """Parses @prop declarations from the given Haxe script.""" + with open(filename, 'r', encoding='utf-8') as sourcefile: + source = sourcefile.read() - # Read next line - read_prop = False - for lineno, line in enumerate(lines): - # enumerate() starts with 0 - lineno += 1 + if source == '': + return - if not read_prop: - read_prop = line.lstrip().startswith('@prop') + name = filename.rsplit('.', 1)[0] + + # Convert the name into a package path relative to the "Sources" dir + if 'Sources' in name: + name = name[name.index('Sources') + 8:] + if '/' in name: + name = name.replace('/', '.') + if '\\' in filename: + name = name.replace('\\', '.') + + script_props[name] = [] + script_props_defaults[name] = [] + script_warnings[name] = [] + + for match in re.finditer(PROP_REGEX, source): + + p_modifiers: Optional[str] = match.group('modifiers') + p_identifier: str = match.group('identifier') + p_type: Optional[str] = match.group('type') + p_default_val: Optional[str] = match.group('value') + + if p_modifiers is not None: + if 'static' in p_modifiers: + script_warnings[name].append((p_identifier, '`static` modifier might cause unwanted behaviour!')) + if 'inline' in p_modifiers: + script_warnings[name].append((p_identifier, '`inline` modifier is not supported!')) + continue + if 'final' in p_modifiers or match.group('attr_type') == 'final': + script_warnings[name].append((p_identifier, '`final` properties are not supported!')) continue - if read_prop: - if 'var ' in line: - # Line of code - code_ref = line.split('var ')[1].split(';')[0] - else: - script_warnings[name].append(f"Line {lineno - 1}: Unused @prop") - read_prop = line.lstrip().startswith('@prop') - continue + # Property type is annotated + if p_type is not None: + if p_type.startswith("iron.object."): + p_type = p_type[12:] + elif p_type.startswith("iron.math."): + p_type = p_type[10:] - valid_prop = False + type_default_val = get_type_default_value(p_type) + if type_default_val is None: + script_warnings[name].append((p_identifier, f'unsupported type `{p_type}`!')) + continue - # Declaration = Assignment; - var_sides = code_ref.split('=') - # DeclarationName: DeclarationType - decl_sides = var_sides[0].split(':') + # Default value exists + if p_default_val is not None: + # Remove string quotes + p_default_val = p_default_val.replace('\'', '').replace('"', '') + else: + p_default_val = type_default_val - prop_name = decl_sides[0].strip() + # Default value is given instead, try to infer the properties type from it + elif p_default_val is not None: + p_type = get_prop_type_from_value(p_default_val) - if 'static ' in line: - # Static properties can be overwritten multiple times - # from multiple property lists - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Static properties may result in undefined behaviours!") + # Type is not recognized + if p_type is None: + script_warnings[name].append((p_identifier, 'could not infer property type from given value!')) + continue + if p_type == "String": + p_default_val = p_default_val.replace('\'', '').replace('"', '') - # If the prop type is annotated in the code - # (= declaration has two parts) - if len(decl_sides) > 1: - prop_type = decl_sides[1].strip() - if prop_type.startswith("iron.object."): - prop_type = prop_type[12:] - elif prop_type.startswith("iron.math."): - prop_type = prop_type[10:] + else: + script_warnings[name].append((p_identifier, 'missing type or default value!')) + continue - # Default value exists - if len(var_sides) > 1 and var_sides[1].strip() != "": - # Type is not supported - if get_type_default_value(prop_type) is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Type {prop_type} is not supported!") - read_prop = False - continue + # Register prop + prop = (p_identifier, p_type) + script_props[name].append(prop) + script_props_defaults[name].append(p_default_val) - prop_value = var_sides[1].replace('\'', '').replace('"', '').strip() - - else: - prop_value = get_type_default_value(prop_type) - - # Type is not supported - if prop_value is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Type {prop_type} is not supported!") - read_prop = False - continue - - valid_prop = True - - # Default value exists - elif len(var_sides) > 1 and var_sides[1].strip() != "": - prop_value = var_sides[1].strip() - prop_type = get_prop_type_from_value(prop_value) - - # Type is not recognized - if prop_type is None: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Property type not recognized!") - read_prop = False - continue - if prop_type == "String": - prop_value = prop_value.replace('\'', '').replace('"', '') - - valid_prop = True - - else: - script_warnings[name].append(f"Line {lineno} (\"{prop_name}\"): Not a valid property!") - read_prop = False - continue - - prop = (prop_name, prop_type) - - # Register prop - if valid_prop: - script_props[name].append(prop) - script_props_defaults[name].append(prop_value) - - read_prop = False def get_prop_type_from_value(value: str): """ @@ -555,7 +566,8 @@ def fetch_prop(o): item.arm_traitpropswarnings.clear() for warning in warnings: entry = item.arm_traitpropswarnings.add() - entry.warning = warning + entry.propName = warning[0] + entry.warning = warning[1] def fetch_bundled_trait_props(): # Bundled script props @@ -594,7 +606,7 @@ def safesrc(s): def safestr(s: str) -> str: """Outputs a string where special characters have been replaced with '_', which can be safely used in file and path names.""" - for c in r'[]/\;,><&*:%=+@!#^()|?^': + for c in r'''[]/\;,><&*:%=+@!#^()|?^'"''': s = s.replace(c, '_') return ''.join([i if ord(i) < 128 else '_' for i in s]) @@ -964,7 +976,11 @@ def get_link_web_server(): return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server def compare_version_blender_arm(): - return not (bpy.app.version[0] != 2 or bpy.app.version[1] != 83) + return not (bpy.app.version[0] != 2 or bpy.app.version[1] != 93) + +def get_file_arm_version_tuple() -> tuple[int]: + wrd = bpy.data.worlds['Arm'] + return tuple(map(int, wrd.arm_version.split('.'))) def type_name_to_type(name: str) -> bpy.types.bpy_struct: """Return the Blender type given by its name, if registered.""" @@ -995,11 +1011,11 @@ def get_visual_studio_from_version(version: str) -> str: def get_list_installed_vs(get_version: bool, get_name: bool, get_path: bool) -> []: err = '' items = [] - path_file = os.path.join(get_sdk_path(), 'Kha', 'Kinc', 'Tools', 'kincmake', 'Data', 'windows', 'vswhere.exe') + path_file = os.path.join(get_sdk_path(), 'Kha', 'Kinc', 'Tools', 'kincmake', 'Data', 'windows', 'vswhere.exe') if not os.path.isfile(path_file): err = 'File "'+ path_file +'" not found.' return items, err - + if (not get_version) and (not get_name) and (not get_path): return items, err @@ -1025,8 +1041,8 @@ def get_list_installed_vs(get_version: bool, get_name: bool, get_path: bool) -> return items, err for i in range(count_items): - v = items_ver[i][0] if len(items_ver) > i else '' - v_full = items_ver[i][1] if len(items_ver) > i else '' + v = items_ver[i][0] if len(items_ver) > i else '' + v_full = items_ver[i][1] if len(items_ver) > i else '' n = items_name[i] if len(items_name) > i else '' p = items_path[i] if len(items_path) > i else '' items.append((v, n, p, v_full)) @@ -1096,4 +1112,4 @@ def register(local_sdk=False): use_local_sdk = local_sdk def unregister(): - pass \ No newline at end of file + pass diff --git a/blender/arm/write_data.py b/blender/arm/write_data.py index 9f88e335..cace8058 100755 --- a/blender/arm/write_data.py +++ b/blender/arm/write_data.py @@ -11,6 +11,14 @@ import arm.assets as assets import arm.make_state as state import arm.utils +if arm.is_reload(__name__): + import arm + assets = arm.reload_module(assets) + state = arm.reload_module(state) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + def on_same_drive(path1: str, path2: str) -> bool: drive_path1, _ = os.path.splitdrive(path1) @@ -54,7 +62,7 @@ def remove_readonly(func, path, excinfo): def write_khafilejs(is_play, export_physics: bool, export_navigation: bool, export_ui: bool, is_publish: bool, - enable_dce: bool, import_traits: List[str], import_logicnodes) -> None: + enable_dce: bool, import_traits: List[str]) -> None: wrd = bpy.data.worlds['Arm'] sdk_path = arm.utils.get_sdk_path() @@ -160,20 +168,20 @@ project.addSources('Sources'); # if export_navigation: # khafile.write("""project.addParameter("--macro include('armory.trait.navigation')");\n""") - # if import_logicnodes: # Live patching for logic nodes - # khafile.write("""project.addParameter("--macro include('armory.logicnode')");\n""") - if not wrd.arm_compiler_inline: khafile.write("project.addParameter('--no-inline');\n") if enable_dce: khafile.write("project.addParameter('-dce full');\n") - live_patch = wrd.arm_live_patch and state.target == 'krom' - if wrd.arm_debug_console or live_patch: + use_live_patch = arm.utils.is_livepatch_enabled() + if wrd.arm_debug_console or use_live_patch: import_traits.append('armory.trait.internal.Bridge') - if live_patch: + if use_live_patch: assets.add_khafile_def('arm_patch') + # Include all logic node classes so that they can later + # get instantiated + khafile.write("""project.addParameter("--macro include('armory.logicnode')");\n""") import_traits = list(set(import_traits)) for i in range(0, len(import_traits)): @@ -253,7 +261,7 @@ project.addSources('Sources'); if wrd.arm_debug_console: assets.add_khafile_def('arm_debug') khafile.write(add_shaders(sdk_path + "/armory/Shaders/debug_draw/**", rel_path=do_relpath_sdk)) - + if not is_publish and state.target == 'html5': khafile.write("project.addParameter('--debug');\n") @@ -537,6 +545,9 @@ def write_compiledglsl(defs, make_variants): #endif """) + if state.target == 'html5' or arm.utils.get_gapi() == 'direct3d11': + f.write("#define _FlipY\n") + f.write("""const float PI = 3.1415926535; const float PI2 = PI * 2.0; const vec2 shadowmapSize = vec2(""" + str(shadowmap_size) + """, """ + str(shadowmap_size) + """); @@ -708,7 +719,7 @@ const float voxelgiAperture = """ + str(round(rpdat.arm_voxelgi_aperture * 100) f.write( """const int maxLights = """ + max_lights + """; const int maxLightsCluster = """ + max_lights_clusters + """; -const float clusterNear = 4.0; +const float clusterNear = 3.0; """) f.write(add_compiledglsl + '\n') # External defined constants diff --git a/blender/arm/write_probes.py b/blender/arm/write_probes.py index 11529394..857aa88c 100644 --- a/blender/arm/write_probes.py +++ b/blender/arm/write_probes.py @@ -1,23 +1,95 @@ -import bpy +from contextlib import contextmanager +import math import multiprocessing import os -import sys -import subprocess -import json import re -import arm.utils +import subprocess + +import bpy + import arm.assets as assets +import arm.log as log +import arm.utils + +if arm.is_reload(__name__): + import arm + assets = arm.reload_module(assets) + log = arm.reload_module(log) + arm.utils = arm.reload_module(arm.utils) +else: + arm.enable_reload(__name__) + def add_irr_assets(output_file_irr): assets.add(output_file_irr + '.arm') + def add_rad_assets(output_file_rad, rad_format, num_mips): assets.add(output_file_rad + '.' + rad_format) for i in range(0, num_mips): assets.add(output_file_rad + '_' + str(i) + '.' + rad_format) -# Generate probes from environment map -def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True): + +@contextmanager +def setup_envmap_render(): + """Creates a background scene for rendering environment textures. + Use it as a context manager to automatically clean up on errors. + """ + rpdat = arm.utils.get_rp() + radiance_size = int(rpdat.arm_radiance_size) + + # Render worlds in a different scene so that there are no other + # objects. The actual scene might be called differently if the name + # is already taken + scene = bpy.data.scenes.new("_arm_envmap_render") + scene.render.engine = "CYCLES" + scene.render.image_settings.file_format = "JPEG" + scene.render.image_settings.quality = 100 + scene.render.resolution_x = radiance_size + scene.render.resolution_y = radiance_size // 2 + + # Set GPU as rendering device if the user enabled it + if bpy.context.preferences.addons["cycles"].preferences.compute_device_type == "CUDA": + scene.cycles.device = "GPU" + else: + log.info('Armory: Using CPU for environment render (might be slow). Enable CUDA if possible.') + + # One sample is enough for world background only + scene.cycles.samples = 1 + + # Setup scene + cam = bpy.data.cameras.new("_arm_cam_envmap_render") + cam_obj = bpy.data.objects.new("_arm_cam_envmap_render", cam) + scene.collection.objects.link(cam_obj) + scene.camera = cam_obj + + cam_obj.location = [0.0, 0.0, 0.0] + cam.type = "PANO" + cam.cycles.panorama_type = "EQUIRECTANGULAR" + cam_obj.rotation_euler = [math.radians(90), 0, math.radians(-90)] + + try: + yield + finally: + bpy.data.objects.remove(cam_obj) + bpy.data.cameras.remove(cam) + bpy.data.scenes.remove(scene) + + +def render_envmap(target_dir: str, world: bpy.types.World): + """Renders an environment texture for the given world into the + target_dir. Use in combination with setup_envmap_render().""" + scene = bpy.data.scenes["_arm_envmap_render"] + scene.world = world + + render_path = os.path.join(target_dir, f"env_{arm.utils.safesrc(world.name)}.jpg") + scene.render.filepath = render_path + + bpy.ops.render.render(write_still=True, scene=scene.name) + + +def write_probes(image_filepath: str, disable_hdr: bool, cached_num_mips: int, arm_radiance=True) -> int: + """Generate probes from environment map and returns the mipmap count""" envpath = arm.utils.get_fp_build() + '/compiled/Assets/envmaps' if not os.path.exists(envpath): @@ -63,7 +135,7 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True scaled_file = output_file_rad + '.' + rad_format if arm.utils.get_os() == 'win': - output = subprocess.check_output([ \ + subprocess.check_output([ kraffiti_path, 'from=' + input_file, 'to=' + scaled_file, @@ -71,35 +143,35 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True 'width=' + str(target_w), 'height=' + str(target_h)]) else: - output = subprocess.check_output([ \ - kraffiti_path + \ - ' from="' + input_file + '"' + \ - ' to="' + scaled_file + '"' + \ - ' format=' + rad_format + \ - ' width=' + str(target_w) + \ - ' height=' + str(target_h)], shell=True) + subprocess.check_output([ + kraffiti_path + + ' from="' + input_file + '"' + + ' to="' + scaled_file + '"' + + ' format=' + rad_format + + ' width=' + str(target_w) + + ' height=' + str(target_h)], shell=True) # Irradiance spherical harmonics if arm.utils.get_os() == 'win': - subprocess.call([ \ + subprocess.call([ cmft_path, '--input', scaled_file, '--filter', 'shcoeffs', '--outputNum', '1', '--output0', output_file_irr]) else: - subprocess.call([ \ - cmft_path + \ - ' --input ' + '"' + scaled_file + '"' + \ - ' --filter shcoeffs' + \ - ' --outputNum 1' + \ - ' --output0 ' + '"' + output_file_irr + '"'], shell=True) + subprocess.call([ + cmft_path + + ' --input ' + '"' + scaled_file + '"' + + ' --filter shcoeffs' + + ' --outputNum 1' + + ' --output0 ' + '"' + output_file_irr + '"'], shell=True) sh_to_json(output_file_irr) add_irr_assets(output_file_irr) # Mip-mapped radiance - if arm_radiance == False: + if not arm_radiance: return cached_num_mips # 4096 = 256 face @@ -200,37 +272,37 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True if disable_hdr is True: for f in generated_files: if arm.utils.get_os() == 'win': - subprocess.call([ \ + subprocess.call([ kraffiti_path, 'from=' + f + '.hdr', 'to=' + f + '.jpg', 'format=jpg']) else: - subprocess.call([ \ - kraffiti_path + \ - ' from="' + f + '.hdr"' + \ - ' to="' + f + '.jpg"' + \ - ' format=jpg'], shell=True) + subprocess.call([ + kraffiti_path + + ' from="' + f + '.hdr"' + + ' to="' + f + '.jpg"' + + ' format=jpg'], shell=True) os.remove(f + '.hdr') # Scale from (4x2 to 1x1> - for i in range (0, 2): + for i in range(0, 2): last = generated_files[-1] out = output_file_rad + '_' + str(mip_count + i) if arm.utils.get_os() == 'win': - subprocess.call([ \ + subprocess.call([ kraffiti_path, 'from=' + last + '.' + rad_format, 'to=' + out + '.' + rad_format, 'scale=0.5', 'format=' + rad_format], shell=True) else: - subprocess.call([ \ - kraffiti_path + \ - ' from=' + '"' + last + '.' + rad_format + '"' + \ - ' to=' + '"' + out + '.' + rad_format + '"' + \ - ' scale=0.5' + \ - ' format=' + rad_format], shell=True) + subprocess.call([ + kraffiti_path + + ' from=' + '"' + last + '.' + rad_format + '"' + + ' to=' + '"' + out + '.' + rad_format + '"' + + ' scale=0.5' + + ' format=' + rad_format], shell=True) generated_files.append(out) mip_count += 2 @@ -239,6 +311,7 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True return mip_count + def sh_to_json(sh_file): """Parse sh coefs produced by cmft into json array""" with open(sh_file + '.c') as f: @@ -251,6 +324,8 @@ def sh_to_json(sh_file): parse_band_floats(irradiance_floats, band0_line) parse_band_floats(irradiance_floats, band1_line) parse_band_floats(irradiance_floats, band2_line) + for i in range(0, len(irradiance_floats)): + irradiance_floats[i] /= 2 sh_json = {'irradiance': irradiance_floats} ext = '.arm' if bpy.data.worlds['Arm'].arm_minimize else '' @@ -259,15 +334,26 @@ def sh_to_json(sh_file): # Clean up .c os.remove(sh_file + '.c') + def parse_band_floats(irradiance_floats, band_line): string_floats = re.findall(r'[-+]?\d*\.\d+|\d+', band_line) - string_floats = string_floats[1:] # Remove 'Band 0/1/2' number + string_floats = string_floats[1:] # Remove 'Band 0/1/2' number for s in string_floats: irradiance_floats.append(float(s)) + def write_sky_irradiance(base_name): # Hosek spherical harmonics - irradiance_floats = [1.5519331988822218,2.3352207154503266,2.997277451988076,0.2673894962434794,0.4305630474135794,0.11331825259716752,-0.04453633521758638,-0.038753175134160295,-0.021302768541875794,0.00055858020486499,0.000371654770334503,0.000126606145406403,-0.000135708721978705,-0.000787399554583089,-0.001550090690860059,0.021947399048903773,0.05453650591711572,0.08783641266630278,0.17053593578630663,0.14734127083304463,0.07775404698816404,-2.6924363189795e-05,-7.9350169701934e-05,-7.559914435231e-05,0.27035455385870993,0.23122918445556914,0.12158817295211832] + irradiance_floats = [ + 1.5519331988822218, 2.3352207154503266, 2.997277451988076, + 0.2673894962434794, 0.4305630474135794, 0.11331825259716752, + -0.04453633521758638, -0.038753175134160295, -0.021302768541875794, + 0.00055858020486499, 0.000371654770334503, 0.000126606145406403, + -0.000135708721978705, -0.000787399554583089, -0.001550090690860059, + 0.021947399048903773, 0.05453650591711572, 0.08783641266630278, + 0.17053593578630663, 0.14734127083304463, 0.07775404698816404, + -2.6924363189795e-05, -7.9350169701934e-05, -7.559914435231e-05, + 0.27035455385870993, 0.23122918445556914, 0.12158817295211832] for i in range(0, len(irradiance_floats)): irradiance_floats[i] /= 2 @@ -282,6 +368,7 @@ def write_sky_irradiance(base_name): assets.add(output_file + '.arm') + def write_color_irradiance(base_name, col): """Constant color irradiance""" # Adjust to Cycles diff --git a/blender/start.py b/blender/start.py index d53cc4b3..8f2ff758 100755 --- a/blender/start.py +++ b/blender/start.py @@ -1,3 +1,7 @@ +import time + +import arm +import arm.log import arm.nodes_logic import arm.nodes_material import arm.props_traits_props @@ -15,8 +19,38 @@ import arm.handlers import arm.utils import arm.keymap +reload_started = 0 + +if arm.is_reload(__name__): + arm.log.debug('Reloading Armory SDK...') + reload_started = time.time() + + # Clear the module cache + import importlib + arm = importlib.reload(arm) # type: ignore + + arm.nodes_logic = arm.reload_module(arm.nodes_logic) + arm.nodes_material = arm.reload_module(arm.nodes_material) + arm.props_traits_props = arm.reload_module(arm.props_traits_props) + arm.props_traits = arm.reload_module(arm.props_traits) + arm.props_lod = arm.reload_module(arm.props_lod) + arm.props_tilesheet = arm.reload_module(arm.props_tilesheet) + arm.props_exporter = arm.reload_module(arm.props_exporter) + arm.props_bake = arm.reload_module(arm.props_bake) + arm.props_renderpath = arm.reload_module(arm.props_renderpath) + arm.props_properties = arm.reload_module(arm.props_properties) + arm.props_collision_filter_mask = arm.reload_module(arm.props_collision_filter_mask) + arm.props = arm.reload_module(arm.props) + arm.props_ui = arm.reload_module(arm.props_ui) + arm.handlers = arm.reload_module(arm.handlers) + arm.utils = arm.reload_module(arm.utils) + arm.keymap = arm.reload_module(arm.keymap) +else: + arm.enable_reload(__name__) + registered = False + def register(local_sdk=False): global registered registered = True @@ -37,6 +71,10 @@ def register(local_sdk=False): arm.handlers.register() arm.props_collision_filter_mask.register() + if reload_started != 0: + arm.log.debug(f'Armory SDK: Reloading finished in {time.time() - reload_started:.3f}s') + + def unregister(): global registered registered = False