From da4694f3a910026e854cd4b20081602f06034086 Mon Sep 17 00:00:00 2001 From: "n.savitchev" Date: Tue, 20 Feb 2024 11:59:35 +0300 Subject: [PATCH] updated blender extension plugin --- prog/tools/dag4blend/__build_pack.py | 1 + prog/tools/dag4blend/__init__.py | 4 +- prog/tools/dag4blend/cmp/cmp_panels.py | 2 +- .../dag4blend/dagormat/rw_dagormat_text.py | 4 +- prog/tools/dag4blend/exporter/exporter.py | 5 +- prog/tools/dag4blend/importer/import_panel.py | 2 + prog/tools/dag4blend/importer/importer.py | 10 +- .../object_properties/object_properties.py | 60 ++++-- prog/tools/dag4blend/read_config.py | 9 +- prog/tools/dag4blend/settings.py | 44 ++-- .../smooth_groups/mesh_calc_smooth_groups.py | 189 ++++++++++++++++++ .../dag4blend/smooth_groups/smooth_groups.py | 131 +++++------- prog/tools/dag4blend/tools/tools_panel.py | 5 +- 13 files changed, 335 insertions(+), 131 deletions(-) create mode 100644 prog/tools/dag4blend/smooth_groups/mesh_calc_smooth_groups.py diff --git a/prog/tools/dag4blend/__build_pack.py b/prog/tools/dag4blend/__build_pack.py index 3e9f1b7a4..042136a24 100644 --- a/prog/tools/dag4blend/__build_pack.py +++ b/prog/tools/dag4blend/__build_pack.py @@ -21,6 +21,7 @@ 'dag4blend/exporter/export_panel.py', 'dag4blend/exporter/exporter.py', 'dag4blend/exporter/writer.py', 'dag4blend/importer/import_panel.py', 'dag4blend/importer/importer.py', 'dag4blend/importer/reader.py', 'dag4blend/object_properties/object_properties.py', 'dag4blend/smooth_groups/smooth_groups.py', + 'dag4blend/smooth_groups/mesh_calc_smooth_groups.py', 'dag4blend/colprops/colprops.py', 'dag4blend/pyparsing.py', 'dag4blend/datablock.py', ] diff --git a/prog/tools/dag4blend/__init__.py b/prog/tools/dag4blend/__init__.py index e8f59d7c9..a453a2ded 100644 --- a/prog/tools/dag4blend/__init__.py +++ b/prog/tools/dag4blend/__init__.py @@ -31,8 +31,8 @@ bl_info = {"name": "dag4blend", "description": "Tools for editing dag files", "author": "Gaijin Entertainment", - "version": (2, 1, 6),#2023.12.22 - "blender": (3, 5, 1), + "version": (2, 1, 7),#2024.02.20 + "blender": (4, 0, 2), "location": "File > Export", "wiki_url": "", "tracker_url": "", diff --git a/prog/tools/dag4blend/cmp/cmp_panels.py b/prog/tools/dag4blend/cmp/cmp_panels.py index fe82065fc..4ce1807f7 100644 --- a/prog/tools/dag4blend/cmp/cmp_panels.py +++ b/prog/tools/dag4blend/cmp/cmp_panels.py @@ -523,7 +523,7 @@ def draw_entity_editor(self,context,layout): return #Node explode n stuff def draw_node_converter(self,context,layout): - P = bpy.data.scenes[0].dag4blend.cmp + P = bpy.data.scenes[0].dag4blend.cmp.tools addon_name = basename(__package__) pref = bpy.context.preferences.addons[addon_name].preferences box = layout.box() diff --git a/prog/tools/dag4blend/dagormat/rw_dagormat_text.py b/prog/tools/dag4blend/dagormat/rw_dagormat_text.py index e496d7c18..1ba085dc3 100644 --- a/prog/tools/dag4blend/dagormat/rw_dagormat_text.py +++ b/prog/tools/dag4blend/dagormat/rw_dagormat_text.py @@ -74,6 +74,8 @@ def dagormat_to_text(mat,text): text.write('\n twosided:b=') if DM.sides == 1: text.write('yes\n') + elif DM.sides == 2: + text.write('\n script:t="real_two_sided=yes"\n') else: text.write('no\n') text.write('\n tex16support:b=yes') @@ -94,8 +96,6 @@ def dagormat_to_text(mat,text): text.write(str(int(DM.emissive[0]*255))+',') text.write(str(int(DM.emissive[1]*255))+',') text.write(str(int(DM.emissive[2]*255))) - if DM.sides == 2: - text.write('\n script:t="real_two_sided=yes"\n') for param in list(DM.optional.keys()): text.write('\n script:t="'+param+'=') try: diff --git a/prog/tools/dag4blend/exporter/exporter.py b/prog/tools/dag4blend/exporter/exporter.py index d003c9c02..d0d17386d 100644 --- a/prog/tools/dag4blend/exporter/exporter.py +++ b/prog/tools/dag4blend/exporter/exporter.py @@ -22,7 +22,8 @@ from ..tools.tools_panel import apply_modifiers, fix_mat_slots, optimize_mat_slots, preserve_sharp from ..tools.tools_functions import * -from ..smooth_groups.smooth_groups import int_to_uint,precalc_sg +from ..smooth_groups.smooth_groups import int_to_uint +from ..smooth_groups.mesh_calc_smooth_groups import mesh_calc_smooth_groups SUPPORTED_TYPES = ('MESH', 'CURVE') # ,'CURVE','EMPTY','TEXT','CAMERA','LAMP') @@ -364,7 +365,7 @@ def dumpMesh(self, obj, node, scene): fix_mat_slots(exp_obj) reorder_uv_layers(me) if exp_obj.data.attributes.get('SG') is None: - precalc_sg(exp_obj.data) + mesh_calc_smooth_groups(exp_obj.data) #collecting from pre-triangulated mesh to make it reversable on import edge_keys = self.getEdgeKeys(exp_obj, scene) #dag contains only triangles diff --git a/prog/tools/dag4blend/importer/import_panel.py b/prog/tools/dag4blend/importer/import_panel.py index 651457ebf..bc5e18670 100644 --- a/prog/tools/dag4blend/importer/import_panel.py +++ b/prog/tools/dag4blend/importer/import_panel.py @@ -37,6 +37,7 @@ def draw(self,context): layout = self.layout layout.prop(P, 'with_subfolders') layout.prop(P, 'mopt') + layout.prop(P, 'preserve_sg') layout.prop(P, 'replace_existing') layout.prop(P, 'preserve_path') layout.prop(P, 'masks') @@ -91,6 +92,7 @@ def execute(self, context): for dag in dags: try: bpy.ops.import_scene.dag(filepath = dag, + preserve_sg = P.preserve_sg, replace_existing = P.replace_existing, mopt = P.mopt, preserve_path = P.preserve_path) diff --git a/prog/tools/dag4blend/importer/importer.py b/prog/tools/dag4blend/importer/importer.py index 1b3a677a7..38e8fe63e 100644 --- a/prog/tools/dag4blend/importer/importer.py +++ b/prog/tools/dag4blend/importer/importer.py @@ -210,6 +210,11 @@ class DagImporter(Operator, ImportHelper): description = 'Override export path for imported files', default = False) + preserve_sg: BoolProperty( + name = "Preserve Smoothing Groups", + description = "Store Smoothing Groups in a integer face attribute called 'SG'", + default = False) + rootNode = None reader = reader.DagReader() @@ -355,6 +360,8 @@ def buildMesh(self, node, tm, parent): sg.value=uint_to_int(SG[i]) i+=1 sg_to_sharp_edges(me) + if not self.preserve_sg: + me.attributes.remove(me.attributes.get('SG')) if len(node.mesh.normals_ver_list): normals = [] @@ -975,7 +982,8 @@ def execute(self, context): msg = f'IMPORTING {filepath}\n' pref = context.preferences.addons[basename(__package__)].preferences if not pref.project_active: - show_popup(message='Please configure at least one project in addon preferences',title='ERROR!', icon='ERROR') + show_popup(message='Please configure at least one project in addon preferences', + title='ERROR!', icon='ERROR') return {'CANCELLED'} log(msg)#isn't necessary in the info panel if self.with_dmgs is False and self.with_dps is False and self.with_lods is False: diff --git a/prog/tools/dag4blend/object_properties/object_properties.py b/prog/tools/dag4blend/object_properties/object_properties.py index b6ad90bbe..bb2ae301b 100644 --- a/prog/tools/dag4blend/object_properties/object_properties.py +++ b/prog/tools/dag4blend/object_properties/object_properties.py @@ -1,6 +1,6 @@ import bpy, os from bpy.props import StringProperty, PointerProperty, FloatProperty -from bpy.utils import register_class, unregister_class, user_resource +from bpy.utils import register_class, unregister_class from bpy.types import Operator, Panel, PropertyGroup from ..helpers.texts import * from ..helpers.popup import show_popup @@ -54,8 +54,8 @@ def props_to_text(obj): def get_presets_list(): addon_name = basename(__package__) - path = user_resource('SCRIPTS') + f'\\addons\\{addon_name}\\object_properties\\presets\\' - items = [] + pref = bpy.context.preferences.addons[addon_name].preferences + path = pref.props_presets_path files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path,f))] return files @@ -108,11 +108,14 @@ def execute(self, context): addon_name = basename(__package__) pref=bpy.context.preferences.addons[addon_name].preferences preset = pref.prop_preset - path = user_resource('SCRIPTS') + f"\\addons\\{addon_name}\\object_properties\\presets\\{preset}.txt" - with open(path,'r') as t: - temp.write(t.read()) - t.close() - text_to_props(obj) + path = pref.props_presets_path + f"\\{preset}.txt" + if os.path.exists(path): + with open(path,'r') as t: + temp.write(t.read()) + t.close() + text_to_props(obj) + else: + show_popup(message = f'"{preset}.txt" not found in {pref.props_presets_path}', title='Error', icon='ERROR') return {'FINISHED'} @@ -132,7 +135,7 @@ def execute(self, context): name = pref.prop_preset_name if name == '': name = 'unnamed' - dirpath = user_resource('SCRIPTS') + f"\\addons\\{addon_name}\\object_properties\\presets" + dirpath = pref.props_presets_path if not os.path.exists(dirpath): os.makedirs(dirpath) path = f"{dirpath}\\{name}.txt" @@ -158,7 +161,7 @@ def execute(self, context): addon_name = basename(__package__) pref=bpy.context.preferences.addons[addon_name].preferences name = pref.prop_preset - path = user_resource('SCRIPTS') + f"\\addons\\{addon_name}\\object_properties\\presets\\{name}.txt" + path = pref.props_presets_path + f"\\{name}.txt" os.remove(path) list = get_presets_list() if list.__len__()>0: @@ -166,7 +169,7 @@ def execute(self, context): return {'FINISHED'} -class DAGOR_invert_dagbool(bpy.types.Operator): +class DAGOR_invert_dagbool(Operator): bl_idname = 'dt.invert_dagbool' bl_label = 'Switch value' bl_description = 'invert dag_bool value' @@ -286,22 +289,35 @@ def draw(self, context): else: rem.prop(C.object.dagorprops,'["'+key+'"]',text='') rem.operator('dt.remove_prop',text='',icon='TRASH').prop=key + presets = l.box() header=presets.row() - header.prop(pref, 'props_preset_maximized',icon = 'DOWNARROW_HLT'if pref.props_preset_maximized else 'RIGHTARROW_THIN', - emboss=False,text='Presets') + header.prop(pref, 'props_preset_maximized', + icon = 'DOWNARROW_HLT'if pref.props_preset_maximized else 'RIGHTARROW_THIN', emboss=False,text='Presets') header.label(text='',icon='THREE_DOTS') + if pref.props_preset_maximized: - presets.operator('dt.save_op_preset',text = "Save preset as:") - presets.prop(pref, 'prop_preset_name', text = '') - presets.operator('dt.apply_op_preset', text = 'Apply preset:') - act = presets.row() - act.prop(pref, 'prop_preset', text = "") - act.operator('dt.remove_op_preset', text = "", icon = "TRASH") - #no need to use custom operator for each folder + addon_name = basename(__package__) - path = user_resource('SCRIPTS') + f"\\addons\\{addon_name}\\object_properties\\presets" - presets.operator('wm.path_open', icon = 'FILE_FOLDER', text = "open presets folder").filepath = path + path = pref.props_presets_path + path_exists = os.path.exists(path) + if path_exists: + presets.operator('dt.save_op_preset',text = "Save preset as:") + presets.prop(pref, 'prop_preset_name', text = '') + presets.operator('dt.apply_op_preset', text = 'Apply preset:') + act = presets.row() + act.prop(pref, 'prop_preset', text = "") + act.operator('dt.remove_op_preset', text = "", icon = "TRASH") + #no need to use custom operator for each folder + col = presets.column(align = True) + row = col.row(align = True) + row.operator('wm.path_open', icon = 'FILE_FOLDER', text = "open presets folder").filepath = path + else: + col = presets.column() + col.label(icon = 'ERROR') + col.label(text = 'Presets folder not found') + col.label(text = 'Please, set existing one') + col.label(text = 'in addon preferences!') tools = l.box() header = tools.row() diff --git a/prog/tools/dag4blend/read_config.py b/prog/tools/dag4blend/read_config.py index d5db4b88f..a30c4fb18 100644 --- a/prog/tools/dag4blend/read_config.py +++ b/prog/tools/dag4blend/read_config.py @@ -1,13 +1,16 @@ import configparser import os import bpy +from .helpers.basename import basename from shutil import copyfile #modifying of original file to make it readable by python configparser #TODO: make custom parser instead of fixing unreadable comments in copy def cfg_upd(): blend_cfg=os.path.join(os.path.dirname(__file__), 'fixed_dagorShaders.cfg') - max_cfg=os.path.join(os.path.dirname(__file__), 'dagorShaders.cfg') + addon_name = basename(__package__) + pref = bpy.context.preferences.addons[addon_name].preferences + max_cfg = pref.cfg_path copyfile (max_cfg,blend_cfg) cfg=open(blend_cfg,'r') fixed=cfg.read().replace('//','#')#turning comments into python-like @@ -21,7 +24,9 @@ def read_config(): shader_categories = [] config = configparser.ConfigParser() - config.read(os.path.join(os.path.dirname(__file__), 'fixed_dagorShaders.cfg')) + config_filepath = os.path.join(os.path.dirname(__file__), 'fixed_dagorShaders.cfg') + config.read(config_filepath) + os.remove(config_filepath) category = None global_params_section = config["_global_params"] global_params = [] diff --git a/prog/tools/dag4blend/settings.py b/prog/tools/dag4blend/settings.py index b413cea30..0180283b4 100644 --- a/prog/tools/dag4blend/settings.py +++ b/prog/tools/dag4blend/settings.py @@ -157,7 +157,7 @@ class DagShaderClass(PropertyGroup): def get_obj_prop_presets(self, context): pref=bpy.context.preferences.addons[__package__].preferences - path = user_resource('SCRIPTS') + f'\\addons\\{__package__}\\object_properties\\presets\\' + path = pref.props_presets_path items = [] files = [f for f in listdir(path) if isfile(join(path,f))] for f in files: @@ -167,13 +167,22 @@ def get_obj_prop_presets(self, context): #IMPORTER class Dag_Import_Props(PropertyGroup): - with_subfolders :BoolProperty (name="Search in subfolders", default=False, description = 'Search for .dags in subfolders as well') - mopt :BoolProperty (name="Optimize material slots", default=True, description = 'Remove unnecessary material slots') - replace_existing:BoolProperty (name="Reimport existing", default=False, description = 'Replace dags that already exist in scene by imported versions') - preserve_path :BoolProperty (name="Preserve paths", default=False, description = 'Override export path for each imported dag') - dirpath :StringProperty(name="Path", default='C:\\tmp\\', subtype = 'DIR_PATH', description = "Where search for .dag files?",update=upd_imp_path) - masks :StringProperty(name="Masks", default='', description = 'name should contain at least one to be imported. Split by";"') - excludes :StringProperty(name="Excludes",default='', description = 'name should not contain any to be imported. Split by";"') + with_subfolders :BoolProperty (name="Search in subfolders", default=False, + description = 'Search for .dags in subfolders as well') + mopt :BoolProperty (name="Optimize material slots", default=True, + description = 'Remove unnecessary material slots') + preserve_sg :BoolProperty (name="Preserve Smoothing Groups", default=False, + description = "Store Smoothing Groups in a integer face attribute called 'SG'") + replace_existing:BoolProperty (name="Reimport existing", default=False, + description = 'Replace dags that already exist in scene by imported versions') + preserve_path :BoolProperty (name="Preserve paths", default=False, + description = 'Override export path for each imported dag') + dirpath :StringProperty(name="Path", default='C:\\tmp\\', subtype = 'DIR_PATH', + description = "Where search for .dag files?",update=upd_imp_path) + masks :StringProperty(name="Masks", default='', + description = 'name should contain at least one to be imported. Split by";"') + excludes :StringProperty(name="Excludes",default='', + description = 'name should not contain any to be imported. Split by";"') #EXPORTER class Dag_Export_Props(PropertyGroup): @@ -249,10 +258,12 @@ class dag4blend_props(PropertyGroup): class DagSettings(AddonPreferences): bl_idname = __package__ def_path='C:\\replace_by_correct_path\\' - def_cfg_path='D:\\dagor2\tools\\dagor3_cdk\\plugin3dsMax-x64\\dagorShaders.cfg' + def_cfg_path=bpy.utils.user_resource('SCRIPTS') + f"\\addons\\{__package__}\\dagorShaders.cfg" #global assets_path: StringProperty(default=def_path, subtype = 'DIR_PATH', description = 'assets location') - #cfg_path: StringProperty(default=def_cfg_path, subtype = 'FILE_PATH',description = 'dagorShaders.cfg location') + props_presets_path: StringProperty(subtype = 'DIR_PATH', + default = bpy.utils.user_resource('SCRIPTS') + f"\\addons\\{__package__}\\object_properties\\presets") + cfg_path: StringProperty(default=def_cfg_path, subtype = 'FILE_PATH',description = 'dagorShaders.cfg location') new_project_name: StringProperty(description = 'name of project') new_project_path: StringProperty(description = 'where assets(dags, textures, composits) of that project stored?', @@ -322,16 +333,19 @@ class DagSettings(AddonPreferences): def draw(self, context): l = self.layout - proj=l.box() - new=proj.row() - new.prop (self,'new_project_name',text='Name') - new.prop (self,'new_project_path',text='Path') - new.operator('dt.add_project',icon='ADD',text='ADD Project') + paths = l.box() + paths.prop(self, 'props_presets_path', text = "ObjProps presets") + paths.prop(self, 'cfg_path', text = "dagorShaders.cfg") projects = l.box() header = projects.row() header.prop(self, 'projects_maximized', text = 'Projects',icon = 'DOWNARROW_HLT'if self.projects_maximized else 'RIGHTARROW_THIN', emboss=False,expand=True) if self.projects_maximized: + box = projects.box() + new=box.row() + new.prop (self,'new_project_name',text='Name') + new.prop (self,'new_project_path',text='Path') + new.operator('dt.add_project',icon='ADD',text='ADD Project') index=0 for project in self.projects: box = projects.box() diff --git a/prog/tools/dag4blend/smooth_groups/mesh_calc_smooth_groups.py b/prog/tools/dag4blend/smooth_groups/mesh_calc_smooth_groups.py new file mode 100644 index 000000000..f46a9e9a4 --- /dev/null +++ b/prog/tools/dag4blend/smooth_groups/mesh_calc_smooth_groups.py @@ -0,0 +1,189 @@ +import bpy, bmesh + +#bmesh functions-------------------------------------------------------------------------------------------------------- + +#converts mesh/edit_mesh to bmesh +def mesh_to_bmesh(mesh): + current_mode = bpy.context.mode + if current_mode == 'EDIT_MESH': + bm = bmesh.from_edit_mesh(mesh) + else: + bm = bmesh.new() + bm.from_mesh(mesh) + #it's not always necessary, but in most cases it's faster than re-calling function several times + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + bm.faces.ensure_lookup_table() + return bm + +#converts bmesh data back to mesh/edit_mesh +def bmesh_to_mesh(bm, mesh): + current_mode = bpy.context.mode + if current_mode == 'EDIT_MESH': + bmesh.update_edit_mesh(mesh) + else: + bm.to_mesh(mesh) + return mesh + + +def bmesh_select_elements(elements): + #works only after bm..ensure_lookup_table() + for el in elements: + el.select_set(True) + return + + +def bmesh_deselect_elements(elements): + #works only after bm..ensure_lookup_table() + for el in elements: + el.select_set(False) + return + +#returns faces, that have at least one +def bmesh_get_perimeter_faces(faces): + neighbours = [] + for f in faces: + for v in f.verts: + for link_face in v.link_faces: + if link_face.select or link_face == f: + continue + link_face.select_set(True) + neighbours.append(link_face) + return neighbours + + +#SmoothGroups specific functions---------------------------------------------------------------------------------------- + +#constants +SG_MAX_UINT = 2**32#overflow happens here +SG_CONVERT = 2**31#greater than signed int32 max + +#unsigned to signed +def uint_to_int(int): + if int0: + return int + else: + return SG_MAX_UINT+int#2**32-something + +#returns logical OR of SG for list of faces +def get_sg_combined(faces, SmoothGroups): + sg = 0 + for face in faces: + sg = sg|SmoothGroups[face.index] + return sg + +''' +Returns an int value, that has no shared bits with (in binary representation) +and has at least one shared bit with each value in [includes] +Returns 0, if such SG does not exist +''' +def get_free_sg(includes, exclude_sg): + for el in includes: + el = int_to_uint(el) + exclude_sg = int_to_uint(exclude_sg) + #no SG should be included. One would be enough. Each bit is a power of 2 + if includes.__len__() == 0: + for pow in range (32): + if 2**pow & exclude_sg == 0: + return uint_to_int(2**pow) + #if we're here, all 32 bits were skipped, no more legit options. + return 0 + optimized = []#includes, but without bits of exclude_sg + for value in includes: + shared_bits = exclude_sg & value + if shared_bits == value: + #SG can't include and exclude same set of bits + return 0 + else: + diff = value - shared_bits + if diff not in optimized: + optimized.append(diff) + #combining into single SG + found_sg = 0 + for value in optimized: + found_sg = found_sg|value + + #removing unnecessary bits if they exist + current_bit = 1 + for power in range(32):#['0b1', '0b10', '0b100', etc.] + is_necessary = False + if found_sg & current_bit == 0: + current_bit = current_bit*2 + continue + possible_sg = found_sg - current_bit + for opt in optimized: + if opt & possible_sg == 0: + is_necessary = True + if is_necessary: + break + current_bit = current_bit*2 + if is_necessary: + continue + found_sg = possible_sg + found_sg = uint_to_int(found_sg) + return found_sg + +#converts list of island indecies to islands +def ids_to_islands(IDs, faces): + islands = [[] for island in range(IDs[1])] + for face_index in range(IDs[0].__len__()): + current_island = IDs[0][face_index]-1 #starts with 1 instead of 0 + islands[current_island].append(faces[face_index]) + return islands + +#Calcs and assigns SG attribute +def mesh_calc_smooth_groups(mesh): + bm = mesh_to_bmesh(mesh) + SmoothGroups = [0 for face in range(bm.faces.__len__())] + IDs = mesh.calc_smooth_groups(use_bitflags = False)#each island gets unic int index. ([numbers], amount of islands) + islands = ids_to_islands(IDs, bm.faces) + bmesh_deselect_elements(bm.faces)#selection used to speed up search for neighbours + for island in islands: + bmesh_select_elements(island) + neighbours = bmesh_get_perimeter_faces(island) + bmesh_deselect_elements(island+neighbours) + exclude_sg = get_sg_combined(neighbours, SmoothGroups) + value = get_free_sg([],exclude_sg) + for f in island: + SmoothGroups[f.index] = value + Attr = bm.faces.layers.int.get("SG") + if Attr is None: + Attr = bm.faces.layers.int.new("SG") + for face in bm.faces: + face[Attr] = SmoothGroups[face.index] if face.smooth else 0 + return + +def init_sg_attribute(object): + attributes = object.data.attributes + attr = attributes.get("SG") + if attr is None: + attributes.new("SG", 'INT', 'FACE') + elif attr.domain!='FACE' or attr.data_type!='INT': + attr.name = "SG.incorrect" + attributes.new("SG", 'INT', 'FACE') + return + +#Calculates SG attribute for input 'MESH' objects +def objects_calc_smooth_groups(objects): + current_mode = bpy.context.mode + for obj in objects: + init_sg_attribute(obj) + #applying edit_mesh changes to meshes if necessary + if current_mode == 'EDIT_MESH': + needs_update = False + for obj in objects: + if obj.mode == 'EDIT': + needs_update = True + break + if needs_update: + bpy.ops.object.editmode_toggle()# "edit_mesh" might not match "mesh", it updates only on switching + bpy.ops.object.editmode_toggle()# keepeng user in the same mode by switching back to edit + for obj in objects: + mesh_calc_smooth_groups(obj.data) + return \ No newline at end of file diff --git a/prog/tools/dag4blend/smooth_groups/smooth_groups.py b/prog/tools/dag4blend/smooth_groups/smooth_groups.py index 42e49879f..7f3c2ef26 100644 --- a/prog/tools/dag4blend/smooth_groups/smooth_groups.py +++ b/prog/tools/dag4blend/smooth_groups/smooth_groups.py @@ -5,23 +5,8 @@ from bpy.types import Operator from bpy.props import IntProperty -from ..helpers.basename import basename - - -#constants -SG_MAX_UINT = 4294967295#2**0..2**31 -SG_CONVERT = 2147483648#greater than signed int32 max - -def uint_to_int(int): - if int>=SG_CONVERT: - return int-4294967296#int-2**32, which is = SG_MAX_UINT+1 - else: - return int -def int_to_uint(int): - if int<0: - return 4294967296+int#2**32+int - else: - return int +from ..helpers.basename import basename +from .mesh_calc_smooth_groups import * dic={} # update method @@ -31,33 +16,16 @@ def update_sg(self, context): face = bm.faces.active SG = bm.faces.layers.int.get("SG") if face and SG: - face[SG] = self.layer_int_value + face[SG] = self.sg_current_value return None -#layer_int_value is used to set the value +#sg_current_value is used to set the value #functions -def precalc_sg(mesh): - SG=mesh.attributes.get('SG') - if SG is None: - SG=mesh.attributes.new('SG','INT','FACE') - elif SG.domain!='FACE' or SG.data_type!='INT': - SG.name=SG.name+'.wrong_type' - SG=mesh.attributes.new('SG','INT','FACE') - precalc=mesh.calc_smooth_groups(use_bitflags=True)[0] - for i in range (SG.data.__len__()): - SG.data[i].value=precalc[i] if mesh.polygons[i].use_smooth else 0 - return -#edit mode only + def sg_to_sharp_edges(mesh): mesh.use_auto_smooth=True mesh.auto_smooth_angle=pi - if bpy.context.mode=='EDIT_MESH': - bm=bmesh.from_edit_mesh(mesh) - else: - bm=bmesh.new() - bm.from_mesh(mesh) - bm.faces.ensure_lookup_table() - bm.edges.ensure_lookup_table() + bm = mesh_to_bmesh(mesh) edge_group=[0 for index in range(bm.edges.__len__())] smoothed=[edge_group.copy() for i in range(32)] SG=bm.faces.layers.int['SG'] @@ -75,21 +43,16 @@ def sg_to_sharp_edges(mesh): if edge_count>1: bm.edges[i].smooth=True i+=1 - if bpy.context.mode=='EDIT_MESH': - bmesh.update_edit_mesh(mesh) - else: - bm.to_mesh(mesh) - bm.free() - del bm + bmesh_to_mesh(bm, mesh) return -#keep wm.layer_int_value updated to active face -def set_smooth_group(bm): +#keep wm.sg_current_value updated to match selection +def update_sg_current(bm): face = bm.faces.active if face is not None: SG = bm.faces.layers.int.get("SG") if SG: - bpy.context.window_manager.layer_int_value = face[SG] + bpy.context.window_manager.sg_current_value = face[SG] return None #scene update handler @persistent @@ -100,7 +63,7 @@ def edit_object_change_handler(self,context): # add one instance of edit bmesh to global dic if context.mode == 'EDIT_MESH': bm = dic.setdefault(obj.name, bmesh.from_edit_mesh(obj.data)) - set_smooth_group(bm) + update_sg_current(bm) return None dic.clear() return None @@ -111,12 +74,8 @@ class DAGOR_OT_SmoothGroupInit(bpy.types.Operator): bl_idname = 'dt.init_smooth_group' bl_description = 'Initialize smoothing groups' bl_options = {'UNDO'} - object: bpy.props.StringProperty(default='') def execute(self, context): - if context.mode=='EDIT_MESH': - bpy.ops.object.editmode_toggle() - precalc_sg(context.object.data) - bpy.ops.object.editmode_toggle() + objects_calc_smooth_groups([context.object]) return {'FINISHED'} class DAGOR_OT_SmoothGroupsRemove(bpy.types.Operator): @@ -124,15 +83,11 @@ class DAGOR_OT_SmoothGroupsRemove(bpy.types.Operator): bl_idname = 'dt.remove_smooth_groups' bl_description = 'Remove SG data' bl_options = {'UNDO'} - object: bpy.props.StringProperty(default='') def execute(self, context): - if context.mode=='EDIT_MESH': - bpy.ops.object.editmode_toggle() - mesh = context.object.data - SG = mesh.attributes.get('SG') - if SG is not None: - mesh.attributes.remove(SG) - bpy.ops.object.editmode_toggle() + mesh = context.object.data + SG = mesh.attributes.get('SG') + if SG is not None: + mesh.attributes.remove(SG) return {'FINISHED'} class DAGOR_OT_SmoothGroupToSharpEdges(bpy.types.Operator): @@ -147,7 +102,7 @@ def execute(self, context): class DAGOR_OT_SmoothGroupSet(bpy.types.Operator): bl_label = 'Set SmoothGroup' - bl_idname = 'dt.set_smooth_group' + bl_idname = 'dt.update_sg_current' bl_description = 'Set smoothing group' bl_options = {'UNDO'} index: bpy.props.IntProperty(default=-1) @@ -155,14 +110,16 @@ class DAGOR_OT_SmoothGroupSet(bpy.types.Operator): def execute(self, context): i=self.index pressed=self.pressed - if i<0: - return{'CANCELLED'} addon_name = basename(__package__) pref=context.preferences.addons[addon_name].preferences obj=context.edit_object bm = dic.setdefault(obj.name, bmesh.from_edit_mesh(obj.data)) SG = bm.faces.layers.int.get("SG") sel=[f for f in bm.faces if f.select] + if i<0: + for face in sel: + face[SG] = 0 + return{'FINISHED'} active_bit=2**i if pressed&active_bit!=0: for face in sel: @@ -182,16 +139,19 @@ class DAGOR_OT_SmoothGroupSelect(Operator): index: bpy.props.IntProperty(default=-1) def execute(self,context): i=self.index - if i<0: - return{'CANCELLED'} active_bit=2**i obj=context.edit_object mesh = bmesh.from_edit_mesh(obj.data) bm = dic.setdefault(obj.name, mesh) SG = bm.faces.layers.int.get("SG") - for face in bm.faces: - if face[SG]&active_bit!=0: - face.select = True + if i<0: + for face in bm.faces: + if face[SG]==0: + face.select = True + else: + for face in bm.faces: + if face[SG]&active_bit!=0: + face.select = True bmesh.update_edit_mesh(obj.data) return {'FINISHED'} @@ -233,17 +193,17 @@ def draw(self, context): l.operator('dt.preview_sg',text='Convert to sharp edges') #buttons state cheaper calc if sel.__len__()>0: - all_pressed=uint_to_int(SG_MAX_UINT) - tex_ones=sel[0][smooth_groups] - tex_zeros=~sel[0][smooth_groups] + all_pressed=uint_to_int(SG_MAX_UINT-1) + ones=sel[0][smooth_groups] + zeros=~sel[0][smooth_groups] for face in sel: all_pressed&=face[smooth_groups] - tex_ones&=face[smooth_groups]#similar 1 bits - tex_zeros&=~face[smooth_groups]#similar 0 bits - all_w_text=tex_ones|tex_zeros + ones&=face[smooth_groups]#similar 1 bits + zeros&=~face[smooth_groups]#similar 0 bits + all_active=ones|zeros else: all_pressed=0 - all_w_text=SG_MAX_UINT + all_active=SG_MAX_UINT-1 #buttons state end SG_set=l.box() header = SG_set.row() @@ -251,15 +211,21 @@ def draw(self, context): icon = 'DOWNARROW_HLT'if pref.sg_set_maximized else 'RIGHTARROW_THIN', emboss=False, expand=True) if pref.sg_set_maximized: - SG_set.prop(pref, 'sg_live_refresh', text = "Live Update", toggle = True) + SG_set.prop(pref, 'sg_live_refresh', text = "Live Update", toggle = True, + icon = 'CHECKBOX_HLT' if pref.sg_live_refresh else 'CHECKBOX_DEHLT') + SG_set=SG_set.column(align = True) + reset = SG_set.row(align = True) + reset.operator('dt.update_sg_current', text = "Clear Selected").index = -1 text=0 row = SG_set.row(align = True) columns = [row.column(align = True) for i in range(4)] for line in range(8): for column in range(4): pressed=all_pressed&2**text!=0 - w_text=all_w_text&2**text!=0 - btn=columns[column].operator('dt.set_smooth_group',text=f'{text+1}' if w_text else '',depress=pressed) + active=all_active&2**text!=0 + btn_container = columns[column].row() + btn = btn_container.operator('dt.update_sg_current',text=f'{text+1}',depress=pressed) + btn_container.active = active btn.index=text text+=1 btn.pressed=all_pressed @@ -269,12 +235,15 @@ def draw(self, context): icon = 'DOWNARROW_HLT'if pref.sg_select_maximized else 'RIGHTARROW_THIN', emboss=False, expand=True) if pref.sg_select_maximized: + SG_select=SG_select.column(align = True) + SG_select.operator('dt.select_smooth_group',text='0').index = -1 text = 0 row = SG_select.row(align = True) columns = [row.column(align = True) for i in range(4)] for line in range(8): for column in range(4): - btn=columns[column].operator('dt.select_smooth_group',text=f'{text+1}') + btn_container = columns[column].row() + btn=btn_container.operator('dt.select_smooth_group',text=f'{text+1}') btn.index=text text+=1 return @@ -290,7 +259,7 @@ def draw(self, context): def register(): for cl in classes: bpy.utils.register_class(cl) - bpy.types.WindowManager.layer_int_value = IntProperty(name="DEBUG", update=update_sg) + bpy.types.WindowManager.sg_current_value = IntProperty(name="DEBUG", update=update_sg) bpy.app.handlers.depsgraph_update_post.append(edit_object_change_handler) def unregister(): diff --git a/prog/tools/dag4blend/tools/tools_panel.py b/prog/tools/dag4blend/tools/tools_panel.py index 2604473bf..5ed4846bd 100644 --- a/prog/tools/dag4blend/tools/tools_panel.py +++ b/prog/tools/dag4blend/tools/tools_panel.py @@ -48,10 +48,9 @@ def save_textures(sel,path): return tex_to_save.__len__() def clear_normals(objects): - ctx = bpy.context.copy() for obj in objects: - ctx['object']=obj - bpy.ops.mesh.customdata_custom_splitnormals_clear(ctx) + with bpy.context.temp_override(object = obj): + bpy.ops.mesh.customdata_custom_splitnormals_clear() return def sort_collections(COL):