Merge pull request #2082 from armory3d/blender2.9

Blender 2.9 LTS support
This commit is contained in:
Lubos Lenco 2021-05-10 11:14:39 +02:00 committed by GitHub
commit 98eeccd71d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 4825 additions and 1653 deletions

163
Shaders/std/sky.glsl Normal file
View file

@ -0,0 +1,163 @@
/* 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_
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
// 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
#define nishita_ozone_coeff vec3(1.59051840791988e-6, 0.00000096707041180970, 0.00000007309568762914)
// Values from [Hill: 60]
#define sun_limb_darkening_col vec3(0.397, 0.503, 0.652)
float random(vec2 coords) {
return fract(sin(dot(coords.xy, vec2(12.9898,78.233))) * 43758.5453);
}
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 = acos(dot(normalize(iPos), normalize(pSun)));
vec3 jODepth = nishita_lookupLUT(iHeight, sunTheta);// * vec3(14000000 / 255, 14000000 / 255, 2000000 / 255);
// Apply dithering to reduce visible banding
jODepth += mix(-1000, 1000, random(r.xy));
// Calculate attenuation
vec3 attn = exp(-(
nishita_mie_coeff * (iOdMie + jODepth.y)
+ (nishita_rayleigh_coeff) * (iOdRlh + jODepth.x)
+ nishita_ozone_coeff * jODepth.z
));
// 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

View file

@ -72,4 +72,13 @@ class Helper {
if (value <= leftMin) return rightMin; if (value <= leftMin) return rightMin;
return map(value, leftMin, leftMax, rightMin, rightMax); 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;
}
} }

View file

@ -11,176 +11,204 @@ class Uniforms {
public static function register() { public static function register() {
iron.object.Uniforms.externalTextureLinks = [textureLink]; iron.object.Uniforms.externalTextureLinks = [textureLink];
iron.object.Uniforms.externalVec2Links = []; iron.object.Uniforms.externalVec2Links = [vec2Link];
iron.object.Uniforms.externalVec3Links = [vec3Link]; iron.object.Uniforms.externalVec3Links = [vec3Link];
iron.object.Uniforms.externalVec4Links = []; iron.object.Uniforms.externalVec4Links = [];
iron.object.Uniforms.externalFloatLinks = [floatLink]; iron.object.Uniforms.externalFloatLinks = [floatLink];
iron.object.Uniforms.externalIntLinks = []; iron.object.Uniforms.externalIntLinks = [];
} }
public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image { public static function textureLink(object: Object, mat: MaterialData, link: String): Null<kha.Image> {
#if arm_ltc switch (link) {
if (link == "_ltcMat") { case "_nishitaLUT": {
if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC(); if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world);
return armory.data.ConstData.ltcMatTex; 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); var target = iron.RenderPath.active.renderTargets.get(link.endsWith("_depth") ? link.substr(0, link.length - 6) : link);
return target != null ? target.image : null; 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<iron.math.Vec4> {
var v: Vec4 = null; var v: Vec4 = null;
#if arm_hosek switch (link) {
if (link == "_hosekA") { #if arm_hosek
if (armory.renderpath.HosekWilkie.data == null) { case "_hosekA": {
armory.renderpath.HosekWilkie.recompute(Scene.active.world); 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 = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.A.x; var camera = iron.Scene.active.camera;
v.y = armory.renderpath.HosekWilkie.data.A.y; v.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
v.z = armory.renderpath.HosekWilkie.data.A.z; 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<iron.math.Vec4> {
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; return v;
} }
public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> { public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
#if rp_dynres switch (link) {
if (link == "_dynamicScale") { #if rp_dynres
return armory.renderpath.DynamicResolutionScale.dynamicScale; 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; return null;
} }
} }

View file

@ -0,0 +1,190 @@
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 scale parameter. **/
public static var rayleighScale = 8e3;
/** Mie scattering scale parameter. **/
public static var mieScale = 1.2e3;
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;
}
return jODepth.mult(jStepSize);
}
}

View file

@ -52,7 +52,6 @@ class PhysicsWorld extends Trait {
public var rbMap: Map<Int, RigidBody>; public var rbMap: Map<Int, RigidBody>;
public var conMap: Map<Int, PhysicsConstraint>; public var conMap: Map<Int, PhysicsConstraint>;
public var timeScale = 1.0; public var timeScale = 1.0;
var timeStep = 1 / 60;
var maxSteps = 1; var maxSteps = 1;
public var solverIterations = 10; public var solverIterations = 10;
public var hitPointWorld = new Vec4(); public var hitPointWorld = new Vec4();
@ -67,7 +66,7 @@ class PhysicsWorld extends Trait {
public static var physTime = 0.0; public static var physTime = 0.0;
#end #end
public function new(timeScale = 1.0, timeStep = 1 / 60, solverIterations = 10) { public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10) {
super(); super();
if (nullvec) { if (nullvec) {
@ -81,8 +80,7 @@ class PhysicsWorld extends Trait {
sceneRemoved = false; sceneRemoved = false;
this.timeScale = timeScale; this.timeScale = timeScale;
this.timeStep = timeStep; this.maxSteps = maxSteps;
maxSteps = timeStep < 1 / 60 ? 10 : 1;
this.solverIterations = solverIterations; this.solverIterations = solverIterations;
// First scene // First scene
@ -269,7 +267,13 @@ class PhysicsWorld extends Trait {
if (preUpdates != null) for (f in preUpdates) f(); 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(); updateContacts();
for (rb in rbMap) @:privateAccess rb.physicsUpdate(); for (rb in rbMap) @:privateAccess rb.physicsUpdate();

View file

@ -2635,7 +2635,7 @@ Make sure the mesh only has tris/quads.""")
rbw = self.scene.rigidbody_world rbw = self.scene.rigidbody_world
if rbw is not None and rbw.enabled: if rbw is not None and rbw.enabled:
out_trait['parameters'] = [str(rbw.time_scale), str(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) self.output['traits'].append(out_trait)
@ -2882,6 +2882,7 @@ Make sure the mesh only has tris/quads.""")
out_world['sun_direction'] = list(world.arm_envtex_sun_direction) out_world['sun_direction'] = list(world.arm_envtex_sun_direction)
out_world['turbidity'] = world.arm_envtex_turbidity out_world['turbidity'] = world.arm_envtex_turbidity
out_world['ground_albedo'] = world.arm_envtex_ground_albedo 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') disable_hdr = world.arm_envtex_name.endswith('.jpg')
@ -2896,16 +2897,9 @@ Make sure the mesh only has tris/quads.""")
rpdat = arm.utils.get_rp() rpdat = arm.utils.get_rp()
solid_mat = rpdat.arm_material_model == 'Solid' solid_mat = rpdat.arm_material_model == 'Solid'
arm_irradiance = rpdat.arm_irradiance and not solid_mat arm_irradiance = rpdat.arm_irradiance and not solid_mat
arm_radiance = False arm_radiance = rpdat.arm_radiance
radtex = world.arm_envtex_name.rsplit('.', 1)[0] radtex = world.arm_envtex_name.rsplit('.', 1)[0] # Remove file extension
irrsharmonics = world.arm_envtex_irr_name 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 num_mips = world.arm_envtex_num_mips
strength = world.arm_envtex_strength strength = world.arm_envtex_strength

View file

@ -1 +1 @@
__all__ = ('Operators', 'Properties', 'Utility', 'Keymap') __all__ = ('Operators', 'Panels', 'Properties', 'Preferences', 'Utility', 'Keymap')

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -9,6 +9,8 @@ def register():
winman = bpy.context.window_manager winman = bpy.context.window_manager
keyman = winman.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW") 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.build_lightmaps', type='F6', value='PRESS')
keyman.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS') keyman.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS')
tlm_keymaps.append(keyman) tlm_keymaps.append(keyman)

View file

@ -6,10 +6,11 @@ classes = [
tlm.TLM_BuildLightmaps, tlm.TLM_BuildLightmaps,
tlm.TLM_CleanLightmaps, tlm.TLM_CleanLightmaps,
tlm.TLM_ExploreLightmaps, tlm.TLM_ExploreLightmaps,
tlm.TLM_EnableSelection, tlm.TLM_EnableSet,
tlm.TLM_DisableSelection, tlm.TLM_DisableSelection,
tlm.TLM_RemoveLightmapUV, tlm.TLM_RemoveLightmapUV,
tlm.TLM_SelectLightmapped, tlm.TLM_SelectLightmapped,
tlm.TLM_ToggleTexelDensity,
installopencv.TLM_Install_OpenCV, installopencv.TLM_Install_OpenCV,
tlm.TLM_AtlasListNewItem, tlm.TLM_AtlasListNewItem,
tlm.TLM_AtlastListDeleteItem, tlm.TLM_AtlastListDeleteItem,
@ -20,6 +21,8 @@ classes = [
tlm.TLM_StartServer, tlm.TLM_StartServer,
tlm.TLM_BuildEnvironmentProbes, tlm.TLM_BuildEnvironmentProbes,
tlm.TLM_CleanBuildEnvironmentProbes, tlm.TLM_CleanBuildEnvironmentProbes,
tlm.TLM_PrepareUVMaps,
tlm.TLM_LoadLightmaps,
imagetools.TLM_ImageUpscale, imagetools.TLM_ImageUpscale,
imagetools.TLM_ImageDownscale imagetools.TLM_ImageDownscale

View file

@ -1,4 +1,4 @@
import bpy, os, time import bpy, os, time, importlib
class TLM_ImageUpscale(bpy.types.Operator): class TLM_ImageUpscale(bpy.types.Operator):
bl_idname = "tlm.image_upscale" bl_idname = "tlm.image_upscale"
@ -8,6 +8,69 @@ class TLM_ImageUpscale(bpy.types.Operator):
def invoke(self, context, event): 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") print("Upscale")
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@ -20,6 +83,111 @@ class TLM_ImageDownscale(bpy.types.Operator):
def invoke(self, context, event): 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'} return {'RUNNING_MODAL'}

View file

@ -21,7 +21,10 @@ class TLM_Install_OpenCV(bpy.types.Operator):
print("Module OpenCV") 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": if platform.system() == "Windows":
pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib") pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib")

View file

@ -1,9 +1,36 @@
import bpy, os, time, blf, webbrowser, platform import bpy, os, time, blf, webbrowser, platform, numpy, bmesh
import math, subprocess, multiprocessing import math, subprocess, multiprocessing
from .. utility import utility
from .. utility import build from .. utility import build
from .. utility.cycles import cache from .. utility.cycles import cache
from .. network import server 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): class TLM_BuildLightmaps(bpy.types.Operator):
bl_idname = "tlm.build_lightmaps" bl_idname = "tlm.build_lightmaps"
bl_label = "Build Lightmaps" bl_label = "Build Lightmaps"
@ -52,13 +79,13 @@ class TLM_CleanLightmaps(bpy.types.Operator):
for file in os.listdir(dirpath): for file in os.listdir(dirpath):
os.remove(os.path.join(dirpath + "/" + file)) os.remove(os.path.join(dirpath + "/" + file))
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
cache.backup_material_restore(obj) cache.backup_material_restore(obj)
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
cache.backup_material_rename(obj) cache.backup_material_rename(obj)
@ -75,8 +102,8 @@ class TLM_CleanLightmaps(bpy.types.Operator):
if image.name.endswith("_baked"): if image.name.endswith("_baked"):
bpy.data.images.remove(image, do_unlink=True) bpy.data.images.remove(image, do_unlink=True)
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postpack_object:
@ -92,14 +119,17 @@ class TLM_CleanLightmaps(bpy.types.Operator):
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True) obj.select_set(True)
bpy.context.view_layer.objects.active = obj bpy.context.view_layer.objects.active = obj
#print(x)
uv_layers = obj.data.uv_layers 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)): 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 uv_layers.active_index = i
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Lightmap shift A")
break break
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
@ -111,9 +141,11 @@ class TLM_CleanLightmaps(bpy.types.Operator):
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: 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) print("Resized for obj: " + obj.name)
if "Lightmap" in obj:
del obj["Lightmap"]
return {'FINISHED'} return {'FINISHED'}
class TLM_ExploreLightmaps(bpy.types.Operator): class TLM_ExploreLightmaps(bpy.types.Operator):
@ -153,63 +185,285 @@ class TLM_ExploreLightmaps(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class TLM_EnableSelection(bpy.types.Operator): class TLM_EnableSet(bpy.types.Operator):
"""Enable for selection""" """Enable for set"""
bl_idname = "tlm.enable_selection" bl_idname = "tlm.enable_set"
bl_label = "Enable for selection" bl_label = "Enable for set"
bl_description = "Enable for selection" bl_description = "Enable for set"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
def execute(self, context): def execute(self, context):
scene = context.scene scene = context.scene
for obj in bpy.context.selected_objects: weightList = {} #ObjName : [Dimension,Weight]
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True max = 0
if scene.TLM_SceneProperties.tlm_override_object_settings: if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution for obj in bpy.context.scene.objects:
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode if obj.type == "MESH":
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 obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": print("Enabling for scene: " + obj.name)
obj.TLM_ObjectProperties.tlm_atlas_pointer = scene.TLM_SceneProperties.tlm_atlas_pointer
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'} return{'FINISHED'}
class TLM_DisableSelection(bpy.types.Operator): class TLM_DisableSelection(bpy.types.Operator):
"""Disable for selection""" """Disable for set"""
bl_idname = "tlm.disable_selection" bl_idname = "tlm.disable_selection"
bl_label = "Disable for selection" bl_label = "Disable for set"
bl_description = "Disable for selection" bl_description = "Disable for selection"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
def execute(self, context): def execute(self, context):
for obj in bpy.context.selected_objects: scene = context.scene
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
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'} return{'FINISHED'}
class TLM_RemoveLightmapUV(bpy.types.Operator): class TLM_RemoveLightmapUV(bpy.types.Operator):
"""Remove Lightmap UV for selection""" """Remove Lightmap UV for set"""
bl_idname = "tlm.remove_uv_selection" bl_idname = "tlm.remove_uv_selection"
bl_label = "Remove Lightmap UV" bl_label = "Remove Lightmap UV"
bl_description = "Remove Lightmap UV for selection" bl_description = "Remove Lightmap UV for set"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
def execute(self, context): def execute(self, context):
for obj in bpy.context.selected_objects: if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
if obj.type == "MESH": for obj in bpy.context.scene.objects:
uv_layers = obj.data.uv_layers if obj.type == "MESH":
for uvlayer in uv_layers: uv_layers = obj.data.uv_layers
if uvlayer.name == "UVMap_Lightmap":
uv_layers.remove(uvlayer) 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'} return{'FINISHED'}
@ -222,8 +476,8 @@ class TLM_SelectLightmapped(bpy.types.Operator):
def execute(self, context): def execute(self, context):
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
obj.select_set(True) obj.select_set(True)
@ -278,7 +532,7 @@ class TLM_AtlastListDeleteItem(bpy.types.Operator):
list = scene.TLM_AtlasList list = scene.TLM_AtlasList
index = scene.TLM_AtlasListItem index = scene.TLM_AtlasListItem
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
atlasName = scene.TLM_AtlasList[index].name atlasName = scene.TLM_AtlasList[index].name
@ -310,7 +564,7 @@ class TLM_PostAtlastListDeleteItem(bpy.types.Operator):
list = scene.TLM_PostAtlasList list = scene.TLM_PostAtlasList
index = scene.TLM_PostAtlasListItem index = scene.TLM_PostAtlasListItem
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
atlasName = scene.TLM_PostAtlasList[index].name atlasName = scene.TLM_PostAtlasList[index].name
@ -437,7 +691,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
def invoke(self, context, event): 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.type == "LIGHT_PROBE":
if obj.data.type == "CUBEMAP": if obj.data.type == "CUBEMAP":
@ -500,7 +754,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
cam.rotation_euler = positions[val] cam.rotation_euler = positions[val]
filename = os.path.join(directory, val) + "_" + camobj_name + ".hdr" 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) print("Writing out: " + val)
bpy.ops.render.render(write_still=True) bpy.ops.render.render(write_still=True)
@ -642,7 +896,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
subprocess.call([envpipe3], shell=True) subprocess.call([envpipe3], shell=True)
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
obj.select_set(False) obj.select_set(False)
cam_obj.select_set(True) cam_obj.select_set(True)
@ -686,7 +940,92 @@ class TLM_MergeAdjacentActors(bpy.types.Operator):
scene = context.scene 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'} return {'FINISHED'}
@ -698,7 +1037,4 @@ def TLM_HalfResolution():
pass pass
def TLM_DivideLMGroups(): def TLM_DivideLMGroups():
pass
def TLM_LoadFromFolder():
pass pass

View file

@ -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")

View file

@ -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

View file

@ -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")

View file

@ -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")

View file

@ -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

View file

@ -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)

View file

@ -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")

View file

@ -1,7 +1,7 @@
import bpy import bpy
from bpy.utils import register_class, unregister_class from bpy.utils import register_class, unregister_class
from . import scene, object, atlas from . import scene, object, atlas, image
from . renderer import cycles, luxcorerender from . renderer import cycles, luxcorerender, octanerender
from . denoiser import oidn, optix from . denoiser import oidn, optix
classes = [ classes = [
@ -9,12 +9,14 @@ classes = [
object.TLM_ObjectProperties, object.TLM_ObjectProperties,
cycles.TLM_CyclesSceneProperties, cycles.TLM_CyclesSceneProperties,
luxcorerender.TLM_LuxCoreSceneProperties, luxcorerender.TLM_LuxCoreSceneProperties,
octanerender.TLM_OctanerenderSceneProperties,
oidn.TLM_OIDNEngineProperties, oidn.TLM_OIDNEngineProperties,
optix.TLM_OptixEngineProperties, optix.TLM_OptixEngineProperties,
atlas.TLM_AtlasListItem, atlas.TLM_AtlasListItem,
atlas.TLM_UL_AtlasList, atlas.TLM_UL_AtlasList,
atlas.TLM_PostAtlasListItem, atlas.TLM_PostAtlasListItem,
atlas.TLM_UL_PostAtlasList atlas.TLM_UL_PostAtlasList,
image.TLM_ImageProperties
] ]
def register(): def register():
@ -25,12 +27,14 @@ def register():
bpy.types.Object.TLM_ObjectProperties = bpy.props.PointerProperty(type=object.TLM_ObjectProperties) 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_EngineProperties = bpy.props.PointerProperty(type=cycles.TLM_CyclesSceneProperties)
bpy.types.Scene.TLM_Engine2Properties = bpy.props.PointerProperty(type=luxcorerender.TLM_LuxCoreSceneProperties) 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_OIDNEngineProperties = bpy.props.PointerProperty(type=oidn.TLM_OIDNEngineProperties)
bpy.types.Scene.TLM_OptixEngineProperties = bpy.props.PointerProperty(type=optix.TLM_OptixEngineProperties) 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_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_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_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.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) 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.Object.TLM_ObjectProperties
del bpy.types.Scene.TLM_EngineProperties del bpy.types.Scene.TLM_EngineProperties
del bpy.types.Scene.TLM_Engine2Properties del bpy.types.Scene.TLM_Engine2Properties
del bpy.types.Scene.TLM_Engine3Properties
del bpy.types.Scene.TLM_OIDNEngineProperties del bpy.types.Scene.TLM_OIDNEngineProperties
del bpy.types.Scene.TLM_OptixEngineProperties del bpy.types.Scene.TLM_OptixEngineProperties
del bpy.types.Scene.TLM_AtlasListItem del bpy.types.Scene.TLM_AtlasListItem
del bpy.types.Scene.TLM_AtlasList del bpy.types.Scene.TLM_AtlasList
del bpy.types.Scene.TLM_PostAtlasListItem del bpy.types.Scene.TLM_PostAtlasListItem
del bpy.types.Scene.TLM_PostAtlasList del bpy.types.Scene.TLM_PostAtlasList
del bpy.types.Image.TLM_ImageProperties

View file

@ -34,12 +34,16 @@ class TLM_PostAtlasListItem(bpy.types.PropertyGroup):
max=1.0, max=1.0,
subtype='FACTOR') subtype='FACTOR')
tlm_atlas_lightmap_unwrap_mode : EnumProperty( unwrap_modes = [('Lightmap', 'Lightmap', 'Use Blender Lightmap Pack algorithm'),
items = [('Lightmap', 'Lightmap', 'TODO'), ('SmartProject', 'Smart Project', 'Use Blender Smart Project algorithm')]
('SmartProject', 'Smart Project', 'TODO'),
('Xatlas', 'Xatlas', 'TODO')], 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", name = "Unwrap Mode",
description="TODO", description="Atlas unwrapping method",
default='SmartProject') default='SmartProject')
class TLM_UL_PostAtlasList(bpy.types.UIList): class TLM_UL_PostAtlasList(bpy.types.UIList):
@ -51,7 +55,7 @@ class TLM_UL_PostAtlasList(bpy.types.UIList):
#In list object counter #In list object counter
amount = 0 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_use:
if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postpack_object:
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name:
@ -69,9 +73,6 @@ class TLM_UL_PostAtlasList(bpy.types.UIList):
layout.alignment = 'CENTER' layout.alignment = 'CENTER'
layout.label(text="", icon = custom_icon) layout.label(text="", icon = custom_icon)
class TLM_AtlasListItem(bpy.types.PropertyGroup): class TLM_AtlasListItem(bpy.types.PropertyGroup):
obj: PointerProperty(type=bpy.types.Object, description="The object to bake") obj: PointerProperty(type=bpy.types.Object, description="The object to bake")
tlm_atlas_lightmap_resolution : EnumProperty( tlm_atlas_lightmap_resolution : EnumProperty(
@ -95,12 +96,17 @@ class TLM_AtlasListItem(bpy.types.PropertyGroup):
max=1.0, max=1.0,
subtype='FACTOR') 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( tlm_atlas_lightmap_unwrap_mode : EnumProperty(
items = [('Lightmap', 'Lightmap', 'TODO'), items = unwrap_modes,
('SmartProject', 'Smart Project', 'TODO'),
('Xatlas', 'Xatlas', 'TODO')],
name = "Unwrap Mode", name = "Unwrap Mode",
description="TODO", description="Atlas unwrapping method",
default='SmartProject') default='SmartProject')
class TLM_UL_AtlasList(bpy.types.UIList): class TLM_UL_AtlasList(bpy.types.UIList):
@ -111,7 +117,7 @@ class TLM_UL_AtlasList(bpy.types.UIList):
amount = 0 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_use:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name: if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name:

View file

@ -1,10 +1,26 @@
import bpy import bpy
from bpy.props import * from bpy.props import *
class TLM_ObjectProperties(bpy.types.PropertyGroup): class TLM_ImageProperties(bpy.types.PropertyGroup):
tlm_image_scale_method : EnumProperty( tlm_image_scale_engine : EnumProperty(
items = [('Native', 'Native', 'TODO'), items = [('OpenCV', 'OpenCV', 'TODO')],
('OpenCV', 'OpenCV', 'TODO')],
name = "Scaling engine", name = "Scaling engine",
description="TODO", description="TODO",
default='Native') 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)

View file

@ -7,7 +7,7 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup):
tlm_atlas_pointer : StringProperty( tlm_atlas_pointer : StringProperty(
name = "Atlas Group", name = "Atlas Group",
description = "Atlas Lightmap Group", description = "",
default = "") default = "")
tlm_postatlas_pointer : StringProperty( tlm_postatlas_pointer : StringProperty(
@ -51,8 +51,7 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup):
unwrap_modes = [('Lightmap', 'Lightmap', 'TODO'), unwrap_modes = [('Lightmap', 'Lightmap', 'TODO'),
('SmartProject', 'Smart Project', 'TODO'), ('SmartProject', 'Smart Project', 'TODO'),
('CopyExisting', 'Copy Existing', 'TODO'), ('AtlasGroupA', 'Atlas Group (Prepack)', 'Attaches the object to a prepack Atlas group. Will overwrite UV map on build.')]
('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO')]
tlm_postpack_object : BoolProperty( #CHECK INSTEAD OF ATLASGROUPB tlm_postpack_object : BoolProperty( #CHECK INSTEAD OF ATLASGROUPB
name="Postpack object", name="Postpack object",
@ -145,4 +144,14 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup):
name="Median kernel", name="Median kernel",
default=3, default=3,
min=1, min=1,
max=5) 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")

View file

@ -21,6 +21,16 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup):
description="Select baking quality", description="Select baking quality",
default="0") 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( tlm_resolution_scale : EnumProperty(
items = [('1', '1/1', '1'), items = [('1', '1/1', '1'),
('2', '1/2', '2'), ('2', '1/2', '2'),
@ -45,10 +55,12 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup):
description="Select bake mode", description="Select bake mode",
default="Foreground") 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( tlm_caching_mode : EnumProperty(
items = [('Copy', 'Copy', 'More overhead; allows for network.'), items = caching_modes,
('Cache', 'Cache', 'Cache in separate blend'),
('Node', 'Node restore', 'EXPERIMENTAL! Use with care')],
name = "Caching mode", name = "Caching mode",
description="Select cache mode", description="Select cache mode",
default="Copy") default="Copy")
@ -88,8 +100,16 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup):
tlm_lighting_mode : EnumProperty( tlm_lighting_mode : EnumProperty(
items = [('combined', 'Combined', 'Bake combined lighting'), items = [('combined', 'Combined', 'Bake combined lighting'),
('combinedao', 'Combined+AO', 'Bake combined lighting with Ambient Occlusion'),
('indirect', 'Indirect', 'Bake indirect lighting'), ('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", name = "Lighting mode",
description="TODO.", description="TODO.",
default="combined") default="combined")
tlm_premultiply_ao : BoolProperty(
name="Premultiply AO",
description="Ambient Occlusion will be premultiplied together with lightmaps, requiring less textures.",
default=True)

View file

@ -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")

View file

@ -1,11 +1,19 @@
import bpy import bpy, os
from bpy.props import * 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): class TLM_SceneProperties(bpy.types.PropertyGroup):
engines = [('Cycles', 'Cycles', 'Use Cycles for lightmapping')] 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')) #engines.append(('OctaneRender', 'Octane Render', 'Use Octane Render for lightmapping'))
tlm_atlas_pointer : StringProperty( tlm_atlas_pointer : StringProperty(
@ -112,7 +120,7 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
#FILTERING SETTINGS GROUP #FILTERING SETTINGS GROUP
tlm_filtering_use : BoolProperty( tlm_filtering_use : BoolProperty(
name="Enable Filtering", name="Enable denoising",
description="Enable denoising for lightmaps", description="Enable denoising for lightmaps",
default=False) default=False)
@ -182,6 +190,17 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
min=1, min=1,
max=5) 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 #Encoding properties
tlm_encoding_use : BoolProperty( tlm_encoding_use : BoolProperty(
name="Enable encoding", 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.'), 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.'), ('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.'), encoding_modes_2 = [('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'),
('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'),
('LogLuv', 'LogLuv', '8-bit HDR encoding. Different.'), ('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( tlm_encoding_mode_a : EnumProperty(
items = encoding_modes_1, items = encoding_modes_1,
@ -275,8 +295,7 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
tlm_mesh_lightmap_unwrap_mode : EnumProperty( tlm_mesh_lightmap_unwrap_mode : EnumProperty(
items = [('Lightmap', 'Lightmap', 'TODO'), items = [('Lightmap', 'Lightmap', 'TODO'),
('SmartProject', 'Smart Project', 'TODO'), ('SmartProject', 'Smart Project', 'TODO'),
('CopyExisting', 'Copy Existing', 'TODO'), ('AtlasGroupA', 'Atlas Group (Prepack)', 'Attaches the object to a prepack Atlas group. Will overwrite UV map on build.'),
('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO'),
('Xatlas', 'Xatlas', 'TODO')], ('Xatlas', 'Xatlas', 'TODO')],
name = "Unwrap Mode", name = "Unwrap Mode",
description="TODO", description="TODO",
@ -317,12 +336,30 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
tlm_metallic_clamp : EnumProperty( tlm_metallic_clamp : EnumProperty(
items = [('ignore', 'Ignore', 'Ignore clamping'), items = [('ignore', 'Ignore', 'Ignore clamping'),
('skip', 'Skip', 'Skip baking metallic materials'),
('zero', 'Zero', 'Set zero'), ('zero', 'Zero', 'Set zero'),
('limit', 'Limit', 'Clamp to 0.9')], ('limit', 'Limit', 'Clamp to 0.9')],
name = "Metallic clamping", name = "Metallic clamping",
description="TODO.", description="TODO.",
default="ignore") 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( tlm_verbose : BoolProperty(
name="Verbose", name="Verbose",
description="Verbose console output", description="Verbose console output",
@ -409,4 +446,52 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
('CYCLES', 'Cycles', 'TODO')], ('CYCLES', 'Cycles', 'TODO')],
name = "Probe Render Engine", name = "Probe Render Engine",
description="TODO", description="TODO",
default='BLENDER_EEVEE') 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')

View file

@ -1,17 +1,20 @@
import bpy, os, subprocess, sys, platform, aud, json, datetime, socket import bpy, os, subprocess, sys, platform, aud, json, datetime, socket
import threading
from . import encoding, pack from . import encoding, pack
from . cycles import lightmap, prepare, nodes, cache from . cycles import lightmap, prepare, nodes, cache
from . luxcore import setup
from . octane import configure, lightmap2
from . denoiser import integrated, oidn, optix from . denoiser import integrated, oidn, optix
from . filtering import opencv from . filtering import opencv
from . gui import Viewport
from .. network import client from .. network import client
from os import listdir from os import listdir
from os.path import isfile, join from os.path import isfile, join
from time import time, sleep from time import time, sleep
from importlib import util from importlib import util
previous_settings = {} previous_settings = {}
postprocess_shutdown = False postprocess_shutdown = False
def prepare_build(self=0, background_mode=False, shutdown_after_build=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") 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: if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Foreground" or background_mode==True:
global start_time global start_time
start_time = time() start_time = time()
bpy.app.driver_namespace["tlm_start_time"] = time()
scene = bpy.context.scene scene = bpy.context.scene
sceneProperties = scene.TLM_SceneProperties 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(): if check_save():
print("Please save your file first") 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") self.report({'INFO'}, "Error:Filtering - OpenCV not installed")
return{'FINISHED'} 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() setMode()
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) 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
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": if sceneProperties.tlm_lightmap_engine == "Cycles":
prepare.init(self, previous_settings) 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": if sceneProperties.tlm_lightmap_engine == "OctaneRender":
pass configure.init(self, previous_settings)
#Renderer - Store settings
#Renderer - Set settings
#Renderer - Config objects, lights, world
begin_build() begin_build()
else: else:
print("Baking in background")
filepath = bpy.data.filepath filepath = bpy.data.filepath
bpy.ops.wm.save_as_mainfile(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 scene = bpy.context.scene
sceneProperties = scene.TLM_SceneProperties 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 #Timer start here bound to global
if check_save(): if check_save():
print("Please save your file first") print("Please save your file first")
self.report({'INFO'}, "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) 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() finish_assemble()
else: else:
print("Background driver process")
bpy.app.driver_namespace["alpha"] = 0 bpy.app.driver_namespace["alpha"] = 0
bpy.app.driver_namespace["tlm_process"] = False 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(): def distribute_building():
print("Distributing lightmap building")
#CHECK IF THERE'S AN EXISTING SUBPROCESS #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")): 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: with open(os.path.join(write_directory, "process.tlm"), 'w') as file:
json.dump(process_status, file, indent=2) 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: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Started process: " + str(bpy.app.driver_namespace["tlm_process"]) + " at " + str(datetime.datetime.now())) 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": if sceneProperties.tlm_lightmap_engine == "OctaneRender":
pass pass
if not 'start_time' in globals():
global start_time
start_time = time()
manage_build(True) manage_build(True)
def begin_build(): def begin_build():
@ -288,7 +272,8 @@ def begin_build():
pass pass
if sceneProperties.tlm_lightmap_engine == "OctaneRender": if sceneProperties.tlm_lightmap_engine == "OctaneRender":
pass
lightmap2.bake()
#Denoiser #Denoiser
if sceneProperties.tlm_denoise_use: if sceneProperties.tlm_denoise_use:
@ -429,7 +414,7 @@ def begin_build():
print("Encoding:" + str(file)) print("Encoding:" + str(file))
encoding.encodeImageRGBMCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) 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: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("ENCODING RGBD") print("ENCODING RGBD")
@ -455,6 +440,36 @@ def begin_build():
print("Encoding:" + str(file)) print("Encoding:" + str(file))
encoding.encodeImageRGBDCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) 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: else:
if sceneProperties.tlm_encoding_mode_b == "HDR": if sceneProperties.tlm_encoding_mode_b == "HDR":
@ -562,6 +577,33 @@ def begin_build():
print("Encoding:" + str(file)) print("Encoding:" + str(file))
encoding.encodeImageRGBDGPU(img, sceneProperties.tlm_encoding_range, dirpath, 0) 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() manage_build()
def manage_build(background_pass=False): def manage_build(background_pass=False):
@ -610,6 +652,10 @@ def manage_build(background_pass=False):
formatEnc = "_encoded.png" formatEnc = "_encoded.png"
if sceneProperties.tlm_encoding_mode_a == "SDR":
formatEnc = ".png"
else: else:
print("GPU Encoding") print("GPU Encoding")
@ -632,6 +678,10 @@ def manage_build(background_pass=False):
formatEnc = "_encoded.png" formatEnc = "_encoded.png"
if sceneProperties.tlm_encoding_mode_b == "SDR":
formatEnc = ".png"
if not background_pass: if not background_pass:
nodes.exchangeLightmapsToPostfix("_baked", end, formatEnc) nodes.exchangeLightmapsToPostfix("_baked", end, formatEnc)
@ -653,13 +703,13 @@ def manage_build(background_pass=False):
filepath = bpy.data.filepath filepath = bpy.data.filepath
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir) dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir)
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
cache.backup_material_restore(obj) cache.backup_material_restore(obj)
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
cache.backup_material_rename(obj) cache.backup_material_rename(obj)
@ -672,77 +722,181 @@ def manage_build(background_pass=False):
if "_Original" in mat.name: if "_Original" in mat.name:
bpy.data.materials.remove(mat) bpy.data.materials.remove(mat)
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.type == "MESH":
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
img_name = obj.name + '_baked'
Lightmapimage = bpy.data.images[img_name] if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
obj["Lightmap"] = Lightmapimage.filepath_raw 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: for image in bpy.data.images:
if image.name.endswith("_baked"): if image.name.endswith("_baked"):
bpy.data.images.remove(image, do_unlink=True) bpy.data.images.remove(image, do_unlink=True)
total_time = sec_to_hours((time() - start_time)) if "tlm_plus_mode" in bpy.app.driver_namespace: #First DIR pass
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(total_time)
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": for image in bpy.data.images:
pass 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: else:
soundfile = "noot.ogg"
scriptDir = os.path.dirname(os.path.realpath(__file__)) total_time = sec_to_hours((time() - start_time))
sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile)) if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(total_time)
device = aud.Device() bpy.context.scene["TLM_Buildstat"] = total_time
sound = aud.Sound.file(sound_path)
device.play(sound)
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: if sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
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")): 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: if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Background":
json.dump(process_status, file, indent=2) pass
if postprocess_shutdown: if not background_pass and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao":
sys.exit() #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 #TODO - SET BELOW TO UTILITY
@ -770,9 +924,8 @@ def reset_settings(prev_settings):
def naming_check(): def naming_check():
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.type == "MESH":
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
@ -829,6 +982,9 @@ def check_save():
def check_denoiser(): def check_denoiser():
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Checking denoiser path")
scene = bpy.context.scene scene = bpy.context.scene
if scene.TLM_SceneProperties.tlm_denoise_use: if scene.TLM_SceneProperties.tlm_denoise_use:
@ -847,8 +1003,8 @@ def check_denoiser():
return 0 return 0
def check_materials(): def check_materials():
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
for slot in obj.material_slots: for slot in obj.material_slots:
mat = slot.material mat = slot.material
@ -868,15 +1024,39 @@ def check_materials():
def sec_to_hours(seconds): def sec_to_hours(seconds):
a=str(seconds//3600) a=str(seconds//3600)
b=str((seconds%3600)//60) 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)] d=["{} hours {} mins {} seconds".format(a, b, c)]
return d return d
def setMode(): 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') bpy.ops.object.mode_set(mode='OBJECT')
#TODO Make some checks that returns to previous selection #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(): def checkAtlasSize():
overflow = False overflow = False
@ -897,7 +1077,7 @@ def checkAtlasSize():
utilized = 0 utilized = 0
atlasUsedArea = 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_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postpack_object:
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:
@ -912,4 +1092,5 @@ def checkAtlasSize():
if overflow == True: if overflow == True:
return True return True
else: else:
return False return False

View file

@ -2,7 +2,6 @@ import bpy
#Todo - Check if already exists, in case multiple objects has the same material #Todo - Check if already exists, in case multiple objects has the same material
def backup_material_copy(slot): def backup_material_copy(slot):
material = slot.material material = slot.material
dup = material.copy() dup = material.copy()
@ -16,25 +15,49 @@ def backup_material_cache_restore(slot, path):
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Restore cache") print("Restore cache")
def backup_material_rename(obj): # def backup_material_restore(obj): #??
if "TLM_PrevMatArray" in obj: # if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: # print("Restoring material for: " + obj.name)
print("Renaming 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 no:
if slot.material.name.endswith("_Original"): # - In which cases are there not?
newname = slot.material.name[1:-9] # - If a lightmapped material was applied to a non-lightmap object?
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
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: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Restoring material for: " + obj.name) print("Restoring material for: " + obj.name)
@ -59,9 +82,43 @@ def backup_material_restore(obj):
originalMaterial = "" originalMaterial = ""
if slot.material is not None: if slot.material is not None:
#slot.material.user_clear() Seems to be bad; See: https://developer.blender.org/T49837 #if slot.material.users < 2:
bpy.data.materials.remove(slot.material) #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: if "." + originalMaterial + "_Original" in bpy.data.materials:
slot.material = bpy.data.materials["." + originalMaterial + "_Original"] slot.material = bpy.data.materials["." + originalMaterial + "_Original"]
slot.material.use_fake_user = False 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)

View file

@ -1,25 +1,26 @@
import bpy, os 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') bpy.ops.object.select_all(action='DESELECT')
obj.select_set(False) obj.select_set(False)
iterNum = 0 iterNum = 0
currentIterNum = 0 currentIterNum = 0
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
iterNum = iterNum + 1 iterNum = iterNum + 1
if iterNum > 1: if iterNum > 1:
iterNum = iterNum - 1 iterNum = iterNum - 1
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == 'MESH': 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_mesh_lightmap_use:
scene = bpy.context.scene scene = bpy.context.scene
@ -32,19 +33,45 @@ def bake():
obj.hide_render = False obj.hide_render = False
scene.render.bake.use_clear = False scene.render.bake.use_clear = False
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: #os.system("cls")
print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name)
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) 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) 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": elif scene.TLM_EngineProperties.tlm_lighting_mode == "ao":
bpy.ops.object.bake(type="AO", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) 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": 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: else:
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False) 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') bpy.ops.object.select_all(action='DESELECT')
currentIterNum = currentIterNum + 1 currentIterNum = currentIterNum + 1

View file

@ -1,8 +1,8 @@
import bpy, os import bpy, os
def apply_lightmaps(): def apply_lightmaps():
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
for slot in obj.material_slots: for slot in obj.material_slots:
mat = slot.material mat = slot.material
@ -32,8 +32,12 @@ def apply_lightmaps():
def apply_materials(): 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: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
uv_layers = obj.data.uv_layers uv_layers = obj.data.uv_layers
@ -90,7 +94,7 @@ def apply_materials():
for node in nodes: for node in nodes:
if node.name == "Baked Image": if node.name == "Baked Image":
lightmapNode = node lightmapNode = node
lightmapNode.location = -800, 300 lightmapNode.location = -1200, 300
lightmapNode.name = "TLM_Lightmap" lightmapNode.name = "TLM_Lightmap"
foundBakedNode = True foundBakedNode = True
@ -98,9 +102,10 @@ def apply_materials():
if not foundBakedNode: if not foundBakedNode:
lightmapNode = node_tree.nodes.new(type="ShaderNodeTexImage") lightmapNode = node_tree.nodes.new(type="ShaderNodeTexImage")
lightmapNode.location = -300, 300 lightmapNode.location = -1200, 300
lightmapNode.name = "TLM_Lightmap" 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 != ""): 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"] lightmapNode.image = bpy.data.images[obj.TLM_ObjectProperties.tlm_atlas_pointer + "_baked"]
@ -118,37 +123,32 @@ def apply_materials():
#Find mainnode #Find mainnode
mainNode = outputNode.inputs[0].links[0].from_node 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 all nodes first
#Add lightmap multipliction texture #Add lightmap multipliction texture
mixNode = node_tree.nodes.new(type="ShaderNodeMixRGB") mixNode = node_tree.nodes.new(type="ShaderNodeMixRGB")
mixNode.name = "Lightmap_Multiplication" 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": if scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO":
mixNode.blend_type = 'ADD' mixNode.blend_type = 'ADD'
else: else:
mixNode.blend_type = 'MULTIPLY' 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 = 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.name = "Lightmap_UV"
UVLightmap.location = -1000, 300 UVLightmap.location = -1500, 300
if(scene.TLM_SceneProperties.tlm_decoder_setup): if(scene.TLM_SceneProperties.tlm_decoder_setup):
if scene.TLM_SceneProperties.tlm_encoding_device == "CPU": if scene.TLM_SceneProperties.tlm_encoding_device == "CPU":
@ -196,7 +196,7 @@ def apply_materials():
baseColorValue = mainNode.inputs[0].default_value baseColorValue = mainNode.inputs[0].default_value
baseColorNode = node_tree.nodes.new(type="ShaderNodeRGB") baseColorNode = node_tree.nodes.new(type="ShaderNodeRGB")
baseColorNode.outputs[0].default_value = baseColorValue 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" baseColorNode.name = "Lightmap_BasecolorNode_A"
else: else:
baseColorNode = mainNode.inputs[0].links[0].from_node 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(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 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"): def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"):
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(ext_postfix, new_postfix, formatHDR) print(ext_postfix, new_postfix, formatHDR)
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
for slot in obj.material_slots: for slot in obj.material_slots:
mat = slot.material mat = slot.material
@ -267,6 +273,53 @@ def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"):
for image in bpy.data.images: for image in bpy.data.images:
image.reload() 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): def load_library(asset_name):
scriptDir = os.path.dirname(os.path.realpath(__file__)) scriptDir = os.path.dirname(os.path.realpath(__file__))

View file

@ -1,4 +1,4 @@
import bpy import bpy, math
from . import cache from . import cache
from .. utility import * from .. utility import *
@ -31,13 +31,16 @@ def configure_lights():
def configure_meshes(self): def configure_meshes(self):
for obj in bpy.data.objects: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
if obj.type == "MESH": 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: if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_restore(obj) cache.backup_material_restore(obj)
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
cache.backup_material_rename(obj) cache.backup_material_rename(obj)
@ -59,9 +62,18 @@ def configure_meshes(self):
scene = bpy.context.scene scene = bpy.context.scene
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_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: for slot in obj.material_slots:
if "." + slot.name + '_Original' in bpy.data.materials: if "." + slot.name + '_Original' in bpy.data.materials:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
@ -77,33 +89,35 @@ def configure_meshes(self):
bpy.ops.object.select_all(action='DESELECT') 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": if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
uv_layers = obj.data.uv_layers 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: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("UVMap made A") print("UV map created for object: " + obj.name)
uvmap = uv_layers.new(name="UVMap_Lightmap") uvmap = uv_layers.new(name=uv_channel)
uv_layers.active_index = len(uv_layers) - 1 uv_layers.active_index = len(uv_layers) - 1
else: else:
print("Existing found...skipping") print("Existing UV map found for object: " + obj.name)
for i in range(0, len(uv_layers)): for i in range(0, len(uv_layers)):
if uv_layers[i].name == 'UVMap_Lightmap': if uv_layers[i].name == 'UVMap_Lightmap':
uv_layers.active_index = i uv_layers.active_index = i
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Lightmap shift A")
break break
atlas_items.append(obj) atlas_items.append(obj)
obj.select_set(True) 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 atlasgroup.tlm_atlas_lightmap_unwrap_mode == "SmartProject":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: 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: for obj in atlas_items:
print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name) 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.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT') 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.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Lightmap": elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Lightmap":
@ -123,8 +142,9 @@ def configure_meshes(self):
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Xatlas": elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Xatlas":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: 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: for obj in atlas_items:
obj.select_set(True) obj.select_set(True)
@ -139,176 +159,213 @@ def configure_meshes(self):
else: else:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: 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: for obj in bpy.context.scene.objects:
if obj.type == "MESH": 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_mesh_lightmap_use:
iterNum = iterNum + 1 iterNum = iterNum + 1
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.type == "MESH": if obj.name in bpy.context.view_layer.objects: #Possible fix for view layer error
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use: 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 #For some reason, a Blender bug might prevent invisible objects from being smart projected
#We will turn the object temporarily visible #We will turn the object temporarily visible
obj.hide_viewport = False obj.hide_viewport = False
obj.hide_set(False) obj.hide_set(False)
currentIterNum = currentIterNum + 1 currentIterNum = currentIterNum + 1
#Configure selection #Configure selection
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj bpy.context.view_layer.objects.active = obj
obj.select_set(True) obj.select_set(True)
obs = bpy.context.view_layer.objects
active = obs.active
#Provide material if none exists obs = bpy.context.view_layer.objects
preprocess_material(obj, scene) active = obs.active
#UV Layer management here #Provide material if none exists
if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA": preprocess_material(obj, scene)
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
#If lightmap #UV Layer management here
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap": if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
if scene.TLM_SceneProperties.tlm_apply_on_unwrap: uv_layers = obj.data.uv_layers
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 not obj.TLM_ObjectProperties.tlm_use_default_channel:
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
#If smart project else:
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject": uv_channel = "UVMap_Lightmap"
if not uv_channel in uv_layers:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Smart Project B") print("UV map created for obj: " + obj.name)
if scene.TLM_SceneProperties.tlm_apply_on_unwrap: uvmap = uv_layers.new(name=uv_channel)
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) uv_layers.active_index = len(uv_layers) - 1
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":
if scene.TLM_SceneProperties.tlm_apply_on_unwrap: #If lightmap
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) 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 #If smart project
#blender_xatlas.Unwrap_Lightmap_Group_Xatlas_2(bpy.context) 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: if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Lightmap shift B") print("Smart Project B")
break 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 elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
for slot in obj.material_slots:
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: for node in nodetree.nodes:
if node.type == "OUTPUT_MATERIAL": if node.type == "OUTPUT_MATERIAL":
outputNode = node mainNode = node
break 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 # for node in nodes:
self.report({'INFO'}, "The primary material node is not supported. Seeking first principled.") # 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: # for node in nodes:
mainNode = find_node_by_type(nodetree.nodes, Node_Types.pbr_node)[0] # if "Lightmap" in node.name:
else: # nodes.remove(node)
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)
def preprocess_material(obj, scene): def preprocess_material(obj, scene):
if len(obj.material_slots) == 0: if len(obj.material_slots) == 0:
@ -537,7 +594,7 @@ def store_existing(prev_container):
selected = [] selected = []
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
if obj.select_get(): if obj.select_get():
selected.append(obj.name) selected.append(obj.name)

View file

@ -22,7 +22,7 @@ class TLM_Integrated_Denoise:
bpy.ops.object.camera_add() bpy.ops.object.camera_add()
#Just select the first camera we find, needed for the compositor #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": if obj.type == "CAMERA":
bpy.context.scene.camera = obj bpy.context.scene.camera = obj
return return

View file

@ -332,6 +332,205 @@ def encodeImageRGBDGPU(image, maxRange, outDir, quality):
#Todo - Find a way to save #Todo - Find a way to save
#bpy.ops.image.save_all_modified() #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): def encodeImageRGBMCPU(image, maxRange, outDir, quality):
input_image = bpy.data.images[image.name] input_image = bpy.data.images[image.name]
image_name = input_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+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+2] = math.pow(result_pixel[i+2] * D, 1/2.2)
result_pixel[i+3] = D 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 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.filepath_raw = outDir + "/" + input_image.name + ".png"
input_image.file_format = "PNG" input_image.file_format = "PNG"
bpy.context.scene.render.image_settings.quality = quality bpy.context.scene.render.image_settings.quality = quality
input_image.save() 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);
# }

View file

@ -62,7 +62,7 @@ class TLM_CV_Filtering:
#SEAM TESTING# ##################### #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 override = bpy.data.objects[obj_name].TLM_ObjectProperties.tlm_mesh_filter_override
elif obj_name in scene.TLM_AtlasList: elif obj_name in scene.TLM_AtlasList:
override = False override = False

View file

@ -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')

View file

@ -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"
"""

View file

@ -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]
]

View file

@ -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!")

View file

@ -106,7 +106,7 @@ def postpack():
rect = [] rect = []
#For each object that targets the atlas #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_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postpack_object:
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:
@ -156,7 +156,13 @@ def postpack():
obj = bpy.data.objects[aob] obj = bpy.data.objects[aob]
for idx, layer in enumerate(obj.data.uv_layers): 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 obj.data.uv_layers.active_index = idx
print("UVLayer set to: " + str(obj.data.uv_layers.active_index)) 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))) print("Written: " + str(os.path.join(lightmap_directory, atlas.name + end + formatEnc)))
#Change the material for each material, slot #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_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postpack_object:
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:
@ -219,7 +225,7 @@ def postpack():
existing_image.user_clear() existing_image.user_clear()
#Add dilation map here... #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_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_postpack_object: if obj.TLM_ObjectProperties.tlm_postpack_object:
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name: if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:

View file

@ -1,5 +1,5 @@
import bpy.ops as O 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 io import StringIO
from threading import Thread from threading import Thread
from queue import Queue, Empty from queue import Queue, Empty
@ -81,15 +81,8 @@ def save_image(image):
image.filepath_raw = savepath 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() image.save()
def get_file_size(filepath): def get_file_size(filepath):
size = "Unpack Files" size = "Unpack Files"
try: try:
@ -141,7 +134,7 @@ def check_is_org_material(self,material):
def clean_empty_materials(self): def clean_empty_materials(self):
for obj in bpy.data.objects: for obj in bpy.context.scene.objects:
for slot in obj.material_slots: for slot in obj.material_slots:
mat = slot.material mat = slot.material
if mat is None: if mat is None:
@ -319,6 +312,11 @@ def lightmap_to_ao(material,lightmap_node):
# https://github.com/mattedicksoncom/blender-xatlas/ # 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): def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
blender_xatlas = importlib.util.find_spec("blender_xatlas") 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 packOptions = bpy.context.scene.pack_tool
chartOptions = bpy.context.scene.chart_tool chartOptions = bpy.context.scene.chart_tool
sharedProperties = bpy.context.scene.shared_properties sharedProperties = bpy.context.scene.shared_properties
#sharedProperties.unwrapSelection
context = bpy.context 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 #save whatever mode the user was in
uvName = "UVMap_Lightmap" startingMode = bpy.context.object.mode
if sharedProperties.lightmapUVChoiceType == "NAME": selected_objects = bpy.context.selected_objects
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: #check something is actually selected
uvmap = uv_layers.new(name=uvName) #external function/operator will select them
uv_layers.active_index = len(uv_layers) - 1 if len(selected_objects) == 0:
else: print("Nothing Selected")
for i in range(0, len(uv_layers)): self.report({"WARNING"}, "Nothing Selected, please select Something")
if uv_layers[i].name == uvName: return {'FINISHED'}
uv_layers.active_index = i
obj.select_set(True) #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 #save all the current edges
if sharedProperties.packOnly: if sharedProperties.packOnly:
@ -381,8 +401,11 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
#Create a fake obj export to a string
#Will strip this down further later
fakeFile = StringIO() fakeFile = StringIO()
blender_xatlas.export_obj_simple.save( blender_xatlas.export_obj_simple.save(
rename_dict=rename_dict,
context=bpy.context, context=bpy.context,
filepath=fakeFile, filepath=fakeFile,
mainUVChoiceType=sharedProperties.mainUVChoiceType, mainUVChoiceType=sharedProperties.mainUVChoiceType,
@ -393,20 +416,26 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
use_mesh_modifiers=True, use_mesh_modifiers=True,
use_edges=True, use_edges=True,
use_smooth_groups=False, use_smooth_groups=False,
use_smooth_groups_bitflags=False, use_smooth_groups_bitflags=False,
use_normals=True, use_normals=True,
use_uvs=True, use_uvs=True,
use_materials=False, use_materials=False,
use_triangles=False, use_triangles=False,
use_nurbs=False, use_nurbs=False,
use_vertex_groups=False, use_vertex_groups=False,
use_blen_objects=True, use_blen_objects=True,
group_by_object=False, group_by_object=False,
group_by_material=False, group_by_material=False,
keep_vertex_order=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": if platform.system() == "Windows":
xatlas_path = os.path.join(file_path, "xatlas", "xatlas-blender.exe") xatlas_path = os.path.join(file_path, "xatlas", "xatlas-blender.exe")
elif platform.system() == "Linux": elif platform.system() == "Linux":
@ -458,6 +487,8 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
shell=True shell=True
) )
print(xatlas_path)
#shove the fake file in stdin #shove the fake file in stdin
stdin = xatlas_process.stdin stdin = xatlas_process.stdin
value = bytes(fakeFile.getvalue() + "\n", 'UTF-8') #The \n is needed to end the input properly 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 = "" obName: string = ""
uvArray: List[float] = field(default_factory=list) uvArray: List[float] = field(default_factory=list)
faceArray: List[int] = field(default_factory=list) faceArray: List[int] = field(default_factory=list)
convertedObjects = [] convertedObjects = []
uvArrayComplete = [] uvArrayComplete = []
#search through the out put for STARTOBJ #search through the out put for STARTOBJ
#then start reading the objects #then start reading the objects
obTest = None obTest = None
startRead = False startRead = False
for line in outObj.splitlines(): for line in outObj.splitlines():
line_split = line.split() line_split = line.split()
if not line_split: if not line_split:
@ -504,14 +535,14 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
print("Start reading the objects----------------------------------------") print("Start reading the objects----------------------------------------")
startRead = True startRead = True
# obTest = uvObject() # obTest = uvObject()
if startRead: if startRead:
#if it's a new obj #if it's a new obj
if line_start == 'o': if line_start == 'o':
#if there is already an object append it #if there is already an object append it
if obTest is not None: if obTest is not None:
convertedObjects.append(obTest) convertedObjects.append(obTest)
obTest = uvObject() #create new uv object obTest = uvObject() #create new uv object
obTest.obName = line_split[1] obTest.obName = line_split[1]
@ -536,9 +567,9 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
#append the final object #append the final object
convertedObjects.append(obTest) convertedObjects.append(obTest)
# print(convertedObjects) print(convertedObjects)
#apply the output------------------------------------------------------------- #apply the output-------------------------------------------------------------
#copy the uvs to the original objects #copy the uvs to the original objects
# objIndex = 0 # objIndex = 0
@ -548,7 +579,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
obTest = importObject obTest = importObject
obTest.obName = safe_dict[obTest.obName] #probably shouldn't just replace it
bpy.context.scene.objects[obTest.obName].select_set(True) bpy.context.scene.objects[obTest.obName].select_set(True)
context.view_layer.objects.active = bpy.context.scene.objects[obTest.obName] context.view_layer.objects.active = bpy.context.scene.objects[obTest.obName]
bpy.ops.object.mode_set(mode = 'OBJECT') bpy.ops.object.mode_set(mode = 'OBJECT')
@ -563,7 +594,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
nFaces = len(bm.faces) nFaces = len(bm.faces)
#need to ensure lookup table for some reason? #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() bm.faces.ensure_lookup_table()
#loop through the faces #loop through the faces
@ -601,7 +632,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
currentObject = bpy.context.scene.objects[edgeList['object']] currentObject = bpy.context.scene.objects[edgeList['object']]
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(currentObject.data) bm.from_mesh(currentObject.data)
if hasattr(bm.edges, "ensure_lookup_table"): if hasattr(bm.edges, "ensure_lookup_table"):
bm.edges.ensure_lookup_table() bm.edges.ensure_lookup_table()
#assume that all the triangulated edges come after the original edges #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() bm.free()
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
#End setting the quads back again------------------------------------------------------------ #End setting the quads back again-------------------------------------------------------------
print("Finished Xatlas----------------------------------------") #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)

View file

@ -112,6 +112,7 @@ class ArmNodeAddInputButton(bpy.types.Operator):
"""Add a new input socket to the node set by node_index.""" """Add a new input socket to the node set by node_index."""
bl_idname = 'arm.node_add_input' bl_idname = 'arm.node_add_input'
bl_label = 'Add Input' bl_label = 'Add Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
@ -135,6 +136,7 @@ class ArmNodeAddInputValueButton(bpy.types.Operator):
"""Add new input""" """Add new input"""
bl_idname = 'arm.node_add_input_value' bl_idname = 'arm.node_add_input_value'
bl_label = 'Add Input' bl_label = 'Add Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
@ -148,6 +150,7 @@ class ArmNodeRemoveInputButton(bpy.types.Operator):
"""Remove last input""" """Remove last input"""
bl_idname = 'arm.node_remove_input' bl_idname = 'arm.node_remove_input'
bl_label = 'Remove Input' bl_label = 'Remove Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
def execute(self, context): def execute(self, context):
@ -163,6 +166,7 @@ class ArmNodeRemoveInputValueButton(bpy.types.Operator):
"""Remove last input""" """Remove last input"""
bl_idname = 'arm.node_remove_input_value' bl_idname = 'arm.node_remove_input_value'
bl_label = 'Remove Input' bl_label = 'Remove Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
def execute(self, context): def execute(self, context):
@ -178,6 +182,7 @@ class ArmNodeAddOutputButton(bpy.types.Operator):
"""Add a new output socket to the node set by node_index""" """Add a new output socket to the node set by node_index"""
bl_idname = 'arm.node_add_output' bl_idname = 'arm.node_add_output'
bl_label = 'Add Output' bl_label = 'Add Output'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
@ -201,6 +206,7 @@ class ArmNodeRemoveOutputButton(bpy.types.Operator):
"""Remove last output""" """Remove last output"""
bl_idname = 'arm.node_remove_output' bl_idname = 'arm.node_remove_output'
bl_label = 'Remove Output' bl_label = 'Remove Output'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
def execute(self, context): def execute(self, context):
@ -216,6 +222,7 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator):
"""Add new input and output""" """Add new input and output"""
bl_idname = 'arm.node_add_input_output' bl_idname = 'arm.node_add_input_output'
bl_label = 'Add Input Output' bl_label = 'Add Input Output'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
in_socket_type: StringProperty(name='In Socket Type', default='NodeSocketShader') in_socket_type: StringProperty(name='In Socket Type', default='NodeSocketShader')
@ -246,6 +253,7 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
"""Remove last input and output""" """Remove last input and output"""
bl_idname = 'arm.node_remove_input_output' bl_idname = 'arm.node_remove_input_output'
bl_label = 'Remove Input Output' bl_label = 'Remove Input Output'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
def execute(self, context): def execute(self, context):
@ -265,7 +273,7 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
class ArmNodeSearch(bpy.types.Operator): class ArmNodeSearch(bpy.types.Operator):
bl_idname = "arm.node_search" bl_idname = "arm.node_search"
bl_label = "Search..." bl_label = "Search..."
bl_options = {"REGISTER"} bl_options = {"REGISTER", "INTERNAL"}
bl_property = "item" bl_property = "item"
def get_search_items(self, context): def get_search_items(self, context):

View file

@ -5,6 +5,7 @@ class NodeAddOutputButton(bpy.types.Operator):
"""Add 4 States""" """Add 4 States"""
bl_idname = 'arm.add_output_4_parameters' bl_idname = 'arm.add_output_4_parameters'
bl_label = 'Add 4 States' bl_label = 'Add 4 States'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader') socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
name_format: StringProperty(name='Name Format', default='Output {0}') name_format: StringProperty(name='Name Format', default='Output {0}')
@ -31,6 +32,7 @@ class NodeRemoveOutputButton(bpy.types.Operator):
"""Remove 4 last states""" """Remove 4 last states"""
bl_idname = 'arm.remove_output_4_parameters' bl_idname = 'arm.remove_output_4_parameters'
bl_label = 'Remove 4 States' bl_label = 'Remove 4 States'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='') node_index: StringProperty(name='Node Index', default='')
def execute(self, context): def execute(self, context):

View file

@ -433,7 +433,7 @@ def compilation_server_done():
def build_done(): def build_done():
print('Finished in {:0.3f}s'.format(time.time() - profile_time)) print('Finished in {:0.3f}s'.format(time.time() - profile_time))
if log.num_warnings > 0: 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: if state.proc_build is None:
return return
result = state.proc_build.poll() result = state.proc_build.poll()

View file

@ -17,17 +17,59 @@ shader_datas = []
def build(): def build():
"""Builds world shaders for all exported worlds."""
global shader_datas 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 = [] worlds = []
shader_datas = [] shader_datas = []
for scene in bpy.data.scenes: with write_probes.setup_envmap_render():
# Only export worlds from enabled scenes
if scene.arm_export and scene.world is not None and scene.world not in worlds: for scene in bpy.data.scenes:
worlds.append(scene.world) world = scene.world
create_world_shaders(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): def create_world_shaders(world: bpy.types.World):
@ -131,14 +173,7 @@ def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: Sha
col = world.color col = world.color
world.arm_envtex_color = [col[0], col[1], col[2], 1.0] world.arm_envtex_color = [col[0], col[1], col[2], 1.0]
world.arm_envtex_strength = 1.0 world.arm_envtex_strength = 1.0
world.world_defs += '_EnvCol'
# 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)
# Clouds enabled # Clouds enabled
if rpdat.arm_clouds and world.arm_use_clouds: if rpdat.arm_clouds and world.arm_use_clouds:
@ -279,7 +314,12 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n' func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n'
if '_EnvSky' in world.world_defs: 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: else:
func_cloud_radiance += '\tvec3 sun_dir = vec3(0, 0, -1);\n' func_cloud_radiance += '\tvec3 sun_dir = vec3(0, 0, -1);\n'
func_cloud_radiance += '''\tconst int steps = 8; func_cloud_radiance += '''\tconst int steps = 8;
@ -293,7 +333,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
}''' }'''
frag.add_function(func_cloud_radiance) 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); \tconst float step_size = 0.5 / float(cloudsSteps);
\tfloat T = 1.0; \tfloat T = 1.0;
\tfloat C = 0.0; \tfloat C = 0.0;
@ -312,6 +352,17 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
\t\t} \t\t}
\t\tuv += (dir.xy / dir.z) * step_size * cloudsUpper; \t\tuv += (dir.xy / dir.z) * step_size * cloudsUpper;
\t} \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)

View file

@ -642,8 +642,47 @@ def to_vec1(v):
return str(v) return str(v)
def to_vec2(v):
return f'vec2({v[0]}, {v[1]})'
def to_vec3(v): 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: def rgb_to_bw(res_var: vec3str) -> floatstr:

View file

@ -1,6 +1,8 @@
import bpy
from typing import Union from typing import Union
import bpy
import mathutils
import arm.log as log import arm.log as log
import arm.material.cycles as c import arm.material.cycles as c
import arm.material.cycles_functions as c_functions import arm.material.cycles_functions as c_functions
@ -10,42 +12,81 @@ import arm.utils
def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]: def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
# Color out_type = 'float' if out_socket.type == 'VALUE' else 'vec3'
if out_socket == node.outputs[0]:
# Vertex colors only for now
state.con.add_elem('col', 'short4norm')
return 'vcolor'
# Vector if node.attribute_name == 'time':
elif out_socket == node.outputs[1]: state.curshader.add_uniform('float time', link='_time')
# UV maps only for now
state.con.add_elem('tex', 'short2norm') 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 = c.mat_get_material()
mat_users = c.mat_get_material_users() mat_users = c.mat_get_material_users()
if mat_users is not None and mat in mat_users: if mat_users is not None and mat in mat_users:
mat_user = mat_users[mat][0] 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'): if hasattr(mat_user.data, 'uv_layers'):
lays = 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 # 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') 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 # Check object properties
else: # see https://developer.blender.org/rB6fdcca8de6 for reference
if node.attribute_name == 'time': mat = c.mat_get_material()
state.curshader.add_uniform('float time', link='_time') mat_users = c.mat_get_material_users()
return 'time' 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 val = None
else: # Custom properties first
return '0.0' 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: def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:

View file

@ -41,7 +41,7 @@ def parse_addshader(node: bpy.types.ShaderNodeAddShader, out_socket: NodeSocket,
def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfprincipled(node: bpy.types.ShaderNodeBsdfPrincipled, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:
c.write_normal(node.inputs[19]) c.write_normal(node.inputs[20])
state.out_basecol = c.parse_vector_input(node.inputs[0]) state.out_basecol = c.parse_vector_input(node.inputs[0])
# subsurface = c.parse_vector_input(node.inputs[1]) # subsurface = c.parse_vector_input(node.inputs[1])
# subsurface_radius = c.parse_vector_input(node.inputs[2]) # subsurface_radius = c.parse_vector_input(node.inputs[2])
@ -62,11 +62,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: 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.out_emission = '({0}.x)'.format(c.parse_vector_input(node.inputs[17]))
state.emission_found = True state.emission_found = True
# clearcoar_normal = c.parse_vector_input(node.inputs[20]) # clearcoar_normal = c.parse_vector_input(node.inputs[21])
# tangent = c.parse_vector_input(node.inputs[21]) # tangent = c.parse_vector_input(node.inputs[22])
if state.parse_opacity: if state.parse_opacity:
if len(node.inputs) > 20: if len(node.inputs) > 21:
state.out_opacity = c.parse_value_input(node.inputs[18]) state.out_opacity = c.parse_value_input(node.inputs[19])
def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None: def parse_bsdfdiffuse(node: bpy.types.ShaderNodeBsdfDiffuse, out_socket: NodeSocket, state: ParserState) -> None:
if state.parse_surface: if state.parse_surface:

View file

@ -1,3 +1,4 @@
import math
import os import os
from typing import Union from typing import Union
@ -293,13 +294,29 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo
# Pass through # Pass through
return c.to_vec3([0.0, 0.0, 0.0]) 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 world = state.world
curshader = state.curshader curshader = state.curshader
# Match to cycles # Match to cycles
world.arm_envtex_strength *= 0.1 world.arm_envtex_strength *= 0.1
world.world_defs += '_EnvSky'
assets.add_khafile_def('arm_hosek') assets.add_khafile_def('arm_hosek')
curshader.add_uniform('vec3 A', link="_hosekA") curshader.add_uniform('vec3 A', link="_hosekA")
curshader.add_uniform('vec3 B', link="_hosekB") curshader.add_uniform('vec3 B', link="_hosekB")
@ -312,10 +329,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 I', link="_hosekI")
curshader.add_uniform('vec3 Z', link="_hosekZ") curshader.add_uniform('vec3 Z', link="_hosekZ")
curshader.add_uniform('vec3 hosekSunDirection', link="_hosekSunDirection") 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)); \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)); \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_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]]
world.arm_envtex_turbidity = node.turbidity world.arm_envtex_turbidity = node.turbidity
@ -353,6 +370,40 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo
return 'Z * hosekWilkie(cos_theta, gamma_val, cos_gamma) * envmapStrength;' 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: def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
if state.context == ParserContext.OBJECT: if state.context == ParserContext.OBJECT:
log.warn('Environment Texture node is not supported for object node trees, using default value') log.warn('Environment Texture node is not supported for object node trees, using default value')

View file

@ -73,7 +73,8 @@ def is_transluc_type(node):
node.type == 'BSDF_TRANSPARENT' or \ node.type == 'BSDF_TRANSPARENT' or \
node.type == 'BSDF_TRANSLUCENT' 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 == '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 True
return False return False

View file

@ -119,16 +119,29 @@ class ShaderContext:
c['link'] = link c['link'] = link
self.constants.append(c) 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):
for c in self.tunits: for c in self.tunits:
if c['name'] == name: if c['name'] == name:
return return
c = { 'name': name } c = {'name': name}
if link != None: if link is not None:
c['link'] = link c['link'] = link
if is_image != None: if is_image is not None:
c['is_image'] = is_image 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
self.tunits.append(c) self.tunits.append(c)
def make_vert(self, custom_name: str = None): def make_vert(self, custom_name: str = None):
@ -222,10 +235,10 @@ class Shader:
if s not in self.outs: if s not in self.outs:
self.outs.append(s) 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,
# prevent duplicates tex_addr_u=None, tex_addr_v=None,
if s in self.uniforms or s in self.uniforms_top: tex_filter_min=None, tex_filter_mag=None,
return tex_mipmap_filter=None):
ar = s.split(' ') ar = s.split(' ')
# layout(RGBA8) image3D voxels # layout(RGBA8) image3D voxels
utype = ar[-2] utype = ar[-2]
@ -236,9 +249,15 @@ class Shader:
# Add individual units - mySamplers[0], mySamplers[1] # Add individual units - mySamplers[0], mySamplers[1]
for i in range(int(uname[-2])): for i in range(int(uname[-2])):
uname_array = uname[:-2] + str(i) + ']' 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: 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)
else: else:
# Prefer vec4[] for d3d to avoid padding # Prefer vec4[] for d3d to avoid padding
if ar[0] == 'float' and '[' in ar[1]: if ar[0] == 'float' and '[' in ar[1]:
@ -251,9 +270,8 @@ class Shader:
if top: if top:
if not included and s not in self.uniforms_top: if not included and s not in self.uniforms_top:
self.uniforms_top.append(s) self.uniforms_top.append(s)
else: elif not included and s not in self.uniforms:
if not included and s not in self.uniforms: self.uniforms.append(s)
self.uniforms.append(s)
def add_const(self, type_str: str, name: str, value_str: str, array_size: int = 0): def add_const(self, type_str: str, name: str, value_str: str, array_size: int = 0):
""" """

View file

@ -8,6 +8,7 @@ import arm.logicnode.arm_nodes as arm_nodes
import arm.logicnode.replacement import arm.logicnode.replacement
import arm.logicnode import arm.logicnode
import arm.props_traits import arm.props_traits
import arm.ui_icons as ui_icons
import arm.utils import arm.utils
registered_nodes = [] registered_nodes = []
@ -58,13 +59,14 @@ class ARM_OT_AddNodeOverride(bpy.types.Operator):
bl_idname = "arm.add_node_override" bl_idname = "arm.add_node_override"
bl_label = "Add Node" bl_label = "Add Node"
bl_property = "type" bl_property = "type"
bl_options = {'INTERNAL'}
type: StringProperty(name="NodeItem type") type: StringProperty(name="NodeItem type")
use_transform: BoolProperty(name="Use Transform") use_transform: BoolProperty(name="Use Transform")
def invoke(self, context, event): def invoke(self, context, event):
bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.type, use_transform=self.use_transform) bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.type, use_transform=self.use_transform)
return {"FINISHED"} return {'FINISHED'}
@classmethod @classmethod
def description(cls, context, properties): def description(cls, context, properties):
@ -173,7 +175,7 @@ class ARM_PT_LogicNodePanel(bpy.types.Panel):
layout.operator('arm.open_node_documentation', icon='HELP') layout.operator('arm.open_node_documentation', icon='HELP')
column = layout.column(align=True) column = layout.column(align=True)
column.operator('arm.open_node_python_source', icon='FILE_SCRIPT') 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): class ArmOpenNodeHaxeSource(bpy.types.Operator):
@ -261,7 +263,7 @@ class ARM_PT_Variables(bpy.types.Panel):
setN.ntype = ID setN.ntype = ID
class ARMAddVarNode(bpy.types.Operator): 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_idname = 'arm.add_var_node'
bl_label = 'Add Get' bl_label = 'Add Get'
bl_options = {'GRAB_CURSOR', 'BLOCKING'} bl_options = {'GRAB_CURSOR', 'BLOCKING'}
@ -296,7 +298,7 @@ class ARMAddVarNode(bpy.types.Operator):
return({'FINISHED'}) return({'FINISHED'})
class ARMAddSetVarNode(bpy.types.Operator): 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_idname = 'arm.add_setvar_node'
bl_label = 'Add Set' bl_label = 'Add Set'
bl_options = {'GRAB_CURSOR', 'BLOCKING'} bl_options = {'GRAB_CURSOR', 'BLOCKING'}

View file

@ -178,7 +178,7 @@ def init_properties():
('ErrorsOnly', 'Errors Only', 'Show only errors')], ('ErrorsOnly', 'Errors Only', 'Show only errors')],
name="Compile Log Parameter", update=assets.invalidate_compiler_cache, name="Compile Log Parameter", update=assets.invalidate_compiler_cache,
default="Summary") 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_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) 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 +212,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_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_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_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_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_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_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_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_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) bpy.types.World.arm_stream_scene = BoolProperty(name="Stream Scene", description="Stream scene content", default=False, update=assets.invalidate_compiler_cache)
@ -332,6 +332,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_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_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_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_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_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True)
bpy.types.Material.arm_overlay = BoolProperty(name="Overlay", default=False) bpy.types.Material.arm_overlay = BoolProperty(name="Overlay", default=False)
@ -398,7 +399,7 @@ def init_properties():
('destination_color', 'Destination Color', 'Destination Color'), ('destination_color', 'Destination Color', 'Destination Color'),
('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'), ('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'),
('inverse_destination_color', 'Inverse Destination Color', 'Inverse Destination 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( bpy.types.Material.arm_blending_destination_alpha = EnumProperty(
items=[('blend_one', 'One', 'One'), items=[('blend_one', 'One', 'One'),
('blend_zero', 'Zero', 'Zero'), ('blend_zero', 'Zero', 'Zero'),
@ -410,14 +411,14 @@ def init_properties():
('destination_color', 'Destination Color', 'Destination Color'), ('destination_color', 'Destination Color', 'Destination Color'),
('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'), ('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'),
('inverse_destination_color', 'Inverse Destination Color', 'Inverse Destination 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( bpy.types.Material.arm_blending_operation_alpha = EnumProperty(
items=[('add', 'Add', 'Add'), items=[('add', 'Add', 'Add'),
('subtract', 'Subtract', 'Subtract'), ('subtract', 'Subtract', 'Subtract'),
('reverse_subtract', 'Reverse Subtract', 'Reverse Subtract'), ('reverse_subtract', 'Reverse Subtract', 'Reverse Subtract'),
('min', 'Min', 'Min'), ('min', 'Min', 'Min'),
('max', 'Max', 'Max')], ('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 # For scene
bpy.types.Scene.arm_export = BoolProperty(name="Export", description="Export scene data", default=True) 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") bpy.types.Scene.arm_terrain_textures = StringProperty(name="Textures", description="Set root folder for terrain assets", default="//Bundled/", subtype="DIR_PATH")
@ -442,6 +443,10 @@ def init_properties():
bpy.types.World.compo_defs = StringProperty(name="Compositor Shader Defs", default='') 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_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_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_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) bpy.types.World.arm_clouds_wind = FloatVectorProperty(name="Wind", default=[1.0, 0.0], size=2, update=assets.invalidate_shader_cache)

View file

@ -1,6 +1,5 @@
import bpy import bpy
from bpy.props import *
from bpy.types import Panel
class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel): class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel):
bl_label = "Collections Filter Mask" bl_label = "Collections Filter Mask"
@ -18,7 +17,7 @@ class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.use_property_split = True layout.use_property_split = False
layout.use_property_decorate = False layout.use_property_decorate = False
obj = context.object obj = context.object
layout.prop(obj, 'arm_rb_collision_filter_mask', text="", expand=True) layout.prop(obj, 'arm_rb_collision_filter_mask', text="", expand=True)
@ -30,8 +29,10 @@ class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel):
row.alignment = 'RIGHT' row.alignment = 'RIGHT'
row.label(text=f'Integer Mask Value: {str(int(col_mask, 2))}') row.label(text=f'Integer Mask Value: {str(int(col_mask, 2))}')
def register(): def register():
bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel) bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel)
def unregister(): def unregister():
bpy.utils.unregister_class(ARM_PT_RbCollisionFilterMaskPanel) bpy.utils.unregister_class(ARM_PT_RbCollisionFilterMaskPanel)

View file

@ -358,7 +358,7 @@ class ArmExporterSpecialsMenu(bpy.types.Menu):
layout.operator("arm.exporter_gpuprofile") layout.operator("arm.exporter_gpuprofile")
class ArmoryExporterOpenFolderButton(bpy.types.Operator): class ArmoryExporterOpenFolderButton(bpy.types.Operator):
'''Open published folder''' """Open published folder"""
bl_idname = 'arm.exporter_open_folder' bl_idname = 'arm.exporter_open_folder'
bl_label = 'Open Folder' bl_label = 'Open Folder'

View file

@ -34,29 +34,29 @@ class ArmLodListItem(bpy.types.PropertyGroup):
class ARM_UL_LodList(bpy.types.UIList): class ARM_UL_LodList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): 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... layout.use_property_split = False
custom_icon = 'OBJECT_DATAMODE'
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}: 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 name = item.name
if name == '': if name == '':
name = 'None' name = 'None'
row = layout.row() row.label(text=name, icon='OBJECT_DATAMODE')
row.label(text=name, icon=custom_icon)
col = row.column() col = row.column()
col.alignment = 'RIGHT' col.alignment = 'RIGHT'
col.label(text="{:.2f}".format(item.screen_size_prop)) col.label(text="{:.2f}".format(item.screen_size_prop))
elif self.layout_type in {'GRID'}: elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER' layout.alignment = 'CENTER'
layout.label(text="", icon = custom_icon) layout.label(text="", icon='OBJECT_DATAMODE')
class ArmLodListNewItem(bpy.types.Operator): class ArmLodListNewItem(bpy.types.Operator):
# Add a new item to the list # Add a new item to the list
bl_idname = "arm_lodlist.new_item" bl_idname = "arm_lodlist.new_item"
bl_label = "Add a new item" bl_label = "Add a new item"
bl_options = {'UNDO'}
def execute(self, context): def execute(self, context):
mdata = bpy.context.object.data mdata = bpy.context.object.data
@ -69,10 +69,13 @@ class ArmLodListDeleteItem(bpy.types.Operator):
# Delete the selected item from the list # Delete the selected item from the list
bl_idname = "arm_lodlist.delete_item" bl_idname = "arm_lodlist.delete_item"
bl_label = "Deletes an item" bl_label = "Deletes an item"
bl_options = {'INTERNAL', 'UNDO'}
@classmethod @classmethod
def poll(self, context): def poll(cls, context):
""" Enable if there's something in the list """ """ Enable if there's something in the list """
if bpy.context.object is None:
return False
mdata = bpy.context.object.data mdata = bpy.context.object.data
return len(mdata.arm_lodlist) > 0 return len(mdata.arm_lodlist) > 0
@ -98,6 +101,7 @@ class ArmLodListMoveItem(bpy.types.Operator):
# Move an item in the list # Move an item in the list
bl_idname = "arm_lodlist.move_item" bl_idname = "arm_lodlist.move_item"
bl_label = "Move an item in the list" bl_label = "Move an item in the list"
bl_options = {'INTERNAL', 'UNDO'}
direction: EnumProperty( direction: EnumProperty(
items=( items=(
('UP', 'Up', ""), ('UP', 'Up', ""),

View file

@ -1,30 +1,26 @@
import shutil
import bpy import bpy
import os
import json
from bpy.types import Menu, Panel, UIList
from bpy.props import * from bpy.props import *
class ArmTilesheetActionListItem(bpy.types.PropertyGroup): class ArmTilesheetActionListItem(bpy.types.PropertyGroup):
name: StringProperty( name: StringProperty(
name="Name", name="Name",
description="A name for this item", description="A name for this item",
default="Untitled") default="Untitled")
start_prop: IntProperty( start_prop: IntProperty(
name="Start", name="Start",
description="A name for this item", description="A name for this item",
default=0) default=0)
end_prop: IntProperty( end_prop: IntProperty(
name="End", name="End",
description="A name for this item", description="A name for this item",
default=0) default=0)
loop_prop: BoolProperty( loop_prop: BoolProperty(
name="Loop", name="Loop",
description="A name for this item", description="A name for this item",
default=True) default=True)
class ARM_UL_TilesheetActionList(bpy.types.UIList): class ARM_UL_TilesheetActionList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): 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'} return{'FINISHED'}
class ArmTilesheetActionListDeleteItem(bpy.types.Operator): 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_idname = "arm_tilesheetactionlist.delete_item"
bl_label = "Deletes an item" bl_label = "Deletes an item"
@classmethod @classmethod
def poll(self, context): 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'] wrd = bpy.data.worlds['Arm']
if len(wrd.arm_tilesheetlist) == 0:
return False
trait = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index] trait = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index]
return len(trait.arm_tilesheetactionlist) > 0 return len(trait.arm_tilesheetactionlist) > 0
@ -78,18 +76,23 @@ class ArmTilesheetActionListDeleteItem(bpy.types.Operator):
return{'FINISHED'} return{'FINISHED'}
class ArmTilesheetActionListMoveItem(bpy.types.Operator): class ArmTilesheetActionListMoveItem(bpy.types.Operator):
# Move an item in the list """Move an item in the list"""
bl_idname = "arm_tilesheetactionlist.move_item" bl_idname = "arm_tilesheetactionlist.move_item"
bl_label = "Move an item in the list" bl_label = "Move an item in the list"
bl_options = {'INTERNAL'}
direction: EnumProperty( direction: EnumProperty(
items=( items=(
('UP', 'Up', ""), ('UP', 'Up', ""),
('DOWN', 'Down', ""),)) ('DOWN', 'Down', "")
))
@classmethod @classmethod
def poll(self, context): 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'] wrd = bpy.data.worlds['Arm']
if len(wrd.arm_tilesheetlist) == 0:
return False
trait = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index] trait = wrd.arm_tilesheetlist[wrd.arm_tilesheetlist_index]
return len(trait.arm_tilesheetactionlist) > 0 return len(trait.arm_tilesheetactionlist) > 0
@ -129,27 +132,27 @@ class ArmTilesheetActionListMoveItem(bpy.types.Operator):
class ArmTilesheetListItem(bpy.types.PropertyGroup): class ArmTilesheetListItem(bpy.types.PropertyGroup):
name: StringProperty( name: StringProperty(
name="Name", name="Name",
description="A name for this item", description="A name for this item",
default="Untitled") default="Untitled")
tilesx_prop: IntProperty( tilesx_prop: IntProperty(
name="Tiles X", name="Tiles X",
description="A name for this item", description="A name for this item",
default=0) default=0)
tilesy_prop: IntProperty( tilesy_prop: IntProperty(
name="Tiles Y", name="Tiles Y",
description="A name for this item", description="A name for this item",
default=0) default=0)
framerate_prop: FloatProperty( framerate_prop: FloatProperty(
name="Frame Rate", name="Frame Rate",
description="A name for this item", description="A name for this item",
default=4.0) default=4.0)
arm_tilesheetactionlist: CollectionProperty(type=ArmTilesheetActionListItem) 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): class ARM_UL_TilesheetList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): 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'}: elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER' layout.alignment = 'CENTER'
layout.label(text="", icon = custom_icon) layout.label(text="", icon=custom_icon)
class ArmTilesheetListNewItem(bpy.types.Operator): 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_idname = "arm_tilesheetlist.new_item"
bl_label = "Add a new item" bl_label = "Add a new item"
@ -176,7 +179,7 @@ class ArmTilesheetListNewItem(bpy.types.Operator):
return{'FINISHED'} return{'FINISHED'}
class ArmTilesheetListDeleteItem(bpy.types.Operator): 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_idname = "arm_tilesheetlist.delete_item"
bl_label = "Deletes an item" bl_label = "Deletes an item"
@ -200,13 +203,16 @@ class ArmTilesheetListDeleteItem(bpy.types.Operator):
return{'FINISHED'} return{'FINISHED'}
class ArmTilesheetListMoveItem(bpy.types.Operator): class ArmTilesheetListMoveItem(bpy.types.Operator):
# Move an item in the list """Move an item in the list"""
bl_idname = "arm_tilesheetlist.move_item" bl_idname = "arm_tilesheetlist.move_item"
bl_label = "Move an item in the list" bl_label = "Move an item in the list"
bl_options = {'INTERNAL'}
direction: EnumProperty( direction: EnumProperty(
items=( items=(
('UP', 'Up', ""), ('UP', 'Up', ""),
('DOWN', 'Down', ""),)) ('DOWN', 'Down', "")
))
@classmethod @classmethod
def poll(self, context): def poll(self, context):
@ -247,7 +253,6 @@ class ArmTilesheetListMoveItem(bpy.types.Operator):
return{'FINISHED'} return{'FINISHED'}
def register(): def register():
bpy.utils.register_class(ArmTilesheetActionListItem) bpy.utils.register_class(ArmTilesheetActionListItem)
bpy.utils.register_class(ARM_UL_TilesheetActionList) bpy.utils.register_class(ARM_UL_TilesheetActionList)
bpy.utils.register_class(ArmTilesheetActionListNewItem) bpy.utils.register_class(ArmTilesheetActionListNewItem)
@ -261,7 +266,7 @@ def register():
bpy.utils.register_class(ArmTilesheetListMoveItem) bpy.utils.register_class(ArmTilesheetListMoveItem)
bpy.types.World.arm_tilesheetlist = CollectionProperty(type=ArmTilesheetListItem) 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(): def unregister():
bpy.utils.unregister_class(ArmTilesheetListItem) bpy.utils.unregister_class(ArmTilesheetListItem)

View file

@ -2,6 +2,7 @@ import json
import os import os
import shutil import shutil
import subprocess import subprocess
from typing import Union
import webbrowser import webbrowser
from bpy.types import NodeTree from bpy.types import NodeTree
@ -10,10 +11,26 @@ import bpy.utils.previews
import arm.make as make import arm.make as make
from arm.props_traits_props import * from arm.props_traits_props import *
import arm.proxy as proxy import arm.proxy as proxy
import arm.ui_icons as ui_icons
import arm.utils import arm.utils
import arm.write_data as write_data import arm.write_data as write_data
icons_dict: bpy.utils.previews.ImagePreviewCollection 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): def trigger_recompile(self, context):
@ -61,78 +78,81 @@ def update_trait_group(self, context):
pass pass
class ArmTraitListItem(bpy.types.PropertyGroup): 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="") 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) enabled_prop: BoolProperty(name="", description="A name for this item", default=True, update=trigger_recompile)
is_object: BoolProperty(name="", default=True) is_object: BoolProperty(name="", default=True)
fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False) fake_user: BoolProperty(name="Fake User", description="Export this trait even if it is deactivated", default=False)
type_prop: EnumProperty( type_prop: EnumProperty(name="Type", items=PROP_TYPES_ENUM)
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")
class_name_prop: StringProperty(name="Class", description="A name for this item", default="", update=update_trait_group) 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) 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) 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: CollectionProperty(type=ArmTraitPropListItem)
arm_traitpropslist_index: IntProperty(name="Index for my_list", default=0) arm_traitpropslist_index: IntProperty(name="Index for my_list", default=0)
arm_traitpropswarnings: CollectionProperty(type=ArmTraitPropWarning) arm_traitpropswarnings: CollectionProperty(type=ArmTraitPropWarning)
class ARM_UL_TraitList(bpy.types.UIList): class ARM_UL_TraitList(bpy.types.UIList):
"""List of traits."""
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
layout.use_property_split = False
custom_icon = "NONE" custom_icon = "NONE"
custom_icon_value = 0 custom_icon_value = 0
if item.type_prop == "Haxe Script": if item.type_prop == "Haxe Script":
custom_icon_value = icons_dict["haxe"].icon_id custom_icon_value = ui_icons.get_id("haxe")
elif item.type_prop == "WebAssembly": elif item.type_prop == "WebAssembly":
custom_icon_value = icons_dict["wasm"].icon_id custom_icon_value = ui_icons.get_id("wasm")
elif item.type_prop == "UI Canvas": elif item.type_prop == "UI Canvas":
custom_icon = "OBJECT_DATAMODE" custom_icon = "NODE_COMPOSITING"
elif item.type_prop == "Bundled Script": elif item.type_prop == "Bundled Script":
custom_icon_value = icons_dict["bundle"].icon_id custom_icon_value = ui_icons.get_id("bundle")
elif item.type_prop == "Logic Nodes": elif item.type_prop == "Logic Nodes":
custom_icon = 'NODETREE' custom_icon = 'NODETREE'
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}: 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 # Display " " for props without a name to right-align the
# fake_user button # 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'}: elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER' layout.alignment = 'CENTER'
layout.label(text="", icon=custom_icon, icon_value=custom_icon_value) 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): class ArmTraitListNewItem(bpy.types.Operator):
bl_idname = "arm_traitlist.new_item" 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" 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) is_object: BoolProperty(name="Is Object Trait", description="Whether this trait belongs to an object or a scene", default=False)
type_prop: EnumProperty( type_prop: EnumProperty(name="Type", items=PROP_TYPES_ENUM)
items = [('Haxe Script', 'Haxe', 'Haxe Script'),
('WebAssembly', 'Wasm', 'WebAssembly'), # Show more options when invoked from the operator search menu
('UI Canvas', 'UI', 'UI Canvas'), invoked_by_search: BoolProperty(name="", default=True)
('Bundled Script', 'Bundled', 'Bundled Script'),
('Logic Nodes', 'Nodes', 'Logic Nodes')
],
name = "Type")
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager wm = context.window_manager
return wm.invoke_props_dialog(self) return wm.invoke_props_dialog(self, width=400)
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
# Todo: show is_object property when called from operator search menu
# layout.prop(self, "is_object") if self.invoked_by_search:
layout.prop(self, "type_prop", expand=True) 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): def execute(self, context):
if self.is_object: if self.is_object:
@ -149,7 +169,7 @@ class ArmTraitListNewItem(bpy.types.Operator):
class ArmTraitListDeleteItem(bpy.types.Operator): class ArmTraitListDeleteItem(bpy.types.Operator):
"""Delete the selected item from the list""" """Delete the selected item from the list"""
bl_idname = "arm_traitlist.delete_item" bl_idname = "arm_traitlist.delete_item"
bl_label = "Deletes an item" bl_label = "Remove Trait"
bl_options = {'INTERNAL'} bl_options = {'INTERNAL'}
is_object: BoolProperty(name="", description="A name for this item", default=False) is_object: BoolProperty(name="", description="A name for this item", default=False)
@ -582,8 +602,6 @@ class ArmNewCanvasDialog(bpy.types.Operator):
self.canvas_name = self.canvas_name.replace(' ', '') self.canvas_name = self.canvas_name.replace(' ', '')
write_data.write_canvasjson(self.canvas_name) write_data.write_canvasjson(self.canvas_name)
arm.utils.fetch_script_names() 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 = obj.arm_traitlist[obj.arm_traitlist_index]
item.canvas_name_prop = self.canvas_name item.canvas_name_prop = self.canvas_name
return {'FINISHED'} return {'FINISHED'}
@ -635,11 +653,8 @@ class ARM_PT_TraitPanel(bpy.types.Panel):
bl_context = "object" bl_context = "object"
def draw(self, context): def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
obj = bpy.context.object 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): class ARM_PT_SceneTraitPanel(bpy.types.Panel):
bl_label = "Armory Scene Traits" bl_label = "Armory Scene Traits"
@ -648,11 +663,8 @@ class ARM_PT_SceneTraitPanel(bpy.types.Panel):
bl_context = "scene" bl_context = "scene"
def draw(self, context): def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
obj = bpy.context.scene 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): class ARM_OT_CopyTraitsFromActive(bpy.types.Operator):
bl_label = 'Copy Traits from Active Object' bl_label = 'Copy Traits from Active Object'
@ -728,21 +740,28 @@ class ARM_OT_CopyTraitsFromActive(bpy.types.Operator):
return {'INTERFACE'} 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: if len(obj.arm_traitlist) > 1:
rows = 4 num_rows = 4
row = layout.row() 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) col = row.column(align=True)
op = col.operator("arm_traitlist.new_item", icon='ADD', text="") op = col.operator("arm_traitlist.new_item", icon='ADD', text="")
op.invoked_by_search = False
op.is_object = is_object op.is_object = is_object
if 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: 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 op.is_object = is_object
if len(obj.arm_traitlist) > 1: if len(obj.arm_traitlist) > 1:
@ -754,35 +773,29 @@ def draw_traits(layout, obj, is_object):
op.direction = 'DOWN' op.direction = 'DOWN'
op.is_object = is_object op.is_object = is_object
# Draw trait specific content
if obj.arm_traitlist_index >= 0 and len(obj.arm_traitlist) > 0: if obj.arm_traitlist_index >= 0 and len(obj.arm_traitlist) > 0:
item = obj.arm_traitlist[obj.arm_traitlist_index] 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' or item.type_prop == 'Bundled Script':
if item.type_prop == 'Haxe Script': if item.type_prop == 'Haxe Script':
row = layout.row(align=True) row.operator("arm.new_script", icon="FILE_NEW").is_object = is_object
row.alignment = 'EXPAND'
column = row.column(align=True) column = row.column(align=True)
column.alignment = 'EXPAND' column.enabled = item.class_name_prop != ''
if item.class_name_prop == '': column.operator("arm.edit_script", icon="FILE_SCRIPT").is_object = is_object
column.enabled = False
op = column.operator("arm.edit_script", icon="FILE_SCRIPT") # Bundled scripts
op.is_object = is_object else:
op = row.operator("arm.new_script")
op.is_object = is_object
op = row.operator("arm.refresh_scripts", text="Refresh")
else: # Bundled
if item.class_name_prop == 'NavMesh': if item.class_name_prop == 'NavMesh':
row = layout.row(align=True) row.operator("arm.generate_navmesh", icon="UV_VERTEXSEL")
row.alignment = 'EXPAND' else:
op = layout.operator("arm.generate_navmesh") row.operator("arm.edit_bundled_script", icon="FILE_SCRIPT").is_object = is_object
row = layout.row(align=True)
row.alignment = 'EXPAND' row.operator("arm.refresh_scripts", text="Refresh", icon="FILE_REFRESH")
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")
# Default props # Default props
item.name = item.class_name_prop item.name = item.class_name_prop
@ -797,86 +810,56 @@ def draw_traits(layout, obj, is_object):
elif item.type_prop == 'WebAssembly': elif item.type_prop == 'WebAssembly':
item.name = item.webassembly_prop 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 = layout.row()
row.prop_search(item, "webassembly_prop", bpy.data.worlds['Arm'], "arm_wasm_list", text="Module") 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': elif item.type_prop == 'UI Canvas':
item.name = item.canvas_name_prop item.name = item.canvas_name_prop
row = layout.row(align=True) row.operator("arm.new_canvas", icon="FILE_NEW").is_object = is_object
row.alignment = 'EXPAND'
column = row.column(align=True) column = row.column(align=True)
column.alignment = 'EXPAND' column.enabled = item.canvas_name_prop != ''
if item.canvas_name_prop == '': column.operator("arm.edit_canvas", icon="NODE_COMPOSITING").is_object = is_object
column.enabled = False row.operator("arm.refresh_canvas_list", text="Refresh", icon="FILE_REFRESH")
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")
row = layout.row() row = layout.row()
row.prop_search(item, "canvas_name_prop", bpy.data.worlds['Arm'], "arm_canvas_list", text="Canvas") row.prop_search(item, "canvas_name_prop", bpy.data.worlds['Arm'], "arm_canvas_list", text="Canvas")
elif item.type_prop == 'Logic Nodes': elif item.type_prop == 'Logic Nodes':
# Row for buttons # Check if there is at least one active Logic Node Editor
row = layout.row(align=True) is_editor_active = False
row.alignment = 'EXPAND' if bpy.context.screen is not None:
# New areas = bpy.context.screen.areas
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
for area in areas: for area in areas:
for space in area.spaces: for space in area.spaces:
if space.type == 'NODE_EDITOR': if space.type == 'NODE_EDITOR':
if space.tree_type == 'ArmLogicTreeType' and space.node_tree is not None: if space.tree_type == 'ArmLogicTreeType' and space.node_tree is not None:
is_check_logic_node_editor = True is_editor_active = True
break break
if is_check_logic_node_editor: if is_editor_active:
break 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 = layout.row()
row.prop_search(item, "node_tree_prop", bpy.data, "node_groups", text="Tree") 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': if item.type_prop == 'Haxe Script' or item.type_prop == 'Bundled Script':
# Props
if item.arm_traitpropslist: if item.arm_traitpropslist:
layout.label(text="Trait Properties:") layout.label(text="Trait Properties:")
if item.arm_traitpropswarnings: if item.arm_traitpropswarnings:
@ -887,13 +870,12 @@ def draw_traits(layout, obj, is_object):
for warning in item.arm_traitpropswarnings: for warning in item.arm_traitpropswarnings:
col.label(text=f'"{warning.propName}": {warning.warning}') col.label(text=f'"{warning.propName}": {warning.warning}')
propsrow = layout.row()
propsrows = max(len(item.arm_traitpropslist), 6) propsrows = max(len(item.arm_traitpropslist), 6)
row = layout.row() row = layout.row()
row.template_list("ARM_UL_PropList", "The_List", item, "arm_traitpropslist", item, "arm_traitpropslist_index", rows=propsrows) row.template_list("ARM_UL_PropList", "The_List", item, "arm_traitpropslist", item, "arm_traitpropslist_index", rows=propsrows)
def register(): def register():
global icons_dict
bpy.utils.register_class(ArmTraitListItem) bpy.utils.register_class(ArmTraitListItem)
bpy.utils.register_class(ARM_UL_TraitList) bpy.utils.register_class(ARM_UL_TraitList)
bpy.utils.register_class(ArmTraitListNewItem) bpy.utils.register_class(ArmTraitListNewItem)
@ -921,14 +903,8 @@ def register():
bpy.types.Scene.arm_traitlist = CollectionProperty(type=ArmTraitListItem) bpy.types.Scene.arm_traitlist = CollectionProperty(type=ArmTraitListItem)
bpy.types.Scene.arm_traitlist_index = IntProperty(name="Index for arm_traitlist", default=0) 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(): def unregister():
global icons_dict
bpy.utils.unregister_class(ARM_OT_CopyTraitsFromActive) bpy.utils.unregister_class(ARM_OT_CopyTraitsFromActive)
bpy.utils.unregister_class(ArmTraitListItem) bpy.utils.unregister_class(ArmTraitListItem)
bpy.utils.unregister_class(ARM_UL_TraitList) bpy.utils.unregister_class(ARM_UL_TraitList)
@ -950,4 +926,3 @@ def unregister():
bpy.utils.unregister_class(ArmRefreshCanvasListButton) bpy.utils.unregister_class(ArmRefreshCanvasListButton)
bpy.utils.unregister_class(ARM_PT_TraitPanel) bpy.utils.unregister_class(ARM_PT_TraitPanel)
bpy.utils.unregister_class(ARM_PT_SceneTraitPanel) bpy.utils.unregister_class(ARM_PT_SceneTraitPanel)
bpy.utils.previews.remove(icons_dict)

File diff suppressed because it is too large Load diff

33
blender/arm/ui_icons.py Normal file
View file

@ -0,0 +1,33 @@
"""
Blender user interface icon handling.
"""
import os.path
from typing import Optional
import bpy.utils.previews
_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() -> None:
"""(Re)loads all icons"""
global _icons_dict
if _icons_dict is not None:
bpy.utils.previews.remove(_icons_dict)
_icons_dict = bpy.utils.previews.new()
_icons_dict.load("bundle", os.path.join(_icons_dir, "bundle.png"), 'IMAGE')
_icons_dict.load("haxe", os.path.join(_icons_dir, "haxe.png"), 'IMAGE')
_icons_dict.load("wasm", os.path.join(_icons_dir, "wasm.png"), 'IMAGE')
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

View file

@ -946,7 +946,7 @@ def get_link_web_server():
return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server return '' if not hasattr(addon_prefs, 'link_web_server') else addon_prefs.link_web_server
def compare_version_blender_arm(): 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 type_name_to_type(name: str) -> bpy.types.bpy_struct: def type_name_to_type(name: str) -> bpy.types.bpy_struct:
"""Return the Blender type given by its name, if registered.""" """Return the Blender type given by its name, if registered."""

View file

@ -1,23 +1,87 @@
import bpy from contextlib import contextmanager
import math
import multiprocessing import multiprocessing
import os import os
import sys
import subprocess
import json
import re import re
import arm.utils import subprocess
import bpy
import arm.assets as assets import arm.assets as assets
import arm.log as log
import arm.utils
def add_irr_assets(output_file_irr): def add_irr_assets(output_file_irr):
assets.add(output_file_irr + '.arm') assets.add(output_file_irr + '.arm')
def add_rad_assets(output_file_rad, rad_format, num_mips): def add_rad_assets(output_file_rad, rad_format, num_mips):
assets.add(output_file_rad + '.' + rad_format) assets.add(output_file_rad + '.' + rad_format)
for i in range(0, num_mips): for i in range(0, num_mips):
assets.add(output_file_rad + '_' + str(i) + '.' + rad_format) 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' envpath = arm.utils.get_fp_build() + '/compiled/Assets/envmaps'
if not os.path.exists(envpath): if not os.path.exists(envpath):
@ -63,7 +127,7 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True
scaled_file = output_file_rad + '.' + rad_format scaled_file = output_file_rad + '.' + rad_format
if arm.utils.get_os() == 'win': if arm.utils.get_os() == 'win':
output = subprocess.check_output([ \ subprocess.check_output([
kraffiti_path, kraffiti_path,
'from=' + input_file, 'from=' + input_file,
'to=' + scaled_file, 'to=' + scaled_file,
@ -71,35 +135,35 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True
'width=' + str(target_w), 'width=' + str(target_w),
'height=' + str(target_h)]) 'height=' + str(target_h)])
else: else:
output = subprocess.check_output([ \ subprocess.check_output([
kraffiti_path + \ kraffiti_path
' from="' + input_file + '"' + \ + ' from="' + input_file + '"'
' to="' + scaled_file + '"' + \ + ' to="' + scaled_file + '"'
' format=' + rad_format + \ + ' format=' + rad_format
' width=' + str(target_w) + \ + ' width=' + str(target_w)
' height=' + str(target_h)], shell=True) + ' height=' + str(target_h)], shell=True)
# Irradiance spherical harmonics # Irradiance spherical harmonics
if arm.utils.get_os() == 'win': if arm.utils.get_os() == 'win':
subprocess.call([ \ subprocess.call([
cmft_path, cmft_path,
'--input', scaled_file, '--input', scaled_file,
'--filter', 'shcoeffs', '--filter', 'shcoeffs',
'--outputNum', '1', '--outputNum', '1',
'--output0', output_file_irr]) '--output0', output_file_irr])
else: else:
subprocess.call([ \ subprocess.call([
cmft_path + \ cmft_path
' --input ' + '"' + scaled_file + '"' + \ + ' --input ' + '"' + scaled_file + '"'
' --filter shcoeffs' + \ + ' --filter shcoeffs'
' --outputNum 1' + \ + ' --outputNum 1'
' --output0 ' + '"' + output_file_irr + '"'], shell=True) + ' --output0 ' + '"' + output_file_irr + '"'], shell=True)
sh_to_json(output_file_irr) sh_to_json(output_file_irr)
add_irr_assets(output_file_irr) add_irr_assets(output_file_irr)
# Mip-mapped radiance # Mip-mapped radiance
if arm_radiance == False: if not arm_radiance:
return cached_num_mips return cached_num_mips
# 4096 = 256 face # 4096 = 256 face
@ -200,37 +264,37 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True
if disable_hdr is True: if disable_hdr is True:
for f in generated_files: for f in generated_files:
if arm.utils.get_os() == 'win': if arm.utils.get_os() == 'win':
subprocess.call([ \ subprocess.call([
kraffiti_path, kraffiti_path,
'from=' + f + '.hdr', 'from=' + f + '.hdr',
'to=' + f + '.jpg', 'to=' + f + '.jpg',
'format=jpg']) 'format=jpg'])
else: else:
subprocess.call([ \ subprocess.call([
kraffiti_path + \ kraffiti_path
' from="' + f + '.hdr"' + \ + ' from="' + f + '.hdr"'
' to="' + f + '.jpg"' + \ + ' to="' + f + '.jpg"'
' format=jpg'], shell=True) + ' format=jpg'], shell=True)
os.remove(f + '.hdr') os.remove(f + '.hdr')
# Scale from (4x2 to 1x1> # Scale from (4x2 to 1x1>
for i in range (0, 2): for i in range(0, 2):
last = generated_files[-1] last = generated_files[-1]
out = output_file_rad + '_' + str(mip_count + i) out = output_file_rad + '_' + str(mip_count + i)
if arm.utils.get_os() == 'win': if arm.utils.get_os() == 'win':
subprocess.call([ \ subprocess.call([
kraffiti_path, kraffiti_path,
'from=' + last + '.' + rad_format, 'from=' + last + '.' + rad_format,
'to=' + out + '.' + rad_format, 'to=' + out + '.' + rad_format,
'scale=0.5', 'scale=0.5',
'format=' + rad_format], shell=True) 'format=' + rad_format], shell=True)
else: else:
subprocess.call([ \ subprocess.call([
kraffiti_path + \ kraffiti_path
' from=' + '"' + last + '.' + rad_format + '"' + \ + ' from=' + '"' + last + '.' + rad_format + '"'
' to=' + '"' + out + '.' + rad_format + '"' + \ + ' to=' + '"' + out + '.' + rad_format + '"'
' scale=0.5' + \ + ' scale=0.5'
' format=' + rad_format], shell=True) + ' format=' + rad_format], shell=True)
generated_files.append(out) generated_files.append(out)
mip_count += 2 mip_count += 2
@ -239,6 +303,7 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True
return mip_count return mip_count
def sh_to_json(sh_file): def sh_to_json(sh_file):
"""Parse sh coefs produced by cmft into json array""" """Parse sh coefs produced by cmft into json array"""
with open(sh_file + '.c') as f: with open(sh_file + '.c') as f:
@ -251,6 +316,8 @@ def sh_to_json(sh_file):
parse_band_floats(irradiance_floats, band0_line) parse_band_floats(irradiance_floats, band0_line)
parse_band_floats(irradiance_floats, band1_line) parse_band_floats(irradiance_floats, band1_line)
parse_band_floats(irradiance_floats, band2_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} sh_json = {'irradiance': irradiance_floats}
ext = '.arm' if bpy.data.worlds['Arm'].arm_minimize else '' ext = '.arm' if bpy.data.worlds['Arm'].arm_minimize else ''
@ -259,15 +326,26 @@ def sh_to_json(sh_file):
# Clean up .c # Clean up .c
os.remove(sh_file + '.c') os.remove(sh_file + '.c')
def parse_band_floats(irradiance_floats, band_line): def parse_band_floats(irradiance_floats, band_line):
string_floats = re.findall(r'[-+]?\d*\.\d+|\d+', 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: for s in string_floats:
irradiance_floats.append(float(s)) irradiance_floats.append(float(s))
def write_sky_irradiance(base_name): def write_sky_irradiance(base_name):
# Hosek spherical harmonics # 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)): for i in range(0, len(irradiance_floats)):
irradiance_floats[i] /= 2 irradiance_floats[i] /= 2
@ -282,6 +360,7 @@ def write_sky_irradiance(base_name):
assets.add(output_file + '.arm') assets.add(output_file + '.arm')
def write_color_irradiance(base_name, col): def write_color_irradiance(base_name, col):
"""Constant color irradiance""" """Constant color irradiance"""
# Adjust to Cycles # Adjust to Cycles