import bpy
import os
import random
import mathutils
import enum
import math
import time
import importlib
from . import utils
from . import batiments_proceduraux
from . import materiaux

@enum.unique
class DIRECTION8(enum.Enum):
	NORD = 1, # +y
	SUD = 2, # -y
	EST = 3, # +x
	OUEST = 4, # -x
	NORD_OUEST = 5, # -x+y
	SUD_OUEST = 6, # -x-y
	NORD_EST = 7, # +x+y
	SUD_EST = 8 # +x-y

@enum.unique
class DIRECTION4(enum.Enum):
	NORD = 1, # +y
	SUD = 2, # -y
	EST = 3, # +x
	OUEST = 4 # -x

class GrilleDeLots:
	def __init__(self, nbLotsCôté):
		self.nbLotsCôté = nbLotsCôté
		self.lots = [[Lot(self, i, j) for j in range(self.nbLotsCôté)] for i in range(self.nbLotsCôté)]
		self.hauteursCoinsDesLots = [[0 for j in range(self.nbLotsCôté+1)] for i in range(self.nbLotsCôté+1)]
	
	def créerDepuisHeightmap(heightmap):
		nbLotsCôté = int(heightmap.dimensions[0] // 10)
		grille = GrilleDeLots(nbLotsCôté)
		# approximer les hauteurs de la grille sur celles du terrain
		total_points = len(grille.hauteursCoinsDesLots)
		for i in range(total_points):
			for j in range(total_points):
				grille.hauteursCoinsDesLots[i][j] = heightmap.hauteur_absolue_point_arbitraire(i/(total_points-1), j/(total_points-1))
		return grille
		
	
	# retourne un dict avec la location (tuple) du coin pour chaque direction diagonale
	def positionDesCoinsDunLot(self, lotX, lotY):
		moitiéTailleGrilleMètres = self.nbLotsCôté * 10 / 2
		return {
			DIRECTION8.NORD_OUEST:	((lotX+0)*10-moitiéTailleGrilleMètres, (lotY+1)*10-moitiéTailleGrilleMètres, self.hauteursCoinsDesLots[lotX+0][lotY+1]),
			DIRECTION8.SUD_OUEST:	((lotX+0)*10-moitiéTailleGrilleMètres, (lotY+0)*10-moitiéTailleGrilleMètres, self.hauteursCoinsDesLots[lotX+0][lotY+0]),
			DIRECTION8.NORD_EST:	((lotX+1)*10-moitiéTailleGrilleMètres, (lotY+1)*10-moitiéTailleGrilleMètres, self.hauteursCoinsDesLots[lotX+1][lotY+1]),
			DIRECTION8.SUD_EST:		((lotX+1)*10-moitiéTailleGrilleMètres, (lotY+0)*10-moitiéTailleGrilleMètres, self.hauteursCoinsDesLots[lotX+1][lotY+0])
		}
	
	# retounre un tuple (x,y,z)
	def positionCentraleDunLot(self, lotX, lotY):
		moitiéTailleGrilleMètres = self.nbLotsCôté * 10 / 2
		return (
			lotX*10-moitiéTailleGrilleMètres + 5, 
			lotY*10-moitiéTailleGrilleMètres + 5,
			(self.hauteursCoinsDesLots[lotX+0][lotY+0] + self.hauteursCoinsDesLots[lotX+0][lotY+1] + self.hauteursCoinsDesLots[lotX+1][lotY+0] + self.hauteursCoinsDesLots[lotX+1][lotY+1]) / 4,
		)

	def pointLePlusBas(self):
		pointLePlusBas = 9999
		for i in range(self.nbLotsCôté+1):
			for j in range(self.nbLotsCôté+1):
				if self.hauteursCoinsDesLots[i][j] < pointLePlusBas:
					pointLePlusBas = self.hauteursCoinsDesLots[i][j]
		return pointLePlusBas

class Lot:
	def __init__(self, grilleDeLots, x, y):
		self.grille = grilleDeLots
		self.x = x
		self.y = y
		self.routeConstruiteDessus = None
		self.batimentConstruitDessus = None
		# self.contientUnBatiment = False
		self.sol_est_urbain = False
	
	# retourne un Lot
	def voisin(self, direction8):
		if direction8 == DIRECTION8.EST and self.x + 1 < len(self.grille.lots):
			return self.grille.lots[self.x + 1][self.y]
		elif direction8 == DIRECTION8.OUEST and self.x - 1 >= 0:
			return self.grille.lots[self.x - 1][self.y]
		elif direction8 == DIRECTION8.NORD and self.y + 1 < len(self.grille.lots[0]):
			return self.grille.lots[self.x][self.y + 1]
		elif direction8 == DIRECTION8.SUD and self.y - 1 >= 0:
			return self.grille.lots[self.x][self.y - 1]
		elif direction8 == DIRECTION8.NORD_EST and self.x + 1 < len(self.grille.lots) and self.y + 1 < len(self.grille.lots[0]):
			return self.grille.lots[self.x + 1][self.y + 1]
		elif direction8 == DIRECTION8.NORD_OUEST and self.x - 1 >= 0 and self.y + 1 < len(self.grille.lots[0]):
			return self.grille.lots[self.x - 1][self.y + 1]
		elif direction8 == DIRECTION8.SUD_EST and self.x + 1 < len(self.grille.lots) and self.y - 1 >= 0:
			return self.grille.lots[self.x + 1][self.y - 1]
		elif direction8 == DIRECTION8.SUD_OUEST and self.x - 1 >= 0 and self.y - 1 >= 0:
			return self.grille.lots[self.x - 1][self.y - 1]
		return None
		
	def avoir_lot_voisin_qui_contient_route(self):
		if self.voisin(DIRECTION8.EST) and self.voisin(DIRECTION8.EST).routeConstruiteDessus: return self.voisin(DIRECTION8.EST)
		if self.voisin(DIRECTION8.OUEST) and self.voisin(DIRECTION8.OUEST).routeConstruiteDessus: return self.voisin(DIRECTION8.OUEST)
		if self.voisin(DIRECTION8.NORD) and self.voisin(DIRECTION8.NORD).routeConstruiteDessus: return self.voisin(DIRECTION8.NORD)
		if self.voisin(DIRECTION8.SUD) and self.voisin(DIRECTION8.SUD).routeConstruiteDessus: return self.voisin(DIRECTION8.SUD)
		if self.voisin(DIRECTION8.NORD_EST) and self.voisin(DIRECTION8.NORD_EST).routeConstruiteDessus: return self.voisin(DIRECTION8.NORD_EST)
		if self.voisin(DIRECTION8.NORD_OUEST) and self.voisin(DIRECTION8.NORD_OUEST).routeConstruiteDessus: return self.voisin(DIRECTION8.NORD_OUEST)
		if self.voisin(DIRECTION8.SUD_EST) and self.voisin(DIRECTION8.SUD_EST).routeConstruiteDessus: return self.voisin(DIRECTION8.SUD_EST)
		if self.voisin(DIRECTION8.SUD_OUEST) and self.voisin(DIRECTION8.SUD_OUEST).routeConstruiteDessus: return self.voisin(DIRECTION8.SUD_OUEST)
		return False
	
	# retourne un DIRECTION4
	def directionVersRoute(self):
		directionsVersRoute = []
		if self.voisin(DIRECTION8.EST) and self.voisin(DIRECTION8.EST).routeConstruiteDessus: directionsVersRoute.append(DIRECTION4.EST)
		if self.voisin(DIRECTION8.OUEST) and self.voisin(DIRECTION8.OUEST).routeConstruiteDessus: directionsVersRoute.append(DIRECTION4.OUEST)
		if self.voisin(DIRECTION8.SUD) and self.voisin(DIRECTION8.SUD).routeConstruiteDessus: directionsVersRoute.append(DIRECTION4.SUD)
		if self.voisin(DIRECTION8.NORD) and self.voisin(DIRECTION8.NORD).routeConstruiteDessus: directionsVersRoute.append(DIRECTION4.NORD)
		if len(directionsVersRoute) <= 0:
			return random.choice(DIRECTION4.__members__.items())[1]
		return random.choice(directionsVersRoute)
		
	# retourne un dict avec la location du coin pour chaque direction diagonale
	def positionDesCoins(self):
		return self.grille.positionDesCoinsDunLot(self.x, self.y)
		
	# retounre un tuple (x,y,z)
	def positionDuCentre(self):
		return self.grille.positionCentraleDunLot(self.x, self.y)
		
	def estEmergé(self):
		return self.positionDuCentre()[2] >= 0
		
	def estPlat(self):
		posCoins = self.positionDesCoins()
		return abs(posCoins[DIRECTION8.NORD_OUEST][2] - posCoins[DIRECTION8.SUD_OUEST][2]) <= 1e-1 and abs(posCoins[DIRECTION8.NORD_OUEST][2] - posCoins[DIRECTION8.NORD_EST][2]) <= 1e-1 and abs(posCoins[DIRECTION8.SUD_OUEST][2] - posCoins[DIRECTION8.SUD_EST][2]) <= 1e-1

def appliquer_sol_urbain_sur_terrain(grille):
	# appliquer sol urbain
	try:
		sceneterrain = importlib.import_module('sceneterrain')
	except ImportError:
		return
		
	# si terrain existant
	if sceneterrain.terrain.heightmap: 
		# rendre les lots urbains
		for i in range(grille.nbLotsCôté):
			for j in range(grille.nbLotsCôté):
				
				# récupérer le lot
				lot = grille.lots[i][j]
				if not lot.routeConstruiteDessus and not lot.batimentConstruitDessus:
					continue
					
				# urbaniser tout autour
				rayonAutour = random.randint(0,2)
				for lotX in range(i-rayonAutour,i+rayonAutour+1):
					if lotX < 0 or lotX >= grille.nbLotsCôté:
						continue
					for lotY in range(j-rayonAutour,j+rayonAutour+1):
						if lotY < 0 or lotY >= grille.nbLotsCôté:
							continue
						grille.lots[lotX][lotY].sol_est_urbain = True
						
		# MAj masque urbain du terrain
		try:
			tableau_de_booléens_pour_pixels = [[False for i in range(grille.nbLotsCôté)] for j in range(grille.nbLotsCôté)]
			for i in range(grille.nbLotsCôté):
				for j in range(grille.nbLotsCôté):
					tableau_de_booléens_pour_pixels[i][j] = grille.lots[i][j].sol_est_urbain
			sceneterrain.terrain.heightmapMateriauManager.generer_masque_sol_urbain(tableau_de_booléens_pour_pixels)
		except:
			pass
		
	# ajouter le vertex group pour la ville sur le mesh du terrain, et recalculer celui des arbres. Faire appel au mesher
	try:
		dict_indiceVert_weightVille = {}
		nbVertsCôté = sceneterrain.terrain.heightmapMesher.heightmap.résolution
		no_vert_actuel = 0
		for i in range(nbVertsCôté):
			for j in range(nbVertsCôté):
				posX = i / (nbVertsCôté) * sceneterrain.terrain.heightmapMesher.heightmap.dimensions[0]
				posY = j / (nbVertsCôté) * sceneterrain.terrain.heightmapMesher.heightmap.dimensions[1]
				iDansGrilleDeLot = int(posX // 10)
				jDansGrilleDeLot = int(posY // 10)
				dict_indiceVert_weightVille[no_vert_actuel] = 1 if grille.lots[iDansGrilleDeLot][jDansGrilleDeLot].sol_est_urbain else 0
				no_vert_actuel += 1
		sceneterrain.terrain.heightmapMesher.maj_vertexGroup_ville(dict_indiceVert_weightVille)
		# forcer à mettre à jour la distribution des particules
		populator = sceneterrain.terrain.HeightmapPopulator()
		populator.forcer_maj_populations(bpy.data.objects['SceneTerrain'])
	except:
		pass

# retourne un élément au hasard de la liste où chaque élément est un tuple-2, en prenant en compte les poids de probabilité
def choisir_aléatoire_pondéré(liste_tuples_élément_poids):
	total_poids = sum(poids for élément, poids in liste_tuples_élément_poids)
	valeur_aléatoire = random.uniform(0, total_poids)
	départ = 0
	for élément, poids in liste_tuples_élément_poids:
		if départ + poids >= valeur_aléatoire:
			return élément
		départ += poids
	assert False, "Aucun choix possible, impossible normalement"


	
# la méthode de placement est retrouvée par datablock
def placer_objets(liste_dict_choses_à_placer, parent, prefixe_nom_dupli_placeur, prefixe_nom_dupli_placé, couches):
	
	'''
	pour "liste_dict_choses_à_placer" besoin des clés suivantes pour chaque dict: 
		datablock (DATA_Mesh ou Group),
		location (iterable 3 de floats), 
		rotation_euler_radians (iterable 3 de floats), 
		name (string), 
		noms_groupes (iterable de Groups), 
		valeur_pixel_red_size_texture (float, optionel),
		
	Les objets seront ajoutés dans la scene actuelle.
	Les rotations ne sont possibles qu'autour de Z
	couches = iterable de ints
	'''
	
	# met le parent dans les même couches
	# for no_couche in couches:
	# 	parent.layers[no_couche] = True
	# récupérer tous les groupes et mettre le parent dedans
	noms_groupes = []
	for dict_datablock_à_placer in liste_dict_choses_à_placer:
		noms_groupes.extend(dict_datablock_à_placer['noms_groupes'])
	for nom_groupe in noms_groupes:
		# try: bpy.data.groups[nom_groupe].objects.link(parent)
		try: bpy.data.collections[nom_groupe].objects.link(parent)
		except: pass
		
	# regrouper les instances par leur datablock
	dict_data_instances = {}
	for dict_datablock_à_placer in liste_dict_choses_à_placer:
		datablock = dict_datablock_à_placer['datablock']
		if datablock not in dict_data_instances:
			dict_data_instances[datablock] = []
		dict_data_instances[datablock].append(dict_datablock_à_placer)
		
	
	no_instance_actuelle = 0
	total_instances_à_créer = len(liste_dict_choses_à_placer)
	temps_dernier_affichage = time.time()
	
	dict_materiaux_par_label = materiaux.avoir_dict_materiaux_par_label()
	
	for datablock, liste_dict_instances in dict_data_instances.items():
		
		méthode_placement = datablock.SceneCity_mesh.méthode_placement if type(datablock) is bpy.types.Mesh else datablock.SceneCity.méthode_placement
		
		### méthode dupliverts
		if méthode_placement == 'dupliverts':
			# créé le mesh+objet placeur, et les met dans les bons groupes
			mesh_placeur = bpy.data.meshes.new(name=prefixe_nom_dupli_placeur+' dupli positions for: '+datablock.name)
			obj_placeur = bpy.data.objects.new(name=mesh_placeur.name, object_data=mesh_placeur)
			# bpy.context.scene.objects.link(obj_placeur)
			obj_placeur.location = (0,0,0)
			obj_placeur.parent = parent
			obj_placeur.instance_type = 'VERTS'
			obj_placeur.use_instance_vertices_rotation = True
			# for dict_datablock_à_placer in liste_dict_instances: <= JE COMPRENDS PAS POURQUOI IL NE FAUT PAS METTRE CETTE LIGNE........????
			# for nom_group in dict_datablock_à_placer['noms_groupes']:
			# 	try: bpy.data.groups[nom_group].objects.link(obj_placeur)
			# 	except: pass

			
			
			# chose à placer est un groupe => créé instance groupe et renomme nouvelle instance
			nom_nouvelle_instance = prefixe_nom_dupli_placé+' duplicated datablock: '+datablock.name
			if type(datablock) == bpy.types.Collection:
				groupe = datablock
				bpy.ops.object.collection_instance_add(name=groupe.name)
				obj_à_placer = bpy.data.objects[groupe.name]
				obj_à_placer.name = nom_nouvelle_instance
				try: bpy.context.scene.collection.objects.unlink(obj_à_placer)
				except: pass
			
			# chose à placer est un mesh => créé nouvel objet, et lui associe le mesh
			elif type(datablock) == bpy.types.Mesh:
				obj_à_placer = bpy.data.objects.new(name=nom_nouvelle_instance, object_data=datablock)
				# bpy.context.scene.objects.link(obj_à_placer)
			
			obj_à_placer.location = (0,0,0)
			obj_à_placer.parent = obj_placeur
			# for no_couche in couches:
			# 	obj_placeur.layers[no_couche] = True
			# 	obj_à_placer.layers[no_couche] = True
			for nom_group in dict_datablock_à_placer['noms_groupes']:
				try:
					collection = bpy.data.collections[nom_group]
				except:
					collection = bpy.data.collections.new(name=nom_group)
					bpy.context.scene.collection.children.link(collection)
				try: collection.objects.link(obj_placeur)
				except: pass
				try: collection.objects.link(obj_à_placer)
				except: pass
				# bpy.context.scene.collection.objects.unlink(obj_placeur)
			
			# placer les verts
			verts = []
			for dict_datablock_à_placer in liste_dict_instances:
				verts.append(dict_datablock_à_placer['location'])
				no_instance_actuelle += 1
				
				if time.time() - temps_dernier_affichage >= 1:
					temps_dernier_affichage = time.time()
					percentage_done = no_instance_actuelle / total_instances_à_créer * 100
					print('SceneCity | '+str(no_instance_actuelle)+' instances created and placed so far...('+str(round(percentage_done,2))+'%)')
			mesh_placeur.from_pydata(vertices=verts, edges=[], faces=[])
			mesh_placeur.update()
			# orienter les verts
			for nb, dict_datablock_à_placer in enumerate(liste_dict_instances):
				rot_euler = dict_datablock_à_placer['rotation_euler_radians']
				normal_x = -math.sin(rot_euler[2])
				normal_y = math.cos(rot_euler[2])
				mesh_placeur.vertices[nb].normal = normal_x, normal_y, 0
	
		
		
		### méthode objets individuels
		elif méthode_placement == 'objects':
			for dict_datablock_à_placer in liste_dict_instances:
				nom_nouvelle_instance = dict_datablock_à_placer['name']
				
				# chose à placer est un groupe => créé instance groupe et renomme nouvelle instance
				# if type(datablock) == bpy.types.Group:
				if type(datablock) == bpy.types.Collection:
					groupe = datablock
					bpy.ops.object.collection_instance_add(name=groupe.name)
					nouvel_objet_instancié = bpy.data.objects[groupe.name]
					nouvel_objet_instancié.name = nom_nouvelle_instance
				
				# chose à placer est un mesh => créé nouvel objet, et lui associe le mesh
				elif type(datablock) == bpy.types.Mesh:
					nouvel_objet_instancié = bpy.data.objects.new(name=nom_nouvelle_instance, object_data=datablock)
					# bpy.context.scene.objects.link(nouvel_objet_instancié)
					materiaux.appliquer_materiaux_aleatoires(nouvel_objet_instancié, dict_materiaux_par_label)
					
				# instance transformations
				nouvel_objet_instancié.location = dict_datablock_à_placer['location']
				nouvel_objet_instancié.rotation_euler = dict_datablock_à_placer['rotation_euler_radians']
				
				# parent + collections
				nouvel_objet_instancié.parent = parent
				for nom_group in dict_datablock_à_placer['noms_groupes']:
					try:
						collection = bpy.data.collections[nom_group]
					except:
						collection = bpy.data.collections.new(name=nom_group)
						bpy.context.scene.collection.children.link(collection)
					try: collection.objects.link(nouvel_objet_instancié)
					except: pass
					# enlever objet de la collection de la scene
					try: bpy.context.scene.collection.objects.unlink(nouvel_objet_instancié)
					except: pass

				# affichage
				no_instance_actuelle += 1
				if time.time() - temps_dernier_affichage >= 1:
					temps_dernier_affichage = time.time()
					percentage_done = no_instance_actuelle / total_instances_à_créer * 100
					print('SceneCity | '+str(no_instance_actuelle)+' instances created and placed so far...('+str(round(percentage_done,2))+'%)')
					
					
					
		### méthode single merged mesh
		elif méthode_placement == 'merged mesh':
			nom_mesh = prefixe_nom_dupli_placeur + ' merged mesh for '+datablock.name
			# créé le mesh
			merged_mesh = bpy.data.meshes.new(nom_mesh)
			# créé l'objet
			merged_object = bpy.data.objects.new(name=nom_mesh, object_data=merged_mesh)
			# bpy.context.scene.objects.link(merged_object)
			for nom_group in dict_datablock_à_placer['noms_groupes']:
				try:
					collection = bpy.data.collections[nom_group]
				except:
					collection = bpy.data.collections.new(name=nom_group)
					bpy.context.scene.collection.children.link(collection)
				try: collection.objects.link(merged_object)
				except: pass
			
			# parent, layers, groups
			merged_object.parent = parent
			# for no_couche in couches:
			# 	merged_object.layers[no_couche] = True
			# for nom_group in dict_datablock_à_placer['noms_groupes']:
			# 	try: bpy.data.groups[nom_group].objects.link(merged_object)
			# 	except: pass
					
			# créé et merge les instances
			for dict_datablock_à_placer in liste_dict_instances:
				# positionne nlle instance
				nouvel_objet_instancié = bpy.data.objects.new(name='SceneCity temp', object_data=datablock)

				# bpy.context.scene.objects.link(nouvel_objet_instancié)
				for nom_group in dict_datablock_à_placer['noms_groupes']:
					try:
						collection = bpy.data.collections[nom_group]
					except:
						collection = bpy.data.collections.new(name=nom_group)
						bpy.context.scene.collection.children.link(collection)
					try: collection.objects.link(nouvel_objet_instancié)
					except: pass

				bpy.ops.object.select_all(action='DESELECT')
				nouvel_objet_instancié.select_set(True)
				bpy.context.view_layer.objects.active = nouvel_objet_instancié
				
				nouveau_mesh_bati_unique = None
				if datablock.SC_proc_building.bati_procedural_make_unique:
					nouveau_mesh_bati_unique = datablock.copy()
					nouvel_objet_instancié.__data = nouveau_mesh_bati_unique
					verts = []
					faces = []
					no_material_slot_par_no_face = []
					# clear mesh
					bpy.context.view_layer.objects.active = nouvel_objet_instancié
					utils.definir_blender_mode('EDIT')
					bpy.ops.mesh.select_all(action='SELECT')
					bpy.ops.mesh.delete(type='VERT')
					utils.definir_blender_mode('OBJECT')
					
					# TODO: il faut sortir la logique des batiments, ce n'est pas à cette méthode de connaitre chaque algo et de les controler
					
					valeur_pixel_red_size_texture = max(.25,dict_datablock_à_placer['valeur_pixel_red_size_texture'])
						
					# BATI aléatoire cube
					for no_building in range(nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_total_buildings):
						if nouveau_mesh_bati_unique.SceneCity_mesh.batiment_type == 'procedural cube':
							hauteur_sol = random.uniform(nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_sol_hauteur[0], nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_sol_hauteur[1]) if nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_mettre_sol else 0
							surface_constructible = (
								(-nouveau_mesh_bati_unique.SceneCity.batiment_surface_10m_x * 10/2,-nouveau_mesh_bati_unique.SceneCity.batiment_surface_10m_y * 10/2), 
								(nouveau_mesh_bati_unique.SceneCity.batiment_surface_10m_x * 10/2,nouveau_mesh_bati_unique.SceneCity.batiment_surface_10m_y * 10/2))
							batiments_proceduraux.generer_bati_cubes(
								# mesh_datablock = nouveau_mesh_bati_unique,
								verts = verts, 
								faces = faces,
								taille_min_xy = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cube_taille_xy[0],
								taille_max_xy = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cube_taille_xy[1],
								hauteur_min = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cube_taille_z[0] * valeur_pixel_red_size_texture**2,
								hauteur_max = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cube_taille_z[1] * valeur_pixel_red_size_texture**2,
								surface_constructible = surface_constructible,
								
								niveaux_à_construire = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_total_niveaux,
								mettre_cubes_sur_toit = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cubes_mettre_sur_toit,
								cubes_sur_toit_min = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cubes_toits_total[0],
								cubes_sur_toit_max = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cubes_toits_total[1],
								cubes_sur_toit_taille_min = (nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cubes_toits_taille_xy[0], nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cubes_toits_taille_z[0]),
								cubes_sur_toit_taille_max = (nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cubes_toits_taille_xy[1], nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cubes_toits_taille_z[1]),
								hauteur_cube_sol = hauteur_sol if no_building == 0 else 0,
								protection_piétons_activée = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_prot_piétons_activée,
								largeur_protection_piétons = random.uniform(nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_prot_piétons_taille_xy[0], nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_prot_piétons_taille_xy[1]),
								hauteur_protection_piétons = random.uniform(nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_prot_piétons_taille_z[0], nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_prot_piétons_taille_z[1]),
								hauteur_boutiques = random.uniform(nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cube_boutiques_taille_z[0], nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cube_boutiques_taille_z[1]),
								hauteur_départ = 0 if no_building == 0 else hauteur_sol,
								no_material_slot_par_no_face = no_material_slot_par_no_face,
								réduction_taille_niveaux_sup_xy = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cube_size_reduc_xy / 100,
								réduction_taille_niveaux_sup_z = nouveau_mesh_bati_unique.SC_proc_building.bati_procedural_cube_size_reduc_z / 100,
							)
					nouveau_mesh_bati_unique.from_pydata(verts, [], faces)
					nouveau_mesh_bati_unique.update()
					
					# UV unwrap
					utils.definir_blender_mode('EDIT')
					bpy.ops.mesh.select_all(action='SELECT')
					bpy.ops.uv.cube_project()
					utils.definir_blender_mode('OBJECT')
					bpy.context.view_layer.objects.active = bpy.context.object
					
					# assigne material slots aux faces
					for face_nb_mat_slot_nb in no_material_slot_par_no_face:
						while len(nouvel_objet_instancié.material_slots) <= face_nb_mat_slot_nb[1]:
							bpy.ops.object.material_slot_add()
						nouveau_mesh_bati_unique.polygons[face_nb_mat_slot_nb[0]].material_index = face_nb_mat_slot_nb[1]
						
					# assigne matériaux définis par utilisateur sur modèle source aux différents slots
					for i, material in enumerate(datablock.materials):
						nouveau_mesh_bati_unique.materials[i] = material
					
				
				# instance transformations
				nouvel_objet_instancié.location = dict_datablock_à_placer['location']
				nouvel_objet_instancié.rotation_euler = dict_datablock_à_placer['rotation_euler_radians']
			
				# merge nlle instance dans le merged mesh
				merged_object.select_set(True)
				bpy.context.view_layer.objects.active = merged_object
				bpy.ops.object.join()
				
				# supprimer mesh bati unique, sinon il sera utilisé à la prochaine génération des batiments, si on ne ferme pas blender pour nettoyer le mesh automatiquement
				try: bpy.data.meshes.remove(nouveau_mesh_bati_unique)
				except: pass
				
				# affichage progrès
				no_instance_actuelle += 1
				if time.time() - temps_dernier_affichage >= 1:
					temps_dernier_affichage = time.time()
					percentage_done = no_instance_actuelle / total_instances_à_créer * 100
					print('SceneCity | '+str(no_instance_actuelle)+' instances created and placed so far...('+str(round(percentage_done,2))+'%)')
