Back there with the final version of the script.
It works like a charm for baking my AO and DIRT+AO and also for my LODs stripping when i have to rework them 
import bpy
import time
import numpy as np
from mathutils import Vector
import math
#import gpu
#from gpu_extras.batch import batch_for_shader
from bpy.props import (StringProperty,
BoolProperty,
IntProperty,
FloatProperty,
EnumProperty,
PointerProperty,
)
from bpy.types import (Panel,
Operator,
PropertyGroup,
)
def ImageBlit(src:bpy.types.IMAGE_MT_image,dest:bpy.types.IMAGE_MT_image,destX:int,destY:int):
sw, sh = src.size
dw, dh = dest.size
if (destX > dw or destX + sw < 0) or (destY > dh or destY + sh < 0):
return
src_data = np.empty((sw, sh, 4), dtype="f4")
src.pixels.foreach_get(src_data.ravel())
sx1 = max(0, -destX)
sx2 = min(sw, dw - destX)
sy1 = max(0, -destY)
sy2 = min(sh, dh - destY)
data = src_data[sy1:sy2, sx1:sx2, :4]
dest_data = np.empty((dw, dh , 4), dtype="f4")
dest.pixels.foreach_get(dest_data.ravel())
dx1 = max(destX, 0)
dx2 = dx1 + len(data[0])
dy1 = max(destY, 0)
dy2 = dy1 + len(data)
dest_data[dy1: dy2, dx1: dx2, :4] = data
dest.pixels.foreach_set(dest_data.ravel())
dest.update()
#==========================================================================================
# 0-255 to 0.0-1.0 conversion
def ColorByteToFloat(c:int)->float:
return(float(c)/255.0)
#==========================================================================================
# displays small message in the panel window....
def ShowMessage(msg:str):
bpy.context.window_manager.stupidPythonNeed.statusStr = msg
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
#==========================================================================================
def SelectUV(obj,uv:str)->str:
previousActiveUV = obj.data.uv_layers.active.name
bpy.context.object.data.uv_layers[uv].active = True
return(previousActiveUV)
#==========================================================================================
def SetMatsForBake(obj,targetImg,mode:bool):
if(mode==True): # settings for bake
for mat in obj.material_slots:
mat_nodes = mat.material.node_tree.nodes # grab the nodes entry
bumpNode = mat_nodes['DIS / EN bump']
bumpNode.outputs[0].default_value = 0.0 # disable bump
RenderTypeNode = mat_nodes['BAKE / RENDER']
RenderTypeNode.outputs[0].default_value = 0.0 # let's switch to bake mode
mat_nodes.active = mat_nodes['BAKE-TARGET'] # select the bake target image
mat_nodes['BAKE-TARGET'].image = targetImg # and assign the newly created pic
else: # setting for back to default
for mat in obj.material_slots:
mat_nodes = mat.material.node_tree.nodes # grab the nodes entry
bumpNode = mat_nodes['DIS / EN bump']
bumpNode.outputs[0].default_value = 1.0 # re-enable bump
RenderTypeNode = mat_nodes['BAKE / RENDER']
RenderTypeNode.outputs[0].default_value = 1.0 # let's switch to render mode
mat_nodes.active = mat_nodes['BAKE-TARGET'] # select the bake target image
mat_nodes['BAKE-TARGET'].image = None # no pic as default target
#==========================================================================================
def LaunchBake(whatToBake:str,samples:int):
bpy.context.scene.cycles.adaptive_threshold = 0.005
bpy.context.scene.cycles.samples = samples
bpy.context.scene.cycles.adaptive_min_samples = int(samples / 2)
ShowMessage("baking AO....")
bpy.ops.object.bake(type=whatToBake, save_mode='EXTERNAL') # hopefully the temp tex is not saved...
#==========================================================================================
# les objets doivent etre unvrappés avec le SMART UV PROJECT
# sur une texture de 512x1024 ou 1024x1024 ou 2048x1024 ou 4096x1024
# angle 75°
# margin 0.002
# area weight 1
# correct aspect: ON
# scale to bounds: OFF
#
# Les UVs sont corrigés ( rescaled a 0.995, virer les overlays etc... )
# ils sont utilisés pour le rendu AO et DIRT et ensuite intégrés à la texture
# globale AO ou AO-DIRT ( selon le type de bake.... )
class ObjToBakeInfos:
# Le dico qui suit permet le trouver l'objet à bake, de le bake s'il existe dans la liste,
# de copier la texture baked vers la texture AO ou AO-DIRT globale et de resize et replace
# les UVAOBASE vert les UVAO pour unity
# le dico est au format suivant:
# idx, 'nom-de-lobjet-a-bake' , larg-bake-texture, posX-sur-tex-globale, posY-sur-tex-globale
objectsUVAOLocation = [
[ 0, 'LOD0-tour_de_moreti_5-1850' ,1024, 3*1024.0, 7*1024.0],
[ 1, 'kjdhfqlkdjfhlqksdjfh' ,512 , 1*1024.0, 7*1024.0]
]
#---------------------------------------------------------------------------------------------------
def __init__(self): # ????? self ? what is the utility of a class if it can access someone-else other than 'self' XD ?
return
# find object from its name in list.
# return: index or -1 if not found
def Find(self,objName:str)->int:
retour:int=-1
for o in self.objectsUVAOLocation: # browse the list
if(o[1] == objName):
return(o[0]); # break the loop and return the found object index
return(retour);
#---------------------------------------------------------------------------------------------------
def GetTextureWidth(self,idx:int)->int:
return(self.objectsUVAOLocation[idx][2]);
#---------------------------------------------------------------------------------------------------
def GetTextureXPos(self,idx:int)->float:
return(self.objectsUVAOLocation[idx][3]);
#---------------------------------------------------------------------------------------------------
def GetTextureYPos(self,idx:int)->float:
return(self.objectsUVAOLocation[idx][4]);
#==========================================================================================
#==========================================================================================
#==========================================================================================
def Bakes(AOBake:bool,AOSamples:int,DIRTBake:bool,DIRTSamples:int):
#do we have anything to bake ?
if(AOBake==False and DIRTBake==False):
return
# first we get the selected obj name and find it in the dictionnary
obj = bpy.context.object
for a in bpy.context.screen.areas:
if a.type == 'IMAGE_EDITOR':
space = a.spaces[0]
previousImageInUVEditor = space.image
OTBInfos = ObjToBakeInfos()
foundIdx:int=OTBInfos.Find(obj.name) # index of object found or -1 if not found
if(foundIdx==-1):
print("object not found in list !!!!! No bake possible")
return
# temp image creation
tempImageWidth = OTBInfos.GetTextureWidth(foundIdx)
tempImageHeight = 1024
tempImage = bpy.data.images.new('tempImage', tempImageWidth, tempImageHeight)
# should we bake AO ?
if(AOBake==True):
tempImage.generated_color = (1,1,1,1) # make image white
# catch the BIG target texture....
AOTargetImage ='Bake-AO-1850.tga'
targetXPos = OTBInfos.GetTextureXPos(foundIdx)
targetYPos = OTBInfos.GetTextureYPos(foundIdx)
# prepare materials
SetMatsForBake(obj,tempImage,True)
# now select the proper UV layer
previousActiveUV = SelectUV(obj,'UVAOBASE')
# Do the bake
ShowMessage("baking AO....")
LaunchBake('AO',AOSamples)
# retrieve previous active UV
SelectUV(obj,previousActiveUV)
# copy the temp tex at the right place in the AO big texture...
ShowMessage("Sending bake result to global AO Image....")
destBlit = bpy.data.images[AOTargetImage]
ImageBlit(tempImage,destBlit,int(targetXPos),int(targetYPos))
# save the big AO image
ShowMessage("Saving AO image to file....")
destBlit.save()
ShowMessage("Cleaning....")
# retrieve materials base settings
SetMatsForBake(obj,None,False)
# previous image of UV editor
for a in bpy.context.screen.areas:
if a.type == 'IMAGE_EDITOR':
space = a.spaces[0]
space.image = previousImageInUVEditor
# Now let's care about the DIRT.
if(DIRTBake==True): # Should we bake the DIRT ?
tempImage.generated_color = (ColorByteToFloat(63),
ColorByteToFloat(63),
ColorByteToFloat(63),
ColorByteToFloat(255)) # texture background a (63,63,63,255)
# catch the BIG target texture....
AODIRTTargetImage ='Bake-AO-DIRT-1850.tga'
targetXPos = OTBInfos.GetTextureXPos(foundIdx)
targetYPos = OTBInfos.GetTextureYPos(foundIdx)
# prepare materials
SetMatsForBake(obj,tempImage,True)
# now select the proper UV layer
previousActiveUV = SelectUV(obj,'UVAOBASE')
# Do the bake
ShowMessage("baking DIRT+AO....")
LaunchBake('EMIT',DIRTSamples)
# retrieve previous active UV
SelectUV(obj,previousActiveUV)
# copy the temp tex at the right place in the AO big texture...
ShowMessage("Sending bake result to global DIRT+AO Image....")
destBlit = bpy.data.images[AODIRTTargetImage]
ImageBlit(tempImage,destBlit,int(targetXPos),int(targetYPos))
# save the big AO image
ShowMessage("Saving AO-DIRT image to file....")
destBlit.save()
ShowMessage("Cleaning....")
# retrieve materials base settings
SetMatsForBake(obj,None,False)
# previous image of UV editor
for a in bpy.context.screen.areas:
if a.type == 'IMAGE_EDITOR':
space = a.spaces[0]
space.image = previousImageInUVEditor
# delete the now useless temp texture
bpy.data.images.remove(tempImage)
# UV scaling to match big image placement...
if(AOBake==True or DIRTBake==True):
ShowMessage("UV placing....")
# both AO and DIRT+AO images have to be the same size: 8Kx8K
targetWidth,targetHeight = bpy.data.images[AODIRTTargetImage].size
# grab the current mode
previousMode = bpy.context.active_object.mode
# switch to object mode
bpy.ops.object.mode_set ( mode = 'OBJECT' )
UVSrc = obj.data.uv_layers['UVAOBASE'].data
UVDst = obj.data.uv_layers['UVAO'].data
for loop in obj.data.loops :
uv_coords = UVSrc[loop.index].uv # uvcoords seems to be a pointer...
UVDst[loop.index].uv = uv_coords # set from pointed data
uv_coords = UVDst[loop.index].uv # point to newly created data
# modify the pointed data
uv_coords[0] = uv_coords[0] / 8.0+(float(targetXPos)/float(targetWidth))
uv_coords[1] = uv_coords[1] / 8.0+(float(targetYPos)/float(targetHeight))
# back to previous mode
bpy.ops.object.mode_set ( mode = previousMode) # active mode ( object or edit )
return
#==========================================================================================
"""
vertices = (
(100, 100), (300, 100),
(100, 200), (300, 200))
indices = (
(0, 1, 2), (2, 1, 3))
shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
def drawtruc():
shader.bind()
shader.uniform_float("color", (1.0, 0.0, 1.0, 1.0))
batch.draw(shader)
"""
def Dist(A,B)-> float:
return math.sqrt((B[0] - A[0]) ** 2.0 +
(B[1] - A[1]) ** 2.0 +
(B[2] - A[2]) ** 2.0)
def Vector3AlmostEQ(A:Vector,B:Vector,deltaMax:float=0.000001)->bool:
if(Dist(A,B) <= deltaMax):
return(True)
else:
return(False)
def CompareFaces(objVectorsA,faceA,objVectorsB,faceB)->bool:
if(len(faceA.vertices) != len(faceB.vertices)):
return(False)
for idxA in faceA.vertices: # vertices are index in data array
# print(objVectorsA[vA].co)
vertexFound=False
for idxB in faceB.vertices:
if(Vector3AlmostEQ(objVectorsB[idxB].co,objVectorsA[idxA].co) == True):
vertexFound = True
break
if(vertexFound==False):
return(False)
# here we sweeped all verts of A and found them in B
# the face is identical
return(True)
#==========================================================================================
def FindFaceInObj(objA,objB,faceB)->bool:
for f in objA.data.polygons:
if(CompareFaces(objA.data.vertices,f,objB.data.vertices,faceB)==True):
return(True)
return(False)
#==========================================================================================
def Strip(objToStrip,modele):
found:int=0
notfound:int=0
# if we're in edit mode, lets go to object mode....
bpy.ops.object.mode_set(mode = 'OBJECT')
bpy.ops.object.select_all(action='DESELECT')
print(len(objToStrip.data.polygons))
# deselect all faces in objects
bpy.context.view_layer.objects.active = objToStrip
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.select_mode(type = 'FACE')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode = 'OBJECT')
# get the object-to-strip mesh entry
for f in objToStrip.data.polygons:
if(FindFaceInObj(modele,objToStrip,f) == True):
found+=1
else:
f.select = True
notfound+=1
# show the 2 objects comparison
print("found = ",found," / not found = ",notfound)
# now all selected faces have to be deleted
bpy.context.view_layer.objects.active = objToStrip
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode = 'OBJECT')
return
#==========================================================================================
#**************************************************
#
# The class method called at bake button press
#
# python: the art of obfuscating simple things....
#
#**************************************************
class MyBakesEntryPoint (bpy.types.Operator):
bl_idname = "carcass.baker"
bl_label = "Bake !!!!!"
def execute(self, context):
SPN = context.window_manager.stupidPythonNeed
Bakes(SPN.enableAOBake,SPN.baseAORenderSamples,SPN.enableDIRTBake,SPN.baseDIRTRenderSamples)
ShowMessage("Idle....")
return {'FINISHED'}
#==========================================================================================
#**************************************************
#
# The class method called at STRIP button press
#
# python: the art of obfuscating simple things....
#
#**************************************************
class MyStripEntryPoint (bpy.types.Operator):
bl_idname = "carcass.striper"
bl_label = "Strip !!!!!"
def execute(self, context):
SPN = context.window_manager.stupidPythonNeed
if(SPN.objectToStrip != None and SPN.objectModelForStrip != None):
Strip(SPN.objectToStrip,SPN.objectModelForStrip)
return {'FINISHED'}
#==========================================================================================
#**************************************************
#
# The class for parameters ( LAUGH MY ASS OFF !! )
#
# python: the art of obfuscating simple things....
#
#**************************************************
class MyBakesParameters(PropertyGroup):
enableAOBake : BoolProperty(
name="useless stupid thing",
description="Enable AO baking",
default = True)
enableDIRTBake : BoolProperty(
name="useless stupid thing",
description="Enable DIRT baking",
default = True)
baseAORenderSamples : IntProperty(
name="useless stupid thing",
description="Base AO render samples",
default = 32)
baseDIRTRenderSamples : IntProperty(
name="useless stupid thing",
description="Base DIRT render samples",
default = 64)
statusStr : StringProperty(
name="useless stupid thing",
description="Status.....",
default = "Idle....")
objectToStrip : PointerProperty(
name = "Obj to strip",
type = bpy.types.Object,
description="Object to be stripped. Usually lower LOD.\n( Will be modified )")
objectModelForStrip : PointerProperty(
name = "Obj model",
type = bpy.types.Object,
description="Object model stripped. Usually higher LOD.\n( Won't be modified )")
#==========================================================================================
#**************************************************
#
# The class for drawing previous both classes
# ( LOOOOOOOOOOL )
#
# python: the art of obfuscating simple things....
#
#**************************************************
class CarcassBakesPanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "Carcass BAKEs"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
def draw(self, context):
obj = context.active_object
SPN = context.window_manager.stupidPythonNeed
layout = self.layout
box = layout.box()
box.alert=True
box.label(text="Baking Carcassonne AO & DIRT maps", icon='TPAINT_HLT')
box.alert=False
row = box.row()
row.label(text="Active object is: " + obj.name)
row = box.row()
row.prop(SPN,"enableAOBake", text="Bake AO")
row.prop(SPN,"enableDIRTBake", text="Bake DIRT")
row = box.row()
row.prop(SPN,"baseAORenderSamples", text="AO base samples")
row.prop(SPN,"baseDIRTRenderSamples", text="DIRT base samples")
row = box.row()
row.active_default = True
row.operator("carcass.baker")
row.active_default = False
row = box.row()
row.label(text="Status: " + SPN.statusStr)
box = layout.box()
box.alert=True
box.label(text="Object LODs stripper", icon='SHORTDISPLAY')
box.label(text="( remember to work on copies )")
box.alert=False
row = box.row()
row.prop(SPN, "objectToStrip")
row = box.row()
row.prop(SPN, "objectModelForStrip")
row = box.row()
row.active_default = True
row.operator("carcass.striper")
def register():
bpy.utils.register_class(CarcassBakesPanel)
bpy.utils.register_class(MyBakesEntryPoint)
bpy.utils.register_class(MyStripEntryPoint)
bpy.utils.register_class(MyBakesParameters)
bpy.types.WindowManager.stupidPythonNeed = PointerProperty(type=MyBakesParameters)
# bpy.types.SpaceProperties.draw_handler_add(drawtruc, (), 'WINDOW', 'POST_PIXEL')
def unregister():
bpy.utils.unregister_class(MyBakesParameters)
bpy.utils.unregister_class(MyStripEntryPoint)
bpy.utils.unregister_class(MyBakesEntryPoint)
bpy.utils.unregister_class(CarcassBakesPanel)
del bpy.types.WindowManager.stupidPythonNeed
if __name__ == "__main__":
register()
Now i have to figure out how to run this script at launch and also will have a later step for baking impostor parts of LODs…
Hope you like it 
Happy blending !