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;
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() {
iron.object.Uniforms.externalTextureLinks = [textureLink];
iron.object.Uniforms.externalVec2Links = [];
iron.object.Uniforms.externalVec2Links = [vec2Link];
iron.object.Uniforms.externalVec3Links = [vec3Link];
iron.object.Uniforms.externalVec4Links = [];
iron.object.Uniforms.externalFloatLinks = [floatLink];
iron.object.Uniforms.externalIntLinks = [];
}
public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
#if arm_ltc
if (link == "_ltcMat") {
if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC();
return armory.data.ConstData.ltcMatTex;
public static function textureLink(object: Object, mat: MaterialData, link: String): Null<kha.Image> {
switch (link) {
case "_nishitaLUT": {
if (armory.renderpath.Nishita.data == null) armory.renderpath.Nishita.recompute(Scene.active.world);
return armory.renderpath.Nishita.data.lut;
}
#if arm_ltc
case "_ltcMat": {
if (armory.data.ConstData.ltcMatTex == null) armory.data.ConstData.initLTC();
return armory.data.ConstData.ltcMatTex;
}
case "_ltcMag": {
if (armory.data.ConstData.ltcMagTex == null) armory.data.ConstData.initLTC();
return armory.data.ConstData.ltcMagTex;
}
#end
}
else if (link == "_ltcMag") {
if (armory.data.ConstData.ltcMagTex == null) armory.data.ConstData.initLTC();
return armory.data.ConstData.ltcMagTex;
}
#end
var target = iron.RenderPath.active.renderTargets.get(link.endsWith("_depth") ? link.substr(0, link.length - 6) : link);
return target != null ? target.image : null;
}
public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
public static function vec3Link(object: Object, mat: MaterialData, link: String): Null<iron.math.Vec4> {
var v: Vec4 = null;
#if arm_hosek
if (link == "_hosekA") {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
switch (link) {
#if arm_hosek
case "_hosekA": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.A.x;
v.y = armory.renderpath.HosekWilkie.data.A.y;
v.z = armory.renderpath.HosekWilkie.data.A.z;
}
}
if (armory.renderpath.HosekWilkie.data != null) {
case "_hosekB": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.B.x;
v.y = armory.renderpath.HosekWilkie.data.B.y;
v.z = armory.renderpath.HosekWilkie.data.B.z;
}
}
case "_hosekC": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.C.x;
v.y = armory.renderpath.HosekWilkie.data.C.y;
v.z = armory.renderpath.HosekWilkie.data.C.z;
}
}
case "_hosekD": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.D.x;
v.y = armory.renderpath.HosekWilkie.data.D.y;
v.z = armory.renderpath.HosekWilkie.data.D.z;
}
}
case "_hosekE": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.E.x;
v.y = armory.renderpath.HosekWilkie.data.E.y;
v.z = armory.renderpath.HosekWilkie.data.E.z;
}
}
case "_hosekF": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.F.x;
v.y = armory.renderpath.HosekWilkie.data.F.y;
v.z = armory.renderpath.HosekWilkie.data.F.z;
}
}
case "_hosekG": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.G.x;
v.y = armory.renderpath.HosekWilkie.data.G.y;
v.z = armory.renderpath.HosekWilkie.data.G.z;
}
}
case "_hosekH": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.H.x;
v.y = armory.renderpath.HosekWilkie.data.H.y;
v.z = armory.renderpath.HosekWilkie.data.H.z;
}
}
case "_hosekI": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.I.x;
v.y = armory.renderpath.HosekWilkie.data.I.y;
v.z = armory.renderpath.HosekWilkie.data.I.z;
}
}
case "_hosekZ": {
if (armory.renderpath.HosekWilkie.data == null) {
armory.renderpath.HosekWilkie.recompute(Scene.active.world);
}
if (armory.renderpath.HosekWilkie.data != null) {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.Z.x;
v.y = armory.renderpath.HosekWilkie.data.Z.y;
v.z = armory.renderpath.HosekWilkie.data.Z.z;
}
}
#end
#if rp_voxelao
case "_cameraPositionSnap": {
v = iron.object.Uniforms.helpVec;
v.x = armory.renderpath.HosekWilkie.data.A.x;
v.y = armory.renderpath.HosekWilkie.data.A.y;
v.z = armory.renderpath.HosekWilkie.data.A.z;
var camera = iron.Scene.active.camera;
v.set(camera.transform.worldx(), camera.transform.worldy(), camera.transform.worldz());
var l = camera.lookWorld();
var e = Main.voxelgiHalfExtents;
v.x += l.x * e * 0.9;
v.y += l.y * e * 0.9;
var f = Main.voxelgiVoxelSize * 8; // Snaps to 3 mip-maps range
v.set(Math.floor(v.x / f) * f, Math.floor(v.y / f) * f, Math.floor(v.z / f) * f);
}
#end
}
return v;
}
public static function vec2Link(object: Object, mat: MaterialData, link: String): Null<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;
}
public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
#if rp_dynres
if (link == "_dynamicScale") {
return armory.renderpath.DynamicResolutionScale.dynamicScale;
switch (link) {
#if rp_dynres
case "_dynamicScale": {
return armory.renderpath.DynamicResolutionScale.dynamicScale;
}
#end
#if arm_debug
case "_debugFloat": {
return armory.trait.internal.DebugConsole.debugFloat;
}
#end
#if rp_voxelao
case "_voxelBlend": { // Blend current and last voxels
var freq = armory.renderpath.RenderPathCreator.voxelFreq;
return (armory.renderpath.RenderPathCreator.voxelFrame % freq) / freq;
}
#end
}
#end
#if arm_debug
if (link == "_debugFloat") {
return armory.trait.internal.DebugConsole.debugFloat;
}
#end
#if rp_voxelao
if (link == "_voxelBlend") { // Blend current and last voxels
var freq = armory.renderpath.RenderPathCreator.voxelFreq;
return (armory.renderpath.RenderPathCreator.voxelFrame % freq) / freq;
}
#end
return null;
}
}

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 conMap: Map<Int, PhysicsConstraint>;
public var timeScale = 1.0;
var timeStep = 1 / 60;
var maxSteps = 1;
public var solverIterations = 10;
public var hitPointWorld = new Vec4();
@ -67,7 +66,7 @@ class PhysicsWorld extends Trait {
public static var physTime = 0.0;
#end
public function new(timeScale = 1.0, timeStep = 1 / 60, solverIterations = 10) {
public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10) {
super();
if (nullvec) {
@ -81,8 +80,7 @@ class PhysicsWorld extends Trait {
sceneRemoved = false;
this.timeScale = timeScale;
this.timeStep = timeStep;
maxSteps = timeStep < 1 / 60 ? 10 : 1;
this.maxSteps = maxSteps;
this.solverIterations = solverIterations;
// First scene
@ -269,7 +267,13 @@ class PhysicsWorld extends Trait {
if (preUpdates != null) for (f in preUpdates) f();
world.stepSimulation(timeStep, maxSteps, t);
//Bullet physics fixed timescale
var fixedTime = 1.0 / 60;
//This condition must be satisfied to not loose time
var currMaxSteps = t < (fixedTime * maxSteps) ? maxSteps : 1;
world.stepSimulation(t, currMaxSteps, fixedTime);
updateContacts();
for (rb in rbMap) @:privateAccess rb.physicsUpdate();

View file

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

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
keyman = winman.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
#TODO - In Armory3D, merge with keymap.py
keyman.keymap_items.new('tlm.build_lightmaps', type='F6', value='PRESS')
keyman.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS')
tlm_keymaps.append(keyman)

View file

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

View file

@ -1,4 +1,4 @@
import bpy, os, time
import bpy, os, time, importlib
class TLM_ImageUpscale(bpy.types.Operator):
bl_idname = "tlm.image_upscale"
@ -8,6 +8,69 @@ class TLM_ImageUpscale(bpy.types.Operator):
def invoke(self, context, event):
cv2 = importlib.util.find_spec("cv2")
if cv2 is None:
print("CV2 not found - Ignoring filtering")
return 0
else:
cv2 = importlib.__import__("cv2")
for area in bpy.context.screen.areas:
if area.type == "IMAGE_EDITOR":
active_image = area.spaces.active.image
if active_image.source == "FILE":
img_path = active_image.filepath_raw
filename = os.path.basename(img_path)
basename = os.path.splitext(filename)[0]
extension = os.path.splitext(filename)[1]
size_x = active_image.size[0]
size_y = active_image.size[1]
dir_path = os.path.dirname(os.path.realpath(img_path))
#newfile = os.path.join(dir_path, basename + "_" + str(size_x) + "_" + str(size_y) + extension)
newfile = os.path.join(dir_path, basename + extension)
os.rename(img_path, newfile)
basefile = cv2.imread(newfile, cv2.IMREAD_UNCHANGED)
scale_percent = 200 # percent of original size
width = int(basefile.shape[1] * scale_percent / 100)
height = int(basefile.shape[0] * scale_percent / 100)
dim = (width, height)
if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest":
interp = cv2.INTER_NEAREST
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area":
interp = cv2.INTER_AREA
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear":
interp = cv2.INTER_LINEAR
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic":
interp = cv2.INTER_CUBIC
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos":
interp = cv2.INTER_LANCZOS4
resized = cv2.resize(basefile, dim, interpolation = interp)
#resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension)
resizedFile = os.path.join(dir_path, basename + extension)
cv2.imwrite(resizedFile, resized)
active_image.filepath_raw = resizedFile
bpy.ops.image.reload()
print(newfile)
print(img_path)
else:
print("Please save image")
print("Upscale")
return {'RUNNING_MODAL'}
@ -20,6 +83,111 @@ class TLM_ImageDownscale(bpy.types.Operator):
def invoke(self, context, event):
print("Downscale")
cv2 = importlib.util.find_spec("cv2")
if cv2 is None:
print("CV2 not found - Ignoring filtering")
return 0
else:
cv2 = importlib.__import__("cv2")
for area in bpy.context.screen.areas:
if area.type == "IMAGE_EDITOR":
active_image = area.spaces.active.image
if active_image.source == "FILE":
img_path = active_image.filepath_raw
filename = os.path.basename(img_path)
basename = os.path.splitext(filename)[0]
extension = os.path.splitext(filename)[1]
size_x = active_image.size[0]
size_y = active_image.size[1]
dir_path = os.path.dirname(os.path.realpath(img_path))
#newfile = os.path.join(dir_path, basename + "_" + str(size_x) + "_" + str(size_y) + extension)
newfile = os.path.join(dir_path, basename + extension)
os.rename(img_path, newfile)
basefile = cv2.imread(newfile, cv2.IMREAD_UNCHANGED)
scale_percent = 50 # percent of original size
width = int(basefile.shape[1] * scale_percent / 100)
height = int(basefile.shape[0] * scale_percent / 100)
dim = (width, height)
if dim[0] > 1 or dim[1] > 1:
if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest":
interp = cv2.INTER_NEAREST
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area":
interp = cv2.INTER_AREA
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear":
interp = cv2.INTER_LINEAR
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic":
interp = cv2.INTER_CUBIC
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos":
interp = cv2.INTER_LANCZOS4
resized = cv2.resize(basefile, dim, interpolation = interp)
#resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension)
resizedFile = os.path.join(dir_path, basename + extension)
cv2.imwrite(resizedFile, resized)
active_image.filepath_raw = resizedFile
bpy.ops.image.reload()
print(newfile)
print(img_path)
else:
print("Please save image")
print("Upscale")
return {'RUNNING_MODAL'}
class TLM_ImageSwitchUp(bpy.types.Operator):
bl_idname = "tlm.image_switchup"
bl_label = "Quickswitch Up"
bl_description = "Switches to a cached upscaled image"
bl_options = {'REGISTER', 'UNDO'}
def invoke(self, context, event):
for area in bpy.context.screen.areas:
if area.type == "IMAGE_EDITOR":
active_image = area.spaces.active.image
if active_image.source == "FILE":
img_path = active_image.filepath_raw
filename = os.path.basename(img_path)
print("Switch up")
return {'RUNNING_MODAL'}
class TLM_ImageSwitchDown(bpy.types.Operator):
bl_idname = "tlm.image_switchdown"
bl_label = "Quickswitch Down"
bl_description = "Switches to a cached downscaled image"
bl_options = {'REGISTER', 'UNDO'}
def invoke(self, context, event):
for area in bpy.context.screen.areas:
if area.type == "IMAGE_EDITOR":
active_image = area.spaces.active.image
if active_image.source == "FILE":
img_path = active_image.filepath_raw
filename = os.path.basename(img_path)
print("Switch Down")
return {'RUNNING_MODAL'}

View file

@ -21,7 +21,10 @@ class TLM_Install_OpenCV(bpy.types.Operator):
print("Module OpenCV")
pythonbinpath = bpy.app.binary_path_python
if (2, 91, 0) > bpy.app.version:
pythonbinpath = bpy.app.binary_path_python
else:
pythonbinpath = sys.executable
if platform.system() == "Windows":
pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib")

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
from .. utility import utility
from .. utility import build
from .. utility.cycles import cache
from .. network import server
def setObjectLightmapByWeight(minimumRes, maximumRes, objWeight):
availableResolutions = [32,64,128,256,512,1024,2048,4096,8192]
minRes = minimumRes
minResIdx = availableResolutions.index(minRes)
maxRes = maximumRes
maxResIdx = availableResolutions.index(maxRes)
exampleWeight = objWeight
if minResIdx == maxResIdx:
pass
else:
increment = 1.0/(maxResIdx-minResIdx)
assortedRange = []
for a in numpy.arange(0.0, 1.0, increment):
assortedRange.append(round(a, 2))
assortedRange.append(1.0)
nearestWeight = min(assortedRange, key=lambda x:abs(x - exampleWeight))
return (availableResolutions[assortedRange.index(nearestWeight) + minResIdx])
class TLM_BuildLightmaps(bpy.types.Operator):
bl_idname = "tlm.build_lightmaps"
bl_label = "Build Lightmaps"
@ -52,13 +79,13 @@ class TLM_CleanLightmaps(bpy.types.Operator):
for file in os.listdir(dirpath):
os.remove(os.path.join(dirpath + "/" + file))
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_restore(obj)
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_rename(obj)
@ -75,8 +102,8 @@ class TLM_CleanLightmaps(bpy.types.Operator):
if image.name.endswith("_baked"):
bpy.data.images.remove(image, do_unlink=True)
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_postpack_object:
@ -92,14 +119,17 @@ class TLM_CleanLightmaps(bpy.types.Operator):
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
#print(x)
uv_layers = obj.data.uv_layers
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
else:
uv_channel = "UVMap_Lightmap"
for i in range(0, len(uv_layers)):
if uv_layers[i].name == 'UVMap_Lightmap':
if uv_layers[i].name == uv_channel:
uv_layers.active_index = i
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Lightmap shift A")
break
bpy.ops.object.mode_set(mode='EDIT')
@ -111,9 +141,11 @@ class TLM_CleanLightmaps(bpy.types.Operator):
bpy.ops.object.mode_set(mode='OBJECT')
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
#print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name)
print("Resized for obj: " + obj.name)
if "Lightmap" in obj:
del obj["Lightmap"]
return {'FINISHED'}
class TLM_ExploreLightmaps(bpy.types.Operator):
@ -153,63 +185,285 @@ class TLM_ExploreLightmaps(bpy.types.Operator):
return {'FINISHED'}
class TLM_EnableSelection(bpy.types.Operator):
"""Enable for selection"""
bl_idname = "tlm.enable_selection"
bl_label = "Enable for selection"
bl_description = "Enable for selection"
class TLM_EnableSet(bpy.types.Operator):
"""Enable for set"""
bl_idname = "tlm.enable_set"
bl_label = "Enable for set"
bl_description = "Enable for set"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
scene = context.scene
for obj in bpy.context.selected_objects:
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True
weightList = {} #ObjName : [Dimension,Weight]
max = 0
if scene.TLM_SceneProperties.tlm_override_object_settings:
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
obj.TLM_ObjectProperties.tlm_postpack_object = scene.TLM_SceneProperties.tlm_postpack_object
if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
obj.TLM_ObjectProperties.tlm_atlas_pointer = scene.TLM_SceneProperties.tlm_atlas_pointer
print("Enabling for scene: " + obj.name)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object
obj.TLM_ObjectProperties.tlm_postatlas_pointer = scene.TLM_SceneProperties.tlm_postatlas_pointer
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer
obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single":
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension":
obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z
weightList[obj.name] = [obj_dimensions, 0]
if obj_dimensions > max:
max = obj_dimensions
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface":
bm = bmesh.new()
bm.from_mesh(obj.data)
area = sum(f.calc_area() for f in bm.faces)
weightList[obj.name] = [area, 0]
if area > max:
max = area
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume":
bm = bmesh.new()
bm.from_mesh(obj.data)
volume = float( bm.calc_volume())
weightList[obj.name] = [volume, 0]
if volume > max:
max = volume
elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection":
for obj in bpy.context.selected_objects:
if obj.type == "MESH":
print("Enabling for selection: " + obj.name)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer
obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single":
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension":
obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z
weightList[obj.name] = [obj_dimensions, 0]
if obj_dimensions > max:
max = obj_dimensions
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface":
bm = bmesh.new()
bm.from_mesh(obj.data)
area = sum(f.calc_area() for f in bm.faces)
weightList[obj.name] = [area, 0]
if area > max:
max = area
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume":
bm = bmesh.new()
bm.from_mesh(obj.data)
volume = float( bm.calc_volume())
weightList[obj.name] = [volume, 0]
if volume > max:
max = volume
else: #Enabled
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
print("Enabling for designated: " + obj.name)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = True
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = bpy.context.scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = bpy.context.scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
obj.TLM_ObjectProperties.tlm_postpack_object = bpy.context.scene.TLM_SceneProperties.tlm_postpack_object
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
obj.TLM_ObjectProperties.tlm_atlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_atlas_pointer
obj.TLM_ObjectProperties.tlm_postatlas_pointer = bpy.context.scene.TLM_SceneProperties.tlm_postatlas_pointer
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Single":
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Dimension":
obj_dimensions = obj.dimensions.x * obj.dimensions.y * obj.dimensions.z
weightList[obj.name] = [obj_dimensions, 0]
if obj_dimensions > max:
max = obj_dimensions
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Surface":
bm = bmesh.new()
bm.from_mesh(obj.data)
area = sum(f.calc_area() for f in bm.faces)
weightList[obj.name] = [area, 0]
if area > max:
max = area
elif bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight == "Volume":
bm = bmesh.new()
bm.from_mesh(obj.data)
volume = float( bm.calc_volume())
weightList[obj.name] = [volume, 0]
if volume > max:
max = volume
if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single":
for key in weightList:
weightList[obj.name][1] = weightList[obj.name][0] / max
a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1])
print(str(a) + "/" + str(weightList[obj.name][1]))
print("Scale: " + str(weightList[obj.name][0]))
print("Obj: " + obj.name)
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a)
elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection":
for obj in bpy.context.selected_objects:
if obj.type == "MESH":
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single":
for key in weightList:
weightList[obj.name][1] = weightList[obj.name][0] / max
a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1])
print(str(a) + "/" + str(weightList[obj.name][1]))
print("Scale: " + str(weightList[obj.name][0]))
print("Obj: " + obj.name)
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a)
else: #Enabled
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
if bpy.context.scene.TLM_SceneProperties.tlm_resolution_weight != "Single":
for key in weightList:
weightList[obj.name][1] = weightList[obj.name][0] / max
a = setObjectLightmapByWeight(int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_min), int(bpy.context.scene.TLM_SceneProperties.tlm_resolution_max), weightList[obj.name][1])
print(str(a) + "/" + str(weightList[obj.name][1]))
print("Scale: " + str(weightList[obj.name][0]))
print("Obj: " + obj.name)
print("")
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = str(a)
return{'FINISHED'}
class TLM_DisableSelection(bpy.types.Operator):
"""Disable for selection"""
"""Disable for set"""
bl_idname = "tlm.disable_selection"
bl_label = "Disable for selection"
bl_label = "Disable for set"
bl_description = "Disable for selection"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
for obj in bpy.context.selected_objects:
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
scene = context.scene
weightList = {} #ObjName : [Dimension,Weight]
max = 0
if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection":
for obj in bpy.context.selected_objects:
if obj.type == "MESH":
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
else: #Enabled
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
obj.TLM_ObjectProperties.tlm_mesh_lightmap_use = False
return{'FINISHED'}
class TLM_RemoveLightmapUV(bpy.types.Operator):
"""Remove Lightmap UV for selection"""
"""Remove Lightmap UV for set"""
bl_idname = "tlm.remove_uv_selection"
bl_label = "Remove Lightmap UV"
bl_description = "Remove Lightmap UV for selection"
bl_description = "Remove Lightmap UV for set"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
for obj in bpy.context.selected_objects:
if obj.type == "MESH":
uv_layers = obj.data.uv_layers
if bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Scene":
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
for uvlayer in uv_layers:
if uvlayer.name == "UVMap_Lightmap":
uv_layers.remove(uvlayer)
uv_layers = obj.data.uv_layers
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
else:
uv_channel = "UVMap_Lightmap"
for uvlayer in uv_layers:
if uvlayer.name == uv_channel:
uv_layers.remove(uvlayer)
elif bpy.context.scene.TLM_SceneProperties.tlm_utility_set == "Selection":
for obj in bpy.context.selected_objects:
if obj.type == "MESH":
uv_layers = obj.data.uv_layers
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
else:
uv_channel = "UVMap_Lightmap"
for uvlayer in uv_layers:
if uvlayer.name == uv_channel:
uv_layers.remove(uvlayer)
else: #Enabled
for obj in bpy.context.scene.objects:
if obj.type == "MESH":
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
uv_layers = obj.data.uv_layers
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
else:
uv_channel = "UVMap_Lightmap"
for uvlayer in uv_layers:
if uvlayer.name == uv_channel:
uv_layers.remove(uvlayer)
return{'FINISHED'}
@ -222,8 +476,8 @@ class TLM_SelectLightmapped(bpy.types.Operator):
def execute(self, context):
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
obj.select_set(True)
@ -278,7 +532,7 @@ class TLM_AtlastListDeleteItem(bpy.types.Operator):
list = scene.TLM_AtlasList
index = scene.TLM_AtlasListItem
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
atlasName = scene.TLM_AtlasList[index].name
@ -310,7 +564,7 @@ class TLM_PostAtlastListDeleteItem(bpy.types.Operator):
list = scene.TLM_PostAtlasList
index = scene.TLM_PostAtlasListItem
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
atlasName = scene.TLM_PostAtlasList[index].name
@ -437,7 +691,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
def invoke(self, context, event):
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
if obj.type == "LIGHT_PROBE":
if obj.data.type == "CUBEMAP":
@ -500,7 +754,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
cam.rotation_euler = positions[val]
filename = os.path.join(directory, val) + "_" + camobj_name + ".hdr"
bpy.data.scenes['Scene'].render.filepath = filename
bpy.context.scene.render.filepath = filename
print("Writing out: " + val)
bpy.ops.render.render(write_still=True)
@ -642,7 +896,7 @@ class TLM_BuildEnvironmentProbes(bpy.types.Operator):
subprocess.call([envpipe3], shell=True)
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
obj.select_set(False)
cam_obj.select_set(True)
@ -686,7 +940,92 @@ class TLM_MergeAdjacentActors(bpy.types.Operator):
scene = context.scene
return {'FINISHED'}
class TLM_PrepareUVMaps(bpy.types.Operator):
bl_idname = "tlm.prepare_uvmaps"
bl_label = "Prepare UV maps"
bl_description = "Prepare UV lightmaps for selected objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
scene = context.scene
return {'FINISHED'}
class TLM_LoadLightmaps(bpy.types.Operator):
bl_idname = "tlm.load_lightmaps"
bl_label = "Load Lightmaps"
bl_description = "Load lightmaps from selected folder"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
scene = context.scene
utility.transfer_load()
build.finish_assemble()
return {'FINISHED'}
class TLM_ToggleTexelDensity(bpy.types.Operator):
bl_idname = "tlm.toggle_texel_density"
bl_label = "Toggle Texel Density"
bl_description = "Toggle visualize lightmap texel density for selected objects"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
scene = context.scene
for obj in bpy.context.selected_objects:
if obj.type == "MESH":
uv_layers = obj.data.uv_layers
#if the object has a td_vis in the uv maps, toggle off
#else toggle on
if obj.TLM_ObjectProperties.tlm_use_default_channel:
for i in range(0, len(uv_layers)):
if uv_layers[i].name == 'UVMap_Lightmap':
uv_layers.active_index = i
break
else:
for i in range(0, len(uv_layers)):
if uv_layers[i].name == obj.TLM_ObjectProperties.tlm_uv_channel:
uv_layers.active_index = i
break
#filepath = r"C:\path\to\image.png"
#img = bpy.data.images.load(filepath)
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
space_data = area.spaces.active
bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
new_window = context.window_manager.windows[-1]
area = new_window.screen.areas[-1]
area.type = 'VIEW_3D'
#bg = space_data.background_images.new()
print(bpy.context.object)
bpy.ops.object.bake_td_uv_to_vc()
#bg.image = img
break
#set active uv_layer to
print("TLM_Viz_Toggle")
return {'FINISHED'}
@ -698,7 +1037,4 @@ def TLM_HalfResolution():
pass
def TLM_DivideLMGroups():
pass
def TLM_LoadFromFolder():
pass

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
from bpy.utils import register_class, unregister_class
from . import scene, object, atlas
from . renderer import cycles, luxcorerender
from . import scene, object, atlas, image
from . renderer import cycles, luxcorerender, octanerender
from . denoiser import oidn, optix
classes = [
@ -9,12 +9,14 @@ classes = [
object.TLM_ObjectProperties,
cycles.TLM_CyclesSceneProperties,
luxcorerender.TLM_LuxCoreSceneProperties,
octanerender.TLM_OctanerenderSceneProperties,
oidn.TLM_OIDNEngineProperties,
optix.TLM_OptixEngineProperties,
atlas.TLM_AtlasListItem,
atlas.TLM_UL_AtlasList,
atlas.TLM_PostAtlasListItem,
atlas.TLM_UL_PostAtlasList
atlas.TLM_UL_PostAtlasList,
image.TLM_ImageProperties
]
def register():
@ -25,12 +27,14 @@ def register():
bpy.types.Object.TLM_ObjectProperties = bpy.props.PointerProperty(type=object.TLM_ObjectProperties)
bpy.types.Scene.TLM_EngineProperties = bpy.props.PointerProperty(type=cycles.TLM_CyclesSceneProperties)
bpy.types.Scene.TLM_Engine2Properties = bpy.props.PointerProperty(type=luxcorerender.TLM_LuxCoreSceneProperties)
bpy.types.Scene.TLM_Engine3Properties = bpy.props.PointerProperty(type=octanerender.TLM_OctanerenderSceneProperties)
bpy.types.Scene.TLM_OIDNEngineProperties = bpy.props.PointerProperty(type=oidn.TLM_OIDNEngineProperties)
bpy.types.Scene.TLM_OptixEngineProperties = bpy.props.PointerProperty(type=optix.TLM_OptixEngineProperties)
bpy.types.Scene.TLM_AtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0)
bpy.types.Scene.TLM_AtlasList = bpy.props.CollectionProperty(type=atlas.TLM_AtlasListItem)
bpy.types.Scene.TLM_PostAtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0)
bpy.types.Scene.TLM_PostAtlasList = bpy.props.CollectionProperty(type=atlas.TLM_PostAtlasListItem)
bpy.types.Image.TLM_ImageProperties = bpy.props.PointerProperty(type=image.TLM_ImageProperties)
bpy.types.Material.TLM_ignore = bpy.props.BoolProperty(name="Skip material", description="Ignore material for lightmapped object", default=False)
@ -42,9 +46,11 @@ def unregister():
del bpy.types.Object.TLM_ObjectProperties
del bpy.types.Scene.TLM_EngineProperties
del bpy.types.Scene.TLM_Engine2Properties
del bpy.types.Scene.TLM_Engine3Properties
del bpy.types.Scene.TLM_OIDNEngineProperties
del bpy.types.Scene.TLM_OptixEngineProperties
del bpy.types.Scene.TLM_AtlasListItem
del bpy.types.Scene.TLM_AtlasList
del bpy.types.Scene.TLM_PostAtlasListItem
del bpy.types.Scene.TLM_PostAtlasList
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,
subtype='FACTOR')
tlm_atlas_lightmap_unwrap_mode : EnumProperty(
items = [('Lightmap', 'Lightmap', 'TODO'),
('SmartProject', 'Smart Project', 'TODO'),
('Xatlas', 'Xatlas', 'TODO')],
unwrap_modes = [('Lightmap', 'Lightmap', 'Use Blender Lightmap Pack algorithm'),
('SmartProject', 'Smart Project', 'Use Blender Smart Project algorithm')]
if "blender_xatlas" in bpy.context.preferences.addons.keys():
unwrap_modes.append(('Xatlas', 'Xatlas', 'Use Xatlas addon packing algorithm'))
tlm_postatlas_lightmap_unwrap_mode : EnumProperty(
items = unwrap_modes,
name = "Unwrap Mode",
description="TODO",
description="Atlas unwrapping method",
default='SmartProject')
class TLM_UL_PostAtlasList(bpy.types.UIList):
@ -51,7 +55,7 @@ class TLM_UL_PostAtlasList(bpy.types.UIList):
#In list object counter
amount = 0
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_postpack_object:
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name:
@ -69,9 +73,6 @@ class TLM_UL_PostAtlasList(bpy.types.UIList):
layout.alignment = 'CENTER'
layout.label(text="", icon = custom_icon)
class TLM_AtlasListItem(bpy.types.PropertyGroup):
obj: PointerProperty(type=bpy.types.Object, description="The object to bake")
tlm_atlas_lightmap_resolution : EnumProperty(
@ -95,12 +96,17 @@ class TLM_AtlasListItem(bpy.types.PropertyGroup):
max=1.0,
subtype='FACTOR')
unwrap_modes = [('Lightmap', 'Lightmap', 'Use Blender Lightmap Pack algorithm'),
('SmartProject', 'Smart Project', 'Use Blender Smart Project algorithm'),
('Copy', 'Copy existing', 'Use the existing UV channel')]
if "blender_xatlas" in bpy.context.preferences.addons.keys():
unwrap_modes.append(('Xatlas', 'Xatlas', 'Use Xatlas addon packing algorithm'))
tlm_atlas_lightmap_unwrap_mode : EnumProperty(
items = [('Lightmap', 'Lightmap', 'TODO'),
('SmartProject', 'Smart Project', 'TODO'),
('Xatlas', 'Xatlas', 'TODO')],
items = unwrap_modes,
name = "Unwrap Mode",
description="TODO",
description="Atlas unwrapping method",
default='SmartProject')
class TLM_UL_AtlasList(bpy.types.UIList):
@ -111,7 +117,7 @@ class TLM_UL_AtlasList(bpy.types.UIList):
amount = 0
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name:

View file

@ -1,10 +1,26 @@
import bpy
from bpy.props import *
class TLM_ObjectProperties(bpy.types.PropertyGroup):
tlm_image_scale_method : EnumProperty(
items = [('Native', 'Native', 'TODO'),
('OpenCV', 'OpenCV', 'TODO')],
class TLM_ImageProperties(bpy.types.PropertyGroup):
tlm_image_scale_engine : EnumProperty(
items = [('OpenCV', 'OpenCV', 'TODO')],
name = "Scaling engine",
description="TODO",
default='Native')
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(
name = "Atlas Group",
description = "Atlas Lightmap Group",
description = "",
default = "")
tlm_postatlas_pointer : StringProperty(
@ -51,8 +51,7 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup):
unwrap_modes = [('Lightmap', 'Lightmap', 'TODO'),
('SmartProject', 'Smart Project', 'TODO'),
('CopyExisting', 'Copy Existing', 'TODO'),
('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO')]
('AtlasGroupA', 'Atlas Group (Prepack)', 'Attaches the object to a prepack Atlas group. Will overwrite UV map on build.')]
tlm_postpack_object : BoolProperty( #CHECK INSTEAD OF ATLASGROUPB
name="Postpack object",
@ -145,4 +144,14 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup):
name="Median kernel",
default=3,
min=1,
max=5)
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",
default="0")
targets = [('texture', 'Image texture', 'Build to image texture')]
if (2, 92, 0) >= bpy.app.version:
targets.append(('vertex', 'Vertex colors', 'Build to vertex colors'))
tlm_target : EnumProperty(
items = targets,
name = "Build Target",
description="Select target to build to",
default="texture")
tlm_resolution_scale : EnumProperty(
items = [('1', '1/1', '1'),
('2', '1/2', '2'),
@ -45,10 +55,12 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup):
description="Select bake mode",
default="Foreground")
caching_modes = [('Copy', 'Copy', 'More overhead; allows for network.')]
#caching_modes.append(('Cache', 'Cache', 'Cache in separate blend'),('Node', 'Node restore', 'EXPERIMENTAL! Use with care'))
tlm_caching_mode : EnumProperty(
items = [('Copy', 'Copy', 'More overhead; allows for network.'),
('Cache', 'Cache', 'Cache in separate blend'),
('Node', 'Node restore', 'EXPERIMENTAL! Use with care')],
items = caching_modes,
name = "Caching mode",
description="Select cache mode",
default="Copy")
@ -88,8 +100,16 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup):
tlm_lighting_mode : EnumProperty(
items = [('combined', 'Combined', 'Bake combined lighting'),
('combinedao', 'Combined+AO', 'Bake combined lighting with Ambient Occlusion'),
('indirect', 'Indirect', 'Bake indirect lighting'),
('ao', 'AO', 'Bake only Ambient Occlusion')],
# ('indirectao', 'Indirect+AO', 'Bake indirect lighting with Ambient Occlusion'),
('ao', 'AO', 'Bake only Ambient Occlusion'),
('complete', 'Complete', 'Bake complete map')],
name = "Lighting mode",
description="TODO.",
default="combined")
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 .. utility import utility
def transfer_load():
load_folder = bpy.context.scene.TLM_SceneProperties.tlm_load_folder
lightmap_folder = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
print(load_folder)
print(lightmap_folder)
#transfer_assets(True, load_folder, lightmap_folder)
class TLM_SceneProperties(bpy.types.PropertyGroup):
engines = [('Cycles', 'Cycles', 'Use Cycles for lightmapping')]
engines.append(('LuxCoreRender', 'LuxCoreRender', 'Use LuxCoreRender for lightmapping'))
#engines.append(('LuxCoreRender', 'LuxCoreRender', 'Use LuxCoreRender for lightmapping'))
#engines.append(('OctaneRender', 'Octane Render', 'Use Octane Render for lightmapping'))
tlm_atlas_pointer : StringProperty(
@ -112,7 +120,7 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
#FILTERING SETTINGS GROUP
tlm_filtering_use : BoolProperty(
name="Enable Filtering",
name="Enable denoising",
description="Enable denoising for lightmaps",
default=False)
@ -182,6 +190,17 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
min=1,
max=5)
tlm_clamp_hdr : BoolProperty(
name="Enable HDR Clamp",
description="Clamp HDR Value",
default=False)
tlm_clamp_hdr_value : IntProperty(
name="HDR Clamp value",
default=10,
min=0,
max=20)
#Encoding properties
tlm_encoding_use : BoolProperty(
name="Enable encoding",
@ -197,12 +216,13 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
encoding_modes_1 = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'),
('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'),
('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')]
('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.'),
('SDR', 'SDR', '8-bit flat encoding.')]
encoding_modes_2 = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'),
('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'),
encoding_modes_2 = [('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'),
('LogLuv', 'LogLuv', '8-bit HDR encoding. Different.'),
('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')]
('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.'),
('SDR', 'SDR', '8-bit flat encoding.')]
tlm_encoding_mode_a : EnumProperty(
items = encoding_modes_1,
@ -275,8 +295,7 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
tlm_mesh_lightmap_unwrap_mode : EnumProperty(
items = [('Lightmap', 'Lightmap', 'TODO'),
('SmartProject', 'Smart Project', 'TODO'),
('CopyExisting', 'Copy Existing', 'TODO'),
('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO'),
('AtlasGroupA', 'Atlas Group (Prepack)', 'Attaches the object to a prepack Atlas group. Will overwrite UV map on build.'),
('Xatlas', 'Xatlas', 'TODO')],
name = "Unwrap Mode",
description="TODO",
@ -317,12 +336,30 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
tlm_metallic_clamp : EnumProperty(
items = [('ignore', 'Ignore', 'Ignore clamping'),
('skip', 'Skip', 'Skip baking metallic materials'),
('zero', 'Zero', 'Set zero'),
('limit', 'Limit', 'Clamp to 0.9')],
name = "Metallic clamping",
description="TODO.",
default="ignore")
tlm_texture_interpolation : EnumProperty(
items = [('Smart', 'Smart', 'Bicubic when magnifying.'),
('Cubic', 'Cubic', 'Cubic interpolation'),
('Closest', 'Closest', 'No interpolation'),
('Linear', 'Linear', 'Linear')],
name = "Texture interpolation",
description="Texture interpolation.",
default="Linear")
tlm_texture_extrapolation : EnumProperty(
items = [('REPEAT', 'Repeat', 'Repeat in both direction.'),
('EXTEND', 'Extend', 'Extend by repeating edge pixels.'),
('CLIP', 'Clip', 'Clip to image size')],
name = "Texture extrapolation",
description="Texture extrapolation.",
default="EXTEND")
tlm_verbose : BoolProperty(
name="Verbose",
description="Verbose console output",
@ -409,4 +446,52 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
('CYCLES', 'Cycles', 'TODO')],
name = "Probe Render Engine",
description="TODO",
default='BLENDER_EEVEE')
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 threading
from . import encoding, pack
from . cycles import lightmap, prepare, nodes, cache
from . luxcore import setup
from . octane import configure, lightmap2
from . denoiser import integrated, oidn, optix
from . filtering import opencv
from . gui import Viewport
from .. network import client
from os import listdir
from os.path import isfile, join
from time import time, sleep
from importlib import util
previous_settings = {}
postprocess_shutdown = False
def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
@ -21,15 +24,31 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
print("Building lightmaps")
if bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao":
scene = bpy.context.scene
if not "tlm_plus_mode" in bpy.app.driver_namespace or bpy.app.driver_namespace["tlm_plus_mode"] == 0:
filepath = bpy.data.filepath
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir)
if os.path.isdir(dirpath):
for file in os.listdir(dirpath):
os.remove(os.path.join(dirpath + "/" + file))
bpy.app.driver_namespace["tlm_plus_mode"] = 1
print("Plus Mode")
if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Foreground" or background_mode==True:
global start_time
start_time = time()
bpy.app.driver_namespace["tlm_start_time"] = time()
scene = bpy.context.scene
sceneProperties = scene.TLM_SceneProperties
#Timer start here bound to global
if not background_mode and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao":
#pass
setGui(1)
if check_save():
print("Please save your file first")
@ -52,12 +71,6 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
self.report({'INFO'}, "Error:Filtering - OpenCV not installed")
return{'FINISHED'}
#TODO DO some resolution change
#if checkAtlasSize():
# print("Error: AtlasGroup overflow")
# self.report({'INFO'}, "Error: AtlasGroup overflow - Too many objects")
# return{'FINISHED'}
setMode()
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
@ -67,19 +80,6 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
#Naming check
naming_check()
# if sceneProperties.tlm_reset_uv or sceneProperties.tlm_atlas_mode == "Postpack":
# for obj in bpy.data.objects:
# if obj.type == "MESH":
# uv_layers = obj.data.uv_layers
#for uvlayer in uv_layers:
# if uvlayer.name == "UVMap_Lightmap":
# uv_layers.remove(uvlayer)
## RENDER DEPENDENCY FROM HERE
if sceneProperties.tlm_lightmap_engine == "Cycles":
prepare.init(self, previous_settings)
@ -90,18 +90,14 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
if sceneProperties.tlm_lightmap_engine == "OctaneRender":
pass
#Renderer - Store settings
#Renderer - Set settings
#Renderer - Config objects, lights, world
configure.init(self, previous_settings)
begin_build()
else:
print("Baking in background")
filepath = bpy.data.filepath
bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
@ -111,22 +107,7 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
scene = bpy.context.scene
sceneProperties = scene.TLM_SceneProperties
#We dynamically load the renderer and denoiser, instead of loading something we don't use
if sceneProperties.tlm_lightmap_engine == "Cycles":
pass
if sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
pass
if sceneProperties.tlm_lightmap_engine == "OctaneRender":
pass
#Timer start here bound to global
if check_save():
print("Please save your file first")
self.report({'INFO'}, "Please save your file first")
@ -168,23 +149,12 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
client.connect_client(HOST, PORT, bpy.data.filepath, 0)
# with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# s.connect((HOST, PORT))
# message = {
# "call" : 1,
# "command" : 1,
# "enquiry" : 0,
# "args" : bpy.data.filepath
# }
# s.sendall(json.dumps(message).encode())
# data = s.recv(1024)
# print(data.decode())
finish_assemble()
else:
print("Background driver process")
bpy.app.driver_namespace["alpha"] = 0
bpy.app.driver_namespace["tlm_process"] = False
@ -196,6 +166,8 @@ def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
def distribute_building():
print("Distributing lightmap building")
#CHECK IF THERE'S AN EXISTING SUBPROCESS
if not os.path.isfile(os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir, "process.tlm")):
@ -215,8 +187,16 @@ def distribute_building():
with open(os.path.join(write_directory, "process.tlm"), 'w') as file:
json.dump(process_status, file, indent=2)
bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
if (2, 91, 0) > bpy.app.version:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stdout=subprocess.PIPE)
else:
bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
else:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([bpy.app.binary_path,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stdout=subprocess.PIPE)
else:
bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([bpy.app.binary_path,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Started process: " + str(bpy.app.driver_namespace["tlm_process"]) + " at " + str(datetime.datetime.now()))
@ -269,6 +249,10 @@ def finish_assemble(self=0):
if sceneProperties.tlm_lightmap_engine == "OctaneRender":
pass
if not 'start_time' in globals():
global start_time
start_time = time()
manage_build(True)
def begin_build():
@ -288,7 +272,8 @@ def begin_build():
pass
if sceneProperties.tlm_lightmap_engine == "OctaneRender":
pass
lightmap2.bake()
#Denoiser
if sceneProperties.tlm_denoise_use:
@ -429,7 +414,7 @@ def begin_build():
print("Encoding:" + str(file))
encoding.encodeImageRGBMCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0)
if sceneProperties.tlm_encoding_mode_b == "RGBD":
if sceneProperties.tlm_encoding_mode_a == "RGBD":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("ENCODING RGBD")
@ -455,6 +440,36 @@ def begin_build():
print("Encoding:" + str(file))
encoding.encodeImageRGBDCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0)
if sceneProperties.tlm_encoding_mode_a == "SDR":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("EXR Format")
ren = bpy.context.scene.render
ren.image_settings.file_format = "PNG"
#ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec"
end = "_baked"
baked_image_array = []
if sceneProperties.tlm_denoise_use:
end = "_denoised"
if sceneProperties.tlm_filtering_use:
end = "_filtered"
#For each image in folder ending in denoised/filtered
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
for file in dirfiles:
if file.endswith(end + ".hdr"):
img = bpy.data.images.load(os.path.join(dirpath,file))
img.save_render(img.filepath_raw[:-4] + ".png")
else:
if sceneProperties.tlm_encoding_mode_b == "HDR":
@ -562,6 +577,33 @@ def begin_build():
print("Encoding:" + str(file))
encoding.encodeImageRGBDGPU(img, sceneProperties.tlm_encoding_range, dirpath, 0)
if sceneProperties.tlm_encoding_mode_b == "PNG":
ren = bpy.context.scene.render
ren.image_settings.file_format = "PNG"
#ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec"
end = "_baked"
baked_image_array = []
if sceneProperties.tlm_denoise_use:
end = "_denoised"
if sceneProperties.tlm_filtering_use:
end = "_filtered"
#For each image in folder ending in denoised/filtered
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
for file in dirfiles:
if file.endswith(end + ".hdr"):
img = bpy.data.images.load(os.path.join(dirpath,file))
img.save_render(img.filepath_raw[:-4] + ".png")
manage_build()
def manage_build(background_pass=False):
@ -610,6 +652,10 @@ def manage_build(background_pass=False):
formatEnc = "_encoded.png"
if sceneProperties.tlm_encoding_mode_a == "SDR":
formatEnc = ".png"
else:
print("GPU Encoding")
@ -632,6 +678,10 @@ def manage_build(background_pass=False):
formatEnc = "_encoded.png"
if sceneProperties.tlm_encoding_mode_b == "SDR":
formatEnc = ".png"
if not background_pass:
nodes.exchangeLightmapsToPostfix("_baked", end, formatEnc)
@ -653,13 +703,13 @@ def manage_build(background_pass=False):
filepath = bpy.data.filepath
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir)
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_restore(obj)
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_rename(obj)
@ -672,77 +722,181 @@ def manage_build(background_pass=False):
if "_Original" in mat.name:
bpy.data.materials.remove(mat)
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
img_name = obj.name + '_baked'
Lightmapimage = bpy.data.images[img_name]
obj["Lightmap"] = Lightmapimage.filepath_raw
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
atlasName = obj.TLM_ObjectProperties.tlm_atlas_pointer
img_name = atlasName + '_baked'
Lightmapimage = bpy.data.images[img_name]
obj["Lightmap"] = Lightmapimage.filepath_raw
elif obj.TLM_ObjectProperties.tlm_postpack_object:
atlasName = obj.TLM_ObjectProperties.tlm_postatlas_pointer
img_name = atlasName + '_baked' + ".hdr"
Lightmapimage = bpy.data.images[img_name]
obj["Lightmap"] = Lightmapimage.filepath_raw
else:
img_name = obj.name + '_baked'
Lightmapimage = bpy.data.images[img_name]
obj["Lightmap"] = Lightmapimage.filepath_raw
for image in bpy.data.images:
if image.name.endswith("_baked"):
bpy.data.images.remove(image, do_unlink=True)
total_time = sec_to_hours((time() - start_time))
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(total_time)
if "tlm_plus_mode" in bpy.app.driver_namespace: #First DIR pass
bpy.context.scene["TLM_Buildstat"] = total_time
if bpy.app.driver_namespace["tlm_plus_mode"] == 1: #First DIR pass
reset_settings(previous_settings["settings"])
filepath = bpy.data.filepath
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir)
if sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_restore(obj)
pass
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_rename(obj)
if sceneProperties.tlm_lightmap_engine == "OctaneRender":
for mat in bpy.data.materials:
if mat.users < 1:
bpy.data.materials.remove(mat)
pass
for mat in bpy.data.materials:
if mat.name.startswith("."):
if "_Original" in mat.name:
bpy.data.materials.remove(mat)
if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Background":
pass
for image in bpy.data.images:
if image.name.endswith("_baked"):
bpy.data.images.remove(image, do_unlink=True)
if scene.TLM_SceneProperties.tlm_alert_on_finish:
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
alertSelect = scene.TLM_SceneProperties.tlm_alert_sound
files = os.listdir(dirpath)
for index, file in enumerate(files):
filename = extension = os.path.splitext(file)[0]
extension = os.path.splitext(file)[1]
os.rename(os.path.join(dirpath, file), os.path.join(dirpath, filename + "_dir" + extension))
print("First DIR pass complete")
bpy.app.driver_namespace["tlm_plus_mode"] = 2
prepare_build(self=0, background_mode=False, shutdown_after_build=False)
if not background_pass and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao":
#pass
setGui(0)
elif bpy.app.driver_namespace["tlm_plus_mode"] == 2:
filepath = bpy.data.filepath
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
files = os.listdir(dirpath)
for index, file in enumerate(files):
filename = os.path.splitext(file)[0]
extension = os.path.splitext(file)[1]
if not filename.endswith("_dir"):
os.rename(os.path.join(dirpath, file), os.path.join(dirpath, filename + "_ao" + extension))
print("Second AO pass complete")
total_time = sec_to_hours((time() - start_time))
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(total_time)
bpy.context.scene["TLM_Buildstat"] = total_time
reset_settings(previous_settings["settings"])
bpy.app.driver_namespace["tlm_plus_mode"] = 0
if not background_pass:
#TODO CHANGE!
nodes.exchangeLightmapsToPostfix(end, end + "_dir", formatEnc)
nodes.applyAOPass()
if alertSelect == "dash":
soundfile = "dash.ogg"
elif alertSelect == "pingping":
soundfile = "pingping.ogg"
elif alertSelect == "gentle":
soundfile = "gentle.ogg"
else:
soundfile = "noot.ogg"
scriptDir = os.path.dirname(os.path.realpath(__file__))
sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile))
total_time = sec_to_hours((time() - start_time))
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(total_time)
device = aud.Device()
sound = aud.Sound.file(sound_path)
device.play(sound)
bpy.context.scene["TLM_Buildstat"] = total_time
print("Lightmap building finished")
reset_settings(previous_settings["settings"])
if bpy.app.background:
print("Lightmap building finished")
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Writing background process report")
write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
if sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
if os.path.exists(os.path.join(write_directory, "process.tlm")):
pass
process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read())
if sceneProperties.tlm_lightmap_engine == "OctaneRender":
process_status[1]["completed"] = True
pass
with open(os.path.join(write_directory, "process.tlm"), 'w') as file:
json.dump(process_status, file, indent=2)
if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Background":
pass
if postprocess_shutdown:
sys.exit()
if not background_pass and bpy.context.scene.TLM_EngineProperties.tlm_lighting_mode != "combinedao":
#pass
setGui(0)
if scene.TLM_SceneProperties.tlm_alert_on_finish:
alertSelect = scene.TLM_SceneProperties.tlm_alert_sound
if alertSelect == "dash":
soundfile = "dash.ogg"
elif alertSelect == "pingping":
soundfile = "pingping.ogg"
elif alertSelect == "gentle":
soundfile = "gentle.ogg"
else:
soundfile = "noot.ogg"
scriptDir = os.path.dirname(os.path.realpath(__file__))
sound_path = os.path.abspath(os.path.join(scriptDir, '..', 'assets/'+soundfile))
device = aud.Device()
sound = aud.Sound.file(sound_path)
device.play(sound)
if bpy.app.background:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Writing background process report")
write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
if os.path.exists(os.path.join(write_directory, "process.tlm")):
process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read())
process_status[1]["completed"] = True
with open(os.path.join(write_directory, "process.tlm"), 'w') as file:
json.dump(process_status, file, indent=2)
if postprocess_shutdown:
sys.exit()
#TODO - SET BELOW TO UTILITY
@ -770,9 +924,8 @@ def reset_settings(prev_settings):
def naming_check():
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
@ -829,6 +982,9 @@ def check_save():
def check_denoiser():
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Checking denoiser path")
scene = bpy.context.scene
if scene.TLM_SceneProperties.tlm_denoise_use:
@ -847,8 +1003,8 @@ def check_denoiser():
return 0
def check_materials():
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
for slot in obj.material_slots:
mat = slot.material
@ -868,15 +1024,39 @@ def check_materials():
def sec_to_hours(seconds):
a=str(seconds//3600)
b=str((seconds%3600)//60)
c=str((seconds%3600)%60)
c=str(round((seconds%3600)%60,1))
d=["{} hours {} mins {} seconds".format(a, b, c)]
return d
def setMode():
obj = bpy.context.scene.objects[0]
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.mode_set(mode='OBJECT')
#TODO Make some checks that returns to previous selection
def setGui(mode):
if mode == 0:
context = bpy.context
driver = bpy.app.driver_namespace
if "TLM_UI" in driver:
driver["TLM_UI"].remove_handle()
if mode == 1:
#bpy.context.area.tag_redraw()
context = bpy.context
driver = bpy.app.driver_namespace
driver["TLM_UI"] = Viewport.ViewportDraw(context, "Building Lightmaps")
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
def checkAtlasSize():
overflow = False
@ -897,7 +1077,7 @@ def checkAtlasSize():
utilized = 0
atlasUsedArea = 0
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
if obj.TLM_ObjectProperties.tlm_postpack_object:
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:
@ -912,4 +1092,5 @@ def checkAtlasSize():
if overflow == True:
return True
else:
return False
return False

View file

@ -2,7 +2,6 @@ import bpy
#Todo - Check if already exists, in case multiple objects has the same material
def backup_material_copy(slot):
material = slot.material
dup = material.copy()
@ -16,25 +15,49 @@ def backup_material_cache_restore(slot, path):
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Restore cache")
def backup_material_rename(obj):
if "TLM_PrevMatArray" in obj:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Renaming material for: " + obj.name)
# def backup_material_restore(obj): #??
# if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
# print("Restoring material for: " + obj.name)
for slot in obj.material_slots:
#Check if object has TLM_PrevMatArray
# if yes
# - check if array.len is bigger than 0:
# if yes:
# for slot in object:
# originalMaterial = TLM_PrevMatArray[index]
#
#
# if no:
# - In which cases are these?
if slot.material is not None:
if slot.material.name.endswith("_Original"):
newname = slot.material.name[1:-9]
if newname in bpy.data.materials:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Removing material: " + bpy.data.materials[newname].name)
bpy.data.materials.remove(bpy.data.materials[newname])
slot.material.name = newname
# if no:
# - In which cases are there not?
# - If a lightmapped material was applied to a non-lightmap object?
del obj["TLM_PrevMatArray"]
def backup_material_restore(obj):
# if bpy.data.materials[originalMaterial].users > 0: #TODO - Check if all lightmapped
# print("Material has multiple users")
# if originalMaterial in bpy.data.materials:
# slot.material = bpy.data.materials[originalMaterial]
# slot.material.use_fake_user = False
# elif "." + originalMaterial + "_Original" in bpy.data.materials:
# slot.material = bpy.data.materials["." + originalMaterial + "_Original"]
# slot.material.use_fake_user = False
# else:
# print("Material has one user")
# if "." + originalMaterial + "_Original" in bpy.data.materials:
# slot.material = bpy.data.materials["." + originalMaterial + "_Original"]
# slot.material.use_fake_user = False
# elif originalMaterial in bpy.data.materials:
# slot.material = bpy.data.materials[originalMaterial]
# slot.material.use_fake_user = False
def backup_material_restore(obj): #??
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Restoring material for: " + obj.name)
@ -59,9 +82,43 @@ def backup_material_restore(obj):
originalMaterial = ""
if slot.material is not None:
#slot.material.user_clear() Seems to be bad; See: https://developer.blender.org/T49837
bpy.data.materials.remove(slot.material)
#if slot.material.users < 2:
#slot.material.user_clear() #Seems to be bad; See: https://developer.blender.org/T49837
#bpy.data.materials.remove(slot.material)
if "." + originalMaterial + "_Original" in bpy.data.materials:
slot.material = bpy.data.materials["." + originalMaterial + "_Original"]
slot.material.use_fake_user = False
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
from .. import build
from time import time, sleep
def bake():
def bake(plus_pass=0):
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(False)
iterNum = 0
currentIterNum = 0
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
iterNum = iterNum + 1
if iterNum > 1:
iterNum = iterNum - 1
for obj in bpy.data.objects:
if obj.type == 'MESH':
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
scene = bpy.context.scene
@ -32,19 +33,45 @@ def bake():
obj.hide_render = False
scene.render.bake.use_clear = False
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name)
#os.system("cls")
if scene.TLM_EngineProperties.tlm_lighting_mode == "combined" or scene.TLM_EngineProperties.tlm_lighting_mode == "combinedAO":
#if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name)
#elapsed = build.sec_to_hours((time() - bpy.app.driver_namespace["tlm_start_time"]))
#print("Baked: " + str(currentIterNum) + " | Left: " + str(iterNum-currentIterNum))
elapsedSeconds = time() - bpy.app.driver_namespace["tlm_start_time"]
bakedObjects = currentIterNum
bakedLeft = iterNum-currentIterNum
if bakedObjects == 0:
bakedObjects = 1
averagePrBake = elapsedSeconds / bakedObjects
remaining = averagePrBake * bakedLeft
#print(time() - bpy.app.driver_namespace["tlm_start_time"])
print("Elapsed time: " + str(round(elapsedSeconds, 2)) + "s | ETA remaining: " + str(round(remaining, 2)) + "s") #str(elapsed[0])
#print("Averaged: " + str(averagePrBake))
#print("Remaining: " + str(remaining))
if scene.TLM_EngineProperties.tlm_target == "vertex":
scene.render.bake_target = "VERTEX_COLORS"
if scene.TLM_EngineProperties.tlm_lighting_mode == "combined":
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
elif scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO":
elif scene.TLM_EngineProperties.tlm_lighting_mode == "indirect":
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
elif scene.TLM_EngineProperties.tlm_lighting_mode == "ao":
bpy.ops.object.bake(type="AO", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
elif scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao":
if bpy.app.driver_namespace["tlm_plus_mode"] == 1:
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
elif bpy.app.driver_namespace["tlm_plus_mode"] == 2:
bpy.ops.object.bake(type="AO", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
elif scene.TLM_EngineProperties.tlm_lighting_mode == "complete":
bpy.ops.object.bake(type="COMBINED", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
bpy.ops.object.bake(type="COMBINED", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
else:
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
bpy.ops.object.select_all(action='DESELECT')
currentIterNum = currentIterNum + 1

View file

@ -1,8 +1,8 @@
import bpy, os
def apply_lightmaps():
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
for slot in obj.material_slots:
mat = slot.material
@ -32,8 +32,12 @@ def apply_lightmaps():
def apply_materials():
for obj in bpy.data.objects:
if obj.type == "MESH":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Applying materials")
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
uv_layers = obj.data.uv_layers
@ -90,7 +94,7 @@ def apply_materials():
for node in nodes:
if node.name == "Baked Image":
lightmapNode = node
lightmapNode.location = -800, 300
lightmapNode.location = -1200, 300
lightmapNode.name = "TLM_Lightmap"
foundBakedNode = True
@ -98,9 +102,10 @@ def apply_materials():
if not foundBakedNode:
lightmapNode = node_tree.nodes.new(type="ShaderNodeTexImage")
lightmapNode.location = -300, 300
lightmapNode.location = -1200, 300
lightmapNode.name = "TLM_Lightmap"
lightmapNode.interpolation = "Smart"
lightmapNode.interpolation = bpy.context.scene.TLM_SceneProperties.tlm_texture_interpolation
lightmapNode.extension = bpy.context.scene.TLM_SceneProperties.tlm_texture_extrapolation
if (obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA" and obj.TLM_ObjectProperties.tlm_atlas_pointer != ""):
lightmapNode.image = bpy.data.images[obj.TLM_ObjectProperties.tlm_atlas_pointer + "_baked"]
@ -118,37 +123,32 @@ def apply_materials():
#Find mainnode
mainNode = outputNode.inputs[0].links[0].from_node
#Clamp metallic
# if scene.TLM_SceneProperties.tlm_metallic_clamp != "ignore":
# if mainNode.type == "BSDF_PRINCIPLED":
# if len(mainNode.inputs[4].links) == 0:
# if scene.TLM_SceneProperties.tlm_metallic_clamp == "zero":
# mainNode.inputs[4].default_value = 0.0
# else:
# mainNode.inputs[4].default_value = 0.99
# else:
# pass
#Add all nodes first
#Add lightmap multipliction texture
mixNode = node_tree.nodes.new(type="ShaderNodeMixRGB")
mixNode.name = "Lightmap_Multiplication"
mixNode.location = -300, 300
mixNode.location = -800, 300
if scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO":
mixNode.blend_type = 'ADD'
else:
mixNode.blend_type = 'MULTIPLY'
mixNode.inputs[0].default_value = 1.0
if scene.TLM_EngineProperties.tlm_lighting_mode == "complete":
mixNode.inputs[0].default_value = 0.0
else:
mixNode.inputs[0].default_value = 1.0
UVLightmap = node_tree.nodes.new(type="ShaderNodeUVMap")
UVLightmap.uv_map = "UVMap_Lightmap"
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
else:
uv_channel = "UVMap_Lightmap"
UVLightmap.uv_map = uv_channel
UVLightmap.name = "Lightmap_UV"
UVLightmap.location = -1000, 300
UVLightmap.location = -1500, 300
if(scene.TLM_SceneProperties.tlm_decoder_setup):
if scene.TLM_SceneProperties.tlm_encoding_device == "CPU":
@ -196,7 +196,7 @@ def apply_materials():
baseColorValue = mainNode.inputs[0].default_value
baseColorNode = node_tree.nodes.new(type="ShaderNodeRGB")
baseColorNode.outputs[0].default_value = baseColorValue
baseColorNode.location = ((mainNode.location[0] - 500, mainNode.location[1] - 300))
baseColorNode.location = ((mainNode.location[0] - 1100, mainNode.location[1] - 300))
baseColorNode.name = "Lightmap_BasecolorNode_A"
else:
baseColorNode = mainNode.inputs[0].links[0].from_node
@ -235,13 +235,19 @@ def apply_materials():
mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[0]) #Connect mixnode to pbr node
mat.node_tree.links.new(UVLightmap.outputs[0], lightmapNode.inputs[0]) #Connect uvnode to lightmapnode
#If skip metallic
if scene.TLM_SceneProperties.tlm_metallic_clamp == "skip":
if mainNode.inputs[4].default_value > 0.1: #DELIMITER
moutput = mainNode.inputs[0].links[0].from_node
mat.node_tree.links.remove(moutput.outputs[0].links[0])
def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"):
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(ext_postfix, new_postfix, formatHDR)
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
for slot in obj.material_slots:
mat = slot.material
@ -267,6 +273,53 @@ def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"):
for image in bpy.data.images:
image.reload()
def applyAOPass():
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
for slot in obj.material_slots:
mat = slot.material
node_tree = mat.node_tree
nodes = mat.node_tree.nodes
for node in nodes:
if node.name == "Baked Image" or node.name == "TLM_Lightmap":
filepath = bpy.data.filepath
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
LightmapPath = node.image.filepath_raw
filebase = os.path.basename(LightmapPath)
filename = os.path.splitext(filebase)[0]
extension = os.path.splitext(filebase)[1]
AOImagefile = filename[:-4] + "_ao"
AOImagePath = os.path.join(dirpath, AOImagefile + extension)
AOMap = nodes.new('ShaderNodeTexImage')
AOMap.name = "TLM_AOMap"
AOImage = bpy.data.images.load(AOImagePath)
AOMap.image = AOImage
AOMap.location = -800, 0
AOMult = nodes.new(type="ShaderNodeMixRGB")
AOMult.name = "TLM_AOMult"
AOMult.blend_type = 'MULTIPLY'
AOMult.inputs[0].default_value = 1.0
AOMult.location = -300, 300
multyNode = nodes["Lightmap_Multiplication"]
mainNode = nodes["Principled BSDF"]
UVMapNode = nodes["Lightmap_UV"]
node_tree.links.remove(multyNode.outputs[0].links[0])
node_tree.links.new(multyNode.outputs[0], AOMult.inputs[1])
node_tree.links.new(AOMap.outputs[0], AOMult.inputs[2])
node_tree.links.new(AOMult.outputs[0], mainNode.inputs[0])
node_tree.links.new(UVMapNode.outputs[0], AOMap.inputs[0])
def load_library(asset_name):
scriptDir = os.path.dirname(os.path.realpath(__file__))

View file

@ -1,4 +1,4 @@
import bpy
import bpy, math
from . import cache
from .. utility import *
@ -31,13 +31,16 @@ def configure_lights():
def configure_meshes(self):
for obj in bpy.data.objects:
if obj.type == "MESH":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Configuring meshes")
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_restore(obj)
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
cache.backup_material_rename(obj)
@ -59,9 +62,18 @@ def configure_meshes(self):
scene = bpy.context.scene
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
if scene.TLM_SceneProperties.tlm_apply_on_unwrap:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Applying transform to: " + obj.name)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
obj.hide_select = False #Remember to toggle this back
for slot in obj.material_slots:
if "." + slot.name + '_Original' in bpy.data.materials:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
@ -77,33 +89,35 @@ def configure_meshes(self):
bpy.ops.object.select_all(action='DESELECT')
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
uv_layers = obj.data.uv_layers
if not "UVMap_Lightmap" in uv_layers:
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
else:
uv_channel = "UVMap_Lightmap"
if not uv_channel in uv_layers:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("UVMap made A")
uvmap = uv_layers.new(name="UVMap_Lightmap")
print("UV map created for object: " + obj.name)
uvmap = uv_layers.new(name=uv_channel)
uv_layers.active_index = len(uv_layers) - 1
else:
print("Existing found...skipping")
print("Existing UV map found for object: " + obj.name)
for i in range(0, len(uv_layers)):
if uv_layers[i].name == 'UVMap_Lightmap':
uv_layers.active_index = i
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Lightmap shift A")
break
atlas_items.append(obj)
obj.select_set(True)
if scene.TLM_SceneProperties.tlm_apply_on_unwrap:
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
if atlasgroup.tlm_atlas_lightmap_unwrap_mode == "SmartProject":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Smart Project A for: " + str(atlas_items))
print("Atlasgroup Smart Project for: " + str(atlas_items))
for obj in atlas_items:
print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name)
@ -112,7 +126,12 @@ def configure_meshes(self):
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=atlasgroup.tlm_atlas_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False)
#API changes in 2.91 causes errors:
if (2, 91, 0) > bpy.app.version:
bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False)
else:
angle = math.radians(45.0)
bpy.ops.uv.smart_project(angle_limit=angle, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, area_weight=1.0, correct_aspect=True, scale_to_bounds=False)
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Lightmap":
@ -123,8 +142,9 @@ def configure_meshes(self):
bpy.ops.object.mode_set(mode='OBJECT')
elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Xatlas":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Temporary skip: COPYING SMART PROJECT")
print("Using Xatlas on Atlas Group: " + atlas)
for obj in atlas_items:
obj.select_set(True)
@ -139,176 +159,213 @@ def configure_meshes(self):
else:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Copied Existing A")
print("Copied Existing UV Map for Atlas Group: " + atlas)
for obj in bpy.data.objects:
if obj.type == "MESH":
for obj in bpy.context.scene.objects:
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
iterNum = iterNum + 1
for obj in bpy.data.objects:
if obj.type == "MESH":
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
for obj in bpy.context.scene.objects:
if obj.name in bpy.context.view_layer.objects: #Possible fix for view layer error
if obj.type == 'MESH' and obj.name in bpy.context.view_layer.objects:
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
objWasHidden = False
objWasHidden = False
#For some reason, a Blender bug might prevent invisible objects from being smart projected
#We will turn the object temporarily visible
obj.hide_viewport = False
obj.hide_set(False)
#For some reason, a Blender bug might prevent invisible objects from being smart projected
#We will turn the object temporarily visible
obj.hide_viewport = False
obj.hide_set(False)
currentIterNum = currentIterNum + 1
currentIterNum = currentIterNum + 1
#Configure selection
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
obs = bpy.context.view_layer.objects
active = obs.active
#Configure selection
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
#Provide material if none exists
preprocess_material(obj, scene)
obs = bpy.context.view_layer.objects
active = obs.active
#UV Layer management here
if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
uv_layers = obj.data.uv_layers
if not "UVMap_Lightmap" in uv_layers:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("UVMap made B")
uvmap = uv_layers.new(name="UVMap_Lightmap")
uv_layers.active_index = len(uv_layers) - 1
#Provide material if none exists
preprocess_material(obj, scene)
#If lightmap
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap":
if scene.TLM_SceneProperties.tlm_apply_on_unwrap:
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin)
#If smart project
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject":
#UV Layer management here
if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
uv_layers = obj.data.uv_layers
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
uv_channel = obj.TLM_ObjectProperties.tlm_uv_channel
else:
uv_channel = "UVMap_Lightmap"
if not uv_channel in uv_layers:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Smart Project B")
if scene.TLM_SceneProperties.tlm_apply_on_unwrap:
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False)
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Xatlas":
print("UV map created for obj: " + obj.name)
uvmap = uv_layers.new(name=uv_channel)
uv_layers.active_index = len(uv_layers) - 1
if scene.TLM_SceneProperties.tlm_apply_on_unwrap:
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
#If lightmap
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Lightmap":
bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin)
#import blender_xatlas
#blender_xatlas.Unwrap_Lightmap_Group_Xatlas_2(bpy.context)
#If smart project
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject":
#bpy.ops.object.setup_unwrap()
Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj)
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer)
else: #if copy existing
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Copied Existing B")
else:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Existing found...skipping")
for i in range(0, len(uv_layers)):
if uv_layers[i].name == 'UVMap_Lightmap':
uv_layers.active_index = i
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Lightmap shift B")
break
print("Smart Project B")
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
#API changes in 2.91 causes errors:
if (2, 91, 0) > bpy.app.version:
bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False)
else:
angle = math.radians(45.0)
bpy.ops.uv.smart_project(angle_limit=angle, island_margin=obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin, area_weight=1.0, correct_aspect=True, scale_to_bounds=False)
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "Xatlas":
Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj)
#Sort out nodes
for slot in obj.material_slots:
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
nodetree = slot.material.node_tree
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer)
else: #if copy existing
outputNode = nodetree.nodes[0] #Presumed to be material output node
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Copied Existing UV Map for object: " + obj.name)
if(outputNode.type != "OUTPUT_MATERIAL"):
else:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Existing UV map found for obj: " + obj.name)
for i in range(0, len(uv_layers)):
if uv_layers[i].name == uv_channel:
uv_layers.active_index = i
break
#print(x)
#Sort out nodes
for slot in obj.material_slots:
nodetree = slot.material.node_tree
outputNode = nodetree.nodes[0] #Presumed to be material output node
if(outputNode.type != "OUTPUT_MATERIAL"):
for node in nodetree.nodes:
if node.type == "OUTPUT_MATERIAL":
outputNode = node
break
mainNode = outputNode.inputs[0].links[0].from_node
if mainNode.type not in ['BSDF_PRINCIPLED','BSDF_DIFFUSE','GROUP']:
#TODO! FIND THE PRINCIPLED PBR
self.report({'INFO'}, "The primary material node is not supported. Seeking first principled.")
if len(find_node_by_type(nodetree.nodes, Node_Types.pbr_node)) > 0:
mainNode = find_node_by_type(nodetree.nodes, Node_Types.pbr_node)[0]
else:
self.report({'INFO'}, "No principled found. Seeking diffuse")
if len(find_node_by_type(nodetree.nodes, Node_Types.diffuse)) > 0:
mainNode = find_node_by_type(nodetree.nodes, Node_Types.diffuse)[0]
else:
self.report({'INFO'}, "No supported nodes. Continuing anyway.")
if mainNode.type == 'GROUP':
if mainNode.node_tree != "Armory PBR":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("The material group is not supported!")
if (mainNode.type == "BSDF_PRINCIPLED"):
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("BSDF_Principled")
if scene.TLM_EngineProperties.tlm_directional_mode == "None":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Directional mode")
if not len(mainNode.inputs[19].links) == 0:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("NOT LEN 0")
ninput = mainNode.inputs[19].links[0]
noutput = mainNode.inputs[19].links[0].from_node
nodetree.links.remove(noutput.outputs[0].links[0])
#Clamp metallic
if bpy.context.scene.TLM_SceneProperties.tlm_metallic_clamp == "limit":
MainMetNodeSocket = mainNode.inputs[4]
if not len(MainMetNodeSocket.links) == 0:
nodes = nodetree.nodes
MetClampNode = nodes.new('ShaderNodeClamp')
MetClampNode.location = (-200,150)
MetClampNode.inputs[2].default_value = 0.9
minput = mainNode.inputs[4].links[0] #Metal input socket
moutput = mainNode.inputs[4].links[0].from_node #Metal output node
nodetree.links.remove(moutput.outputs[0].links[0]) #Works
nodetree.links.new(moutput.outputs[0], MetClampNode.inputs[0]) #minput node to clamp node
nodetree.links.new(MetClampNode.outputs[0],MainMetNodeSocket) #clamp node to metinput
else:
if mainNode.inputs[4].default_value > 0.9:
mainNode.inputs[4].default_value = 0.9
elif bpy.context.scene.TLM_SceneProperties.tlm_metallic_clamp == "zero":
MainMetNodeSocket = mainNode.inputs[4]
if not len(MainMetNodeSocket.links) == 0:
nodes = nodetree.nodes
MetClampNode = nodes.new('ShaderNodeClamp')
MetClampNode.location = (-200,150)
MetClampNode.inputs[2].default_value = 0.0
minput = mainNode.inputs[4].links[0] #Metal input socket
moutput = mainNode.inputs[4].links[0].from_node #Metal output node
nodetree.links.remove(moutput.outputs[0].links[0]) #Works
nodetree.links.new(moutput.outputs[0], MetClampNode.inputs[0]) #minput node to clamp node
nodetree.links.new(MetClampNode.outputs[0],MainMetNodeSocket) #clamp node to metinput
else:
mainNode.inputs[4].default_value = 0.0
if (mainNode.type == "BSDF_DIFFUSE"):
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("BSDF_Diffuse")
# if (mainNode.type == "BSDF_DIFFUSE"):
# if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
# print("BSDF_Diffuse")
#TODO FIX THIS PART!
#THIS IS USED IN CASES WHERE FOR SOME REASON THE USER FORGETS TO CONNECT SOMETHING INTO THE OUTPUT MATERIAL
for slot in obj.material_slots:
nodetree = bpy.data.materials[slot.name].node_tree
nodes = nodetree.nodes
#First search to get the first output material type
for node in nodetree.nodes:
if node.type == "OUTPUT_MATERIAL":
outputNode = node
mainNode = node
break
mainNode = outputNode.inputs[0].links[0].from_node
#Fallback to get search
if not mainNode.type == "OUTPUT_MATERIAL":
mainNode = nodetree.nodes.get("Material Output")
if mainNode.type not in ['BSDF_PRINCIPLED','BSDF_DIFFUSE','GROUP']:
#Last resort to first node in list
if not mainNode.type == "OUTPUT_MATERIAL":
mainNode = nodetree.nodes[0].inputs[0].links[0].from_node
#TODO! FIND THE PRINCIPLED PBR
self.report({'INFO'}, "The primary material node is not supported. Seeking first principled.")
# for node in nodes:
# if "LM" in node.name:
# nodetree.links.new(node.outputs[0], mainNode.inputs[0])
if len(find_node_by_type(nodetree.nodes, Node_Types.pbr_node)) > 0:
mainNode = find_node_by_type(nodetree.nodes, Node_Types.pbr_node)[0]
else:
self.report({'INFO'}, "No principled found. Seeking diffuse")
if len(find_node_by_type(nodetree.nodes, Node_Types.diffuse)) > 0:
mainNode = find_node_by_type(nodetree.nodes, Node_Types.diffuse)[0]
else:
self.report({'INFO'}, "No supported nodes. Continuing anyway.")
if mainNode.type == 'GROUP':
if mainNode.node_tree != "Armory PBR":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("The material group is not supported!")
if (mainNode.type == "BSDF_PRINCIPLED"):
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("BSDF_Principled")
if scene.TLM_EngineProperties.tlm_directional_mode == "None":
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("Directional mode")
if not len(mainNode.inputs[19].links) == 0:
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("NOT LEN 0")
ninput = mainNode.inputs[19].links[0]
noutput = mainNode.inputs[19].links[0].from_node
nodetree.links.remove(noutput.outputs[0].links[0])
#Clamp metallic
if(mainNode.inputs[4].default_value == 1):
mainNode.inputs[4].default_value = 0.0
if (mainNode.type == "BSDF_DIFFUSE"):
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print("BSDF_Diffuse")
for slot in obj.material_slots:
nodetree = bpy.data.materials[slot.name].node_tree
nodes = nodetree.nodes
#First search to get the first output material type
for node in nodetree.nodes:
if node.type == "OUTPUT_MATERIAL":
mainNode = node
break
#Fallback to get search
if not mainNode.type == "OUTPUT_MATERIAL":
mainNode = nodetree.nodes.get("Material Output")
#Last resort to first node in list
if not mainNode.type == "OUTPUT_MATERIAL":
mainNode = nodetree.nodes[0].inputs[0].links[0].from_node
for node in nodes:
if "LM" in node.name:
nodetree.links.new(node.outputs[0], mainNode.inputs[0])
for node in nodes:
if "Lightmap" in node.name:
nodes.remove(node)
# for node in nodes:
# if "Lightmap" in node.name:
# nodes.remove(node)
def preprocess_material(obj, scene):
if len(obj.material_slots) == 0:
@ -537,7 +594,7 @@ def store_existing(prev_container):
selected = []
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
if obj.select_get():
selected.append(obj.name)

View file

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

View file

@ -332,6 +332,205 @@ def encodeImageRGBDGPU(image, maxRange, outDir, quality):
#Todo - Find a way to save
#bpy.ops.image.save_all_modified()
#TODO - FINISH THIS
def encodeImageRGBMGPU(image, maxRange, outDir, quality):
input_image = bpy.data.images[image.name]
image_name = input_image.name
offscreen = gpu.types.GPUOffScreen(input_image.size[0], input_image.size[1])
image = input_image
vertex_shader = '''
uniform mat4 ModelViewProjectionMatrix;
in vec2 texCoord;
in vec2 pos;
out vec2 texCoord_interp;
void main()
{
//gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f);
//gl_Position.z = 1.0;
gl_Position = vec4(pos.xy, 100, 100);
texCoord_interp = texCoord;
}
'''
fragment_shader = '''
in vec2 texCoord_interp;
out vec4 fragColor;
uniform sampler2D image;
//Code from here: https://github.com/BabylonJS/Babylon.js/blob/master/src/Shaders/ShadersInclude/helperFunctions.fx
const float PI = 3.1415926535897932384626433832795;
const float HALF_MIN = 5.96046448e-08; // Smallest positive half.
const float LinearEncodePowerApprox = 2.2;
const float GammaEncodePowerApprox = 1.0 / LinearEncodePowerApprox;
const vec3 LuminanceEncodeApprox = vec3(0.2126, 0.7152, 0.0722);
const float Epsilon = 0.0000001;
#define saturate(x) clamp(x, 0.0, 1.0)
float maxEps(float x) {
return max(x, Epsilon);
}
float toLinearSpace(float color)
{
return pow(color, LinearEncodePowerApprox);
}
vec3 toLinearSpace(vec3 color)
{
return pow(color, vec3(LinearEncodePowerApprox));
}
vec4 toLinearSpace(vec4 color)
{
return vec4(pow(color.rgb, vec3(LinearEncodePowerApprox)), color.a);
}
vec3 toGammaSpace(vec3 color)
{
return pow(color, vec3(GammaEncodePowerApprox));
}
vec4 toGammaSpace(vec4 color)
{
return vec4(pow(color.rgb, vec3(GammaEncodePowerApprox)), color.a);
}
float toGammaSpace(float color)
{
return pow(color, GammaEncodePowerApprox);
}
float square(float value)
{
return value * value;
}
// Check if configurable value is needed.
const float rgbdMaxRange = 255.0;
vec4 toRGBM(vec3 color) {
vec4 rgbm;
color *= 1.0/6.0;
rgbm.a = saturate( max( max( color.r, color.g ), max( color.b, 1e-6 ) ) );
rgbm.a = clamp(floor(D) / 255.0, 0., 1.);
rgbm.rgb = color / rgbm.a;
return
float maxRGB = maxEps(max(color.r, max(color.g, color.b)));
float D = max(rgbdMaxRange / maxRGB, 1.);
D = clamp(floor(D) / 255.0, 0., 1.);
vec3 rgb = color.rgb * D;
// Helps with png quantization.
rgb = toGammaSpace(rgb);
return vec4(rgb, D);
}
vec3 fromRGBD(vec4 rgbd) {
// Helps with png quantization.
rgbd.rgb = toLinearSpace(rgbd.rgb);
// return rgbd.rgb * ((rgbdMaxRange / 255.0) / rgbd.a);
return rgbd.rgb / rgbd.a;
}
void main()
{
fragColor = toRGBM(texture(image, texCoord_interp).rgb);
}
'''
x_screen = 0
off_x = -100
off_y = -100
y_screen_flip = 0
sx = 200
sy = 200
vertices = (
(x_screen + off_x, y_screen_flip - off_y),
(x_screen + off_x, y_screen_flip - sy - off_y),
(x_screen + off_x + sx, y_screen_flip - sy - off_y),
(x_screen + off_x + sx, y_screen_flip - off_x))
if input_image.colorspace_settings.name != 'Linear':
input_image.colorspace_settings.name = 'Linear'
# Removing .exr or .hdr prefix
if image_name[-4:] == '.exr' or image_name[-4:] == '.hdr':
image_name = image_name[:-4]
target_image = bpy.data.images.get(image_name + '_encoded')
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(image_name + '_encoded')
if not target_image:
target_image = bpy.data.images.new(
name = image_name + '_encoded',
width = input_image.size[0],
height = input_image.size[1],
alpha = True,
float_buffer = False
)
shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
batch = batch_for_shader(
shader, 'TRI_FAN',
{
"pos": vertices,
"texCoord": ((0, 1), (0, 0), (1, 0), (1, 1)),
},
)
if image.gl_load():
raise Exception()
with offscreen.bind():
bgl.glActiveTexture(bgl.GL_TEXTURE0)
bgl.glBindTexture(bgl.GL_TEXTURE_2D, image.bindcode)
shader.bind()
shader.uniform_int("image", 0)
batch.draw(shader)
buffer = bgl.Buffer(bgl.GL_BYTE, input_image.size[0] * input_image.size[1] * 4)
bgl.glReadBuffer(bgl.GL_BACK)
bgl.glReadPixels(0, 0, input_image.size[0], input_image.size[1], bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)
offscreen.free()
target_image.pixels = [v / 255 for v in buffer]
input_image = target_image
#Save LogLuv
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
print(input_image.name)
input_image.filepath_raw = outDir + "/" + input_image.name + ".png"
#input_image.filepath_raw = outDir + "_encoded.png"
input_image.file_format = "PNG"
bpy.context.scene.render.image_settings.quality = quality
#input_image.save_render(filepath = input_image.filepath_raw, scene = bpy.context.scene)
input_image.save()
#Todo - Find a way to save
#bpy.ops.image.save_all_modified()
def encodeImageRGBMCPU(image, maxRange, outDir, quality):
input_image = bpy.data.images[image.name]
image_name = input_image.name
@ -431,21 +630,6 @@ def encodeImageRGBDCPU(image, maxRange, outDir, quality):
result_pixel[i+1] = math.pow(result_pixel[i+1] * D, 1/2.2)
result_pixel[i+2] = math.pow(result_pixel[i+2] * D, 1/2.2)
result_pixel[i+3] = D
# for i in range(0,num_pixels,4):
# m = saturate(max(result_pixel[i], result_pixel[i+1], result_pixel[i+2], 1e-6))
# d = max(maxRange / m, 1)
# #d = saturate(math.floor(d) / 255.0)
# d = np.clip((math.floor(d) / 255.0), 0.0, 1.0)
# #TODO TO GAMMA SPACE
# result_pixel[i] = math.pow(result_pixel[i] * d * 255 / maxRange, 1/2.2)
# result_pixel[i+1] = math.pow(result_pixel[i+1] * d * 255 / maxRange, 1/2.2)
# result_pixel[i+2] = math.pow(result_pixel[i+2] * d * 255 / maxRange, 1/2.2)
# result_pixel[i+3] = d
target_image.pixels = result_pixel
@ -457,25 +641,4 @@ def encodeImageRGBDCPU(image, maxRange, outDir, quality):
input_image.filepath_raw = outDir + "/" + input_image.name + ".png"
input_image.file_format = "PNG"
bpy.context.scene.render.image_settings.quality = quality
input_image.save()
# const float rgbdMaxRange = 255.0;
# vec4 toRGBD(vec3 color) {
# float maxRGB = maxEps(max(color.r, max(color.g, color.b)));
# float D = max(rgbdMaxRange / maxRGB, 1.);
# D = clamp(floor(D) / 255.0, 0., 1.);
# vec3 rgb = color.rgb * D;
# // Helps with png quantization.
# rgb = toGammaSpace(rgb);
# return vec4(rgb, D);
# }
# const float Epsilon = 0.0000001;
# #define saturate(x) clamp(x, 0.0, 1.0)
# float maxEps(float x) {
# return max(x, Epsilon);
# }
input_image.save()

View file

@ -62,7 +62,7 @@ class TLM_CV_Filtering:
#SEAM TESTING# #####################
if obj_name in bpy.data.objects:
if obj_name in bpy.context.scene.objects:
override = bpy.data.objects[obj_name].TLM_ObjectProperties.tlm_mesh_filter_override
elif obj_name in scene.TLM_AtlasList:
override = False

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

View file

@ -1,5 +1,5 @@
import bpy.ops as O
import bpy, os, re, sys, importlib, struct, platform, subprocess, threading, string, bmesh
import bpy, os, re, sys, importlib, struct, platform, subprocess, threading, string, bmesh, shutil, glob, uuid
from io import StringIO
from threading import Thread
from queue import Queue, Empty
@ -81,15 +81,8 @@ def save_image(image):
image.filepath_raw = savepath
# if "Normal" in image.name:
# bpy.context.scene.render.image_settings.quality = 90
# image.save_render( filepath = image.filepath_raw, scene = bpy.context.scene )
# else:
image.save()
def get_file_size(filepath):
size = "Unpack Files"
try:
@ -141,7 +134,7 @@ def check_is_org_material(self,material):
def clean_empty_materials(self):
for obj in bpy.data.objects:
for obj in bpy.context.scene.objects:
for slot in obj.material_slots:
mat = slot.material
if mat is None:
@ -319,6 +312,11 @@ def lightmap_to_ao(material,lightmap_node):
# https://github.com/mattedicksoncom/blender-xatlas/
###########################################################
def gen_safe_name():
genId = uuid.uuid4().hex
# genId = "u_" + genId.replace("-","_")
return "u_" + genId
def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
blender_xatlas = importlib.util.find_spec("blender_xatlas")
@ -330,32 +328,54 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
packOptions = bpy.context.scene.pack_tool
chartOptions = bpy.context.scene.chart_tool
sharedProperties = bpy.context.scene.shared_properties
#sharedProperties.unwrapSelection
context = bpy.context
if obj.type == 'MESH':
context.view_layer.objects.active = obj
if obj.data.users > 1:
obj.data = obj.data.copy() #make single user copy
uv_layers = obj.data.uv_layers
#setup the lightmap uvs
uvName = "UVMap_Lightmap"
if sharedProperties.lightmapUVChoiceType == "NAME":
uvName = sharedProperties.lightmapUVName
elif sharedProperties.lightmapUVChoiceType == "INDEX":
if sharedProperties.lightmapUVIndex < len(uv_layers):
uvName = uv_layers[sharedProperties.lightmapUVIndex].name
#save whatever mode the user was in
startingMode = bpy.context.object.mode
selected_objects = bpy.context.selected_objects
if not uvName in uv_layers:
uvmap = uv_layers.new(name=uvName)
uv_layers.active_index = len(uv_layers) - 1
else:
for i in range(0, len(uv_layers)):
if uv_layers[i].name == uvName:
uv_layers.active_index = i
obj.select_set(True)
#check something is actually selected
#external function/operator will select them
if len(selected_objects) == 0:
print("Nothing Selected")
self.report({"WARNING"}, "Nothing Selected, please select Something")
return {'FINISHED'}
#store the names of objects to be lightmapped
rename_dict = dict()
safe_dict = dict()
#make sure all the objects have ligthmap uvs
for obj in selected_objects:
if obj.type == 'MESH':
safe_name = gen_safe_name();
rename_dict[obj.name] = (obj.name,safe_name)
safe_dict[safe_name] = obj.name
context.view_layer.objects.active = obj
if obj.data.users > 1:
obj.data = obj.data.copy() #make single user copy
uv_layers = obj.data.uv_layers
#setup the lightmap uvs
uvName = "UVMap_Lightmap"
if sharedProperties.lightmapUVChoiceType == "NAME":
uvName = sharedProperties.lightmapUVName
elif sharedProperties.lightmapUVChoiceType == "INDEX":
if sharedProperties.lightmapUVIndex < len(uv_layers):
uvName = uv_layers[sharedProperties.lightmapUVIndex].name
if not uvName in uv_layers:
uvmap = uv_layers.new(name=uvName)
uv_layers.active_index = len(uv_layers) - 1
else:
for i in range(0, len(uv_layers)):
if uv_layers[i].name == uvName:
uv_layers.active_index = i
obj.select_set(True)
#save all the current edges
if sharedProperties.packOnly:
@ -381,8 +401,11 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
bpy.ops.object.mode_set(mode='OBJECT')
#Create a fake obj export to a string
#Will strip this down further later
fakeFile = StringIO()
blender_xatlas.export_obj_simple.save(
rename_dict=rename_dict,
context=bpy.context,
filepath=fakeFile,
mainUVChoiceType=sharedProperties.mainUVChoiceType,
@ -393,20 +416,26 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
use_mesh_modifiers=True,
use_edges=True,
use_smooth_groups=False,
use_smooth_groups_bitflags=False,
use_smooth_groups_bitflags=False,
use_normals=True,
use_uvs=True,
use_materials=False,
use_triangles=False,
use_nurbs=False,
use_vertex_groups=False,
use_nurbs=False,
use_vertex_groups=False,
use_blen_objects=True,
group_by_object=False,
group_by_material=False,
keep_vertex_order=False,
)
file_path = os.path.dirname(os.path.abspath(blender_xatlas.__file__))
#print just for reference
# print(fakeFile.getvalue())
#get the path to xatlas
#file_path = os.path.dirname(os.path.abspath(__file__))
scriptsDir = bpy.utils.user_resource('SCRIPTS', "addons")
file_path = os.path.join(scriptsDir, "blender_xatlas")
if platform.system() == "Windows":
xatlas_path = os.path.join(file_path, "xatlas", "xatlas-blender.exe")
elif platform.system() == "Linux":
@ -458,6 +487,8 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
shell=True
)
print(xatlas_path)
#shove the fake file in stdin
stdin = xatlas_process.stdin
value = bytes(fakeFile.getvalue() + "\n", 'UTF-8') #The \n is needed to end the input properly
@ -482,17 +513,17 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
obName: string = ""
uvArray: List[float] = field(default_factory=list)
faceArray: List[int] = field(default_factory=list)
convertedObjects = []
uvArrayComplete = []
#search through the out put for STARTOBJ
#then start reading the objects
obTest = None
startRead = False
for line in outObj.splitlines():
line_split = line.split()
if not line_split:
@ -504,14 +535,14 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
print("Start reading the objects----------------------------------------")
startRead = True
# obTest = uvObject()
if startRead:
#if it's a new obj
if line_start == 'o':
#if there is already an object append it
if obTest is not None:
convertedObjects.append(obTest)
obTest = uvObject() #create new uv object
obTest.obName = line_split[1]
@ -536,9 +567,9 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
#append the final object
convertedObjects.append(obTest)
# print(convertedObjects)
print(convertedObjects)
#apply the output-------------------------------------------------------------
#copy the uvs to the original objects
# objIndex = 0
@ -548,7 +579,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
bpy.ops.object.select_all(action='DESELECT')
obTest = importObject
obTest.obName = safe_dict[obTest.obName] #probably shouldn't just replace it
bpy.context.scene.objects[obTest.obName].select_set(True)
context.view_layer.objects.active = bpy.context.scene.objects[obTest.obName]
bpy.ops.object.mode_set(mode = 'OBJECT')
@ -563,7 +594,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
nFaces = len(bm.faces)
#need to ensure lookup table for some reason?
if hasattr(bm.faces, "ensure_lookup_table"):
if hasattr(bm.faces, "ensure_lookup_table"):
bm.faces.ensure_lookup_table()
#loop through the faces
@ -601,7 +632,7 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
currentObject = bpy.context.scene.objects[edgeList['object']]
bm = bmesh.new()
bm.from_mesh(currentObject.data)
if hasattr(bm.edges, "ensure_lookup_table"):
if hasattr(bm.edges, "ensure_lookup_table"):
bm.edges.ensure_lookup_table()
#assume that all the triangulated edges come after the original edges
@ -617,6 +648,27 @@ def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj):
bm.free()
bpy.ops.object.mode_set(mode='EDIT')
#End setting the quads back again------------------------------------------------------------
#End setting the quads back again-------------------------------------------------------------
print("Finished Xatlas----------------------------------------")
#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."""
bl_idname = 'arm.node_add_input'
bl_label = 'Add Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
@ -135,6 +136,7 @@ class ArmNodeAddInputValueButton(bpy.types.Operator):
"""Add new input"""
bl_idname = 'arm.node_add_input_value'
bl_label = 'Add Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
@ -148,6 +150,7 @@ class ArmNodeRemoveInputButton(bpy.types.Operator):
"""Remove last input"""
bl_idname = 'arm.node_remove_input'
bl_label = 'Remove Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
def execute(self, context):
@ -163,6 +166,7 @@ class ArmNodeRemoveInputValueButton(bpy.types.Operator):
"""Remove last input"""
bl_idname = 'arm.node_remove_input_value'
bl_label = 'Remove Input'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
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"""
bl_idname = 'arm.node_add_output'
bl_label = 'Add Output'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
socket_type: StringProperty(name='Socket Type', default='NodeSocketShader')
@ -201,6 +206,7 @@ class ArmNodeRemoveOutputButton(bpy.types.Operator):
"""Remove last output"""
bl_idname = 'arm.node_remove_output'
bl_label = 'Remove Output'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
def execute(self, context):
@ -216,6 +222,7 @@ class ArmNodeAddInputOutputButton(bpy.types.Operator):
"""Add new input and output"""
bl_idname = 'arm.node_add_input_output'
bl_label = 'Add Input Output'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
in_socket_type: StringProperty(name='In Socket Type', default='NodeSocketShader')
@ -246,6 +253,7 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
"""Remove last input and output"""
bl_idname = 'arm.node_remove_input_output'
bl_label = 'Remove Input Output'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
def execute(self, context):
@ -265,7 +273,7 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
class ArmNodeSearch(bpy.types.Operator):
bl_idname = "arm.node_search"
bl_label = "Search..."
bl_options = {"REGISTER"}
bl_options = {"REGISTER", "INTERNAL"}
bl_property = "item"
def get_search_items(self, context):

View file

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

View file

@ -433,7 +433,7 @@ def compilation_server_done():
def build_done():
print('Finished in {:0.3f}s'.format(time.time() - profile_time))
if log.num_warnings > 0:
log.print_warn(f'{log.num_warnings} warnings occurred during compilation')
log.print_warn(f'{log.num_warnings} warning{"s" if log.num_warnings > 1 else ""} occurred during compilation')
if state.proc_build is None:
return
result = state.proc_build.poll()

View file

@ -17,17 +17,59 @@ shader_datas = []
def build():
"""Builds world shaders for all exported worlds."""
global shader_datas
bpy.data.worlds['Arm'].world_defs = ''
wrd = bpy.data.worlds['Arm']
rpdat = arm.utils.get_rp()
mobile_mat = rpdat.arm_material_model == 'Mobile' or rpdat.arm_material_model == 'Solid'
envpath = os.path.join(arm.utils.get_fp_build(), 'compiled', 'Assets', 'envmaps')
wrd.world_defs = ''
worlds = []
shader_datas = []
for scene in bpy.data.scenes:
# Only export worlds from enabled scenes
if scene.arm_export and scene.world is not None and scene.world not in worlds:
worlds.append(scene.world)
create_world_shaders(scene.world)
with write_probes.setup_envmap_render():
for scene in bpy.data.scenes:
world = scene.world
# Only export worlds from enabled scenes and only once per world
if scene.arm_export and world is not None and world not in worlds:
worlds.append(world)
world.arm_envtex_name = ''
create_world_shaders(world)
if rpdat.arm_irradiance:
# Plain background color
if '_EnvCol' in world.world_defs:
world_name = arm.utils.safestr(world.name)
# Irradiance json file name
world.arm_envtex_name = world_name
world.arm_envtex_irr_name = world_name
write_probes.write_color_irradiance(world_name, world.arm_envtex_color)
# Render world to envmap for (ir)radiance, if no
# other probes are exported
elif world.arm_envtex_name == '':
write_probes.render_envmap(envpath, world)
filename = f'env_{arm.utils.safesrc(world.name)}'
image_file = f'{filename}.jpg'
image_filepath = os.path.join(envpath, image_file)
world.arm_envtex_name = image_file
world.arm_envtex_irr_name = os.path.basename(image_filepath).rsplit('.', 1)[0]
write_radiance = rpdat.arm_radiance and not mobile_mat
mip_count = write_probes.write_probes(image_filepath, True, world.arm_envtex_num_mips, write_radiance)
world.arm_envtex_num_mips = mip_count
if write_radiance:
# Set world def, everything else is handled by write_probes()
wrd.world_defs += '_Rad'
def create_world_shaders(world: bpy.types.World):
@ -131,14 +173,7 @@ def build_node_tree(world: bpy.types.World, frag: Shader, vert: Shader, con: Sha
col = world.color
world.arm_envtex_color = [col[0], col[1], col[2], 1.0]
world.arm_envtex_strength = 1.0
# Irradiance/Radiance: clear to color if no texture or sky is provided
if rpdat.arm_irradiance or rpdat.arm_irradiance:
if '_EnvSky' not in world.world_defs and '_EnvTex' not in world.world_defs and '_EnvImg' not in world.world_defs:
# Irradiance json file name
world.arm_envtex_name = world_name
world.arm_envtex_irr_name = world_name
write_probes.write_color_irradiance(world_name, world.arm_envtex_color)
world.world_defs += '_EnvCol'
# Clouds enabled
if rpdat.arm_clouds and world.arm_use_clouds:
@ -279,7 +314,12 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
func_cloud_radiance = 'float cloudRadiance(vec3 p, vec3 dir) {\n'
if '_EnvSky' in world.world_defs:
func_cloud_radiance += '\tvec3 sun_dir = hosekSunDirection;\n'
# Nishita sky
if 'vec3 sunDir' in frag.uniforms:
func_cloud_radiance += '\tvec3 sun_dir = sunDir;\n'
# Hosek
else:
func_cloud_radiance += '\tvec3 sun_dir = hosekSunDirection;\n'
else:
func_cloud_radiance += '\tvec3 sun_dir = vec3(0, 0, -1);\n'
func_cloud_radiance += '''\tconst int steps = 8;
@ -293,7 +333,7 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
}'''
frag.add_function(func_cloud_radiance)
frag.add_function('''vec3 traceClouds(vec3 sky, vec3 dir) {
func_trace_clouds = '''vec3 traceClouds(vec3 sky, vec3 dir) {
\tconst float step_size = 0.5 / float(cloudsSteps);
\tfloat T = 1.0;
\tfloat C = 0.0;
@ -312,6 +352,17 @@ def frag_write_clouds(world: bpy.types.World, frag: Shader):
\t\t}
\t\tuv += (dir.xy / dir.z) * step_size * cloudsUpper;
\t}
'''
\treturn vec3(C) + sky * T;
}''')
if world.arm_darken_clouds:
func_trace_clouds += '\t// Darken clouds when the sun is low\n'
# Nishita sky
if 'vec3 sunDir' in frag.uniforms:
func_trace_clouds += '\tC *= smoothstep(-0.02, 0.25, sunDir.z);\n'
# Hosek
else:
func_trace_clouds += '\tC *= smoothstep(0.04, 0.32, hosekSunDirection.z);\n'
func_trace_clouds += '\treturn vec3(C) + sky * T;\n}'
frag.add_function(func_trace_clouds)

View file

@ -642,8 +642,47 @@ def to_vec1(v):
return str(v)
def to_vec2(v):
return f'vec2({v[0]}, {v[1]})'
def to_vec3(v):
return 'vec3({0}, {1}, {2})'.format(v[0], v[1], v[2])
return f'vec3({v[0]}, {v[1]}, {v[2]})'
def cast_value(val: str, from_type: str, to_type: str) -> str:
"""Casts a value that is already parsed in a glsl string to another
value in a string.
vec2 types are not supported (not used in the node editor) and there
is no cast towards int types. If casting from vec3 to vec4, the w
coordinate/alpha channel is filled with a 1.
If this function is called with invalid parameters, a TypeError is
raised.
"""
if from_type == to_type:
return val
if from_type in ('int', 'float'):
if to_type in ('int', 'float'):
return val
elif to_type in ('vec2', 'vec3', 'vec4'):
return f'{to_type}({val})'
elif from_type == 'vec3':
if to_type == 'float':
return rgb_to_bw(val)
elif to_type == 'vec4':
return f'vec4({val}, 1.0)'
elif from_type == 'vec4':
if to_type == 'float':
return rgb_to_bw(val)
elif to_type == 'vec3':
return f'{val}.xyz'
raise TypeError("Invalid type cast in shader!")
def rgb_to_bw(res_var: vec3str) -> floatstr:

View file

@ -1,6 +1,8 @@
import bpy
from typing import Union
import bpy
import mathutils
import arm.log as log
import arm.material.cycles as c
import arm.material.cycles_functions as c_functions
@ -10,42 +12,81 @@ import arm.utils
def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
# Color
if out_socket == node.outputs[0]:
# Vertex colors only for now
state.con.add_elem('col', 'short4norm')
return 'vcolor'
out_type = 'float' if out_socket.type == 'VALUE' else 'vec3'
# Vector
elif out_socket == node.outputs[1]:
# UV maps only for now
state.con.add_elem('tex', 'short2norm')
if node.attribute_name == 'time':
state.curshader.add_uniform('float time', link='_time')
if out_socket == node.outputs[3]:
return '1.0'
return c.cast_value('time', from_type='float', to_type=out_type)
# UV maps (higher priority) and vertex colors
if node.attribute_type == 'GEOMETRY':
# Alpha output. Armory doesn't support vertex colors with alpha
# values yet and UV maps don't have an alpha channel
if out_socket == node.outputs[3]:
return '1.0'
# UV maps
mat = c.mat_get_material()
mat_users = c.mat_get_material_users()
if mat_users is not None and mat in mat_users:
mat_user = mat_users[mat][0]
# No UV layers for Curve
# Curves don't have uv layers, so check that first
if hasattr(mat_user.data, 'uv_layers'):
lays = mat_user.data.uv_layers
# First UV map referenced
if len(lays) > 0 and node.attribute_name == lays[0].name:
state.con.add_elem('tex', 'short2norm')
return c.cast_value('vec3(texCoord.x, 1.0 - texCoord.y, 0.0)', from_type='vec3', to_type=out_type)
# Second UV map referenced
if len(lays) > 1 and node.attribute_name == lays[1].name:
elif len(lays) > 1 and node.attribute_name == lays[1].name:
state.con.add_elem('tex1', 'short2norm')
return 'vec3(texCoord1.x, 1.0 - texCoord1.y, 0.0)'
return c.cast_value('vec3(texCoord1.x, 1.0 - texCoord1.y, 0.0)', from_type='vec3', to_type=out_type)
return 'vec3(texCoord.x, 1.0 - texCoord.y, 0.0)'
# Vertex colors
# TODO: support multiple vertex color sets
state.con.add_elem('col', 'short4norm')
return c.cast_value('vcolor', from_type='vec3', to_type=out_type)
# Fac
else:
if node.attribute_name == 'time':
state.curshader.add_uniform('float time', link='_time')
return 'time'
# Check object properties
# see https://developer.blender.org/rB6fdcca8de6 for reference
mat = c.mat_get_material()
mat_users = c.mat_get_material_users()
if mat_users is not None and mat in mat_users:
# Use first material user for now...
mat_user = mat_users[mat][0]
# Return 0.0 till drivers are implemented
else:
return '0.0'
val = None
# Custom properties first
if node.attribute_name in mat_user:
val = mat_user[node.attribute_name]
# Blender properties
elif hasattr(mat_user, node.attribute_name):
val = getattr(mat_user, node.attribute_name)
if val is not None:
if isinstance(val, float):
return c.cast_value(str(val), from_type='float', to_type=out_type)
elif isinstance(val, int):
return c.cast_value(str(val), from_type='int', to_type=out_type)
elif isinstance(val, mathutils.Vector) and len(val) <= 4:
out = val.to_4d()
if out_socket == node.outputs[3]:
return c.to_vec1(out[3])
return c.cast_value(c.to_vec3(out), from_type='vec3', to_type=out_type)
# Default values, attribute name did not match
if out_socket == node.outputs[3]:
return '1.0'
return c.cast_value('0.0', from_type='float', to_type=out_type)
def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:

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

View file

@ -1,3 +1,4 @@
import math
import os
from typing import Union
@ -293,13 +294,29 @@ def parse_tex_sky(node: bpy.types.ShaderNodeTexSky, out_socket: bpy.types.NodeSo
# Pass through
return c.to_vec3([0.0, 0.0, 0.0])
state.world.world_defs += '_EnvSky'
if node.sky_type == 'PREETHAM' or node.sky_type == 'HOSEK_WILKIE':
if node.sky_type == 'PREETHAM':
log.info('Info: Preetham sky model is not supported, using Hosek Wilkie sky model instead')
return parse_sky_hosekwilkie(node, state)
elif node.sky_type == 'NISHITA':
return parse_sky_nishita(node, state)
else:
log.error(f'Unsupported sky model: {node.sky_type}!')
return c.to_vec3([0.0, 0.0, 0.0])
def parse_sky_hosekwilkie(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str:
world = state.world
curshader = state.curshader
# Match to cycles
world.arm_envtex_strength *= 0.1
world.world_defs += '_EnvSky'
assets.add_khafile_def('arm_hosek')
curshader.add_uniform('vec3 A', link="_hosekA")
curshader.add_uniform('vec3 B', link="_hosekB")
@ -312,10 +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 Z', link="_hosekZ")
curshader.add_uniform('vec3 hosekSunDirection', link="_hosekSunDirection")
curshader.add_function('''vec3 hosekWilkie(float cos_theta, float gamma, float cos_gamma) {
curshader.add_function("""vec3 hosekWilkie(float cos_theta, float gamma, float cos_gamma) {
\tvec3 chi = (1 + cos_gamma * cos_gamma) / pow(1 + H * H - 2 * cos_gamma * H, vec3(1.5));
\treturn (1 + A * exp(B / (cos_theta + 0.01))) * (C + D * exp(E * gamma) + F * (cos_gamma * cos_gamma) + G * chi + I * sqrt(cos_theta));
}''')
}""")
world.arm_envtex_sun_direction = [node.sun_direction[0], node.sun_direction[1], node.sun_direction[2]]
world.arm_envtex_turbidity = node.turbidity
@ -353,6 +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;'
def parse_sky_nishita(node: bpy.types.ShaderNodeTexSky, state: ParserState) -> vec3str:
curshader = state.curshader
curshader.add_include('std/sky.glsl')
curshader.add_uniform('vec3 sunDir', link='_sunDirection')
curshader.add_uniform('sampler2D nishitaLUT', link='_nishitaLUT', included=True,
tex_addr_u='clamp', tex_addr_v='clamp')
curshader.add_uniform('vec2 nishitaDensity', link='_nishitaDensity', included=True)
planet_radius = 6360e3 # Earth radius used in Blender
ray_origin_z = planet_radius + node.altitude
state.world.arm_nishita_density = [node.air_density, node.dust_density, node.ozone_density]
sun = ''
if node.sun_disc:
# The sun size is calculated relative in terms of the distance
# between the sun position and the sky dome normal at every
# pixel (see sun_disk() in sky.glsl).
#
# An isosceles triangle is created with the camera at the
# opposite side of the base with node.sun_size being the vertex
# angle from which the base angle theta is calculated. Iron's
# skydome geometry roughly resembles a unit sphere, so the leg
# size is set to 1. The base size is the doubled normal-relative
# target size.
# sun_size is already in radians despite being degrees in the UI
theta = 0.5 * (math.pi - node.sun_size)
size = math.cos(theta)
sun = f'* sun_disk(n, sunDir, {size}, {node.sun_intensity})'
return f'nishita_atmosphere(n, vec3(0, 0, {ray_origin_z}), sunDir, {planet_radius}){sun}'
def parse_tex_environment(node: bpy.types.ShaderNodeTexEnvironment, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
if state.context == ParserContext.OBJECT:
log.warn('Environment Texture node is not supported for object node trees, using default value')

View file

@ -73,7 +73,8 @@ def is_transluc_type(node):
node.type == 'BSDF_TRANSPARENT' or \
node.type == 'BSDF_TRANSLUCENT' or \
(node.type == 'GROUP' and node.node_tree.name.startswith('Armory PBR') and (node.inputs[1].is_linked or node.inputs[1].default_value != 1.0)) or \
(node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 20 and (node.inputs[18].is_linked or node.inputs[18].default_value != 1.0)):
(node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 20 and (node.inputs[18].is_linked or node.inputs[18].default_value != 1.0)) or \
(node.type == 'BSDF_PRINCIPLED' and len(node.inputs) > 21 and (node.inputs[19].is_linked or node.inputs[19].default_value != 1.0)):
return True
return False

View file

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

View file

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

View file

@ -178,7 +178,7 @@ def init_properties():
('ErrorsOnly', 'Errors Only', 'Show only errors')],
name="Compile Log Parameter", update=assets.invalidate_compiler_cache,
default="Summary")
bpy.types.World.arm_project_win_build_cpu = IntProperty(name="Count CPU", description="Specifies the maximum number of concurrent processes to use when building", default=1, min=1, max=multiprocessing.cpu_count())
bpy.types.World.arm_project_win_build_cpu = IntProperty(name="CPU Count", description="Specifies the maximum number of concurrent processes to use when building", default=1, min=1, max=multiprocessing.cpu_count())
bpy.types.World.arm_project_win_build_open = BoolProperty(name="Open Build Directory", description="Open the build directory after successfully assemble", default=False)
bpy.types.World.arm_project_icon = StringProperty(name="Icon (PNG)", description="Exported project icon, must be a PNG image", default="", subtype="FILE_PATH", update=assets.invalidate_compiler_cache)
@ -212,11 +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_texture_quality = FloatProperty(name="Texture Quality", default=1.0, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache)
bpy.types.World.arm_sound_quality = FloatProperty(name="Sound Quality", default=0.9, min=0.0, max=1.0, subtype='FACTOR', update=assets.invalidate_compiler_cache)
bpy.types.World.arm_minimize = BoolProperty(name="Minimize Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data)
bpy.types.World.arm_minimize = BoolProperty(name="Binary Scene Data", description="Export scene data in binary", default=True, update=assets.invalidate_compiled_data)
bpy.types.World.arm_minify_js = BoolProperty(name="Minify JS", description="Minimize JavaScript output when publishing", default=True)
bpy.types.World.arm_optimize_data = BoolProperty(name="Optimize Data", description="Export more efficient geometry and shader data, prolongs build times", default=True, update=assets.invalidate_compiled_data)
bpy.types.World.arm_deinterleaved_buffers = BoolProperty(name="Deinterleaved Buffers", description="Use deinterleaved vertex buffers", default=False, update=assets.invalidate_compiler_cache)
bpy.types.World.arm_export_tangents = BoolProperty(name="Export Tangents", description="Precompute tangents for normal mapping, otherwise computed in shader", default=True, update=assets.invalidate_compiled_data)
bpy.types.World.arm_export_tangents = BoolProperty(name="Precompute Tangents", description="Precompute tangents for normal mapping, otherwise computed in shader", default=True, update=assets.invalidate_compiled_data)
bpy.types.World.arm_batch_meshes = BoolProperty(name="Batch Meshes", description="Group meshes by materials to speed up rendering", default=False, update=assets.invalidate_compiler_cache)
bpy.types.World.arm_batch_materials = BoolProperty(name="Batch Materials", description="Marge similar materials into single pipeline state", default=False, update=assets.invalidate_shader_cache)
bpy.types.World.arm_stream_scene = BoolProperty(name="Stream Scene", description="Stream scene content", default=False, update=assets.invalidate_compiler_cache)
@ -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_turbidity = FloatProperty(name="Turbidity", default=1.0)
bpy.types.World.arm_envtex_ground_albedo = FloatProperty(name="Ground Albedo", default=0.0)
bpy.types.World.arm_nishita_density = FloatVectorProperty(name="Nishita Density", size=3, default=[1, 1, 1])
bpy.types.Material.arm_cast_shadow = BoolProperty(name="Cast Shadow", default=True)
bpy.types.Material.arm_receive_shadow = BoolProperty(name="Receive Shadow", description="Requires forward render path", default=True)
bpy.types.Material.arm_overlay = BoolProperty(name="Overlay", default=False)
@ -398,7 +399,7 @@ def init_properties():
('destination_color', 'Destination Color', 'Destination Color'),
('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'),
('inverse_destination_color', 'Inverse Destination Color', 'Inverse Destination Color')],
name='Source', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache)
name='Source (Alpha)', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache)
bpy.types.Material.arm_blending_destination_alpha = EnumProperty(
items=[('blend_one', 'One', 'One'),
('blend_zero', 'Zero', 'Zero'),
@ -410,14 +411,14 @@ def init_properties():
('destination_color', 'Destination Color', 'Destination Color'),
('inverse_source_color', 'Inverse Source Color', 'Inverse Source Color'),
('inverse_destination_color', 'Inverse Destination Color', 'Inverse Destination Color')],
name='Destination', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache)
name='Destination (Alpha)', default='blend_one', description='Blending factor', update=assets.invalidate_shader_cache)
bpy.types.Material.arm_blending_operation_alpha = EnumProperty(
items=[('add', 'Add', 'Add'),
('subtract', 'Subtract', 'Subtract'),
('reverse_subtract', 'Reverse Subtract', 'Reverse Subtract'),
('min', 'Min', 'Min'),
('max', 'Max', 'Max')],
name='Operation', default='add', description='Blending operation', update=assets.invalidate_shader_cache)
name='Operation (Alpha)', default='add', description='Blending operation', update=assets.invalidate_shader_cache)
# For scene
bpy.types.Scene.arm_export = BoolProperty(name="Export", description="Export scene data", default=True)
bpy.types.Scene.arm_terrain_textures = StringProperty(name="Textures", description="Set root folder for terrain assets", default="//Bundled/", subtype="DIR_PATH")
@ -442,6 +443,10 @@ def init_properties():
bpy.types.World.compo_defs = StringProperty(name="Compositor Shader Defs", default='')
bpy.types.World.arm_use_clouds = BoolProperty(name="Clouds", default=False, update=assets.invalidate_shader_cache)
bpy.types.World.arm_darken_clouds = BoolProperty(
name="Darken Clouds at Night",
description="Darkens the clouds when the sun is low. This setting is for artistic purposes and is not physically correct",
default=False, update=assets.invalidate_shader_cache)
bpy.types.World.arm_clouds_lower = FloatProperty(name="Lower", default=1.0, min=0.1, max=10.0, update=assets.invalidate_shader_cache)
bpy.types.World.arm_clouds_upper = FloatProperty(name="Upper", default=1.0, min=0.1, max=10.0, update=assets.invalidate_shader_cache)
bpy.types.World.arm_clouds_wind = FloatVectorProperty(name="Wind", default=[1.0, 0.0], size=2, update=assets.invalidate_shader_cache)

View file

@ -1,6 +1,5 @@
import bpy
from bpy.props import *
from bpy.types import Panel
class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel):
bl_label = "Collections Filter Mask"
@ -18,7 +17,7 @@ class ARM_PT_RbCollisionFilterMaskPanel(bpy.types.Panel):
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_split = False
layout.use_property_decorate = False
obj = context.object
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.label(text=f'Integer Mask Value: {str(int(col_mask, 2))}')
def register():
bpy.utils.register_class(ARM_PT_RbCollisionFilterMaskPanel)
def unregister():
bpy.utils.unregister_class(ARM_PT_RbCollisionFilterMaskPanel)

View file

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

View file

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

View file

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

View file

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

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
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:
"""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 os
import sys
import subprocess
import json
import re
import arm.utils
import subprocess
import bpy
import arm.assets as assets
import arm.log as log
import arm.utils
def add_irr_assets(output_file_irr):
assets.add(output_file_irr + '.arm')
def add_rad_assets(output_file_rad, rad_format, num_mips):
assets.add(output_file_rad + '.' + rad_format)
for i in range(0, num_mips):
assets.add(output_file_rad + '_' + str(i) + '.' + rad_format)
# Generate probes from environment map
def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True):
@contextmanager
def setup_envmap_render():
"""Creates a background scene for rendering environment textures.
Use it as a context manager to automatically clean up on errors.
"""
rpdat = arm.utils.get_rp()
radiance_size = int(rpdat.arm_radiance_size)
# Render worlds in a different scene so that there are no other
# objects. The actual scene might be called differently if the name
# is already taken
scene = bpy.data.scenes.new("_arm_envmap_render")
scene.render.engine = "CYCLES"
scene.render.image_settings.file_format = "JPEG"
scene.render.image_settings.quality = 100
scene.render.resolution_x = radiance_size
scene.render.resolution_y = radiance_size // 2
# Set GPU as rendering device if the user enabled it
if bpy.context.preferences.addons["cycles"].preferences.compute_device_type == "CUDA":
scene.cycles.device = "GPU"
else:
log.info('Armory: Using CPU for environment render (might be slow). Enable CUDA if possible.')
# One sample is enough for world background only
scene.cycles.samples = 1
# Setup scene
cam = bpy.data.cameras.new("_arm_cam_envmap_render")
cam_obj = bpy.data.objects.new("_arm_cam_envmap_render", cam)
scene.collection.objects.link(cam_obj)
scene.camera = cam_obj
cam_obj.location = [0.0, 0.0, 0.0]
cam.type = "PANO"
cam.cycles.panorama_type = "EQUIRECTANGULAR"
cam_obj.rotation_euler = [math.radians(90), 0, math.radians(-90)]
try:
yield
finally:
bpy.data.objects.remove(cam_obj)
bpy.data.cameras.remove(cam)
bpy.data.scenes.remove(scene)
def render_envmap(target_dir: str, world: bpy.types.World):
"""Renders an environment texture for the given world into the
target_dir. Use in combination with setup_envmap_render()."""
scene = bpy.data.scenes["_arm_envmap_render"]
scene.world = world
render_path = os.path.join(target_dir, f"env_{arm.utils.safesrc(world.name)}.jpg")
scene.render.filepath = render_path
bpy.ops.render.render(write_still=True, scene=scene.name)
def write_probes(image_filepath: str, disable_hdr: bool, cached_num_mips: int, arm_radiance=True) -> int:
"""Generate probes from environment map and returns the mipmap count"""
envpath = arm.utils.get_fp_build() + '/compiled/Assets/envmaps'
if not os.path.exists(envpath):
@ -63,7 +127,7 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True
scaled_file = output_file_rad + '.' + rad_format
if arm.utils.get_os() == 'win':
output = subprocess.check_output([ \
subprocess.check_output([
kraffiti_path,
'from=' + input_file,
'to=' + scaled_file,
@ -71,35 +135,35 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True
'width=' + str(target_w),
'height=' + str(target_h)])
else:
output = subprocess.check_output([ \
kraffiti_path + \
' from="' + input_file + '"' + \
' to="' + scaled_file + '"' + \
' format=' + rad_format + \
' width=' + str(target_w) + \
' height=' + str(target_h)], shell=True)
subprocess.check_output([
kraffiti_path
+ ' from="' + input_file + '"'
+ ' to="' + scaled_file + '"'
+ ' format=' + rad_format
+ ' width=' + str(target_w)
+ ' height=' + str(target_h)], shell=True)
# Irradiance spherical harmonics
if arm.utils.get_os() == 'win':
subprocess.call([ \
subprocess.call([
cmft_path,
'--input', scaled_file,
'--filter', 'shcoeffs',
'--outputNum', '1',
'--output0', output_file_irr])
else:
subprocess.call([ \
cmft_path + \
' --input ' + '"' + scaled_file + '"' + \
' --filter shcoeffs' + \
' --outputNum 1' + \
' --output0 ' + '"' + output_file_irr + '"'], shell=True)
subprocess.call([
cmft_path
+ ' --input ' + '"' + scaled_file + '"'
+ ' --filter shcoeffs'
+ ' --outputNum 1'
+ ' --output0 ' + '"' + output_file_irr + '"'], shell=True)
sh_to_json(output_file_irr)
add_irr_assets(output_file_irr)
# Mip-mapped radiance
if arm_radiance == False:
if not arm_radiance:
return cached_num_mips
# 4096 = 256 face
@ -200,37 +264,37 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True
if disable_hdr is True:
for f in generated_files:
if arm.utils.get_os() == 'win':
subprocess.call([ \
subprocess.call([
kraffiti_path,
'from=' + f + '.hdr',
'to=' + f + '.jpg',
'format=jpg'])
else:
subprocess.call([ \
kraffiti_path + \
' from="' + f + '.hdr"' + \
' to="' + f + '.jpg"' + \
' format=jpg'], shell=True)
subprocess.call([
kraffiti_path
+ ' from="' + f + '.hdr"'
+ ' to="' + f + '.jpg"'
+ ' format=jpg'], shell=True)
os.remove(f + '.hdr')
# Scale from (4x2 to 1x1>
for i in range (0, 2):
for i in range(0, 2):
last = generated_files[-1]
out = output_file_rad + '_' + str(mip_count + i)
if arm.utils.get_os() == 'win':
subprocess.call([ \
subprocess.call([
kraffiti_path,
'from=' + last + '.' + rad_format,
'to=' + out + '.' + rad_format,
'scale=0.5',
'format=' + rad_format], shell=True)
else:
subprocess.call([ \
kraffiti_path + \
' from=' + '"' + last + '.' + rad_format + '"' + \
' to=' + '"' + out + '.' + rad_format + '"' + \
' scale=0.5' + \
' format=' + rad_format], shell=True)
subprocess.call([
kraffiti_path
+ ' from=' + '"' + last + '.' + rad_format + '"'
+ ' to=' + '"' + out + '.' + rad_format + '"'
+ ' scale=0.5'
+ ' format=' + rad_format], shell=True)
generated_files.append(out)
mip_count += 2
@ -239,6 +303,7 @@ def write_probes(image_filepath, disable_hdr, cached_num_mips, arm_radiance=True
return mip_count
def sh_to_json(sh_file):
"""Parse sh coefs produced by cmft into json array"""
with open(sh_file + '.c') as f:
@ -251,6 +316,8 @@ def sh_to_json(sh_file):
parse_band_floats(irradiance_floats, band0_line)
parse_band_floats(irradiance_floats, band1_line)
parse_band_floats(irradiance_floats, band2_line)
for i in range(0, len(irradiance_floats)):
irradiance_floats[i] /= 2
sh_json = {'irradiance': irradiance_floats}
ext = '.arm' if bpy.data.worlds['Arm'].arm_minimize else ''
@ -259,15 +326,26 @@ def sh_to_json(sh_file):
# Clean up .c
os.remove(sh_file + '.c')
def parse_band_floats(irradiance_floats, band_line):
string_floats = re.findall(r'[-+]?\d*\.\d+|\d+', band_line)
string_floats = string_floats[1:] # Remove 'Band 0/1/2' number
string_floats = string_floats[1:] # Remove 'Band 0/1/2' number
for s in string_floats:
irradiance_floats.append(float(s))
def write_sky_irradiance(base_name):
# Hosek spherical harmonics
irradiance_floats = [1.5519331988822218,2.3352207154503266,2.997277451988076,0.2673894962434794,0.4305630474135794,0.11331825259716752,-0.04453633521758638,-0.038753175134160295,-0.021302768541875794,0.00055858020486499,0.000371654770334503,0.000126606145406403,-0.000135708721978705,-0.000787399554583089,-0.001550090690860059,0.021947399048903773,0.05453650591711572,0.08783641266630278,0.17053593578630663,0.14734127083304463,0.07775404698816404,-2.6924363189795e-05,-7.9350169701934e-05,-7.559914435231e-05,0.27035455385870993,0.23122918445556914,0.12158817295211832]
irradiance_floats = [
1.5519331988822218, 2.3352207154503266, 2.997277451988076,
0.2673894962434794, 0.4305630474135794, 0.11331825259716752,
-0.04453633521758638, -0.038753175134160295, -0.021302768541875794,
0.00055858020486499, 0.000371654770334503, 0.000126606145406403,
-0.000135708721978705, -0.000787399554583089, -0.001550090690860059,
0.021947399048903773, 0.05453650591711572, 0.08783641266630278,
0.17053593578630663, 0.14734127083304463, 0.07775404698816404,
-2.6924363189795e-05, -7.9350169701934e-05, -7.559914435231e-05,
0.27035455385870993, 0.23122918445556914, 0.12158817295211832]
for i in range(0, len(irradiance_floats)):
irradiance_floats[i] /= 2
@ -282,6 +360,7 @@ def write_sky_irradiance(base_name):
assets.add(output_file + '.arm')
def write_color_irradiance(base_name, col):
"""Constant color irradiance"""
# Adjust to Cycles