
bl_info = {
	'name': 'SceneCity',
	'author': 'Arnold (real name: Arnaud Couturier, BA pseudo: piiichan) - couturier.arnaud@gmail.com',
	'version': (1, 9, 1),
	'blender': (2, 93, 0),
	'location': 'New panel type: SceneCity Node Editor',
	'description': 'Generate cities',
	'category': 'Node',
	# 'wiki_url': 'https://www.cgchan.com/store/scenecity/',
	'doc_url': "https://scenecity19doc.cgchan.com",
	# 'tracker_url': 'https://www.cgchan.com/bug_report',
}
# try:
import sys, pathlib, importlib, os, urllib.request, json, time, inspect, pprint, pkgutil, platform
from pathlib import Path

if platform.system().lower() == 'linux':
	current_os_name = 'linux'
elif platform.system().lower() == 'windows':
	current_os_name = 'windows'
	os.system("color")
else:
	current_os_name = 'mac'
libs_path = str(pathlib.Path(__file__).parent / 'libs' / current_os_name)
if libs_path not in sys.path:
	sys.path.append(libs_path)

grille_de_lots = None

est_première_exécution = 'bpy' not in locals()
import bpy, rna_keymap_ui
from bpy.props import (EnumProperty, FloatProperty, IntProperty, BoolProperty, StringProperty)
from bpy_extras.io_utils import ImportHelper
from bpy.utils.toolsystem import ToolDef
from bl_ui.space_toolsystem_toolbar import VIEW3D_PT_tools_active as view3d_tools
import bpy.utils.previews
# from PySide2.QtWidgets import QApplication
from . import (my_globals, utils, preferences, commun, rues, batiments, batiments_proceduraux, materiaux, nodes)

# try: from . import analyze
# except: pass
# try:
# from . import analyze
# from .analyze import code_drawer_treemap
# from .analyze import code_drawer_treemap
# except:
# 	pass

import shapely
# import PySide2
# from PySide2.QtWidgets import QApplication, QLabel

# import scipy
# from . import test
# import shapely.geometry
# linestring2 = shapely.geometry.LineString([(1, 1), (2, 1), (3, 2)])
# polygon = shapely.geometry.Polygon([(0,0),(0,1),(1,1),(1,0)])
# for coord in polygon.exterior.coords:
# 	print(coord)

from .nodes import (textures, images, curves, meshes, layouts, maps, objects, roads, buildings,
					export, terrains, geoms2d, menus, grids, uvs, materials)

# from .ant import add_mesh_ant_landscape, ant_noise, ant_functions, mesh_ant_displace

# from .nodes import grids, uvs, materials

if not est_première_exécution:
	# try: importlib.reload(analyze)
	# except: pass

	importlib.reload(my_globals)
	importlib.reload(utils)
	importlib.reload(preferences)
	importlib.reload(commun)
	importlib.reload(rues)
	importlib.reload(batiments)
	importlib.reload(batiments_proceduraux)
	importlib.reload(materiaux)
	importlib.reload(nodes)
	importlib.reload(nodes.textures)
	importlib.reload(nodes.images)
	importlib.reload(nodes.curves)
	importlib.reload(nodes.meshes)
	importlib.reload(nodes.layouts)
	importlib.reload(nodes.maps)
	importlib.reload(nodes.objects)
	importlib.reload(nodes.roads)
	importlib.reload(nodes.buildings)
	importlib.reload(nodes.export)
	importlib.reload(nodes.terrains)
	importlib.reload(nodes.geoms2d)
	importlib.reload(nodes.menus)
	importlib.reload(nodes.grids)
	importlib.reload(nodes.uvs)
	importlib.reload(nodes.materials)


# importlib.reload(analyze)
# importlib.reload(analyze.code_drawer_treemap)

def avoir_datablocks_rues_par_type():
	rues_détectées_par_type = {
		'STRAIGHT': [],
		'turn': [],
		'dead end': [],
		'intersection 3': [],
		'intersection 4': [],
	}
	for datablock in bpy.data.meshes.values() + bpy.data.collections.values():
		if 'SceneCity' in datablock and datablock.SceneCity.utiliser_comme_rue:
			rues_détectées_par_type[datablock.SceneCity.rue_type].append(datablock)
	return rues_détectées_par_type


def compter_total_datablocks_rues():
	rues_détectées_par_type = avoir_datablocks_rues_par_type()
	return len(rues_détectées_par_type['STRAIGHT']) + len(rues_détectées_par_type['turn']) + len(rues_détectées_par_type['dead end']) + len(
		rues_détectées_par_type['intersection 3']) + len(rues_détectées_par_type['intersection 4'])


def avoir_batis_datablocks_par_longueur_max():
	dico_batiments_par_longueur_max = {}
	total_batiments = 0
	for datablock in bpy.data.meshes.values() + bpy.data.collections.values():
		if 'SceneCity' not in datablock or not datablock.SceneCity.utiliser_comme_batiment:
			continue
		total_batiments += 1
		longueur_max_bat = max(datablock.SceneCity.batiment_surface_10m_x, datablock.SceneCity.batiment_surface_10m_y)
		if longueur_max_bat not in dico_batiments_par_longueur_max:
			dico_batiments_par_longueur_max[longueur_max_bat] = []
		dico_batiments_par_longueur_max[longueur_max_bat].append(datablock)
	return dico_batiments_par_longueur_max


def compter_batis_datablocks():
	dico_batiments_par_longueur_max = avoir_batis_datablocks_par_longueur_max()
	total_batiments = 0
	for taille, datablocks in dico_batiments_par_longueur_max.items():
		total_batiments += len(datablocks)
	return total_batiments


# class SC_PT_nodes(bpy.types.Panel):
# 	bl_region_type = 'UI'
# 	bl_space_type = 'NODE_EDITOR'
# 	# bl_context = 'scene'
# 	bl_category = 'SceneCity'
# 	# bl_options = {'DEFAULT_CLOSED'}
# 	bl_label = 'Add node'
#
# 	@classmethod
# 	def poll(self, context):
# 		return context.area.ui_type == "sc_node_tree_c3ey4om9toq4iwzf8ynl"
#
# 	def draw(self, context):
# 		self.layout.label(text='Hello')
# 		print(dir(context.area))

class SC_PT_general(bpy.types.Panel):
	bl_region_type = 'WINDOW'
	bl_space_type = 'PROPERTIES'
	bl_context = 'scene'
	bl_options = {'DEFAULT_CLOSED'}
	bl_label = 'SceneCity (Deprecated, use the nodes instead)'

	def draw_header(self, context):
		self.layout.label(text='', icon_value=my_globals.icônes['logo'].icon_id)

	def draw(self, context):
		# self.layout.operator(SC_OT_test_qt.bl_idname)
		# self.layout.operator('sc.todel')
		# UI GLOBAL
		row = self.layout.row()
		heightmap = None
		# try:
		# 	sceneterrain = importlib.import_module('sceneterrain')
		# 	heightmap = sceneterrain.terrain.heightmap
		# except ImportError:
		# 	pass

		if heightmap:
			row.prop(context.scene.SceneCity, 'utiliser_terrain')

		if heightmap and bpy.context.scene.SceneCity.utiliser_terrain:
			row.label(text='City scale = terrain scale = ' + str(round(heightmap.échelle, 3)))
		else:
			row.prop(context.scene.SceneCity, 'échelle')
			row = self.layout.row()
			row.prop(context.scene.SceneCity, 'nbLotsCôté')
		row = self.layout.row()
		row.prop(context.scene.SceneCity, 'seed_generale')
		row = self.layout.row()
		row.operator('scene.sc_op_link_assets', icon_value=my_globals.icônes['librairie'].icon_id)
		row = self.layout.row()
		row.label(text='')

		box = self.layout.box()
		row = box.row()
		row.prop(context.scene.SceneCity, 'ui_afficher_catégorie', expand=True)
		# UI RUES
		if context.scene.SceneCity.ui_afficher_catégorie == '0':
			row = box.row()
			row.label(text='Choose a placement method')
			row = box.row()
			row.prop(context.scene.SceneCity, 'ui_routes_afficher_méthode_placement', expand=True)
			# UI RUES ORGANIQUES
			if context.scene.SceneCity.ui_routes_afficher_méthode_placement == '0':
				# row = box.row(); row.label('')
				# row = box.row(); row.label('PROCEDURAL PLACEMENT', icon='SETTINGS')

				row = box.row()
				row.prop(context.scene.SceneCity, 'ruesLongueurMaxMètres')
				row = box.row()
				row.prop(context.scene.SceneCity, 'probaEmbranchements')
				# row = box.row(); row.operator('scene.scenecity_op_generer_routes_organiques_dans_image', icon='PLAY')
				if compter_total_datablocks_rues() <= 0:
					row = box.row()
					row.label(text='No road datablock found, create new ones or import them')
				else:
					row = box.row()
					row.operator('scene.scenecity_op_generer_routes_organiques_dans_scene', icon='OBJECT_DATAMODE')


			# UI RUES GRILLE
			elif context.scene.SceneCity.ui_routes_afficher_méthode_placement == '1':
				row = box.row()
				row.prop(context.scene.SceneCity, 'rues_grille_espacement')
				# row = box.row(); row.operator('scene.scenecity_op_generer_routes_grille_dans_image', icon='PLAY')
				if compter_total_datablocks_rues() <= 0:
					row = box.row()
					row.label(text='No road datablock found, create new ones or import them')
				else:
					row = box.row()
					row.operator('scene.scenecity_op_generer_routes_grille_dans_scene', icon='OBJECT_DATAMODE')

			# UI RUES TREEMAP
			elif context.scene.SceneCity.ui_routes_afficher_méthode_placement == '2':
				pass

			# UI RUES DEPUIS IMAGE
			elif context.scene.SceneCity.ui_routes_afficher_méthode_placement == '3':
				# row = box.row(); row.label('PLACEMENT FROM IMAGE', icon='IMAGEFILE')
				row = box.row()
				row.prop(context.scene.SceneCity, 'rues_nom_texture_placement')
				if context.scene.SceneCity.rues_nom_texture_placement != '':
					if context.scene.SceneCity.rues_nom_texture_placement not in bpy.data.images:
						row.label(text='', icon='CANCEL')
					else:
						row.label(text='', icon='FILE_TICK')
				row = box.row()
				row.operator('scene.scenecity_op_generer_routes_depuis_image', icon='OBJECT_DATAMODE')

			# IMPORT DES LIBRAIRIES DES RUES
			# row = self.layout.row(); row.label('');
			# row = self.layout.row(); row.operator('scene.scenecity_op_importer_rues', icon_value=globals.icônes['librairie'].icon_id)

			# DETECTED ROADS SUMMARY
			rues_détectées_par_type = avoir_datablocks_rues_par_type()
			row = self.layout.row()
			row.prop(context.scene.SceneCity, 'rues_afficher_récapitulatif')
			if context.scene.SceneCity.rues_afficher_récapitulatif:
				for rue_type, datablocks in rues_détectées_par_type.items():
					row = self.layout.row()
					row.label(text=(str(len(datablocks)) + ' ' + rue_type).upper())
					for datablock in datablocks:
						row = self.layout.row()
						if type(datablock) is bpy.types.Mesh:
							row.label(text=
							'------- ' + datablock.name + ' | ' + type(datablock).__name__ + ' | placed as ' + datablock.SceneCity_mesh.méthode_placement)
						else:
							row.label(
								text='------- ' + datablock.name + ' | ' + type(
									datablock).__name__ + ' | placed as ' + datablock.SceneCity.méthode_placement)

		# UI BATIMENTS
		elif context.scene.SceneCity.ui_afficher_catégorie == '1':
			row = box.row()
			row.prop(context.scene.SceneCity, 'quantitéBatiments')
			row = box.row()
			row.prop(context.scene.SceneCity, 'batiment_réduction_proba_selon_taille_percent')

			global grille_de_lots
			if compter_batis_datablocks() <= 0:
				row = box.row()
				row.label(text='No building datablock found, create new ones or import them')
			elif not grille_de_lots:
				row = box.row()
				row.label(text='Generate the roads first')
			else:
				row = box.row()
				row.operator('scene.scenecity_op_batiments_placer', icon='OBJECT_DATAMODE')

			# IMPORT DES LIBRAIRIES DES BATIMENTS
			# row = self.layout.row(); row.label('');
			# row = self.layout.row(); row.operator('scene.sc_op_append_assets', icon_value=globals.icônes['librairie'].icon_id)

			# RECAPITULATIF BATIMENTS
			row = self.layout.row()
			row.prop(context.scene.SceneCity, 'batiment_afficher_récapitulatif')
			if context.scene.SceneCity.batiment_afficher_récapitulatif:
				# afficher tous les batiments, par taille, leur nom, et type de datablock
				dico_batiments_par_longueur_max = avoir_batis_datablocks_par_longueur_max()
				total_batiments = compter_batis_datablocks()

				row = self.layout.row()
				row.label(text=str(total_batiments) + ' building(s) detected in total')
				for longueur_max_bat, datablocks in dico_batiments_par_longueur_max.items():
					row = self.layout.row()
					row.label(text=(str(longueur_max_bat * 10) + 'm on longest side: ' + str(len(datablocks)) + ' building(s)'))
					for datablock in datablocks:
						row = self.layout.row()
						if type(datablock) is bpy.types.Mesh:
							row.label(text=
							'------- ' + datablock.name + ' | ' + type(datablock).__name__ + ' | placed as ' + datablock.SceneCity_mesh.méthode_placement)
						else:
							row.label(
								text='------- ' + datablock.name + ' | ' + type(
									datablock).__name__ + ' | placed as ' + datablock.SceneCity.méthode_placement)


#
# RUES
#

def avoir_echelle_et_heightmap_et_grille_de_lots():
	# trouver heightmap
	heightmap = None
	# try:
	# 	sceneterrain = importlib.import_module('sceneterrain')
	# 	heightmap = sceneterrain.terrain.heightmap
	# except ImportError:
	# 	pass

	# créer grille de lots
	if heightmap and bpy.context.scene.SceneCity.utiliser_terrain:
		grille_de_lots = commun.GrilleDeLots.créerDepuisHeightmap(sceneterrain.terrain.heightmap)
	else:
		grille_de_lots = commun.GrilleDeLots(nbLotsCôté=bpy.context.scene.SceneCity.nbLotsCôté)

	# trouver échelle
	échelle = bpy.context.scene.SceneCity.échelle
	if heightmap and bpy.context.scene.SceneCity.utiliser_terrain:
		échelle = heightmap.échelle

	return échelle, heightmap, grille_de_lots


# from PySide2.QtCore import Slot
# from PySide2.QtGui import QKeySequence
# from PySide2.QtWidgets import QMainWindow, QAction

# class SC_OT_test_qt(bpy.types.Operator):
# 	bl_idname = 'scene.scenecity_op_qt'
# 	bl_description = ''
# 	bl_label = 'Open QT'
#
# 	# bl_options = {'REGISTER'}
#
# 	def execute(self, context):
# 		app = QApplication.instance()
# 		if not app:
# 			app = QApplication(sys.argv)
# 		label = QLabel("Hello World")
# 		label.show()
# 		app.exec_()
#
# class MainWindow(QMainWindow):
# 	def __init__(self):
# 		QMainWindow.__init__(self)
# 		self.setWindowTitle("Eartquakes information")
#
# 		# Menu
# 		self.menu = self.menuBar()
# 		self.file_menu = self.menu.addMenu("File")
#
# 		# Exit QAction
# 		# exit_action = QAction("Exit", self)
# 		# exit_action.setShortcut(QKeySequence.Quit)
# 		# exit_action.triggered.connect(self.close)
# 		#
# 		# self.file_menu.addAction(exit_action)
#
# 		# Status Bar
# 		self.status = self.statusBar()
# 		self.status.showMessage("Data loaded and plotted")

# Window dimensions
# geometry = qApp.desktop().availableGeometry(self)
# self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7)
# MainWindow()
# return {'FINISHED'}

class SC_OT_generer_routes_organiques_dans_image(bpy.types.Operator):
	bl_idname = 'scene.scenecity_op_generer_routes_organiques_dans_image'
	bl_description = 'Generate the organic road network in a new image. Useful for further manual tweaking (adding or deleting roads) before adding them to the 3d scene'
	bl_label = 'Place roads organically in image'
	bl_options = {'REGISTER', 'UNDO'}

	def execute(self, context):
		global grille_de_lots
		échelle, heightmap, grille_de_lots = avoir_echelle_et_heightmap_et_grille_de_lots()

		réseauRoutier = rues.RéseauRoutier(
			seed=bpy.context.scene.SceneCity.seed_generale if bpy.context.scene.SceneCity.seed_generale != 0 else None,
			ruesLongueurMaxMètres=bpy.context.scene.SceneCity.ruesLongueurMaxMètres,
			probaEmbranchements=bpy.context.scene.SceneCity.probaEmbranchements / 100,
			grille_de_lots=grille_de_lots,
			échelle=échelle,
		)
		réseauRoutier.générer_rues_organiques()
		img = réseauRoutier.dessiner_rues_dans_image()
		context.scene.SceneCity.rues_nom_texture_placement = img.name
		self.report({'INFO'}, 'Check image ' + img.name)

		return {'FINISHED'}


class SC_OT_generer_routes_organiques_dans_scene(bpy.types.Operator):
	bl_idname = 'scene.scenecity_op_generer_routes_organiques_dans_scene'
	bl_description = 'Generate the organic road network directly into the 3d scene'
	bl_label = 'Place roads in 3d scene'
	bl_options = {'REGISTER', 'UNDO'}

	def execute(self, context):
		global grille_de_lots
		échelle, heightmap, grille_de_lots = avoir_echelle_et_heightmap_et_grille_de_lots()

		réseauRoutier = rues.RéseauRoutier(
			grille_de_lots=grille_de_lots,
			seed=bpy.context.scene.SceneCity.seed_generale if bpy.context.scene.SceneCity.seed_generale != 0 else None,
			ruesLongueurMaxMètres=bpy.context.scene.SceneCity.ruesLongueurMaxMètres,
			probaEmbranchements=bpy.context.scene.SceneCity.probaEmbranchements / 100,
			échelle=échelle
		)
		réseauRoutier.générer_rues_organiques()
		réseauRoutier.nettoyer_ancien_réseau()
		réseauRoutier.placer_portions_dans_scene_3d()
		commun.appliquer_sol_urbain_sur_terrain(grille_de_lots)

		return {'FINISHED'}


class SC_OT_generer_routes_grille_dans_image(bpy.types.Operator):
	bl_idname = 'scene.scenecity_op_generer_routes_grille_dans_image'
	bl_description = 'Generate the grid road network in a new image. Useful for further manual tweaking (adding or deleting roads) before adding them to the 3d scene'
	bl_label = 'Place grid of roads in image'
	bl_options = {'REGISTER', 'UNDO'}

	def execute(self, context):
		global grille_de_lots
		échelle, heightmap, grille_de_lots = avoir_echelle_et_heightmap_et_grille_de_lots()

		réseauRoutier = rues.RéseauRoutier(
			grille_de_lots=grille_de_lots,
			seed=bpy.context.scene.SceneCity.seed_generale if bpy.context.scene.SceneCity.seed_generale != 0 else None,
			échelle=échelle
		)
		réseauRoutier.générer_rues_grille(grille_espacement=bpy.context.scene.SceneCity.rues_grille_espacement)
		img = réseauRoutier.dessiner_rues_dans_image()
		context.scene.SceneCity.rues_nom_texture_placement = img.name
		self.report({'INFO'}, 'Check image ' + img.name)

		return {'FINISHED'}


class SC_OT_generer_routes_grille_dans_scene(bpy.types.Operator):
	bl_idname = 'scene.scenecity_op_generer_routes_grille_dans_scene'
	bl_description = 'Generate the grid road network directly into the 3d scene'
	bl_label = 'Place roads in 3d scene'
	bl_options = {'REGISTER', 'UNDO'}

	def execute(self, context):
		global grille_de_lots
		échelle, heightmap, grille_de_lots = avoir_echelle_et_heightmap_et_grille_de_lots()

		réseauRoutier = rues.RéseauRoutier(
			grille_de_lots=grille_de_lots,
			seed=bpy.context.scene.SceneCity.seed_generale if bpy.context.scene.SceneCity.seed_generale != 0 else None,
			échelle=échelle
		)
		réseauRoutier.générer_rues_grille(grille_espacement=bpy.context.scene.SceneCity.rues_grille_espacement)
		réseauRoutier.nettoyer_ancien_réseau()
		réseauRoutier.placer_portions_dans_scene_3d()
		commun.appliquer_sol_urbain_sur_terrain(grille_de_lots)

		return {'FINISHED'}


class SC_OT_generer_routes_depuis_image(bpy.types.Operator):
	bl_idname = 'scene.scenecity_op_generer_routes_depuis_image'
	bl_description = 'Build the road network from the provided image.'
	bl_label = 'Place roads from image'
	bl_options = {'REGISTER', 'UNDO'}

	def execute(self, context):
		global grille_de_lots
		échelle, heightmap, grille_de_lots = avoir_echelle_et_heightmap_et_grille_de_lots()

		try:
			image_placement_rues = bpy.data.images[context.scene.SceneCity.rues_nom_texture_placement]
		except:
			self.report({'ERROR'}, 'Image not found')
			return {'CANCELLED'}

		réseauRoutier = rues.RéseauRoutier(
			seed=bpy.context.scene.SceneCity.seed_generale if bpy.context.scene.SceneCity.seed_generale != 0 else None,
			grille_de_lots=grille_de_lots,
			échelle=échelle,
			image_placement_rues=image_placement_rues,
		)
		réseauRoutier.placer_rues_sur_lots_selon_image()
		réseauRoutier.nettoyer_ancien_réseau()
		réseauRoutier.placer_portions_dans_scene_3d()
		commun.appliquer_sol_urbain_sur_terrain(grille_de_lots)

		return {'FINISHED'}


'''class scenecity_op_importer_rues(bpy.types.Operator):
	bl_idname = 'scene.scenecity_op_importer_rues'
	bl_description = ''
	bl_label = 'Import roads library in active layers'
	bl_options = {'REGISTER', 'UNDO'}

	def execute(self, context):
		bpy.ops.wm.link(directory = os.path.join(os.path.dirname(os.path.realpath(__file__)),'models','library.blend','Group'), link=False, relative_path=True, instance_groups=False, files=[{'name':'road_1x1_1_straight'},{'name':'road_1x1_1_deadend'},{'name':'road_1x1_1_intersect3'},{'name':'road_1x1_1_intersect4'},{'name':'road_1x1_1_turn'}])
		return {'FINISHED'}'''


#
# BÂTIMENTS
#

def dessiner_ui_commun_meshes_et_groupes(datablock, layout):
	# options batiments
	box = layout.box()
	# self.layout.label(text='', icon_value=icônes['logo'].icon_id)
	row = box.row()
	row.label(text='', icon_value=my_globals.icônes['batiment'].icon_id)
	row.prop(datablock.SceneCity, 'utiliser_comme_batiment')
	if datablock.SceneCity.utiliser_comme_batiment:
		row = box.row()
		row.prop(datablock.SceneCity, 'batiment_surface_10m_x')
		row = box.row()
		row.prop(datablock.SceneCity, 'batiment_surface_10m_y')
		# row = box.row(); row.label('Building surface: '+str(datablock.SceneCity.batiment_surface_10m_x*10)+'m by '+str(datablock.SceneCity.batiment_surface_10m_x*10)+'m = '+str((datablock.SceneCity.batiment_surface_10m_x*10)**2)+'m²')
		row = box.row()
		row.prop(datablock.SceneCity, 'batiment_probabilité_relative')
		row = box.row()
		row.prop(datablock.SceneCity, 'batiment_nom_texture_controle_distribution')
		if datablock.SceneCity.batiment_nom_texture_controle_distribution != '':
			if datablock.SceneCity.batiment_nom_texture_controle_distribution not in bpy.data.textures:
				row.label(text='', icon='CANCEL')
			else:
				row.label(text='', icon_value=my_globals.icônes['tick'].icon_id)

		# options à afficher selon type de batiment
		row = box.row()
		row.label(text='')
		if type(datablock) is bpy.types.Mesh:
			mesh = datablock
			row = box.row()
			row.prop(mesh.SceneCity_mesh, 'batiment_type')

		batiments_proceduraux.dessiner_options(datablock, box)

	# batis stackables
	# if mesh.SceneCity_mesh.batiment_type == 'stackable':
	# 	row = box.row()
	# 	row.prop(mesh.SceneCity_mesh, 'stack_building_name')
	# 	row = box.row()
	# 	row.prop(mesh.SceneCity_mesh, 'stack_building_part_order')
	# 	row = box.row()
	# 	row.prop(mesh.SceneCity_mesh, 'stack_building_part_cardinality')
	# 	row = box.row()
	# 	row.prop(mesh.SceneCity_mesh, 'stack_building_part_height')
	# 	if mesh.SceneCity_mesh.stack_building_part_height == 'value':
	# 		row = box.row()
	# 		row.prop(mesh.SceneCity_mesh, 'stack_building_part_height_value')

	# options rues
	box = layout.box()
	row = box.row()
	row.label(text='', icon_value=my_globals.icônes['rue'].icon_id)
	row.prop(datablock.SceneCity, 'utiliser_comme_rue')
	if datablock.SceneCity.utiliser_comme_rue:
		row = box.row()
		row.prop(datablock.SceneCity, 'rue_type')
		row = box.row()
		row.prop(datablock.SceneCity, 'rue_probabilité_relative')

	# options communes batis et rues
	if datablock.SceneCity.utiliser_comme_batiment or datablock.SceneCity.utiliser_comme_rue:
		if type(datablock) is bpy.types.Mesh:
			row = layout.row()
			row.prop(datablock.SceneCity_mesh, 'méthode_placement')
		else:
			row = layout.row()
			row.prop(datablock.SceneCity, 'méthode_placement')


class SC_PT_batiment_mesh(bpy.types.Panel):
	bl_region_type = 'WINDOW'
	bl_space_type = 'PROPERTIES'
	bl_context = 'data'
	bl_options = {'DEFAULT_CLOSED'}
	bl_label = 'SceneCity (Deprecated! Use the nodes instead)'

	@classmethod
	def poll(cls, context):
		return context.object is not None and type(context.object.__data) is bpy.types.Mesh

	def draw_header(self, context):
		self.layout.label(text='', icon_value=my_globals.icônes['logo'].icon_id)

	def draw(self, context):
		dessiner_ui_commun_meshes_et_groupes(context.object.__data, self.layout)


class SC_PT_batiment_group(bpy.types.Panel):
	bl_region_type = 'WINDOW'
	bl_space_type = 'PROPERTIES'
	bl_context = 'object'
	bl_options = {'DEFAULT_CLOSED'}
	bl_label = 'SceneCity (Deprecated! Use the nodes instead)'

	@classmethod
	def poll(cls, context):
		return context.object is not None

	def draw_header(self, context):
		self.layout.label(text='', icon_value=my_globals.icônes['logo'].icon_id)

	def draw(self, context):
		if len(context.object.users_collection) == 0:
			row = self.layout.row()
			row.label(text="To use this object's groups as buildings in")
			row = self.layout.row()
			row.label(text='the city, start by adding it to a collection')
			return

		for group in context.object.users_collection:
			box = self.layout.box()
			row = box.row()
			row.label(text='Collection: ' + group.name)
			dessiner_ui_commun_meshes_et_groupes(group, box)


class SC_OT_batiments_placer(bpy.types.Operator):
	bl_idname = 'scene.scenecity_op_batiments_placer'
	bl_description = 'Place the buildings, look in the console for progress information'
	bl_label = 'Place buildings along roads'
	bl_options = {'REGISTER', 'UNDO'}

	def execute(self, context):
		global grille_de_lots

		échelle = bpy.context.scene.SceneCity.échelle
		try:
			sceneterrain = importlib.import_module('sceneterrain')
			if bpy.context.scene.SceneCity.utiliser_terrain:
				échelle = sceneterrain.terrain.heightmap.échelle
		except:
			pass

		infos_globales_placement_batiments = batiments.Infos_globales_placement_batiments(
			seed=bpy.context.scene.SceneCity.seed_generale if bpy.context.scene.SceneCity.seed_generale != 0 else None,
			probaAucunBatiment=1 - bpy.context.scene.SceneCity.quantitéBatiments / 100,
			échelle=échelle,
			réduction_proba_selon_taille_percent=bpy.context.scene.SceneCity.batiment_réduction_proba_selon_taille_percent / 100,
		)
		batiments.placer_batiments_dans_scène(infos_globales_placement_batiments, batiments.placer_batiments_sur_grille_de_lots,
			grille_de_lots=grille_de_lots)
		batiments.dessiner_grille_de_lots(grille_de_lots)
		commun.appliquer_sol_urbain_sur_terrain(grille_de_lots)

		return {'FINISHED'}


#
# CUSTOM MENUS
#

'''class SceneCity_CustomMenuAdd(bpy.types.Menu):
	# bl_idname = 'INFO_MT_mesh_custom_menu_add'
	bl_label = 'SceneCity'

	# noinspection PyUnusedLocal
	def draw(self, context):
		self.layout.operator_context = 'INVOKE_REGION_WIN'
		self.layout.operator('scene.scenecity_importer_batiment_op', icon_value=globals.icônes['batiment'].icon_id)

# noinspection PyUnusedLocal
def menu_func(self, context):
	# self.layout.label(text='', icon_value=globals.icônes['logo'].icon_id)
	# self.layout.menu('scenecity_custom_menu_add', icon_value=globals.icônes['logo'].icon_id)
	self.layout.menu('SceneCity_CustomMenuAdd', icon_value=globals.icônes['logo'].icon_id)'''

a: 'hey' = 8


#
## PROPERTY GROUPS
#

class SceneCity_PropertyGroup_Scene(bpy.types.PropertyGroup):
	nbLotsCôté: bpy.props.IntProperty(
		name='City size (nb of 10m blocks on each side)',
		description='Number of 10m² blocks per side of the city. Increase slowly, larger cities take longer to generate and to render, and use more memory.',
		default=40, min=1)
	échelle: bpy.props.FloatProperty(
		name='City scale',
		description='1 meter in the city equals 1 blender unit, but this results in very large scenes, so keep this value low',
		default=.01)
	seed_generale: bpy.props.IntProperty(
		name='Random seed',
		description='0 to have a different city each time, other values to always have the same city corresponding to that seed and other attribute values.',
		default=0)
	probaEmbranchements: bpy.props.FloatProperty(
		name='Amount of crossroads (percent)',
		description='',
		default=10, min=0, max=100)
	ruesLongueurMaxMètres: bpy.props.IntProperty(
		name='Maximum length of roads (meters)',
		description='',
		default=200, min=10)
	quantitéBatiments: bpy.props.FloatProperty(
		name='Amount of buildings (percent)',
		description='',
		default=80, min=0, max=100)
	utiliser_terrain: bpy.props.BoolProperty(
		name='Use terrain',
		description='Enable to put the city onto the terrain. The city will then be restricted to the flat areas of the terrain, make there are enough of them',
		default=True)
	batiment_réduction_proba_selon_taille_percent: bpy.props.FloatProperty(
		name='Larger buildings appear less often (percent)',
		description='100% = the larger a building, the lower its probability to appear in the city. 0%=disabled and then larger buildings have the same probability as smaller ones.',
		default=100, min=0, max=100)
	batiment_afficher_récapitulatif: bpy.props.BoolProperty(
		name='Display detected building models in this file',
		description='',
		default=True)
	rues_afficher_récapitulatif: bpy.props.BoolProperty(
		name='Display detected road models in this file',
		description='',
		default=True)
	rues_nom_texture_placement: bpy.props.StringProperty(
		name='Image name',
		description='Name of the image to control the precise placement of the roads. Each pixel represents a 10mx10m area. If the pixel is white, a road portion will be placed at that location')
	ui_afficher_catégorie: bpy.props.EnumProperty(
		name='Options to display',
		description='Choose which category of options to display',
		items=(
			('0', 'Roads', ''),
			('1', 'Buildings', ''),
		))
	ui_routes_afficher_méthode_placement: bpy.props.EnumProperty(
		name='Options to display',
		description='Choose which category of options to display',
		items=(
			('0', 'Organic', ''),
			('1', 'Grid', ''),
			# ('2','Treemap',''),
			# ('3','Image',''),
		))
	rues_grille_espacement: bpy.props.IntProperty(
		name='Cell size (x10m)',
		description='Space between each grid cell, in increments of 10 meters',
		default=6, min=1)


# pour les object groups et les mesh
class SceneCity_PropertyGroup_commun(bpy.types.PropertyGroup):
	utiliser_comme_batiment: bpy.props.BoolProperty(
		name='Use this datablock as building.',
		description='If enabled, this datablock will be put in the city as a building. Center building around local origin on X and Y. Ground is 0 on local Z, front is positive local X',
		default=False)
	batiment_surface_10m_x: bpy.props.IntProperty(
		name='Building size on X (x10m)',
		description='Length of X side of the surface of the building, in increments of 10 meters. 1m = 1 blender unit. The building is centered around 0. Example: 1 means the building goes from -5 to 5 on the X axis. Make sure your building is constrained within this bounding surface, or it may collide with others in the city',
		default=1, min=1, max=100)  # taille max des batiments: 100 x 10m = 1000m
	batiment_surface_10m_y: bpy.props.IntProperty(
		name='Building size on Y (x10m)',
		description='Length of Y side of the surface of the building, in increments of 10 meters. 1m = 1 blender unit. The building is centered around 0. Example: 1 means the building goes from -5 to 5 on the Y axis. Make sure your building is constrained within this bounding surface, or it may collide with others in the city',
		default=1, min=1, max=100)  # taille max des batiments: 100 x 10m = 1000m
	batiment_probabilité_relative: bpy.props.FloatProperty(
		name='Relative probability to appear in city',
		description="Probability to be added to the city compared to other buildings. Values above other buildings' probability mean this one will appear more often, and vice versa. If all buildings have the same value, they will be created in equal number",
		default=1, min=0)
	# si modifie, modifier aussi pour les meshes
	méthode_placement: bpy.props.EnumProperty(
		name='Place instances',
		description='How this datablock will be placed in the city. "As individual objects": individual objects sharing the same datablock. "With dupliverts": really fast and memory efficient, but individual buildings/roads can\'t be moved/removed afterwards.',
		items=[
			('objects', 'As individual objects', '', 1),
			('dupliverts', 'With dupliverts', '', 2),
		])
	utiliser_comme_rue: bpy.props.BoolProperty(
		name='Use this datablock as road portion',
		description='If enabled, this datablock will be put in the city as a road portion',
		default=False)
	rue_type: bpy.props.EnumProperty(
		name='Road portion type',
		description='Roads are made of portions put together. Choose the type of road of this datablock',
		items=[
			('STRAIGHT', 'Straight along X', '', 1),
			('turn', 'Turn from -Y to +X', '', 2),
			('dead end', 'Dead end, stop at -X', '', 3),
			('intersection 3', 'Intersect 3, along Y and to +X', '', 4),
			('intersection 4', 'Intersect 4, along X and Y', '', 5),
		])
	rue_probabilité_relative: bpy.props.FloatProperty(
		name='Relative probability to appear in city',
		description="Probability to be added to the city compared to other road portions of the same type. Values above other roads' probability mean this one will appear more often, and vice versa. If all roads have the same value, they will be created in equal proportion",
		default=1, min=0)
	batiment_nom_texture_controle_distribution: bpy.props.StringProperty(
		name='Distribution texture',
		description='(Optional) Name of the BI texture to control the distribution of this building in the city. Dark pixels mean low probability of appearance at that location, while bright pixels mean high probability')


class SceneCity_PropertyGroup_Mesh(bpy.types.PropertyGroup):
	# material_slots = bpy.props.CollectionProperty(type=SceneCity_PropertyGroup_MaterialSlots)
	# doit être identique à celle de SceneCity_PropertyGroup_commun, avec les options spécifiques aux meshes
	méthode_placement: bpy.props.EnumProperty(
		name='Place instances',
		description='How this mesh will be placed in the city. "As individual objects": individual objects sharing the same mesh. "With dupliverts": really fast and memory efficient, but individual buildings/roads can\'t be moved/removed afterwards. "As single merged mesh": all instances merged into one mesh',
		items=[
			('objects', 'As individual objects', '', 1),
			('dupliverts', 'With dupliverts', '', 2),
			('merged mesh', 'As single merged mesh', '', 3),
		])
	batiment_type: bpy.props.EnumProperty(
		name='Building type',
		description='',
		items=(
			('mesh as it is', 'Use mesh as it is', ''),
			('procedural cube', 'Procedural cube', ''),
			# ('stackable', 'Stackable', ''),
			# ('procedural skyscraper','Procedural skyscraper', ''),
		))
	# BATIMENTS STACKABLES
	stack_building_name: bpy.props.StringProperty(
		name='Building name',
		description='Use the same name for the different stackable parts (ground floor(s), floor(s), roof(s)) of the same building')
	stack_building_part_order: bpy.props.IntProperty(name='Building part order',
		description='The different building parts will be stacked in this order. Lower order parts will be placed first underneath, and higher order ones will be stacked on top',
		default=0)
	stack_building_part_cardinality: bpy.props.FloatVectorProperty(name='Building part cardinality',
		description='Min / max. How many times this part will be stacked. A minimum of zero means it can be randomly skipped', default=(1, 1), size=2)
	stack_building_part_height: bpy.props.EnumProperty(
		name='Building part height',
		description='',
		items=(
			('bbox', 'Use BBOX\'s height', ''),
			('value', 'Use specific value', ''),
		))
	stack_building_part_height_value: bpy.props.FloatProperty(name='Building part height (meters)',
		description='The specific height to use when stacking this part of the building', default=3)


#
# REGISTER ADDON
#
classes_to_register = [
	preferences.SC_AddonPreferences,

	SC_PT_general,
	# SC_PT_nodes,
	# SC_OT_append_assets,
	# SC_OT_link_assets,
	SC_OT_generer_routes_organiques_dans_image,
	SC_OT_generer_routes_organiques_dans_scene,
	SC_OT_generer_routes_grille_dans_image,
	SC_OT_generer_routes_grille_dans_scene,
	SC_OT_generer_routes_depuis_image,
	# SC_OT_Fix_scene_unit_settings,
	SC_PT_batiment_mesh,
	SC_PT_batiment_group,
	SC_OT_batiments_placer,
	# SC_OT_test_qt,

	SceneCity_PropertyGroup_Scene,
	SceneCity_PropertyGroup_commun,
	SceneCity_PropertyGroup_Mesh,
	batiments_proceduraux.SC_procedural_building,
	batiments_proceduraux.SC_OT_generer_batiment_aleatoire,
	materiaux.SC_PT_material,
	materiaux.SceneCity_PropertyGroup_Material,

	nodes.SC_NodeTree,
	# nodes.export.SC_StringProperty,
]

# nodes_submodules = [submodule for submodule in pkgutil.iter_modules(nodes.__path__)]
# modules_to_import = [nodes] + nodes_submodules
# print(modules_to_import)

# register all needed classes in correct order from all modules
for module in [utils, preferences, nodes, nodes.meshes, nodes.layouts, nodes.maps, nodes.textures, nodes.images, nodes.curves, nodes.objects, nodes.export,
			   nodes.roads,
			   nodes.buildings, nodes.terrains, nodes.geoms2d, nodes.grids, nodes.uvs, nodes.materials]:
	# for module in modules_to_import:

	# register sockets, operator, and property groups
	classes = set([c[1] for c in inspect.getmembers(module, inspect.isclass)
				   if issubclass(c[1], (bpy.types.NodeSocket, bpy.types.Operator, bpy.types.PropertyGroup)) and c[1].__module__ == module.__name__])
	classes_to_register.extend(classes)

	# register nodes
	classes = set([c[1] for c in inspect.getmembers(module, inspect.isclass)
				   if issubclass(c[1], (bpy.types.Node)) and c[1].__module__ == module.__name__])
	classes_to_register.extend(classes)

# register menus
# classes = set([c[1] for c in inspect.getmembers(module, inspect.isclass)
# 			   if issubclass(c[1], (bpy.types.Menu)) and c[1].__module__ == module.__name__])
# classes_to_register.extend(classes)

# register internal classes, so that we can declare operators inside node classes for instance
all_internal_classes = set()
for cl in classes_to_register:
	classes = set([c[1] for c in inspect.getmembers(cl, inspect.isclass)
				   if issubclass(c[1], (bpy.types.Operator))])
	all_internal_classes |= classes
classes_to_register.extend(all_internal_classes)

classes_to_register.extend([
	# SC_MT_NodeTreeCityMenu,
	# nodes.SC_HT_NodeTreePanelHeader,

	menus.MeshMenu,
])


def clear_trailing_separators(tools):
	if not tools[-1]:
		tools.pop()
		clear_trailing_separators(tools)


# @ToolDef.from_fn
# def Todel():
# 	def draw_settings(context, layout, tool):
# 		print('hey')
# 		if context.region.type not in {'UI', 'WINDOW'}:
# 			# print('hey')
# 			row = layout.row(align=True)
# 			row.label(text='Hey')
# 		# layout.separator()
# 		# row = layout.row(align=True)
# 		# row.label(text='Hey')
#
# 	return dict(
# 		idname="Todel",
# 		label="Hello tool",
# 		# icon=os.path.join(os.path.dirname(__file__), '..', 'icons', 'toolbar'),
# 		icon='OUTLINER_OB_LATTICE',
# 		widget=None,
# 		# keymap='3D View Tool: Hops',
# 		draw_settings=draw_settings)

def register():
	try: os.mkdir(utils.get_sc_data_dir())
	except FileExistsError: pass

	# load icons
	my_globals.icônes = bpy.utils.previews.new()
	my_globals.icônes.matériaux = None
	icons_files_paths = (Path(__file__).parent / 'icons').glob('*.png')
	for file_path in icons_files_paths:
		my_globals.icônes.load(file_path.name.split('.')[0], str(file_path), 'IMAGE')

	for c in classes_to_register:
		bpy.utils.register_class(c)

	bpy.types.Scene.SceneCity = bpy.props.PointerProperty(type=SceneCity_PropertyGroup_Scene)
	bpy.types.Mesh.SceneCity = bpy.props.PointerProperty(type=SceneCity_PropertyGroup_commun)
	bpy.types.Mesh.SceneCity_mesh = bpy.props.PointerProperty(type=SceneCity_PropertyGroup_Mesh)
	bpy.types.Mesh.SC_proc_building = bpy.props.PointerProperty(type=batiments_proceduraux.SC_procedural_building)
	# bpy.types.Group.SceneCity = bpy.props.PointerProperty(type=SceneCity_PropertyGroup_commun)
	bpy.types.Collection.SceneCity = bpy.props.PointerProperty(type=SceneCity_PropertyGroup_commun)
	bpy.types.Material.SceneCity = bpy.props.PointerProperty(type=materiaux.SceneCity_PropertyGroup_Material)

	menus.register()
	preferences.register()


def unregister():
	preferences.unregister()
	menus.unregister()

	bpy.utils.previews.remove(my_globals.icônes)
	for c in reversed(classes_to_register):
		bpy.utils.unregister_class(c)

	del bpy.types.Scene.SceneCity
	del bpy.types.Mesh.SceneCity
	del bpy.types.Mesh.SceneCity_mesh
	del bpy.types.Mesh.SC_proc_building
	# del bpy.types.Group.SceneCity
	del bpy.types.Collection.SceneCity
	del bpy.types.Material.SceneCity

# except ImportError:
	# def register():
		# pass
		
	# def unregister():
		# pass
