├── qualitycheck.py ├── README_images ├── Exporters_139.png ├── Importers_139.png ├── Quick_utils_139.png ├── LOD_generator_139.png ├── Texture_patcher_139.png ├── Color_correction_tool_139.png ├── Photogrammetry_tool_139.png └── Spherical_photogrammetry_tool_139.png ├── .gitignore ├── SpPhotogrTool.py ├── developer_utils.py ├── Shift.py ├── README.md ├── __init__.py ├── i_points_txt.py ├── e_points.py ├── TexPatcher.py ├── import.py ├── ccTool.py ├── export.py ├── PhotogrTool.py ├── QuickUtils.py ├── functions.py └── LODgenerator.py /qualitycheck.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README_images/Exporters_139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/HEAD/README_images/Exporters_139.png -------------------------------------------------------------------------------- /README_images/Importers_139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/HEAD/README_images/Importers_139.png -------------------------------------------------------------------------------- /README_images/Quick_utils_139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/HEAD/README_images/Quick_utils_139.png -------------------------------------------------------------------------------- /README_images/LOD_generator_139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/HEAD/README_images/LOD_generator_139.png -------------------------------------------------------------------------------- /README_images/Texture_patcher_139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/HEAD/README_images/Texture_patcher_139.png -------------------------------------------------------------------------------- /README_images/Color_correction_tool_139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/HEAD/README_images/Color_correction_tool_139.png -------------------------------------------------------------------------------- /README_images/Photogrammetry_tool_139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/HEAD/README_images/Photogrammetry_tool_139.png -------------------------------------------------------------------------------- /README_images/Spherical_photogrammetry_tool_139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/HEAD/README_images/Spherical_photogrammetry_tool_139.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/*.* 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /SpPhotogrTool.py: -------------------------------------------------------------------------------- 1 | #import bpy 2 | #import os 3 | #import time 4 | 5 | #from mathutils import Vector 6 | #from bpy_extras.io_utils import ImportHelper 7 | 8 | #from bpy.props import (BoolProperty, 9 | # FloatProperty, 10 | # StringProperty, 11 | # EnumProperty, 12 | # CollectionProperty 13 | # ) 14 | 15 | #import bmesh 16 | #from random import randint, choice 17 | 18 | 19 | #class ToolsPanel100(bpy.types.Panel): 20 | # bl_space_type = "VIEW_3D" 21 | # bl_region_type = "TOOLS" 22 | 23 | # bl_category = "3DSC" 24 | # bl_label = "Spherical Photogrammetry tool (alpha)" 25 | # bl_options = {'DEFAULT_CLOSED'} 26 | 27 | # def draw(self, context): 28 | # layout = self.layout 29 | # obj = context.object 30 | # row = layout.row() 31 | # row.label(text="Folder with the oriented 360 collection") 32 | # row = layout.row() 33 | # row.prop(context.scene, 'BL_oriented360_path', toggle = True) 34 | # row = layout.row() 35 | # row.label(text="Painting Toolbox", icon='TPAINT_HLT') 36 | # row = layout.row() 37 | # self.layout.operator("paint.cam", icon="IMAGE_COL", text='Paint selected from cam') 38 | 39 | 40 | # bpy.types.Scene.BL_oriented360_path = StringProperty( 41 | # name = "Oriented 360 Path", 42 | # default = "", 43 | # description = "Define the root path of the oriented 360 collection", 44 | # subtype = 'DIR_PATH' 45 | # ) -------------------------------------------------------------------------------- /developer_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pkgutil 4 | import importlib 5 | 6 | def setup_addon_modules(path, package_name, reload): 7 | """ 8 | Imports and reloads all modules in this addon. 9 | 10 | path -- __path__ from __init__.py 11 | package_name -- __name__ from __init__.py 12 | 13 | Individual modules can define a __reload_order_index__ property which 14 | will be used to reload the modules in a specific order. The default is 0. 15 | """ 16 | def get_submodule_names(path = path[0], root = ""): 17 | module_names = [] 18 | for importer, module_name, is_package in pkgutil.iter_modules([path]): 19 | if is_package: 20 | sub_path = os.path.join(path, module_name) 21 | sub_root = root + module_name + "." 22 | module_names.extend(get_submodule_names(sub_path, sub_root)) 23 | else: 24 | module_names.append(root + module_name) 25 | return module_names 26 | 27 | def import_submodules(names): 28 | modules = [] 29 | for name in names: 30 | modules.append(importlib.import_module("." + name, package_name)) 31 | return modules 32 | 33 | def reload_modules(modules): 34 | modules.sort(key = lambda module: getattr(module, "__reload_order_index__", 0)) 35 | for module in modules: 36 | importlib.reload(module) 37 | 38 | names = get_submodule_names() 39 | modules = import_submodules(names) 40 | if reload: 41 | reload_modules(modules) 42 | return modules 43 | -------------------------------------------------------------------------------- /Shift.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ToolsPanel444(bpy.types.Panel): 4 | bl_space_type = "VIEW_3D" 5 | bl_region_type = "TOOLS" 6 | bl_context = "objectmode" 7 | bl_category = "3DSC" 8 | bl_label = "Shift" 9 | bl_options = {'DEFAULT_CLOSED'} 10 | 11 | def draw(self, context): 12 | layout = self.layout 13 | scene = context.scene 14 | obj = context.object 15 | 16 | row = layout.row() 17 | row.label(text="Shift values:") 18 | row = layout.row() 19 | row.prop(context.scene, 'SRID', toggle = True) 20 | row = layout.row() 21 | row.prop(context.scene, 'BL_x_shift', toggle = True) 22 | row = layout.row() 23 | row.prop(context.scene, 'BL_y_shift', toggle = True) 24 | row = layout.row() 25 | row.prop(context.scene, 'BL_z_shift', toggle = True) 26 | row = layout.row() 27 | if scene['crs x'] is not None and scene['crs y'] is not None: 28 | if scene['crs x'] > 0 or scene['crs y'] > 0: 29 | self.layout.operator("shift_from.blendergis", icon="PASTEDOWN", text='from Bender GIS') 30 | 31 | 32 | class OBJECT_OT_IMPORTPOINTS(bpy.types.Operator): 33 | """Import points as empty objects from a txt file""" 34 | bl_idname = "shift_from.blendergis" 35 | bl_label = "Copy from BlenderGis" 36 | bl_options = {"REGISTER", "UNDO"} 37 | 38 | def execute(self, context): 39 | scene = context.scene 40 | scene['BL_x_shift'] = scene['crs x'] 41 | scene['BL_y_shift'] = scene['crs y'] 42 | 43 | return {'FINISHED'} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3D Survey Collection (3DSC), ex Blender Landscape 2 | 3 | Hello there! I’m **3D Survey Collection** (Blender Add-on), an open source collection of tools to improve the work-flow of a 3D survey (terrestrial or UAV photogrammetry). 4 | 5 | I am under constant development by [Emanuel Demetrescu](http://www.itabc.cnr.it/team/emanuel-demetrescu) during his research activities at the [CNR-ITABC](http://www.itabc.cnr.it) (Italian National Council for Research, Institute for Technologies Appplied to Cultural Heritage) within the [VHLab](http://www.itabc.cnr.it/pagine/vh-lab) (Virtual Heritage Laboratory). 6 | 7 | Let me introduce the tools with a short list. 8 | 9 | * [**Importers** panel](#importers-pane) 10 | * [**Exporters** panel](#exporters-pane) 11 | * [**Quick utils** panel](#quick_utils-pane) 12 | * [**Photogrammetry** panel](#photogrammetry_tool-pane) 13 | * [**Color Correction** panel](#color_correction_tool-pane) 14 | * [**LOD Generator** panel](#LOD_generator-pane) 15 | 16 | ## Importers 17 | 18 | The **Importers** panel allows to 19 | 20 | * import multiple objs at once (with correct orientation), for instance a bunch of models made in Photoscan, Meshroom or Zephir 3D. 21 | * import points as empty objects from a txt file, for instance a topographic survey made with a total station or DGPS. Optionally I can shift the values of the world coordinates (they are usually too big to be correctly managed by a computer graphic software like Blender and it is a good practice to shift them) 22 | 23 | ![Importers screenshot](https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/master/README_images/Importers_139.png) 24 | 25 | ## Exporters 26 | 27 | The **Exporters** panel allows to 28 | 29 | ![Importers screenshot](https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/master/README_images/Exporters_139.png) 30 | 31 | ## Quick Utils 32 | 33 | The **Quick Utils** panel allows to 34 | 35 | ![Importers screenshot](https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/master/README_images/Quick_utils_139.png) 36 | 37 | ### Fast utilities 38 | #### turn on/off lights 39 | 40 | ### Dealing with Cycles/GLSL materials 41 | #### create cycles materials 42 | 43 | 44 | ## Photogrammetry 45 | 46 | The **Photogrammetry** panel allows to 47 | 48 | ![Importers screenshot](https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/master/README_images/Photogrammetry_tool_139.png) 49 | 50 | ## Color correction 51 | 52 | The **Color correction** panel allows to 53 | 54 | ![Importers screenshot](https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/master/README_images/Color_correction_tool_139.png) 55 | 56 | ## LOD generator 57 | 58 | The **Color correction** panel allows to 59 | 60 | ![Importers screenshot](https://raw.githubusercontent.com/zalmoxes-laran/BlenderLandscape/master/README_images/LOD_generator_139.png) 61 | 62 | ### How to cite this tool 63 | 64 | You can link this github repo [3DSC Website](https://github.com/zalmoxes-laran/BlenderLandscape "Title") or cite 65 | 66 | @misc{demetrescu_3d_2018, 67 | title = {3D {Survey} {Collection}}, 68 | url = {https://github.com/zalmoxes-laran/BlenderLandscape}, 69 | author = {Demetrescu, Emanuel}, 70 | year = {2018} 71 | } 72 | 73 | ### Contact 74 | If something goes wrong with me or if you have comments or suggestions, please report a feedback to 75 | 76 | 77 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created by EMANUEL DEMETRESCU 3 | emanuel.demetrescu@gmail.com 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | ''' 18 | 19 | bl_info = { 20 | "name": "3D Survey Collection", 21 | "author": "E. Demetrescu", 22 | "version": (1,4.0), 23 | "blender": (2, 7, 9), 24 | "location": "Tool Shelf panel", 25 | "description": "A collection of tools for 3D Survey activities", 26 | "warning": "", 27 | "wiki_url": "", 28 | "tracker_url": "", 29 | "category": "Tools"} 30 | 31 | 32 | import bpy 33 | 34 | 35 | # load and reload submodules 36 | ################################## 37 | 38 | import importlib 39 | from . import developer_utils 40 | importlib.reload(developer_utils) 41 | modules = developer_utils.setup_addon_modules(__path__, __name__, "bpy" in locals()) 42 | 43 | from bpy.props import (BoolProperty, 44 | FloatProperty, 45 | StringProperty, 46 | EnumProperty, 47 | CollectionProperty 48 | ) 49 | 50 | # register 51 | ################################## 52 | 53 | import traceback 54 | 55 | 56 | #def register(): 57 | # bpy.utils.register_class(InterfaceVars) 58 | # bpy.types.WindowManager.interface_vars = bpy.props.PointerProperty(type=InterfaceVars) 59 | 60 | 61 | class InterfaceVars(bpy.types.PropertyGroup): 62 | cc_nodes = bpy.props.EnumProperty( 63 | items=[ 64 | ('RGB', 'RGB', 'RGB Curve', '', 0), 65 | ('BC', 'BC', 'Bright/Contrast', '', 1), 66 | ('HS', 'HS', 'Hue/Saturation', '', 2), 67 | ], 68 | default='RGB' 69 | ) 70 | 71 | def register(): 72 | try: bpy.utils.register_module(__name__) 73 | except: traceback.print_exc() 74 | 75 | print("Registered {} with {} modules".format(bl_info["name"], len(modules))) 76 | 77 | # bpy.utils.register_class(InterfaceVars) 78 | bpy.types.WindowManager.interface_vars = bpy.props.PointerProperty(type=InterfaceVars) 79 | 80 | bpy.types.Scene.BL_undistorted_path = StringProperty( 81 | name = "Undistorted Path", 82 | default = "", 83 | description = "Define the root path of the undistorted images", 84 | subtype = 'DIR_PATH' 85 | ) 86 | 87 | bpy.types.Scene.BL_x_shift = FloatProperty( 88 | name = "X shift", 89 | default = 0.0, 90 | description = "Define the shift on the x axis", 91 | ) 92 | 93 | bpy.types.Scene.BL_y_shift = FloatProperty( 94 | name = "Y shift", 95 | default = 0.0, 96 | description = "Define the shift on the y axis", 97 | ) 98 | 99 | bpy.types.Scene.BL_z_shift = FloatProperty( 100 | name = "Z shift", 101 | default = 0.0, 102 | description = "Define the shift on the z axis", 103 | ) 104 | 105 | def unregister(): 106 | try: bpy.utils.unregister_module(__name__) 107 | except: traceback.print_exc() 108 | 109 | print("Unregistered {}".format(bl_info["name"])) 110 | 111 | 112 | if __name__ == "__main__": 113 | register() 114 | -------------------------------------------------------------------------------- /i_points_txt.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | def read_point_data(context, filepath, shift, name_col, x_col, y_col, z_col, separator): 5 | print("running read point file...") 6 | f = open(filepath, 'r', encoding='utf-8') 7 | # data = f.read() 8 | arr=f.readlines() # store the entire file in a variable 9 | f.close() 10 | 11 | for p in arr: 12 | p0 = p.split(separator) # use colon as separator 13 | ItemName = p0[int(name_col)] 14 | x_coor = float(p0[int(x_col)]) 15 | y_coor = float(p0[int(y_col)]) 16 | z_coor = float(p0[int(z_col)]) 17 | 18 | if shift == True: 19 | shift_x = context.scene.BL_x_shift 20 | shift_y = context.scene.BL_y_shift 21 | shift_z = context.scene.BL_z_shift 22 | x_coor = x_coor-shift_x 23 | y_coor = y_coor-shift_y 24 | z_coor = z_coor-shift_z 25 | 26 | # Generate UV sphere at x = lon and y = lat (and z = 0 ) 27 | o = bpy.data.objects.new( ItemName, None ) 28 | bpy.context.scene.objects.link( o ) 29 | o.location.x = x_coor 30 | o.location.y = y_coor 31 | o.location.z = z_coor 32 | o.show_name = True 33 | 34 | return {'FINISHED'} 35 | 36 | 37 | # ImportHelper is a helper class, defines filename and 38 | # invoke() function which calls the file selector. 39 | from bpy_extras.io_utils import ImportHelper 40 | from bpy.props import StringProperty, BoolProperty, EnumProperty 41 | from bpy.types import Operator 42 | 43 | 44 | class ImportCoorPoints(Operator, ImportHelper): 45 | """Tool to import coordinate points from a txt file""" 46 | bl_idname = "import_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed 47 | bl_label = "Import Coordinate Points" 48 | 49 | # ImportHelper mixin class uses this 50 | filename_ext = ".txt" 51 | 52 | filter_glob = StringProperty( 53 | default="*.txt", 54 | options={'HIDDEN'}, 55 | maxlen=255, # Max internal buffer length, longer would be clamped. 56 | ) 57 | 58 | # List of operator properties, the attributes will be assigned 59 | # to the class instance from the operator settings before calling. 60 | shift = BoolProperty( 61 | name="Shift coordinates", 62 | description="Shift coordinates using the General Shift Value (GSV)", 63 | default=False, 64 | ) 65 | 66 | col_name = EnumProperty( 67 | name="Name", 68 | description="Column with the name", 69 | items=(('0', "Column 1", "Column 1"), 70 | ('1', "Column 2", "Column 2"), 71 | ('2', "Column 3", "Column 3"), 72 | ('3', "Column 4", "Column 4")), 73 | default='0', 74 | ) 75 | 76 | col_x = EnumProperty( 77 | name="X", 78 | description="Column with coordinate X", 79 | items=(('0', "Column 1", "Column 1"), 80 | ('1', "Column 2", "Column 2"), 81 | ('2', "Column 3", "Column 3"), 82 | ('3', "Column 4", "Column 4")), 83 | default='1', 84 | ) 85 | 86 | col_y = EnumProperty( 87 | name="Y", 88 | description="Column with coordinate X", 89 | items=(('0', "Column 1", "Column 1"), 90 | ('1', "Column 2", "Column 2"), 91 | ('2', "Column 3", "Column 3"), 92 | ('3', "Column 4", "Column 4")), 93 | default='2', 94 | ) 95 | 96 | col_z = EnumProperty( 97 | name="Z", 98 | description="Column with coordinate X", 99 | items=(('0', "Column 1", "Column 1"), 100 | ('1', "Column 2", "Column 2"), 101 | ('2', "Column 3", "Column 3"), 102 | ('3', "Column 4", "Column 4")), 103 | default='3', 104 | ) 105 | 106 | separator = EnumProperty( 107 | name="separator", 108 | description="Separator type", 109 | items=((',', "comma", "comma"), 110 | (' ', "space", "space"), 111 | (';', "semicolon", "semicolon")), 112 | default=',', 113 | ) 114 | 115 | def execute(self, context): 116 | return read_point_data(context, self.filepath, self.shift, self.col_name, self.col_x, self.col_y, self.col_z, self.separator) 117 | 118 | 119 | # Only needed if you want to add into a dynamic menu 120 | def menu_func_import(self, context): 121 | self.layout.operator(ImportCoorPoints.bl_idname, text="Coordinate points Import Operator") 122 | 123 | 124 | #def register(): 125 | # bpy.utils.register_class(ImportCoorPoints) 126 | # bpy.types.INFO_MT_file_import.append(menu_func_import) 127 | 128 | 129 | #def unregister(): 130 | # bpy.utils.unregister_class(ImportCoorPoints) 131 | # bpy.types.INFO_MT_file_import.remove(menu_func_import) 132 | 133 | 134 | #if __name__ == "__main__": 135 | # register() 136 | 137 | # test call 138 | bpy.ops.import_test.some_data('INVOKE_DEFAULT') -------------------------------------------------------------------------------- /e_points.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import math 3 | 4 | def write_some_data(context, filepath, shift, rot, cam, nam): 5 | print("running write coordinates...") 6 | 7 | 8 | selection = bpy.context.selected_objects 9 | bpy.ops.object.select_all(action='DESELECT') 10 | # activename = bpy.path.clean_name(bpy.context.scene.objects.active.name) 11 | # fn = os.path.join(basedir, activename) 12 | 13 | 14 | f = open(filepath, 'w', encoding='utf-8') 15 | 16 | # file = open(fn + ".txt", 'w') 17 | 18 | # write selected objects coordinate 19 | for obj in selection: 20 | obj.select = True 21 | 22 | x_coor = obj.location[0] 23 | y_coor = obj.location[1] 24 | z_coor = obj.location[2] 25 | 26 | if rot == True or cam == True: 27 | rotation_grad_x = math.degrees(obj.rotation_euler[0]) 28 | rotation_grad_y = math.degrees(obj.rotation_euler[1]) 29 | rotation_grad_z = math.degrees(obj.rotation_euler[2]) 30 | rotation_rad_x = obj.rotation_euler[0] 31 | rotation_rad_y = obj.rotation_euler[1] 32 | rotation_rad_z = obj.rotation_euler[2] 33 | 34 | if rot == True: 35 | scale_x = obj.scale[0] 36 | scale_y = obj.scale[1] 37 | scale_z = obj.scale[2] 38 | 39 | if shift == True: 40 | shift_x = context.scene.BL_x_shift 41 | shift_y = context.scene.BL_y_shift 42 | shift_z = context.scene.BL_z_shift 43 | x_coor = x_coor+shift_x 44 | y_coor = y_coor+shift_y 45 | z_coor = z_coor+shift_z 46 | 47 | # Generate UV sphere at x = lon and y = lat (and z = 0 ) 48 | 49 | if rot == True: 50 | if nam == True: 51 | f.write("%s %s %s %s %s %s %s %s %s %s\n" % (obj.name, x_coor, y_coor, z_coor, rotation_grad_x, rotation_grad_y, rotation_grad_z, scale_x, scale_y, scale_z)) 52 | else: 53 | f.write("%s %s %s %s %s %s %s %s %s\n" % (x_coor, y_coor, z_coor, rotation_grad_x, rotation_rad_y, rotation_grad_z, scale_x, scale_y, scale_z)) 54 | if cam == True: 55 | if obj.type == 'CAMERA': 56 | f.write("%s %s %s %s %s %s %s %s\n" % (obj.name, x_coor, y_coor, z_coor, rotation_grad_x, rotation_grad_y, rotation_grad_z, obj.data.lens)) 57 | if rot == False and cam == False: 58 | if nam == True: 59 | f.write("%s %s %s %s\n" % (obj.name, x_coor, y_coor, z_coor)) 60 | else: 61 | f.write("%s %s %s\n" % (x_coor, y_coor, z_coor)) 62 | 63 | f.close() 64 | 65 | 66 | # f.write("Hello World %s" % use_some_setting) 67 | # f.close() 68 | 69 | return {'FINISHED'} 70 | 71 | 72 | # ExportHelper is a helper class, defines filename and 73 | # invoke() function which calls the file selector. 74 | from bpy_extras.io_utils import ExportHelper 75 | from bpy.props import StringProperty, BoolProperty, EnumProperty 76 | from bpy.types import Operator 77 | 78 | 79 | class ExportSomeData(Operator, ExportHelper): 80 | """This appears in the tooltip of the operator and in the generated docs""" 81 | bl_idname = "export_test.some_data" # important since its how bpy.ops.import_test.some_data is constructed 82 | bl_label = "Export Coordinate Data" 83 | 84 | # ExportHelper mixin class uses this 85 | filename_ext = ".txt" 86 | 87 | filter_glob = StringProperty( 88 | default="*.txt", 89 | options={'HIDDEN'}, 90 | maxlen=255, # Max internal buffer length, longer would be clamped. 91 | ) 92 | 93 | # List of operator properties, the attributes will be assigned 94 | # to the class instance from the operator settings before calling. 95 | 96 | 97 | nam = BoolProperty( 98 | name="Add names of objects", 99 | description="This tool includes name", 100 | default=True, 101 | ) 102 | 103 | rot = BoolProperty( 104 | name="Add coordinates of rotation and scale", 105 | description="This tool includes name, position, rotation and scale", 106 | default=False, 107 | ) 108 | 109 | cam = BoolProperty( 110 | name="Export only cams", 111 | description="This tool includes name, position, rotation and focal lenght", 112 | default=False, 113 | ) 114 | 115 | shift = BoolProperty( 116 | name="World shift coordinates", 117 | description="Shift coordinates using the General Shift Value (GSV)", 118 | default=False, 119 | ) 120 | 121 | def execute(self, context): 122 | return write_some_data(context, self.filepath, self.shift, self.rot, self.cam, self.nam) 123 | 124 | 125 | # Only needed if you want to add into a dynamic menu 126 | def menu_func_export(self, context): 127 | self.layout.operator(ExportSomeData.bl_idname, text="Text Export Operator") 128 | 129 | 130 | #def register(): 131 | # bpy.utils.register_class(ExportSomeData) 132 | # bpy.types.INFO_MT_file_export.append(menu_func_export) 133 | 134 | 135 | #def unregister(): 136 | # bpy.utils.unregister_class(ExportSomeData) 137 | # bpy.types.INFO_MT_file_export.remove(menu_func_export) 138 | 139 | 140 | #if __name__ == "__main__": 141 | # register() 142 | 143 | # # test call 144 | # bpy.ops.export_test.some_data('INVOKE_DEFAULT') 145 | -------------------------------------------------------------------------------- /TexPatcher.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | from .functions import * 4 | 5 | class ToolsPanel1400(bpy.types.Panel): 6 | bl_space_type = "VIEW_3D" 7 | bl_region_type = "TOOLS" 8 | # bl_context = "objectmode" 9 | bl_category = "3DSC" 10 | bl_label = "Texture patcher (Cycles)" 11 | bl_options = {'DEFAULT_CLOSED'} 12 | 13 | def draw(self, context): 14 | layout = self.layout 15 | obj = context.object 16 | scene = context.scene 17 | row = layout.row() 18 | if bpy.context.scene.render.engine != 'CYCLES': 19 | row.label(text="Please, activate cycles engine !") 20 | else: 21 | row = layout.row() 22 | # row.label(text="Select one or more source mesh") 23 | # row = layout.row() 24 | # row.label(text="+ a destination mesh") 25 | self.layout.operator("texture.transfer", icon="FULLSCREEN_EXIT", text='Transfer Texture') 26 | self.layout.operator("applysptexset.material", icon="AUTOMERGE_ON", text='Preview sp tex set') 27 | self.layout.operator("applyoritexset.material", icon="RECOVER_LAST", text='Use original tex set') 28 | self.layout.operator("paint.setup", icon="VPAINT_HLT", text='Paint from source') 29 | if context.object.mode == 'TEXTURE_PAINT': 30 | row = layout.row() 31 | row.prop(scene.tool_settings.image_paint, "seam_bleed") 32 | row = layout.row() 33 | row.prop(scene.tool_settings.image_paint, "use_occlude") 34 | row.prop(scene.tool_settings.image_paint, "use_backface_culling") 35 | row.prop(scene.tool_settings.image_paint, "use_normal_falloff") 36 | 37 | row = layout.row() 38 | self.layout.operator("exit.setup", icon="OBJECT_DATAMODE", text='Exit paint mode') 39 | row = layout.row() 40 | self.layout.operator("savepaint.cam", icon="IMAGE_COL", text='Save new textures') 41 | self.layout.operator("remove.sp", icon="LIBRARY_DATA_BROKEN", text='Remove image source') 42 | 43 | class OBJECT_OT_textransfer(bpy.types.Operator): 44 | """Select two meshes in order to transfer a clone layer""" 45 | bl_idname = "texture.transfer" 46 | bl_label = "Texture transfer" 47 | bl_options = {'REGISTER', 'UNDO'} 48 | 49 | def execute(self, context): 50 | selected_ob = bpy.context.selected_objects 51 | active_ob = bpy.context.scene.objects.active 52 | for matslot in active_ob.material_slots: 53 | mat = matslot.material 54 | nodes = mat.node_tree.nodes 55 | mat.use_nodes = True 56 | source_paint_node = node_retriever(mat, "source_paint_node") 57 | if source_paint_node: 58 | nodes.remove(source_paint_node) 59 | # print("Removed old sp node") 60 | create_new_tex_set(mat, "source_paint_node") 61 | bake_tex_set("source") 62 | 63 | # aggiungere source paint slots 64 | # abiliater painting 65 | # set-up paint 66 | # PAINT 67 | # SAVE paint 68 | pass 69 | return {'FINISHED'} 70 | 71 | class OBJECT_OT_applyoritexset(bpy.types.Operator): 72 | """Use original textures in mats""" 73 | bl_idname = "applyoritexset.material" 74 | bl_label = "Use original textures in mats" 75 | bl_options = {'REGISTER', 'UNDO'} 76 | 77 | def execute(self, context): 78 | 79 | bpy.context.scene.render.engine = 'CYCLES' 80 | 81 | for obj in bpy.context.selected_objects: 82 | for matslot in obj.material_slots: 83 | mat = matslot.material 84 | set_texset(mat, "original") 85 | 86 | return {'FINISHED'} 87 | 88 | class OBJECT_OT_applysptexset(bpy.types.Operator): 89 | """Use sp textures in mats""" 90 | bl_idname = "applysptexset.material" 91 | bl_label = "Use sp textures in mats" 92 | bl_options = {'REGISTER', 'UNDO'} 93 | 94 | def execute(self, context): 95 | 96 | bpy.context.scene.render.engine = 'CYCLES' 97 | 98 | for obj in bpy.context.selected_objects: 99 | for matslot in obj.material_slots: 100 | mat = matslot.material 101 | set_texset(mat, "source_paint_node") 102 | 103 | return {'FINISHED'} 104 | 105 | 106 | class OBJECT_OT_paintsetup(bpy.types.Operator): 107 | """Set up paint from source""" 108 | bl_idname = "paint.setup" 109 | bl_label = "Set up paint from source" 110 | bl_options = {'REGISTER', 'UNDO'} 111 | 112 | def execute(self, context): 113 | bpy.context.scene.render.engine = 'CYCLES' 114 | setupclonepaint() 115 | return {'FINISHED'} 116 | 117 | class OBJECT_OT_exitsetup(bpy.types.Operator): 118 | """Exit paint from source""" 119 | bl_idname = "exit.setup" 120 | bl_label = "Exit paint from source" 121 | bl_options = {'REGISTER', 'UNDO'} 122 | 123 | def execute(self, context): 124 | 125 | bpy.context.scene.render.engine = 'CYCLES' 126 | bpy.context.scene.tool_settings.image_paint.use_clone_layer = False 127 | bpy.ops.object.mode_set ( mode = 'OBJECT' ) 128 | 129 | return {'FINISHED'} 130 | 131 | class OBJECT_OT_removepaintsetup(bpy.types.Operator): 132 | """Remove paint source""" 133 | bl_idname = "remove.sp" 134 | bl_label = "Remove paint source" 135 | bl_options = {'REGISTER', 'UNDO'} 136 | 137 | def execute(self, context): 138 | context = bpy.context 139 | context.scene.render.engine = 'CYCLES' 140 | 141 | for obj in context.selected_objects: 142 | for matslot in obj.material_slots: 143 | mat = matslot.material 144 | remove_node(mat, "source_paint_node") 145 | 146 | return {'FINISHED'} 147 | 148 | 149 | -------------------------------------------------------------------------------- /import.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | from bpy_extras.io_utils import ImportHelper 4 | 5 | from bpy.props import (BoolProperty, 6 | FloatProperty, 7 | StringProperty, 8 | EnumProperty, 9 | CollectionProperty 10 | ) 11 | 12 | class ToolsPanel4(bpy.types.Panel): 13 | bl_space_type = "VIEW_3D" 14 | bl_region_type = "TOOLS" 15 | bl_context = "objectmode" 16 | bl_category = "3DSC" 17 | bl_label = "Importers" 18 | bl_options = {'DEFAULT_CLOSED'} 19 | 20 | def draw(self, context): 21 | layout = self.layout 22 | obj = context.object 23 | # row = layout.row() 24 | # row.label(text="Shift values:") 25 | # row = layout.row() 26 | # row.prop(context.scene, 'BL_x_shift', toggle = True) 27 | # row = layout.row() 28 | # row.prop(context.scene, 'BL_y_shift', toggle = True) 29 | # row = layout.row() 30 | # row.prop(context.scene, 'BL_z_shift', toggle = True) 31 | row = layout.row() 32 | self.layout.operator("import_scene.multiple_objs", icon="WORLD_DATA", text='Import multiple objs') 33 | row = layout.row() 34 | self.layout.operator("import_points.txt", icon="WORLD_DATA", text='Import txt points') 35 | # row = layout.row() 36 | 37 | class OBJECT_OT_IMPORTPOINTS(bpy.types.Operator): 38 | """Import points as empty objects from a txt file""" 39 | bl_idname = "import_points.txt" 40 | bl_label = "ImportPoints" 41 | bl_options = {"REGISTER", "UNDO"} 42 | 43 | def execute(self, context): 44 | bpy.ops.import_test.some_data('INVOKE_DEFAULT') 45 | return {'FINISHED'} 46 | 47 | 48 | class ImportMultipleObjs(bpy.types.Operator, ImportHelper): 49 | """This appears in the tooltip of the operator and in the generated docs""" 50 | bl_idname = "import_scene.multiple_objs" 51 | bl_label = "Import multiple OBJ's" 52 | bl_options = {'PRESET', 'UNDO'} 53 | 54 | # ImportHelper mixin class uses this 55 | filename_ext = ".obj" 56 | 57 | filter_glob = StringProperty( 58 | default="*.obj", 59 | options={'HIDDEN'}, 60 | ) 61 | 62 | # Selected files 63 | files = CollectionProperty(type=bpy.types.PropertyGroup) 64 | 65 | # List of operator properties, the attributes will be assigned 66 | # to the class instance from the operator settings before calling. 67 | ngons_setting = BoolProperty( 68 | name="NGons", 69 | description="Import faces with more than 4 verts as ngons", 70 | default=True, 71 | ) 72 | edges_setting = BoolProperty( 73 | name="Lines", 74 | description="Import lines and faces with 2 verts as edge", 75 | default=True, 76 | ) 77 | smooth_groups_setting = BoolProperty( 78 | name="Smooth Groups", 79 | description="Surround smooth groups by sharp edges", 80 | default=True, 81 | ) 82 | 83 | split_objects_setting = BoolProperty( 84 | name="Object", 85 | description="Import OBJ Objects into Blender Objects", 86 | default=True, 87 | ) 88 | split_groups_setting = BoolProperty( 89 | name="Group", 90 | description="Import OBJ Groups into Blender Objects", 91 | default=True, 92 | ) 93 | 94 | groups_as_vgroups_setting = BoolProperty( 95 | name="Poly Groups", 96 | description="Import OBJ groups as vertex groups", 97 | default=False, 98 | ) 99 | 100 | image_search_setting = BoolProperty( 101 | name="Image Search", 102 | description="Search subdirs for any associated images " 103 | "(Warning, may be slow)", 104 | default=True, 105 | ) 106 | 107 | split_mode_setting = EnumProperty( 108 | name="Split", 109 | items=(('ON', "Split", "Split geometry, omits unused verts"), 110 | ('OFF', "Keep Vert Order", "Keep vertex order from file"), 111 | ), 112 | ) 113 | 114 | clamp_size_setting = FloatProperty( 115 | name="Clamp Size", 116 | description="Clamp bounds under this value (zero to disable)", 117 | min=0.0, max=1000.0, 118 | soft_min=0.0, soft_max=1000.0, 119 | default=0.0, 120 | ) 121 | axis_forward_setting = EnumProperty( 122 | name="Forward", 123 | items=(('X', "X Forward", ""), 124 | ('Y', "Y Forward", ""), 125 | ('Z', "Z Forward", ""), 126 | ('-X', "-X Forward", ""), 127 | ('-Y', "-Y Forward", ""), 128 | ('-Z', "-Z Forward", ""), 129 | ), 130 | default='Y', 131 | ) 132 | 133 | axis_up_setting = EnumProperty( 134 | name="Up", 135 | items=(('X', "X Up", ""), 136 | ('Y', "Y Up", ""), 137 | ('Z', "Z Up", ""), 138 | ('-X', "-X Up", ""), 139 | ('-Y', "-Y Up", ""), 140 | ('-Z', "-Z Up", ""), 141 | ), 142 | default='Z', 143 | ) 144 | 145 | def draw(self, context): 146 | layout = self.layout 147 | 148 | row = layout.row(align=True) 149 | row.prop(self, "ngons_setting") 150 | row.prop(self, "edges_setting") 151 | 152 | layout.prop(self, "smooth_groups_setting") 153 | 154 | box = layout.box() 155 | row = box.row() 156 | row.prop(self, "split_mode_setting", expand=True) 157 | 158 | row = box.row() 159 | if self.split_mode_setting == 'ON': 160 | row.label(text="Split by:") 161 | row.prop(self, "split_objects_setting") 162 | row.prop(self, "split_groups_setting") 163 | else: 164 | row.prop(self, "groups_as_vgroups_setting") 165 | 166 | row = layout.split(percentage=0.67) 167 | row.prop(self, "clamp_size_setting") 168 | layout.prop(self, "axis_forward_setting") 169 | layout.prop(self, "axis_up_setting") 170 | 171 | layout.prop(self, "image_search_setting") 172 | 173 | def execute(self, context): 174 | 175 | # get the folder 176 | folder = (os.path.dirname(self.filepath)) 177 | 178 | # iterate through the selected files 179 | for i in self.files: 180 | 181 | # generate full path to file 182 | path_to_file = (os.path.join(folder, i.name)) 183 | 184 | # call obj operator and assign ui values 185 | bpy.ops.import_scene.obj(filepath = path_to_file, 186 | axis_forward = self.axis_forward_setting, 187 | axis_up = self.axis_up_setting, 188 | use_edges = self.edges_setting, 189 | use_smooth_groups = self.smooth_groups_setting, 190 | use_split_objects = self.split_objects_setting, 191 | use_split_groups = self.split_groups_setting, 192 | use_groups_as_vgroups = self.groups_as_vgroups_setting, 193 | use_image_search = self.image_search_setting, 194 | split_mode = self.split_mode_setting, 195 | global_clamp_size = self.clamp_size_setting) 196 | 197 | return {'FINISHED'} -------------------------------------------------------------------------------- /ccTool.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | import time 4 | from .functions import * 5 | 6 | import nodeitems_utils 7 | from bpy.types import Header, Menu, Panel 8 | 9 | 10 | class ToolsPanel9(bpy.types.Panel): 11 | bl_space_type = "VIEW_3D" 12 | bl_region_type = "TOOLS" 13 | bl_context = "objectmode" 14 | bl_category = "3DSC" 15 | bl_label = "Color Correction tool (cycles)" 16 | bl_options = {'DEFAULT_CLOSED'} 17 | 18 | def draw(self, context): 19 | layout = self.layout 20 | obj = context.object 21 | scene = context.scene 22 | row = layout.row() 23 | if bpy.context.scene.render.engine != 'CYCLES': 24 | row.label(text="Please, activate cycles engine !") 25 | else: 26 | if scene.objects.active: 27 | if obj.type not in ['MESH']: 28 | select_a_mesh(layout) 29 | else: 30 | row.label(text="Step by step procedure") 31 | row = layout.row() 32 | row.label(text="for selected object(s):") 33 | self.layout.operator("bi2cycles.material", icon="SMOOTH", text='Create cycles nodes') 34 | self.layout.operator("create.ccnode", icon="ASSET_MANAGER", text='Create correction node') 35 | 36 | activeobj = scene.objects.active 37 | if get_nodegroupname_from_obj(obj) is not None: 38 | # layout = self.layout 39 | row = self.layout.row() 40 | row.prop(context.window_manager.interface_vars, 'cc_nodes', expand=True) 41 | nodegroupname = get_nodegroupname_from_obj(obj) 42 | node_to_visualize = context.window_manager.interface_vars.cc_nodes 43 | if node_to_visualize == 'RGB': 44 | node = get_cc_node_in_obj_mat(nodegroupname, "RGB") 45 | if node_to_visualize == 'BC': 46 | node = get_cc_node_in_obj_mat(nodegroupname, "BC") 47 | if node_to_visualize == 'HS': 48 | node = get_cc_node_in_obj_mat(nodegroupname, "HS") 49 | row = layout.row() 50 | row.label(text="Active cc node: "+node_to_visualize)# + nodegroupname) 51 | row = layout.row() 52 | row.label(text=nodegroupname) 53 | # set "node" context pointer for the panel layout 54 | layout.context_pointer_set("node", node) 55 | 56 | if hasattr(node, "draw_buttons_ext"): 57 | node.draw_buttons_ext(context, layout) 58 | elif hasattr(node, "draw_buttons"): 59 | node.draw_buttons(context, layout) 60 | 61 | # XXX this could be filtered further to exclude socket types which don't have meaningful input values (e.g. cycles shader) 62 | # value_inputs = [socket for socket in node.inputs if socket.enabled and not socket.is_linked] 63 | # if value_inputs: 64 | # layout.separator() 65 | # layout.label("Inputs:") 66 | # for socket in value_inputs: 67 | # row = layout.row() 68 | # socket.draw(context, row, node, iface_(socket.name, socket.bl_rna.translation_context)) 69 | # 70 | 71 | self.layout.operator("create.newset", icon="FILE_TICK", text='Create new texture set') 72 | row = layout.row() 73 | self.layout.operator("bake.cyclesdiffuse", icon="TPAINT_HLT", text='Bake CC to texture set') 74 | row = layout.row() 75 | self.layout.operator("savepaint.cam", icon="IMAGE_COL", text='Save new textures') 76 | self.layout.operator("applynewtexset.material", icon="AUTOMERGE_ON", text='Use new tex set') 77 | self.layout.operator("applyoritexset.material", icon="RECOVER_LAST", text='Use original tex set') 78 | 79 | self.layout.operator("removeccnode.material", icon="CANCEL", text='remove cc node') 80 | self.layout.operator("removeorimage.material", icon="CANCEL", text='remove ori image') 81 | row = layout.row() 82 | else: 83 | select_a_mesh(layout) 84 | 85 | 86 | class OBJECT_OT_removeccnode(bpy.types.Operator): 87 | """Remove cc node for selected objects""" 88 | bl_idname = "removeccnode.material" 89 | bl_label = "Remove cycles cc node for selected object" 90 | bl_options = {'REGISTER', 'UNDO'} 91 | 92 | def execute(self, context): 93 | for obj in bpy.context.selected_objects: 94 | for matslot in obj.material_slots: 95 | mat = matslot.material 96 | remove_node(mat, "cc_node") 97 | return {'FINISHED'} 98 | 99 | class OBJECT_OT_removeorimage(bpy.types.Operator): 100 | """Remove oiginal image for selected objects""" 101 | bl_idname = "removeorimage.material" 102 | bl_label = "Remove oiginal image for selected objects" 103 | bl_options = {'REGISTER', 'UNDO'} 104 | 105 | def execute(self, context): 106 | for obj in bpy.context.selected_objects: 107 | for matslot in obj.material_slots: 108 | mat = matslot.material 109 | remove_ori_image(mat) 110 | return {'FINISHED'} 111 | 112 | 113 | class OBJECT_OT_createccnode(bpy.types.Operator): 114 | """Create a color correction node for selected objects""" 115 | bl_idname = "create.ccnode" 116 | bl_label = "Create cycles materials for selected object" 117 | bl_options = {'REGISTER', 'UNDO'} 118 | 119 | def execute(self, context): 120 | 121 | bpy.context.scene.render.engine = 'CYCLES' 122 | create_cc_node() 123 | 124 | return {'FINISHED'} 125 | 126 | #------------------------------------------------------------- 127 | 128 | class OBJECT_OT_createnewset(bpy.types.Operator): 129 | """Create new texture set for corrected mats""" 130 | bl_idname = "create.newset" 131 | bl_label = "Create new texture set for corrected mats (cc_ + previous tex name)" 132 | bl_options = {'REGISTER', 'UNDO'} 133 | 134 | def execute(self, context): 135 | bpy.context.scene.render.engine = 'CYCLES' 136 | for obj in bpy.context.selected_objects: 137 | for matslot in obj.material_slots: 138 | mat = matslot.material 139 | create_new_tex_set(mat,"cc_image") 140 | return {'FINISHED'} 141 | 142 | #------------------------------------------------------------- 143 | 144 | 145 | class OBJECT_OT_bakecyclesdiffuse(bpy.types.Operator): 146 | """Color correction to new texture set""" 147 | bl_idname = "bake.cyclesdiffuse" 148 | bl_label = "Transfer new color correction to a new texture set" 149 | bl_options = {'REGISTER', 'UNDO'} 150 | 151 | def execute(self, context): 152 | bake_tex_set("cc") 153 | 154 | return {'FINISHED'} 155 | 156 | ####----------------------------------------------------------- 157 | 158 | 159 | class OBJECT_OT_applyoritexset(bpy.types.Operator): 160 | """Use original textures in mats""" 161 | bl_idname = "applyoritexset.material" 162 | bl_label = "Use original textures in mats" 163 | bl_options = {'REGISTER', 'UNDO'} 164 | 165 | def execute(self, context): 166 | 167 | bpy.context.scene.render.engine = 'CYCLES' 168 | 169 | for obj in bpy.context.selected_objects: 170 | for matslot in obj.material_slots: 171 | mat = matslot.material 172 | set_texset(mat, "original") 173 | 174 | return {'FINISHED'} 175 | 176 | class OBJECT_OT_applynewtexset(bpy.types.Operator): 177 | """Use new textures in mats""" 178 | bl_idname = "applynewtexset.material" 179 | bl_label = "Use new textures in mats" 180 | bl_options = {'REGISTER', 'UNDO'} 181 | 182 | def execute(self, context): 183 | 184 | bpy.context.scene.render.engine = 'CYCLES' 185 | 186 | for obj in bpy.context.selected_objects: 187 | for matslot in obj.material_slots: 188 | mat = matslot.material 189 | set_texset(mat, "cc_image") 190 | 191 | return {'FINISHED'} -------------------------------------------------------------------------------- /export.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | from .functions import * 4 | 5 | class ToolsPanel(bpy.types.Panel): 6 | bl_space_type = "VIEW_3D" 7 | bl_region_type = "TOOLS" 8 | bl_context = "objectmode" 9 | bl_category = "3DSC" 10 | bl_label = "Exporters" 11 | bl_options = {'DEFAULT_CLOSED'} 12 | 13 | def draw(self, context): 14 | layout = self.layout 15 | obj = context.object 16 | row = layout.row() 17 | if obj is not None: 18 | self.layout.operator("export.coordname", icon="WORLD_DATA", text='Coordinates') 19 | row = layout.row() 20 | row.label(text="Active object is: " + obj.name) 21 | row = layout.row() 22 | row.label(text="Override") 23 | row = layout.row() 24 | row.prop(obj, "name") 25 | row = layout.row() 26 | self.layout.operator("export.object", icon="OBJECT_DATA", text='Exp. one obj') 27 | row = layout.row() 28 | row.label(text="Resulting file: " + obj.name + ".obj") 29 | row = layout.row() 30 | self.layout.operator("obj.exportbatch", icon="OBJECT_DATA", text='Exp. several obj') 31 | row = layout.row() 32 | self.layout.operator("fbx.exportbatch", icon="OBJECT_DATA", text='Exp. several fbx UE4') 33 | row = layout.row() 34 | self.layout.operator("fbx.exp", icon="OBJECT_DATA", text='Exp. fbx UE4') 35 | row = layout.row() 36 | # self.layout.operator("osgt.exportbatch", icon="OBJECT_DATA", text='Exp. several osgt files') 37 | # row = layout.row() 38 | # if is_windows(): 39 | # row = layout.row() 40 | # row.label(text="We are under Windows..") 41 | else: 42 | row.label(text="Select object(s) to see tools here.") 43 | row = layout.row() 44 | 45 | 46 | 47 | class OBJECT_OT_ExportButtonName(bpy.types.Operator): 48 | bl_idname = "export.coordname" 49 | bl_label = "Export coord name" 50 | bl_options = {"REGISTER", "UNDO"} 51 | 52 | def execute(self, context): 53 | 54 | bpy.ops.export_test.some_data('INVOKE_DEFAULT') 55 | 56 | return {'FINISHED'} 57 | 58 | #class OBJECT_OT_ExportButtonName(bpy.types.Operator): 59 | # bl_idname = "export.coordname" 60 | # bl_label = "Export coord name" 61 | # bl_options = {"REGISTER", "UNDO"} 62 | 63 | # def execute(self, context): 64 | 65 | # basedir = os.path.dirname(bpy.data.filepath) 66 | 67 | # if not basedir: 68 | # raise Exception("Save the blend file") 69 | 70 | # selection = bpy.context.selected_objects 71 | # bpy.ops.object.select_all(action='DESELECT') 72 | # activename = bpy.path.clean_name(bpy.context.scene.objects.active.name) 73 | # fn = os.path.join(basedir, activename) 74 | # file = open(fn + ".txt", 'w') 75 | 76 | # # write selected objects coordinate 77 | # for obj in selection: 78 | # obj.select = True 79 | # file.write("%s %s %s %s\n" % (obj.name, obj.location[0], obj.location[1], obj.location[2])) 80 | # file.close() 81 | # return {'FINISHED'} 82 | 83 | #class OBJECT_OT_ExportabsButtonName(bpy.types.Operator): 84 | # bl_idname = "export.abscoordname" 85 | # bl_label = "Export abs coord name" 86 | # bl_options = {"REGISTER", "UNDO"} 87 | 88 | # def execute(self, context): 89 | 90 | # basedir = os.path.dirname(bpy.data.filepath) 91 | 92 | # if not basedir: 93 | # raise Exception("Save the blend file") 94 | 95 | # selection = bpy.context.selected_objects 96 | # bpy.ops.object.select_all(action='DESELECT') 97 | # activename = bpy.path.clean_name(bpy.context.scene.objects.active.name) 98 | # fn = os.path.join(basedir, activename) 99 | # file = open(fn + ".txt", 'w') 100 | 101 | # # write selected objects coordinate 102 | # for obj in selection: 103 | # obj.select = True 104 | # x_abs = obj.location[0] + bpy.data.window_managers['WinMan'].crsx 105 | # y_abs = obj.location[1] + bpy.data.window_managers['WinMan'].crsy 106 | # file.write("%s %s %s %s\n" % (obj.name, x_abs, y_abs, obj.location[2])) 107 | # file.close() 108 | # return {'FINISHED'} 109 | 110 | 111 | class OBJECT_OT_ExportObjButton(bpy.types.Operator): 112 | bl_idname = "export.object" 113 | bl_label = "Export object" 114 | bl_options = {"REGISTER", "UNDO"} 115 | 116 | def execute(self, context): 117 | 118 | basedir = os.path.dirname(bpy.data.filepath) 119 | 120 | if not basedir: 121 | raise Exception("Save the blend file") 122 | 123 | # selection = bpy.context.selected_objects 124 | # bpy.ops.object.select_all(action='DESELECT') 125 | activename = bpy.path.clean_name(bpy.context.scene.objects.active.name) 126 | fn = os.path.join(basedir, activename) 127 | 128 | # write active object in obj format 129 | bpy.ops.export_scene.obj(filepath=fn + ".obj", use_selection=True, axis_forward='Y', axis_up='Z', path_mode='RELATIVE') 130 | return {'FINISHED'} 131 | 132 | class OBJECT_OT_objexportbatch(bpy.types.Operator): 133 | bl_idname = "obj.exportbatch" 134 | bl_label = "Obj export batch" 135 | bl_options = {"REGISTER", "UNDO"} 136 | 137 | def execute(self, context): 138 | 139 | basedir = os.path.dirname(bpy.data.filepath) 140 | if not basedir: 141 | raise Exception("Blend file is not saved") 142 | 143 | selection = bpy.context.selected_objects 144 | bpy.ops.object.select_all(action='DESELECT') 145 | 146 | for obj in selection: 147 | obj.select = True 148 | name = bpy.path.clean_name(obj.name) 149 | fn = os.path.join(basedir, name) 150 | bpy.ops.export_scene.obj(filepath=str(fn + '.obj'), use_selection=True, axis_forward='Y', axis_up='Z', path_mode='RELATIVE') 151 | obj.select = False 152 | return {'FINISHED'} 153 | 154 | #_______________________________________________________________________________________________________________ 155 | 156 | class OBJECT_OT_fbxexp(bpy.types.Operator): 157 | bl_idname = "fbx.exp" 158 | bl_label = "Fbx export UE4" 159 | bl_options = {"REGISTER", "UNDO"} 160 | 161 | def execute(self, context): 162 | 163 | basedir = os.path.dirname(bpy.data.filepath) 164 | subfolder = 'FBX' 165 | if not os.path.exists(os.path.join(basedir, subfolder)): 166 | os.mkdir(os.path.join(basedir, subfolder)) 167 | print('There is no FBX folder. Creating one...') 168 | else: 169 | print('Found previously created FBX folder. I will use it') 170 | if not basedir: 171 | raise Exception("Save the blend file") 172 | 173 | obj = bpy.context.scene.objects.active 174 | name = bpy.path.clean_name(obj.name) 175 | fn = os.path.join(basedir, subfolder, name) 176 | bpy.ops.export_scene.fbx(filepath= fn + ".fbx", check_existing=True, axis_forward='-Z', axis_up='Y', filter_glob="*.fbx", version='BIN7400', ui_tab='MAIN', use_selection=True, global_scale=1.0, apply_unit_scale=True, bake_space_transform=False, object_types={'ARMATURE', 'CAMERA', 'EMPTY', 'LAMP', 'MESH', 'OTHER'}, use_mesh_modifiers=True, mesh_smooth_type='EDGE', use_mesh_edges=False, use_tspace=False, use_custom_props=False, add_leaf_bones=True, primary_bone_axis='Y', secondary_bone_axis='X', use_armature_deform_only=False, bake_anim=True, bake_anim_use_all_bones=True, bake_anim_use_nla_strips=True, bake_anim_use_all_actions=True, bake_anim_force_startend_keying=True, bake_anim_step=1.0, bake_anim_simplify_factor=1.0, use_anim=True, use_anim_action_all=True, use_default_take=True, use_anim_optimize=True, anim_optimize_precision=6.0, path_mode='AUTO', embed_textures=False, batch_mode='OFF', use_batch_own_dir=True, use_metadata=True) 177 | #filepath = fn + ".fbx", filter_glob="*.fbx", version='BIN7400', use_selection=True, global_scale=100.0, axis_forward='-Z', axis_up='Y', bake_space_transform=False, object_types={'MESH','EMPTY'}, use_mesh_modifiers=False, mesh_smooth_type='EDGE', use_mesh_edges=False, use_tspace=False, use_armature_deform_only=False, bake_anim=False, bake_anim_use_nla_strips=False, bake_anim_step=1.0, bake_anim_simplify_factor=1.0, use_anim=False, use_anim_action_all=False, use_default_take=False, use_anim_optimize=False, anim_optimize_precision=6.0, path_mode='AUTO', embed_textures=False, batch_mode='OFF', use_batch_own_dir=True, use_metadata=True) 178 | 179 | # obj.select = False 180 | return {'FINISHED'} 181 | 182 | #_______________________________________________________________________________________________________________ 183 | 184 | class OBJECT_OT_fbxexportbatch(bpy.types.Operator): 185 | bl_idname = "fbx.exportbatch" 186 | bl_label = "Fbx export batch UE4" 187 | bl_options = {"REGISTER", "UNDO"} 188 | 189 | def execute(self, context): 190 | 191 | basedir = os.path.dirname(bpy.data.filepath) 192 | subfolder = 'FBX' 193 | if not os.path.exists(os.path.join(basedir, subfolder)): 194 | os.mkdir(os.path.join(basedir, subfolder)) 195 | print('There is no FBX folder. Creating one...') 196 | else: 197 | print('Found previously created FBX folder. I will use it') 198 | if not basedir: 199 | raise Exception("Save the blend file") 200 | 201 | selection = bpy.context.selected_objects 202 | bpy.ops.object.select_all(action='DESELECT') 203 | 204 | for obj in selection: 205 | obj.select = True 206 | name = bpy.path.clean_name(obj.name) 207 | fn = os.path.join(basedir, subfolder, name) 208 | bpy.ops.export_scene.fbx(filepath = fn + ".fbx", filter_glob="*.fbx", version='BIN7400', use_selection=True, global_scale=1.0, axis_forward='-Z', axis_up='Y', bake_space_transform=False, object_types={'MESH'}, use_mesh_modifiers=False, mesh_smooth_type='FACE', use_mesh_edges=False, use_tspace=False, use_armature_deform_only=False, bake_anim=False, bake_anim_use_nla_strips=False, bake_anim_step=1.0, bake_anim_simplify_factor=1.0, use_anim=False, use_anim_action_all=False, use_default_take=False, use_anim_optimize=False, anim_optimize_precision=6.0, path_mode='AUTO', embed_textures=False, batch_mode='OFF', use_batch_own_dir=True, use_metadata=True) 209 | obj.select = False 210 | return {'FINISHED'} 211 | #_______________________________________________________________________________________________________________ 212 | class OBJECT_OT_osgtexportbatch(bpy.types.Operator): 213 | bl_idname = "osgt.exportbatch" 214 | bl_label = "osgt export batch" 215 | bl_options = {"REGISTER", "UNDO"} 216 | 217 | def execute(self, context): 218 | 219 | basedir = os.path.dirname(bpy.data.filepath) 220 | if not basedir: 221 | raise Exception("Blend file is not saved") 222 | 223 | bpy.ops.osg.export(SELECTED=True) 224 | 225 | # selection = bpy.context.selected_objects 226 | # bpy.ops.object.select_all(action='DESELECT') 227 | # 228 | # for obj in selection: 229 | # obj.select = True 230 | # name = bpy.path.clean_name(obj.name) 231 | # fn = os.path.join(basedir, name) 232 | # bpy.ops.osg.export(filepath = fn + ".osgt", SELECTED=True) 233 | # bpy.ops.osg.export(SELECTED=True) 234 | # obj.select = False 235 | return {'FINISHED'} 236 | 237 | 238 | -------------------------------------------------------------------------------- /PhotogrTool.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | from .functions import * 4 | 5 | 6 | #class CAMERA_PH_presets(Menu): 7 | # bl_label = "cameras presets" 8 | # preset_subdir = "ph_camera" 9 | # preset_operator = "script.execute_preset" 10 | # COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'} 11 | # draw = Menu.draw_preset 12 | 13 | 14 | 15 | 16 | class VIEW3D_OT_tex_to_material(bpy.types.Operator): 17 | """Create texture materials for images assigned in UV editor""" 18 | bl_idname = "view3d.tex_to_material" 19 | bl_label = "Texface Images to Material/Texture (Material Utils)" 20 | bl_options = {'REGISTER', 'UNDO'} 21 | 22 | @classmethod 23 | def poll(cls, context): 24 | return context.active_object is not None 25 | 26 | def execute(self, context): 27 | if context.selected_editable_objects: 28 | tex_to_mat() 29 | return {'FINISHED'} 30 | else: 31 | self.report({'WARNING'}, 32 | "No editable selected objects, could not finish") 33 | return {'CANCELLED'} 34 | 35 | 36 | class ToolsPanel5(bpy.types.Panel): 37 | bl_space_type = "VIEW_3D" 38 | bl_region_type = "TOOLS" 39 | # bl_context = "objectmode" 40 | bl_category = "3DSC" 41 | bl_label = "Photogrammetry tool" 42 | bl_options = {'DEFAULT_CLOSED'} 43 | 44 | def draw(self, context): 45 | layout = self.layout 46 | scene = context.scene 47 | cam_ob = None 48 | cam_ob = scene.camera 49 | 50 | if scene.render.engine != 'BLENDER_RENDER': 51 | row = layout.row() 52 | row.label(text="Please, activate BI engine !") 53 | elif cam_ob is None: 54 | row = layout.row() 55 | row.label(text="Please, add a Cam to see tools here") 56 | 57 | else: 58 | obj = context.object 59 | obj_selected = scene.objects.active 60 | cam_cam = scene.camera.data 61 | row = layout.row() 62 | row.label(text="Set up scene", icon='RADIO') 63 | row = layout.row() 64 | self.layout.operator("isometric.scene", icon="RENDER_REGION", text='Isometric scene') 65 | self.layout.operator("canon6d.scene", icon="RENDER_REGION", text='CANON 6D scene') 66 | self.layout.operator("nikond3200.scene", icon="RENDER_REGION", text='NIKON D3200 scene') 67 | if scene.objects.active: 68 | if obj.type in ['MESH']: 69 | pass 70 | elif obj.type in ['CAMERA']: 71 | row = layout.row() 72 | row.label(text="Set selected cams as:", icon='RENDER_STILL') 73 | self.layout.operator("nikond320018mm.camera", icon="RENDER_REGION", text='Nikon d3200 18mm') 74 | self.layout.operator("canon6d35mm.camera", icon="RENDER_REGION", text='Canon6D 35mm') 75 | self.layout.operator("canon6d24mm.camera", icon="RENDER_REGION", text='Canon6D 24mm') 76 | self.layout.operator("canon6d14mm.camera", icon="RENDER_REGION", text='Canon6D 14mm') 77 | row = layout.row() 78 | row.label(text="Visual mode for selected cams:", icon='NODE_SEL') 79 | self.layout.operator("better.cameras", icon="NODE_SEL", text='Better Cams') 80 | self.layout.operator("nobetter.cameras", icon="NODE_SEL", text='Disable Better Cams') 81 | row = layout.row() 82 | row = layout.row() 83 | else: 84 | row = layout.row() 85 | row.label(text="Please select a mesh or a cam", icon='OUTLINER_DATA_CAMERA') 86 | 87 | row = layout.row() 88 | row.label(text="Painting Toolbox", icon='TPAINT_HLT') 89 | row = layout.row() 90 | row.label(text="Folder with undistorted images:") 91 | row = layout.row() 92 | row.prop(context.scene, 'BL_undistorted_path', toggle = True) 93 | row = layout.row() 94 | 95 | if cam_ob is not None: 96 | row.label(text="Active Cam: " + cam_ob.name) 97 | self.layout.operator("object.createcameraimageplane", icon="IMAGE_COL", text='Photo to camera') 98 | row = layout.row() 99 | row = layout.row() 100 | row.prop(cam_cam, "lens") 101 | row = layout.row() 102 | is_cam_ob_plane = check_children_plane(cam_ob) 103 | # row.label(text=str(is_cam_ob_plane)) 104 | if is_cam_ob_plane: 105 | if obj.type in ['MESH']: 106 | row.label(text="Active object: " + obj.name) 107 | self.layout.operator("paint.cam", icon="IMAGE_COL", text='Paint active from cam') 108 | else: 109 | row = layout.row() 110 | row.label(text="Please, set a photo to camera", icon='TPAINT_HLT') 111 | 112 | self.layout.operator("applypaint.cam", icon="IMAGE_COL", text='Apply paint') 113 | self.layout.operator("savepaint.cam", icon="IMAGE_COL", text='Save modified texs') 114 | row = layout.row() 115 | else: 116 | row.label(text="!!! Import some cams to start !!!") 117 | 118 | class OBJECT_OT_IsometricScene(bpy.types.Operator): 119 | bl_idname = "isometric.scene" 120 | bl_label = "Isometric scene" 121 | bl_options = {"REGISTER", "UNDO"} 122 | 123 | def execute(self, context): 124 | set_up_scene(3000,3000,True) 125 | return {'FINISHED'} 126 | 127 | class OBJECT_OT_Canon6Dscene(bpy.types.Operator): 128 | bl_idname = "canon6d.scene" 129 | bl_label = "Canon 6D scene" 130 | bl_options = {"REGISTER", "UNDO"} 131 | 132 | def execute(self, context): 133 | set_up_scene(5472,3648,False) 134 | return {'FINISHED'} 135 | 136 | class OBJECT_OT_nikond3200scene(bpy.types.Operator): 137 | bl_idname = "nikond3200.scene" 138 | bl_label = "Nikon d3200 scene" 139 | bl_options = {"REGISTER", "UNDO"} 140 | 141 | def execute(self, context): 142 | set_up_scene(4512,3000,False) 143 | return {'FINISHED'} 144 | 145 | class OBJECT_OT_nikond320018mm(bpy.types.Operator): 146 | bl_idname = "nikond320018mm.camera" 147 | bl_label = "Set as nikond3200 18mm" 148 | bl_options = {"REGISTER", "UNDO"} 149 | 150 | def execute(self, context): 151 | selection = bpy.context.selected_objects 152 | bpy.ops.object.select_all(action='DESELECT') 153 | for obj in selection: 154 | set_up_lens(obj,23.2,15.4,18) 155 | return {'FINISHED'} 156 | 157 | class OBJECT_OT_Canon6D35(bpy.types.Operator): 158 | bl_idname = "canon6d35mm.camera" 159 | bl_label = "Set as Canon 6D 35mm" 160 | bl_options = {"REGISTER", "UNDO"} 161 | 162 | def execute(self, context): 163 | selection = bpy.context.selected_objects 164 | bpy.ops.object.select_all(action='DESELECT') 165 | for obj in selection: 166 | set_up_lens(obj,35.8,23.9,35) 167 | return {'FINISHED'} 168 | 169 | class OBJECT_OT_Canon6D24(bpy.types.Operator): 170 | bl_idname = "canon6d24mm.camera" 171 | bl_label = "Set as Canon 6D 14mm" 172 | bl_options = {"REGISTER", "UNDO"} 173 | 174 | def execute(self, context): 175 | selection = bpy.context.selected_objects 176 | bpy.ops.object.select_all(action='DESELECT') 177 | for obj in selection: 178 | set_up_lens(obj,35.8,23.9,24) 179 | return {'FINISHED'} 180 | 181 | class OBJECT_OT_Canon6D14(bpy.types.Operator): 182 | bl_idname = "canon6d14mm.camera" 183 | bl_label = "Set as Canon 6D 14mm" 184 | bl_options = {"REGISTER", "UNDO"} 185 | 186 | def execute(self, context): 187 | selection = bpy.context.selected_objects 188 | bpy.ops.object.select_all(action='DESELECT') 189 | for obj in selection: 190 | set_up_lens(obj,35.8,23.9,14.46) 191 | return {'FINISHED'} 192 | 193 | class OBJECT_OT_BetterCameras(bpy.types.Operator): 194 | bl_idname = "better.cameras" 195 | bl_label = "Better Cameras" 196 | bl_options = {"REGISTER", "UNDO"} 197 | 198 | def execute(self, context): 199 | selection = bpy.context.selected_objects 200 | bpy.ops.object.select_all(action='DESELECT') 201 | for cam in selection: 202 | cam.select = True 203 | cam.data.show_limits = True 204 | cam.data.clip_start = 0.5 205 | cam.data.clip_end = 4 206 | cam.scale[0] = 0.1 207 | cam.scale[1] = 0.1 208 | cam.scale[2] = 0.1 209 | return {'FINISHED'} 210 | 211 | class OBJECT_OT_NoBetterCameras(bpy.types.Operator): 212 | bl_idname = "nobetter.cameras" 213 | bl_label = "Disable Better Cameras" 214 | bl_options = {"REGISTER", "UNDO"} 215 | 216 | def execute(self, context): 217 | selection = bpy.context.selected_objects 218 | bpy.ops.object.select_all(action='DESELECT') 219 | for cam in selection: 220 | cam.select = True 221 | cam.data.show_limits = False 222 | return {'FINISHED'} 223 | 224 | #______________________________________________________________ 225 | 226 | class CreateCameraImagePlane(bpy.types.Operator): 227 | """Create image plane for camera""" 228 | bl_idname= "object.createcameraimageplane" 229 | bl_label="Camera Image Plane" 230 | bl_options={'REGISTER', 'UNDO'} 231 | def SetupDriverVariables(self, driver, imageplane): 232 | camAngle = driver.variables.new() 233 | camAngle.name = 'camAngle' 234 | camAngle.type = 'SINGLE_PROP' 235 | camAngle.targets[0].id = imageplane.parent 236 | camAngle.targets[0].data_path="data.angle" 237 | 238 | depth = driver.variables.new() 239 | depth.name = 'depth' 240 | depth.type = 'TRANSFORMS' 241 | depth.targets[0].id = imageplane 242 | depth.targets[0].data_path = 'location' 243 | depth.targets[0].transform_type = 'LOC_Z' 244 | depth.targets[0].transform_space = 'LOCAL_SPACE' 245 | 246 | def SetupDriversForImagePlane(self, imageplane): 247 | driver = imageplane.driver_add('scale',1).driver 248 | driver.type = 'SCRIPTED' 249 | self.SetupDriverVariables( driver, imageplane) 250 | #driver.expression ="-depth*math.tan(camAngle/2)*resolution_y*pixel_y/(resolution_x*pixel_x)" 251 | driver.expression ="-depth*tan(camAngle/2)*bpy.context.scene.render.resolution_y * bpy.context.scene.render.pixel_aspect_y/(bpy.context.scene.render.resolution_x * bpy.context.scene.render.pixel_aspect_x)" 252 | driver = imageplane.driver_add('scale',0).driver 253 | driver.type= 'SCRIPTED' 254 | self.SetupDriverVariables( driver, imageplane) 255 | driver.expression ="-depth*tan(camAngle/2)" 256 | 257 | # get selected camera (might traverse children of selected object until a camera is found?) 258 | # for now just pick the active object 259 | 260 | 261 | def createImagePlaneForCamera(self, camera): 262 | imageplane = None 263 | 264 | depth = 10 265 | 266 | cameraname = correctcameraname(camera.name) 267 | 268 | try: 269 | undistortedpath = bpy.context.scene.BL_undistorted_path 270 | except: 271 | raise Exception("Hey Buddy, you have to set the undistorted images path !") 272 | 273 | try: 274 | cam_texture = bpy.data.images.load(undistortedpath+cameraname) 275 | except: 276 | raise NameError("Cannot load image %s" % bpy.data.images.load(undistortedpath+cameraname)) 277 | 278 | 279 | #create imageplane 280 | bpy.ops.mesh.primitive_plane_add()#radius = 0.5) 281 | imageplane = bpy.context.active_object 282 | 283 | imageplane.name = ("objplane_"+cameraname) 284 | bpy.ops.object.parent_set(type='OBJECT', keep_transform=False) 285 | bpy.ops.object.editmode_toggle() 286 | bpy.ops.mesh.select_all(action='TOGGLE') 287 | bpy.ops.transform.resize( value=(0.5,0.5,0.5)) 288 | bpy.ops.uv.smart_project(angle_limit=66,island_margin=0, user_area_weight=0) 289 | bpy.ops.uv.select_all(action='TOGGLE') 290 | bpy.ops.transform.rotate(value=1.5708, axis=(0,0,1) ) 291 | bpy.ops.object.editmode_toggle() 292 | 293 | imageplane.location = (0,0,-depth) 294 | imageplane.parent = camera 295 | 296 | #calculate scale 297 | #REPLACED WITH CREATING EXPRESSIONS 298 | self.SetupDriversForImagePlane(imageplane) 299 | 300 | #setup material 301 | if( len( imageplane.material_slots) == 0 ): 302 | bpy.ops.object.material_slot_add() 303 | #imageplane.material_slots. 304 | bpy.ops.material.new() 305 | mat_index = len(bpy.data.materials)-1 306 | imageplane.material_slots[0].material = bpy.data.materials[mat_index] 307 | material = imageplane.material_slots[0].material 308 | # if not returned by new use imgeplane.material_slots[0].material 309 | material.name = 'mat_imageplane_'+cameraname 310 | 311 | material.use_nodes = False 312 | 313 | activename = bpy.path.clean_name(bpy.context.scene.objects.active.name) 314 | 315 | bpy.context.object.data.uv_textures.active.data[0].image = cam_texture 316 | 317 | bpy.ops.view3d.tex_to_material() 318 | 319 | return {'FINISHED'} 320 | 321 | def execute(self, context): 322 | # camera = bpy.context.active_object #bpy.data.objects['Camera'] 323 | scene = context.scene 324 | undistortedpath = bpy.context.scene.BL_undistorted_path 325 | cam_ob = bpy.context.scene.camera 326 | 327 | if not undistortedpath: 328 | raise Exception("Set the Undistort path before to activate this command") 329 | else: 330 | obj_exists = False 331 | for obj in cam_ob.children: 332 | if obj.name.startswith("objplane_"): 333 | obj.hide = False 334 | obj_exists = True 335 | bpy.ops.object.select_all(action='DESELECT') 336 | scene.objects.active = obj 337 | obj.select = True 338 | return {'FINISHED'} 339 | if obj_exists is False: 340 | camera = bpy.context.scene.camera 341 | return self.createImagePlaneForCamera(camera) 342 | 343 | class OBJECT_OT_paintcam(bpy.types.Operator): 344 | bl_idname = "paint.cam" 345 | bl_label = "Paint selected from current cam" 346 | bl_options = {"REGISTER", "UNDO"} 347 | 348 | def execute(self, context): 349 | 350 | scene = context.scene 351 | undistortedpath = bpy.context.scene.BL_undistorted_path 352 | cam_ob = bpy.context.scene.camera 353 | 354 | if not undistortedpath: 355 | raise Exception("Set the Undistort path before to activate this command") 356 | else: 357 | for obj in cam_ob.children: 358 | if obj.name.startswith("objplane_"): 359 | obj.hide = True 360 | bpy.ops.paint.texture_paint_toggle() 361 | bpy.context.space_data.show_only_render = True 362 | bpy.ops.image.project_edit() 363 | obj_camera = bpy.context.scene.camera 364 | 365 | undistortedphoto = undistortedpath+correctcameraname(obj_camera.name) 366 | cleanpath = bpy.path.abspath(undistortedphoto) 367 | bpy.ops.image.external_edit(filepath=cleanpath) 368 | 369 | bpy.context.space_data.show_only_render = False 370 | bpy.ops.paint.texture_paint_toggle() 371 | 372 | return {'FINISHED'} 373 | 374 | class OBJECT_OT_applypaintcam(bpy.types.Operator): 375 | bl_idname = "applypaint.cam" 376 | bl_label = "Apply paint" 377 | bl_options = {"REGISTER"} 378 | 379 | def execute(self, context): 380 | bpy.ops.paint.texture_paint_toggle() 381 | bpy.ops.image.project_apply() 382 | bpy.ops.paint.texture_paint_toggle() 383 | return {'FINISHED'} 384 | 385 | 386 | -------------------------------------------------------------------------------- /QuickUtils.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import time 3 | import bmesh 4 | from random import randint, choice 5 | from .functions import * 6 | from .qualitycheck import * 7 | 8 | 9 | class ToolsPanel3(bpy.types.Panel): 10 | bl_space_type = "VIEW_3D" 11 | bl_region_type = "TOOLS" 12 | bl_context = "objectmode" 13 | bl_category = "3DSC" 14 | bl_label = "Quick Utils" 15 | bl_options = {'DEFAULT_CLOSED'} 16 | 17 | def draw(self, context): 18 | layout = self.layout 19 | obj = context.object 20 | scene = context.scene 21 | row = layout.row() 22 | self.layout.operator("center.mass", icon="DOT", text='Center of Mass') 23 | row = layout.row() 24 | self.layout.operator("correct.material", icon="NODE", text='Correct Photoscan mats') 25 | row = layout.row() 26 | self.layout.operator("local.texture", icon="TEXTURE", text='Local texture mode ON') 27 | row = layout.row() 28 | self.layout.operator("light.off", icon="LAMP_DATA", text='Deactivate lights') 29 | row = layout.row() 30 | self.layout.operator("create.personalgroups", icon="GROUP", text='Create per-object groups') 31 | row = layout.row() 32 | self.layout.operator("remove.alluvexcept1", icon="GROUP", text='Only UV0 will survive') 33 | row = layout.row() 34 | self.layout.operator("remove.fromallgroups", icon="LIBRARY_DATA_BROKEN", text='Remove from all groups') 35 | row = layout.row() 36 | self.layout.operator("multimaterial.layout", icon="IMGDISPLAY", text='Multimaterial layout') 37 | row = layout.row() 38 | self.layout.operator("lod0poly.reducer", icon="IMGDISPLAY", text='LOD0 mesh decimator') 39 | row = layout.row() 40 | 41 | self.layout.operator("quality.check", icon="META_DATA", text='Quality check') 42 | row = layout.row() 43 | 44 | self.layout.operator("project.segmentation", icon="SCULPTMODE_HLT", text='Mono-cutter') 45 | row = layout.row() 46 | self.layout.operator("project.segmentationinv", icon="SCULPTMODE_HLT", text='Multi-cutter') 47 | row = layout.row() 48 | 49 | self.layout.operator("tiff2png.relink", icon="META_DATA", text='Relink images from tiff to png') 50 | row = layout.row() 51 | 52 | self.layout.operator("obname.ffn", icon="META_DATA", text='Ren active from namefile') 53 | row = layout.row() 54 | self.layout.operator("rename.ge", icon="META_DATA", text='Ren 4 GE') 55 | row = layout.row() 56 | row.label(text="Switch engine") 57 | self.layout.operator("activatenode.material", icon="PMARKER_SEL", text='Activate cycles nodes') 58 | self.layout.operator("deactivatenode.material", icon="PMARKER", text='De-activate cycles nodes') 59 | self.layout.operator("bi2cycles.material", icon="SMOOTH", text='Create cycles nodes') 60 | self.layout.operator("cycles2bi.material", icon="PMARKER", text='Cycles to BI') 61 | 62 | 63 | class OBJECT_OT_CorrectMaterial(bpy.types.Operator): 64 | bl_idname = "correct.material" 65 | bl_label = "Correct photogr. mats" 66 | bl_options = {"REGISTER", "UNDO"} 67 | 68 | def execute(self, context): 69 | selection = bpy.context.selected_objects 70 | bpy.ops.object.select_all(action='DESELECT') 71 | for obj in selection: 72 | obj.select = True 73 | for i in range(0,len(obj.material_slots)): 74 | # bpy.ops.object.material_slot_remove() 75 | obj.active_material_index = i 76 | ma = obj.active_material 77 | ma.diffuse_intensity = 1 78 | ma.specular_intensity = 0 79 | ma.specular_color[0] = 1 80 | ma.specular_color[1] = 1 81 | ma.specular_color[2] = 1 82 | ma.diffuse_color[0] = 1 83 | ma.diffuse_color[1] = 1 84 | ma.diffuse_color[2] = 1 85 | ma.alpha = 1.0 86 | ma.use_transparency = False 87 | ma.transparency_method = 'Z_TRANSPARENCY' 88 | ma.use_transparent_shadows = True 89 | ma.ambient = 0.0 90 | image = ma.texture_slots[0].texture.image 91 | image.use_alpha = False 92 | return {'FINISHED'} 93 | 94 | class OBJECT_OT_projectsegmentation(bpy.types.Operator): 95 | """Project segmentation""" 96 | bl_idname = "project.segmentation" 97 | bl_label = "Project segmentation" 98 | bl_options = {'REGISTER', 'UNDO'} 99 | 100 | def execute(self, context): 101 | context = bpy.context 102 | start_time = time.time() 103 | ob_counter = 1 104 | 105 | data = bpy.data 106 | ob_cutting = context.scene.objects.active 107 | #ob_cutting = data.objects.get("secante") 108 | ob_to_cut = context.selected_objects 109 | ob_tot = (len(ob_to_cut)-1) 110 | bpy.ops.object.select_all(action='DESELECT') 111 | for ob in ob_to_cut: 112 | if ob == ob_cutting: 113 | pass 114 | else: 115 | start_time_ob = time.time() 116 | print('>>> CUTTING >>>') 117 | print('>>>>>> the object is going to be cutted: ""'+ ob.name+'"" ('+str(ob_counter)+'/'+str(ob_tot)+')') 118 | ob_cutting.select = True 119 | context.scene.objects.active = ob 120 | ob.select = True 121 | bpy.ops.object.editmode_toggle() 122 | bpy.ops.mesh.knife_project(cut_through=True) 123 | try: 124 | bpy.ops.mesh.separate(type='SELECTED') 125 | except: 126 | pass 127 | bpy.ops.mesh.select_all(action='DESELECT') 128 | bpy.ops.object.editmode_toggle() 129 | print('>>> "'+ob.name+'" ('+str(ob_counter)+'/'+ str(ob_tot) +') object cutted in '+str(time.time() - start_time_ob)+' seconds') 130 | ob_counter += 1 131 | bpy.ops.object.select_all(action='DESELECT') 132 | end_time = time.time() - start_time 133 | print('<<<<<<< Process done >>>>>>') 134 | print('>>>'+str(ob_tot)+' objects processed in '+str(end_time)+' seconds') 135 | return {'FINISHED'} 136 | 137 | class OBJECT_OT_projectsegmentationinversed(bpy.types.Operator): 138 | """Project segmentation inverse""" 139 | bl_idname = "project.segmentationinv" 140 | bl_label = "Project segmentation inverse" 141 | bl_options = {'REGISTER', 'UNDO'} 142 | 143 | def execute(self, context): 144 | context = bpy.context 145 | start_time = time.time() 146 | ob_counter = 1 147 | data = bpy.data 148 | ob_to_cut = context.scene.objects.active 149 | #ob_cutting = data.objects.get("secante") 150 | ob_cutting = context.selected_objects 151 | ob_tot = (len(ob_cutting)-1) 152 | bpy.ops.object.select_all(action='DESELECT') 153 | 154 | for ob in ob_cutting: 155 | if ob == ob_to_cut: 156 | pass 157 | else: 158 | start_time_ob = time.time() 159 | print('>>> CUTTING >>>') 160 | print('>>>>>> the object "'+ ob.name +'" ('+str(ob_counter)+'/'+str(ob_tot)+') is cutting the object'+ ob_to_cut.name) 161 | ob.select = True 162 | context.scene.objects.active = ob_to_cut 163 | ob_to_cut.select = True 164 | bpy.ops.object.editmode_toggle() 165 | bpy.ops.mesh.knife_project(cut_through=True) 166 | try: 167 | bpy.ops.mesh.separate(type='SELECTED') 168 | except: 169 | pass 170 | bpy.ops.mesh.select_all(action='DESELECT') 171 | bpy.ops.object.editmode_toggle() 172 | print('>>> "'+ob.name+'" ('+str(ob_counter)+'/'+ str(ob_tot) +') object used to cut in '+str(time.time() - start_time_ob)+' seconds') 173 | ob_counter += 1 174 | bpy.ops.object.select_all(action='DESELECT') 175 | end_time = time.time() - start_time 176 | print('<<<<<<< Process done >>>>>>') 177 | print('>>>'+str(ob_tot)+' objects processed in '+str(end_time)+' seconds') 178 | return {'FINISHED'} 179 | 180 | class OBJECT_OT_renameGEobject(bpy.types.Operator): 181 | """Rename data tree of selected objects using the object name""" 182 | bl_idname = "rename.ge" 183 | bl_label = "Rename data tree of selected objects using the object name (usefull for GE export)" 184 | bl_options = {'REGISTER', 'UNDO'} 185 | 186 | def execute(self, context): 187 | context = bpy.context 188 | scene = context.scene 189 | 190 | for ob in context.selected_objects: 191 | ob.data.name = "ME_"+ob.name 192 | if ob.material_slots: 193 | mslot_index = 0 194 | tslot_index = 0 195 | for m_slot in ob.material_slots: 196 | if m_slot.material: 197 | mslot_index += 1 198 | if m_slot.material.users == 1: 199 | m_slot.material.name = "M_"+str(mslot_index)+"_"+ob.name 200 | else: 201 | m_slot.material.name = "M_"+str(mslot_index)+"_"+ob.name 202 | if m_slot.material.texture_slots: 203 | if(len(m_slot.material.texture_slots) > 0): 204 | tslot_index += 1 205 | m_tex = m_slot.material.texture_slots[0] 206 | m_tex.texture.name = "T_"+str(tslot_index)+"_"+ob.name 207 | m_tex.texture.image.name = "img_"+str(mslot_index)+"_"+ob.name 208 | return {'FINISHED'} 209 | 210 | class OBJECT_OT_objectnamefromfilename(bpy.types.Operator): 211 | """Set active object name from file name""" 212 | bl_idname = "obname.ffn" 213 | bl_label = "Set active object name from file name" 214 | bl_options = {'REGISTER', 'UNDO'} 215 | 216 | def execute(self, context): 217 | objname = bpy.path.basename(bpy.context.blend_data.filepath) 218 | sel = bpy.context.active_object 219 | sel.name = objname.split(".")[0] 220 | return {'FINISHED'} 221 | 222 | 223 | class OBJECT_OT_qualitycheck(bpy.types.Operator): 224 | """Quality check""" 225 | bl_idname = "quality.check" 226 | bl_label = "Report on quality of 3d models (install the UVtools addon)" 227 | bl_options = {'REGISTER', 'UNDO'} 228 | 229 | def execute(self, context): 230 | get_texel_density(self, context) 231 | return {'FINISHED'} 232 | 233 | 234 | class OBJECT_OT_tiff2pngrelink(bpy.types.Operator): 235 | """relink tiff images to png""" 236 | bl_idname = "tiff2png.relink" 237 | bl_label = "Relink tiff images to png" 238 | bl_options = {'REGISTER', 'UNDO'} 239 | 240 | def execute(self, context): 241 | images = bpy.data.images 242 | 243 | def changetiff(img): 244 | if img.filepath.endswith("tif"): 245 | img.filepath = img.filepath.replace(".tif", ".png") 246 | img.reload() 247 | if img.name.endswith("tif"): 248 | img.name = img.name.replace(".tif", ".png") 249 | 250 | for img in images: 251 | changetiff(img) 252 | 253 | return {'FINISHED'} 254 | 255 | 256 | class OBJECT_OT_lightoff(bpy.types.Operator): 257 | """Turn off light sensibility""" 258 | bl_idname = "light.off" 259 | bl_label = "Turn off light sensibility" 260 | bl_options = {'REGISTER', 'UNDO'} 261 | 262 | def execute(self, context): 263 | bpy.context.scene.game_settings.material_mode = 'GLSL' 264 | bpy.context.scene.game_settings.use_glsl_lights = False 265 | return {'FINISHED'} 266 | 267 | class OBJECT_OT_LOD0polyreducer(bpy.types.Operator): 268 | """Reduce the polygon number to a correct LOD0 set up""" 269 | bl_idname = "lod0poly.reducer" 270 | bl_label = "Reduce the polygon number to a correct LOD0 set up" 271 | bl_options = {'REGISTER', 'UNDO'} 272 | 273 | def execute(self, context): 274 | context = bpy.context 275 | 276 | selected_objs = context.selected_objects 277 | 278 | for obj in selected_objs: 279 | me = obj.data 280 | tot_poly = len(me.polygons) 281 | tot_area = areamesh(obj) 282 | final_poly = tot_area*1000 283 | if final_poly < tot_poly: 284 | ratio = final_poly/tot_poly 285 | print("ratio is "+ str(ratio)) 286 | decimate_mesh(context,obj,ratio,'LOD0') 287 | 288 | return {'FINISHED'} 289 | 290 | class OBJECT_OT_cycles2bi(bpy.types.Operator): 291 | """Convert cycles to bi""" 292 | bl_idname = "cycles2bi.material" 293 | bl_label = "Convert cycles to bi" 294 | bl_options = {'REGISTER', 'UNDO'} 295 | 296 | def execute(self, context): 297 | 298 | bpy.context.scene.render.engine = 'BLENDER_RENDER' 299 | cycles2bi() 300 | 301 | return {'FINISHED'} 302 | 303 | 304 | 305 | #________________________________________________________ 306 | 307 | class OBJECT_OT_deactivatematerial(bpy.types.Operator): 308 | """De-activate node materials for selected object""" 309 | bl_idname = "deactivatenode.material" 310 | bl_label = "De-activate cycles node materials for selected object and switch to BI" 311 | bl_options = {'REGISTER', 'UNDO'} 312 | 313 | def execute(self, context): 314 | 315 | bpy.context.scene.render.engine = 'BLENDER_RENDER' 316 | for obj in bpy.context.selected_objects: 317 | for matslot in obj.material_slots: 318 | mat = matslot.material 319 | mat.use_nodes = False 320 | 321 | return {'FINISHED'} 322 | 323 | #------------------------------------------------------------- 324 | 325 | class OBJECT_OT_activatematerial(bpy.types.Operator): 326 | """Activate node materials for selected object""" 327 | bl_idname = "activatenode.material" 328 | bl_label = "Activate cycles node materials for selected object and switch to cycles" 329 | bl_options = {'REGISTER', 'UNDO'} 330 | 331 | def execute(self, context): 332 | 333 | bpy.context.scene.render.engine = 'CYCLES' 334 | for obj in bpy.context.selected_objects: 335 | for matslot in obj.material_slots: 336 | mat = matslot.material 337 | mat.use_nodes = True 338 | 339 | return {'FINISHED'} 340 | 341 | 342 | class OBJECT_OT_CenterMass(bpy.types.Operator): 343 | bl_idname = "center.mass" 344 | bl_label = "Center Mass" 345 | bl_options = {"REGISTER", "UNDO"} 346 | 347 | def execute(self, context): 348 | 349 | selection = bpy.context.selected_objects 350 | # bpy.ops.object.select_all(action='DESELECT') 351 | 352 | # translate objects in SCS coordinate 353 | for obj in selection: 354 | obj.select = True 355 | bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS') 356 | return {'FINISHED'} 357 | 358 | class OBJECT_OT_LocalTexture(bpy.types.Operator): 359 | bl_idname = "local.texture" 360 | bl_label = "Local texture mode ON" 361 | bl_options = {"REGISTER", "UNDO"} 362 | 363 | def execute(self, context): 364 | bpy.ops.file.autopack_toggle() 365 | bpy.ops.file.autopack_toggle() 366 | bpy.ops.file.unpack_all(method='WRITE_LOCAL') 367 | bpy.ops.file.make_paths_relative() 368 | return {'FINISHED'} 369 | 370 | 371 | class OBJECT_OT_createpersonalgroups(bpy.types.Operator): 372 | bl_idname = "create.personalgroups" 373 | bl_label = "Create groups per single object" 374 | bl_options = {"REGISTER", "UNDO"} 375 | 376 | def execute(self, context): 377 | for ob in bpy.context.selected_objects: 378 | bpy.ops.object.select_all(action='DESELECT') 379 | ob.select = True 380 | bpy.context.scene.objects.active = ob 381 | make_group(ob,context) 382 | return {'FINISHED'} 383 | 384 | 385 | class OBJECT_OT_removealluvexcept1(bpy.types.Operator): 386 | bl_idname = "remove.alluvexcept1" 387 | bl_label = "Remove all the UVs except the first one" 388 | bl_options = {"REGISTER", "UNDO"} 389 | 390 | def execute(self, context): 391 | for ob in bpy.context.selected_objects: 392 | uvmaps = len(ob.data.uv_textures) 393 | while uvmaps >1: 394 | uv_textures = ob.data.uv_textures 395 | uv_textures.remove(uv_textures[uvmaps-1]) 396 | uvmaps -=1 397 | return {'FINISHED'} 398 | 399 | class OBJECT_OT_removefromallgroups(bpy.types.Operator): 400 | bl_idname = "remove.fromallgroups" 401 | bl_label = "Remove the object(s) from all the Groups" 402 | bl_options = {"REGISTER", "UNDO"} 403 | 404 | def execute(self, context): 405 | for ob in bpy.context.selected_objects: 406 | bpy.ops.group.objects_remove_all() 407 | return {'FINISHED'} 408 | 409 | 410 | 411 | class OBJECT_OT_multimateriallayout(bpy.types.Operator): 412 | """Create multimaterial layout on selected mesh""" 413 | bl_idname = "multimaterial.layout" 414 | bl_label = "Create a multimaterial layout for selected meshe(s)" 415 | bl_options = {'REGISTER', 'UNDO'} 416 | 417 | def execute(self, context): 418 | start_time = time.time() 419 | totmodels=len(context.selected_objects) 420 | padding = 0.05 421 | #ob = bpy.context.object 422 | print("Found "+str(totmodels)+" models.") 423 | currentmod = 1 424 | for ob in context.selected_objects: 425 | start_time_ob = time.time() 426 | print("") 427 | print("***********************") 428 | print("I'm starting to process: "+ob.name+" model ("+str(currentmod)+"/"+str(totmodels)+")") 429 | print("***********************") 430 | print("") 431 | bpy.ops.object.select_all(action='DESELECT') 432 | ob.select = True 433 | bpy.context.scene.objects.active = ob 434 | currentobjname = ob.name 435 | objectname = ob.name 436 | me = ob.data 437 | tot_poly = len(me.polygons) 438 | materialnumber = desiredmatnumber(ob) #final number of whished materials 439 | materialsoriginal=len(ob.material_slots) 440 | cleaned_obname = clean_name(objectname) 441 | print("Removing the old "+str(materialsoriginal)+" materials..") 442 | 443 | for i in range(0,materialsoriginal): 444 | bpy.ops.object.material_slot_remove() 445 | current_material = 1 446 | for mat in range(materialnumber-1): 447 | bpy.ops.object.editmode_toggle() 448 | print("Selecting polygons for mat: "+str(mat+1)+"/"+str(materialnumber)) 449 | bpy.ops.mesh.select_all(action='DESELECT') 450 | me.update() 451 | poly = len(me.polygons) 452 | bm = bmesh.from_edit_mesh(me) 453 | for i in range(5): 454 | #print(i+1) 455 | r = choice([(0,poly)]) 456 | random_index=(randint(*r)) 457 | if hasattr(bm.faces, "ensure_lookup_table"): 458 | bm.faces.ensure_lookup_table() 459 | bm.faces[random_index].select = True 460 | bmesh.update_edit_mesh(me, True) 461 | poly_sel = 5 462 | while poly_sel <= (tot_poly/materialnumber): 463 | bpy.ops.mesh.select_more(use_face_step=True) 464 | ob.update_from_editmode() 465 | poly_sel = len([p for p in ob.data.polygons if p.select]) 466 | bpy.ops.uv.smart_project(angle_limit=66.0, island_margin=0.01, user_area_weight=0.0, use_aspect=True) 467 | # bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=padding) 468 | bpy.ops.uv.pack_islands(margin=padding) 469 | print("Creating new textures (remember to save them later..)") 470 | bpy.ops.object.editmode_toggle() 471 | current_tex_name = (cleaned_obname+'_t'+str(current_material)) 472 | newimage2selpoly(ob, current_tex_name) 473 | bpy.ops.object.editmode_toggle() 474 | bpy.ops.mesh.separate(type='SELECTED') 475 | bpy.ops.object.editmode_toggle() 476 | current_material += 1 477 | 478 | bpy.ops.object.editmode_toggle() 479 | bpy.ops.mesh.select_all(action='SELECT') 480 | bpy.ops.uv.smart_project(island_margin=padding) 481 | bpy.ops.uv.pack_islands(margin=padding) 482 | bpy.ops.object.editmode_toggle() 483 | current_tex_name = (cleaned_obname+'_t'+str(current_material)) 484 | newimage2selpoly(ob, current_tex_name) 485 | bpy.ops.object.select_all(action='DESELECT') 486 | ob.select = True 487 | bpy.context.scene.objects.active = ob 488 | currentobjname = ob.name 489 | 490 | for mat in range(materialnumber-1): 491 | 492 | bpy.data.objects[getnextobjname(currentobjname)].select = True 493 | nextname = getnextobjname(currentobjname) 494 | currentobjname = nextname 495 | 496 | bpy.ops.object.join() 497 | bpy.ops.object.editmode_toggle() 498 | bpy.ops.mesh.select_all(action='SELECT') 499 | bpy.ops.mesh.remove_doubles() 500 | bpy.ops.object.editmode_toggle() 501 | #bpy.ops.view3d.texface_to_material() 502 | print('>>> "'+ob.name+'" ('+str(currentmod)+'/'+ str(totmodels) +') object baked in '+str(time.time() - start_time_ob)+' seconds') 503 | currentmod += 1 504 | end_time = time.time() - start_time 505 | print(' ') 506 | print('<<<<<<< Process done >>>>>>') 507 | print('>>>'+str(totmodels)+' objects processed in '+str(end_time)+' seconds') 508 | print('>>>>>>>>>>>>>>>>>>>>>>>>>>>') 509 | return {'FINISHED'} 510 | -------------------------------------------------------------------------------- /functions.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | import time 4 | import bmesh 5 | import platform 6 | from random import randint, choice 7 | 8 | 9 | class OBJECT_OT_savepaintcam(bpy.types.Operator): 10 | bl_idname = "savepaint.cam" 11 | bl_label = "Save paint" 12 | bl_options = {"REGISTER", "UNDO"} 13 | 14 | def execute(self, context): 15 | bpy.ops.image.save_dirty() 16 | return {'FINISHED'} 17 | 18 | class OBJECT_OT_createcyclesmat(bpy.types.Operator): 19 | """Create cycles materials for selected objects""" 20 | bl_idname = "bi2cycles.material" 21 | bl_label = "Create cycles materials for selected object" 22 | bl_options = {'REGISTER', 'UNDO'} 23 | 24 | def execute(self, context): 25 | bpy.context.scene.render.engine = 'CYCLES' 26 | bi2cycles() 27 | return {'FINISHED'} 28 | 29 | ########################################################################################## 30 | 31 | def grad(rad): 32 | grad = rad*57.2957795 33 | return grad 34 | 35 | def get_nodegroupname_from_obj(obj): 36 | # if 'cc_node' in [node.node_tree.name for node in obj.material_slots[0].material.node_tree.nodes]: 37 | if obj.material_slots[0].material.node_tree.nodes['cc_node']: 38 | nodegroupname = obj.material_slots[0].material.node_tree.nodes['cc_node'].node_tree.name 39 | else: 40 | nodegroupname = None 41 | return nodegroupname 42 | 43 | 44 | 45 | #if 'Material Output' in [node.name for node in bpy.data.materials['your_material_name'].node_tree.nodes]: 46 | # print('Yes!') 47 | 48 | 49 | 50 | def get_cc_node_in_obj_mat(nodegroupname,type): 51 | if type == 'RGB': 52 | type_name = 'RGB Curves' 53 | if type == 'BC': 54 | type_name = 'Bright/Contrast' 55 | if type == 'HS': 56 | type_name = 'Hue Saturation Value' 57 | node = bpy.data.node_groups[nodegroupname].nodes[type_name] 58 | return node 59 | 60 | 61 | def set_up_lens(obj,sens_width,sens_lenght,lens): 62 | obj.select = True 63 | obj.data.lens = lens 64 | obj.data.sensor_fit = 'HORIZONTAL' 65 | obj.data.sensor_width = sens_width 66 | obj.data.sensor_height = sens_lenght 67 | 68 | def set_up_scene(x,y,ao): 69 | bpy.context.scene.render.resolution_x = x 70 | bpy.context.scene.render.resolution_y = y 71 | bpy.context.scene.render.resolution_percentage = 100 72 | bpy.context.scene.game_settings.material_mode = 'GLSL' 73 | bpy.context.scene.game_settings.use_glsl_lights = False 74 | bpy.context.scene.world.light_settings.use_ambient_occlusion = ao 75 | bpy.context.scene.render.alpha_mode = 'TRANSPARENT' 76 | bpy.context.scene.tool_settings.image_paint.screen_grab_size[0] = x 77 | bpy.context.scene.tool_settings.image_paint.screen_grab_size[1] = y 78 | 79 | def assignmatslots(ob, matlist): 80 | #given an object and a list of material names 81 | #removes all material slots form the object 82 | #adds new ones for each material in matlist 83 | #adds the materials to the slots as well. 84 | 85 | scn = bpy.context.scene 86 | ob_active = bpy.context.active_object 87 | scn.objects.active = ob 88 | 89 | for s in ob.material_slots: 90 | bpy.ops.object.material_slot_remove() 91 | 92 | # re-add them and assign material 93 | i = 0 94 | for m in matlist: 95 | mat = bpy.data.materials[m] 96 | ob.data.materials.append(mat) 97 | bpy.context.object.active_material.use_transparency = True 98 | bpy.context.object.active_material.alpha = 0.5 99 | i += 1 100 | 101 | # restore active object: 102 | scn.objects.active = ob_active 103 | 104 | 105 | def check_texture(img, mat): 106 | #finds a texture from an image 107 | #makes a texture if needed 108 | #adds it to the material if it isn't there already 109 | 110 | tex = bpy.data.textures.get(img.name) 111 | 112 | if tex is None: 113 | tex = bpy.data.textures.new(name=img.name, type='IMAGE') 114 | 115 | tex.image = img 116 | 117 | #see if the material already uses this tex 118 | #add it if needed 119 | found = False 120 | for m in mat.texture_slots: 121 | if m and m.texture == tex: 122 | found = True 123 | break 124 | if not found and mat: 125 | mtex = mat.texture_slots.add() 126 | mtex.texture = tex 127 | mtex.texture_coords = 'UV' 128 | mtex.use_map_color_diffuse = True 129 | 130 | def tex_to_mat(): 131 | # editmode check here! 132 | editmode = False 133 | ob = bpy.context.object 134 | if ob.mode == 'EDIT': 135 | editmode = True 136 | bpy.ops.object.mode_set() 137 | 138 | for ob in bpy.context.selected_editable_objects: 139 | 140 | faceindex = [] 141 | unique_images = [] 142 | 143 | # get the texface images and store indices 144 | if (ob.data.uv_textures): 145 | for f in ob.data.uv_textures.active.data: 146 | if f.image: 147 | img = f.image 148 | #build list of unique images 149 | if img not in unique_images: 150 | unique_images.append(img) 151 | faceindex.append(unique_images.index(img)) 152 | 153 | else: 154 | img = None 155 | faceindex.append(None) 156 | 157 | # check materials for images exist; create if needed 158 | matlist = [] 159 | for i in unique_images: 160 | if i: 161 | try: 162 | m = bpy.data.materials[i.name] 163 | except: 164 | m = bpy.data.materials.new(name=i.name) 165 | continue 166 | 167 | finally: 168 | matlist.append(m.name) 169 | # add textures if needed 170 | check_texture(i, m) 171 | 172 | # set up the object material slots 173 | assignmatslots(ob, matlist) 174 | 175 | #set texface indices to material slot indices.. 176 | me = ob.data 177 | #class CAMERA_PH_presets(Menu): 178 | # bl_label = "cameras presets" 179 | # preset_subdir = "ph_camera" 180 | # preset_operator = "script.execute_preset" 181 | # COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'} 182 | # draw = Menu.draw_preset 183 | 184 | i = 0 185 | for f in faceindex: 186 | if f is not None: 187 | me.polygons[i].material_index = f 188 | i += 1 189 | if editmode: 190 | bpy.ops.object.mode_set(mode='EDIT') 191 | 192 | # self.layout.operator("cam.visibility", icon="RENDER_REGION", text='Cam visibility') 193 | 194 | def check_children_plane(cam_ob): 195 | check = False 196 | for obj in cam_ob.children: 197 | if obj.name.startswith("objplane_"): 198 | check = True 199 | pass 200 | else: 201 | check = False 202 | return check 203 | 204 | def correctcameraname(cameraname): 205 | # extensions = ['.jpg','.JPG'] 206 | # for extension in extensions: 207 | if cameraname.endswith('.JPG'): 208 | return cameraname 209 | pass 210 | else: 211 | cameranamecor = cameraname + ".JPG" 212 | # print(cameranamecor) 213 | return cameranamecor 214 | 215 | def decimate_mesh(context,obj,ratio,lod): 216 | selected_obs = context.selected_objects 217 | bpy.ops.object.select_all(action='DESELECT') 218 | D = bpy.data 219 | obj.select = True 220 | context.scene.objects.active = obj 221 | bpy.ops.object.editmode_toggle() 222 | print('Decimating the original mesh to obtain the '+lod+' mesh...') 223 | bpy.ops.mesh.select_all(action='DESELECT') 224 | bpy.ops.mesh.select_mode(type="VERT") 225 | bpy.ops.mesh.select_non_manifold() 226 | bpy.ops.object.vertex_group_add() 227 | bpy.ops.object.vertex_group_assign() 228 | bpy.ops.object.editmode_toggle() 229 | # bpy.data.objects[lod1name].modifiers.new("Decimate", type='DECIMATE') 230 | D.objects[obj.name].modifiers.new("Decimate", type='DECIMATE') 231 | D.objects[obj.name].modifiers["Decimate"].ratio = ratio 232 | D.objects[obj.name].modifiers["Decimate"].vertex_group = "Group" 233 | D.objects[obj.name].modifiers["Decimate"].invert_vertex_group = True 234 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Decimate") 235 | # print("applied modifier") 236 | 237 | def setupclonepaint(): 238 | bpy.ops.object.mode_set(mode = 'TEXTURE_PAINT') 239 | bpy.ops.paint.brush_select(paint_mode='TEXTURE_PAINT', texture_paint_tool='CLONE') 240 | bpy.context.scene.tool_settings.image_paint.mode = 'MATERIAL' 241 | bpy.context.scene.tool_settings.image_paint.use_clone_layer = True 242 | # bpy.context.scene.tool_settings.image_paint.seam_bleed = 16 243 | obj = bpy.context.scene.objects.active 244 | 245 | for matslot in obj.material_slots: 246 | mat = matslot.material 247 | original_image = node_retriever(mat, "original") 248 | clone_image = node_retriever(mat, "source_paint_node") 249 | for idx, img in enumerate(mat.texture_paint_images): 250 | if img.name == original_image.image.name: 251 | mat.paint_active_slot = idx 252 | print ("I have just set the " + img.name + " image, as a paint image, that corresponds to the index: "+ str(idx)) 253 | if img.name == clone_image.image.name: 254 | mat.paint_clone_slot = idx 255 | print ("I have just set the " + img.name + " image, as a paint image, that corresponds to the index: "+ str(idx)) 256 | 257 | def is_windows(): 258 | if platform.system == 'Windows': 259 | is_win =True 260 | else: 261 | is_win =False 262 | return is_win 263 | 264 | def cycles2bi(): 265 | for ob in bpy.context.selected_objects: 266 | bpy.ops.object.select_all(action='DESELECT') 267 | ob.select = True 268 | bpy.context.scene.objects.active = ob 269 | for matslot in ob.material_slots: 270 | mat = matslot.material 271 | node_original = node_retriever(mat,"original") 272 | # print(node_original.image.name) 273 | # print(mat.texture_slots[0].texture.image.name) 274 | mat.texture_slots[0].texture.image = node_original.image 275 | 276 | def select_a_mesh(layout): 277 | row = layout.row() 278 | row.label(text="Select a mesh to start") 279 | 280 | def select_a_node(mat, type): 281 | nodes = mat.node_tree.nodes 282 | for node in nodes: 283 | if node.name == type: 284 | node.select = True 285 | nodes.active = node 286 | is_node = True 287 | pass 288 | else: 289 | is_node = False 290 | return is_node 291 | 292 | # potenzialmente una migliore scrittura del codice: 293 | # nodes = material_slot.material.node_tree.nodes 294 | # texture_node = nodes.get('Image Texture') 295 | # if texture_node is None: 296 | 297 | def bake_tex_set(type): 298 | scene = bpy.context.scene 299 | scene.render.engine = 'CYCLES' 300 | tot_time = 0 301 | ob_counter = 1 302 | scene.cycles.samples = 1 303 | scene.cycles.max_bounces = 1 304 | scene.cycles.bake_type = 'DIFFUSE' 305 | scene.render.bake.use_pass_color = True 306 | scene.render.bake.use_pass_direct = False 307 | scene.render.bake.use_pass_indirect = False 308 | scene.render.bake.use_selected_to_active = False 309 | scene.render.bake.use_cage = True 310 | scene.render.bake.cage_extrusion = 0.1 311 | scene.render.bake.use_clear = True 312 | start_time = time.time() 313 | if type == "source": 314 | if len(bpy.context.selected_objects) > 1: 315 | ob = scene.objects.active 316 | print('checking presence of a destination texture set..') 317 | for matslot in ob.material_slots: 318 | mat = matslot.material 319 | select_a_node(mat,"source_paint_node") 320 | print('start baking..') 321 | bpy.ops.object.bake(type='DIFFUSE', pass_filter={'COLOR'}, use_selected_to_active=True, use_clear=True, save_mode='INTERNAL') 322 | else: 323 | raise Exception("Select two meshes in order to transfer a clone layer") 324 | return 325 | tot_time += (time.time() - start_time) 326 | if type == "cc": 327 | tot_selected_ob = len(bpy.context.selected_objects) 328 | for ob in bpy.context.selected_objects: 329 | print('start baking "'+ob.name+'" (object '+str(ob_counter)+'/'+str(tot_selected_ob)+')') 330 | bpy.ops.object.select_all(action='DESELECT') 331 | ob.select = True 332 | bpy.context.scene.objects.active = ob 333 | for matslot in ob.material_slots: 334 | mat = matslot.material 335 | select_a_node(mat,"cc_image") 336 | # is_node = select_a_node(mat,"cc_image") 337 | # if is_node == False: 338 | # print("The material " + mat.name +" lacks a cc_image node. I'm creating it..") 339 | # create_new_tex_set(mat, "cc_image") 340 | bpy.ops.object.bake(type='DIFFUSE', pass_filter={'COLOR'}, use_selected_to_active=False, use_clear=True, save_mode='INTERNAL') 341 | tot_time += (time.time() - start_time) 342 | print("--- %s seconds ---" % (time.time() - start_time)) 343 | ob_counter += 1 344 | print("--- JOB complete in %s seconds ---" % tot_time) 345 | 346 | def remove_ori_image(mat): 347 | nodes = mat.node_tree.nodes 348 | links = mat.node_tree.links 349 | orimagenode = node_retriever(mat, "original") 350 | newimagenode = node_retriever(mat, "cc_image") 351 | nodes.remove(orimagenode) 352 | if newimagenode is not None: 353 | newimagenode.name = "original" 354 | newimagenode.location = (-1100, -50) 355 | 356 | def set_texset(mat, type): 357 | nodes = mat.node_tree.nodes 358 | links = mat.node_tree.links 359 | imagenode = node_retriever(mat, type) 360 | diffusenode = node_retriever(mat, "diffuse") 361 | links.new(imagenode.outputs[0], diffusenode.inputs[0]) 362 | 363 | def substring_after(s, delim): 364 | return s.partition(delim)[2] 365 | 366 | def create_new_tex_set(mat, type): 367 | #retrieve image specs and position from material 368 | o_image = mat.texture_slots[0].texture.image 369 | x_image = mat.texture_slots[0].texture.image.size[0] 370 | y_image = mat.texture_slots[0].texture.image.size[1] 371 | o_imagepath = mat.texture_slots[0].texture.image.filepath 372 | o_imagepath_abs = bpy.path.abspath(o_imagepath) 373 | o_imagedir, o_filename = os.path.split(o_imagepath_abs) 374 | o_filename_no_ext = os.path.splitext(o_filename)[0] 375 | 376 | nodes = mat.node_tree.nodes 377 | node_tree = bpy.data.materials[mat.name].node_tree 378 | if type == "cc_image": 379 | if o_filename_no_ext.startswith("cc_"): 380 | print(substring_after(o_filename, "cc_")) 381 | t_image_name = "cc_2_"+o_filename_no_ext 382 | else: 383 | t_image_name = "cc_"+o_filename_no_ext 384 | print(substring_after(o_filename, "cc_")) 385 | if type == "source_paint_node": 386 | t_image_name = "sp_"+o_filename_no_ext 387 | 388 | t_image = bpy.data.images.new(name=t_image_name, width=x_image, height=y_image, alpha=False) 389 | 390 | # set path to new image 391 | fn = os.path.join(o_imagedir, t_image_name) 392 | t_image.filepath_raw = fn+".png" 393 | t_image.file_format = 'PNG' 394 | 395 | tteximg = nodes.new('ShaderNodeTexImage') 396 | tteximg.location = (-1100, -450) 397 | tteximg.image = t_image 398 | tteximg.name = type 399 | 400 | for currnode in nodes: 401 | currnode.select = False 402 | 403 | # node_tree.nodes.select_all(action='DESELECT') 404 | tteximg.select = True 405 | node_tree.nodes.active = tteximg 406 | mat.texture_slots[0].texture.image = t_image 407 | 408 | # provide to thsi function a material and a node type and it will send you back the name of the node. With the option "all" you will get a dictionary of the nodes 409 | def node_retriever(mat, type): 410 | mat_nodes = mat.node_tree.nodes 411 | list_all_node_type = {} 412 | cc_node = "cc_node" 413 | cc_image = "cc_image" 414 | original = "original" 415 | source_paint_node = "source_paint_node" 416 | diffuse = "diffuse" 417 | list_all_node_type[cc_node] = None 418 | list_all_node_type[cc_image] = None 419 | list_all_node_type[original] = None 420 | list_all_node_type[source_paint_node] = None 421 | list_all_node_type[diffuse] = None 422 | node = None 423 | 424 | if type == "all": 425 | for node_type in list_all_node_type: 426 | for node in mat_nodes: 427 | if node.name == node_type: 428 | list_all_node_type[node_type] = node 429 | #print(list_all_node_type) 430 | return dict2list(list_all_node_type) 431 | else: 432 | for node in mat_nodes: 433 | if node.name == type: 434 | #print('Il nodo tipo trovato è :'+ node.name) 435 | list_all_node_type[type] = node 436 | return node 437 | pass 438 | print("non ho trovato nulla") 439 | node = False 440 | return node 441 | 442 | # for cycles material 443 | 444 | def dict2list(dict): 445 | list=[] 446 | for i,j in dict.items(): 447 | list.append(j) 448 | # print (list) 449 | return list 450 | 451 | def create_correction_nodegroup(name): 452 | 453 | # create a group 454 | # active_object_name = bpy.context.scene.objects.active.name 455 | test_group = bpy.data.node_groups.new(name, 'ShaderNodeTree') 456 | # test_group.label = label 457 | 458 | # create group inputs 459 | group_inputs = test_group.nodes.new('NodeGroupInput') 460 | group_inputs.location = (-750,0) 461 | test_group.inputs.new('NodeSocketColor','tex') 462 | 463 | # create group outputs 464 | group_outputs = test_group.nodes.new('NodeGroupOutput') 465 | group_outputs.location = (300,0) 466 | test_group.outputs.new('NodeSocketColor','cortex') 467 | 468 | # create three math nodes in a group 469 | bricon = test_group.nodes.new('ShaderNodeBrightContrast') 470 | bricon.location = (-220, -100) 471 | bricon.label = 'bricon' 472 | 473 | sathue = test_group.nodes.new('ShaderNodeHueSaturation') 474 | sathue.location = (0, -100) 475 | sathue.label = 'sathue' 476 | 477 | RGBcurve = test_group.nodes.new('ShaderNodeRGBCurve') 478 | RGBcurve.location = (-500, -100) 479 | RGBcurve.label = 'RGBcurve' 480 | 481 | # link nodes together 482 | test_group.links.new(sathue.inputs[4], bricon.outputs[0]) 483 | test_group.links.new(bricon.inputs[0], RGBcurve.outputs[0]) 484 | 485 | # link inputs 486 | test_group.links.new(group_inputs.outputs['tex'], RGBcurve.inputs[1]) 487 | 488 | #link output 489 | test_group.links.new(sathue.outputs[0], group_outputs.inputs['cortex']) 490 | 491 | def bi2cycles(): 492 | 493 | for obj in bpy.context.selected_objects: 494 | active_object_name = bpy.context.scene.objects.active.name 495 | 496 | for matslot in obj.material_slots: 497 | mat = matslot.material 498 | image = mat.texture_slots[0].texture.image 499 | mat.use_nodes = True 500 | mat.node_tree.nodes.clear() 501 | links = mat.node_tree.links 502 | nodes = mat.node_tree.nodes 503 | output = nodes.new('ShaderNodeOutputMaterial') 504 | output.location = (0, 0) 505 | mainNode = nodes.new('ShaderNodeBsdfPrincipled') 506 | mainNode.location = (-400, -50) 507 | mainNode.name = "diffuse" 508 | teximg = nodes.new('ShaderNodeTexImage') 509 | teximg.location = (-1100, -50) 510 | teximg.image = image 511 | teximg.name = "original" 512 | # colcor = nodes.new(type="ShaderNodeGroup") 513 | # colcor.node_tree = (bpy.data.node_groups[active_object_name]) 514 | # colcor.location = (-800, -50) 515 | links.new(teximg.outputs[0], mainNode.inputs[0]) 516 | # links.new(colcor.outputs[0], ) 517 | links.new(mainNode.outputs[0], output.inputs[0]) 518 | # colcor.name = "colcornode" 519 | 520 | def create_cc_node():#(ob,context): 521 | active_object_name = bpy.context.scene.objects.active.name 522 | create_correction_nodegroup(active_object_name) 523 | 524 | for obj in bpy.context.selected_objects: 525 | for matslot in obj.material_slots: 526 | mat = matslot.material 527 | # cc_image_node, cc_node, original_node, diffuse_node, source_paint_node = node_retriever(mat, "all") 528 | links = mat.node_tree.links 529 | nodes = mat.node_tree.nodes 530 | mainNode = node_retriever(mat, "diffuse") 531 | teximg = node_retriever(mat, "original") 532 | colcor = nodes.new(type="ShaderNodeGroup") 533 | colcor.node_tree = (bpy.data.node_groups[active_object_name]) 534 | colcor.location = (-800, -50) 535 | colcor.name = "cc_node" 536 | links.new(teximg.outputs[0], colcor.inputs[0]) 537 | links.new(colcor.outputs[0], mainNode.inputs[0]) 538 | 539 | def remove_node(mat, node_to_remove): 540 | node = node_retriever(mat, node_to_remove) 541 | if node is not None: 542 | # links = mat.node_tree.links 543 | # previous_node = cc_node.inputs[0].links[0].from_node 544 | # following_node = cc_node.outputs[0].links[0].to_node 545 | # links.new(previous_node.outputs[0], following_node.inputs[0]) 546 | mat.node_tree.nodes.remove(node) 547 | # else: 548 | # print("There is not a color correction node in this material") 549 | 550 | # for quick utils____________________________________________ 551 | def make_group(ob,context): 552 | nomeoggetto = str(ob.name) 553 | if bpy.data.groups.get(nomeoggetto) is not None: 554 | currentgroup = bpy.data.groups.get(nomeoggetto) 555 | bpy.ops.group.objects_remove_all() 556 | # for object in currentgroup.objects: 557 | # bpy.ops.group.objects_remove(group=currentgroup) 558 | else: 559 | bpy.ops.group.create(name=nomeoggetto) 560 | ob.select = True 561 | bpy.ops.object.group_link(group=nomeoggetto) 562 | 563 | 564 | def getnextobjname(name): 565 | # print("prendo in carico l'oggetto: "+name) 566 | #lst = ['this','is','just','a','test'] 567 | # if fnmatch.filter(name, '.0*'): 568 | if name.endswith(".001") or name.endswith(".002") or name.endswith(".003") or name.endswith(".004") or name.endswith(".005"): 569 | current_nonumber = name[:-3] 570 | # print("ho ridotto il nome a :"+current_nonumber) 571 | current_n_integer = int(name[-3:]) 572 | # print("aggiungo un numero") 573 | current_n_integer +=1 574 | # print(current_n_integer) 575 | if current_n_integer > 9: 576 | nextname = current_nonumber+'0'+str(current_n_integer) 577 | else: 578 | nextname = current_nonumber+'00'+str(current_n_integer) 579 | else: 580 | nextname = name+'.001' 581 | # print(nextname) 582 | return nextname 583 | 584 | def newimage2selpoly(ob, nametex): 585 | # objectname = ob.name 586 | print("I'm creating texture: T_"+nametex+".png") 587 | me = ob.data 588 | tempimage = bpy.data.images.new(name=nametex, width=4096, height=4096, alpha=False) 589 | tempimage.filepath_raw = "//T_"+nametex+".png" 590 | tempimage.file_format = 'PNG' 591 | for uv_face in me.uv_textures.active.data: 592 | uv_face.image = tempimage 593 | return 594 | 595 | def clean_name(name): 596 | if name.endswith(".001") or name.endswith(".002") or name.endswith(".003") or name.endswith(".004") or name.endswith(".005")or name.endswith(".006")or name.endswith(".007")or name.endswith(".008")or name.endswith(".009"): 597 | cname = name[:-4] 598 | else: 599 | cname = name 600 | return cname 601 | 602 | def areamesh(obj): 603 | bm = bmesh.new() 604 | bm.from_mesh(obj.data) 605 | area = sum(f.calc_area() for f in bm.faces) 606 | # print(area) 607 | bm.free() 608 | return area 609 | 610 | def desiredmatnumber(ob): 611 | area = areamesh(ob) 612 | if area > 21: 613 | if area <103: 614 | desmatnumber = 6 615 | if area < 86: 616 | desmatnumber = 5 617 | if area < 68: 618 | desmatnumber = 4 619 | if area < 52: 620 | desmatnumber = 3 621 | if area < 37: 622 | desmatnumber = 2 623 | else: 624 | desmatnumber = 6 625 | print("Be carefull ! the mesh is "+str(area)+" square meters is too big, consider to reduce it under 100. I will use six 4096 texture to describe it.") 626 | 627 | else: 628 | desmatnumber = 1 629 | 630 | return desmatnumber -------------------------------------------------------------------------------- /LODgenerator.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | import time 4 | from .functions import * 5 | from mathutils import Vector 6 | 7 | def selectLOD(listobjects, lodnum, basename): 8 | name2search = basename + '_LOD' + str(lodnum) 9 | for ob in listobjects: 10 | if ob.name == name2search: 11 | objatgivenlod = ob 12 | return objatgivenlod 13 | else: 14 | objatgivenlod = None 15 | return objatgivenlod 16 | 17 | def getChildren(myObject): 18 | children = [] 19 | for ob in bpy.data.objects: 20 | if ob.parent == myObject: 21 | children.append(ob) 22 | return children 23 | 24 | class ToolsPanel2(bpy.types.Panel): 25 | bl_space_type = "VIEW_3D" 26 | bl_region_type = "TOOLS" 27 | bl_context = "objectmode" 28 | bl_category = "3DSC" 29 | bl_label = "LOD generator" 30 | bl_options = {'DEFAULT_CLOSED'} 31 | 32 | def draw(self, context): 33 | layout = self.layout 34 | obj = context.object 35 | row = layout.row() 36 | if obj: 37 | row.label(text="Override name:") 38 | row = layout.row() 39 | row.prop(obj, "name", text='') 40 | row = layout.row() 41 | row.label(text="Actions on selected objects:") 42 | row = layout.row() 43 | self.layout.operator("lod0.b2osg", icon="MESH_UVSPHERE", text='LOD 0 (set as)') 44 | row = layout.row() 45 | row.label(text="Start always selecting LOD0 objs") 46 | self.layout.operator("lod1.b2osg", icon="MESH_ICOSPHERE", text='LOD 1 (creation)') 47 | self.layout.operator("lod2.b2osg", icon="MESH_CUBE", text='LOD 2 (creation)') 48 | self.layout.operator("bake.b2osg", icon="RADIO", text='just bake') 49 | 50 | row = layout.row() 51 | if obj: 52 | row.label(text="Resulting files: ") 53 | row = layout.row() 54 | row.label(text= "LOD1/LOD2_"+ obj.name + ".obj" ) 55 | row = layout.row() 56 | self.layout.operator("create.grouplod", icon="OOPS", text='Create LOD cluster(s)') 57 | row = layout.row() 58 | self.layout.operator("remove.grouplod", icon="CANCEL", text='Remove LOD cluster(s)') 59 | row = layout.row() 60 | self.layout.operator("exportfbx.grouplod", icon="MESH_GRID", text='FBX Export LOD cluster(s)') 61 | row = layout.row() 62 | 63 | class OBJECT_OT_LOD0(bpy.types.Operator): 64 | bl_idname = "lod0.b2osg" 65 | bl_label = "LOD0" 66 | bl_options = {"REGISTER", "UNDO"} 67 | 68 | def execute(self, context): 69 | selected_objs = bpy.context.selected_objects 70 | for obj in bpy.context.selected_objects: 71 | bpy.ops.object.select_all(action='DESELECT') 72 | obj.select = True 73 | bpy.context.scene.objects.active = obj 74 | bpy.ops.object.shade_smooth() 75 | baseobj = obj.name 76 | if not baseobj.endswith('LOD0'): 77 | obj.name = baseobj + '_LOD0' 78 | if len(obj.data.uv_layers) > 1: 79 | if obj.data.uv_textures[0].name =='MultiTex' and obj.data.uv_textures[1].name =='Atlas': 80 | pass 81 | else: 82 | mesh = obj.data 83 | mesh.uv_textures.active_index = 0 84 | multitex_uvmap = mesh.uv_textures.active 85 | multitex_uvmap_name = multitex_uvmap.name 86 | multitex_uvmap.name = 'MultiTex' 87 | atlas_uvmap = mesh.uv_textures.new() 88 | atlas_uvmap.name = 'Atlas' 89 | mesh.uv_textures.active_index = 1 90 | bpy.ops.object.editmode_toggle() 91 | bpy.ops.mesh.select_all(action='SELECT') 92 | bpy.ops.mesh.remove_doubles() 93 | bpy.ops.uv.select_all(action='SELECT') 94 | bpy.ops.uv.pack_islands(margin=0.001) 95 | bpy.ops.object.editmode_toggle() 96 | mesh.uv_textures.active_index = 0 97 | 98 | return {'FINISHED'} 99 | 100 | #_____________________________________________________________________________ 101 | 102 | class OBJECT_OT_BAKE(bpy.types.Operator): 103 | bl_idname = "bake.b2osg" 104 | bl_label = "bake" 105 | bl_options = {"REGISTER", "UNDO"} 106 | 107 | def execute(self, context): 108 | context = bpy.context 109 | start_time = time.time() 110 | basedir = os.path.dirname(bpy.data.filepath) 111 | subfolder = 'BAKE' 112 | if not os.path.exists(os.path.join(basedir, subfolder)): 113 | os.mkdir(os.path.join(basedir, subfolder)) 114 | print('There is no BAKE folder. Creating one...') 115 | else: 116 | print('Found previously created BAKE folder. I will use it') 117 | if not basedir: 118 | raise Exception("Save the blend file") 119 | 120 | ob_counter = 1 121 | ob_tot = len(bpy.context.selected_objects) 122 | print('<<<<<<<<<<<<<< CREATION OF BAKE >>>>>>>>>>>>>>') 123 | print('>>>>>> '+str(ob_tot)+' objects will be processed') 124 | 125 | for obj in bpy.context.selected_objects: 126 | obj.data.uv_textures["MultiTex"].active_render = True 127 | start_time_ob = time.time() 128 | print('>>> BAKE >>>') 129 | print('>>>>>> processing the object ""'+ obj.name+'"" ('+str(ob_counter)+'/'+str(ob_tot)+')') 130 | bpy.ops.object.select_all(action='DESELECT') 131 | obj.select = True 132 | bpy.context.scene.objects.active = obj 133 | baseobjwithlod = obj.name 134 | if '_LOD0' in baseobjwithlod: 135 | baseobj = baseobjwithlod.replace("_LOD0", "") 136 | else: 137 | baseobj = baseobjwithlod 138 | print('Creating new BAKE object..') 139 | bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False}) 140 | 141 | for obj in bpy.context.selected_objects: 142 | obj.name = baseobj + "_BAKE" 143 | newobj = obj 144 | for obj in bpy.context.selected_objects: 145 | lod1name = obj.name 146 | for i in range(0,len(bpy.data.objects[lod1name].material_slots)): 147 | bpy.ops.object.material_slot_remove() 148 | 149 | if obj.data.uv_textures[1] and obj.data.uv_textures[1].name =='Atlas': 150 | print('Found Atlas UV mapping layer. I will use it.') 151 | uv_textures = obj.data.uv_textures 152 | uv_textures = obj.data.uv_textures 153 | uv_textures.remove(uv_textures[0]) 154 | else: 155 | print('Creating new UV mapping layer.') 156 | bpy.ops.object.editmode_toggle() 157 | bpy.ops.mesh.select_all(action='SELECT') 158 | bpy.ops.mesh.remove_doubles() 159 | bpy.ops.uv.select_all(action='SELECT') 160 | bpy.ops.uv.pack_islands(margin=0.001) 161 | bpy.ops.object.editmode_toggle() 162 | 163 | # decimate_mesh(context,obj,0.5,'BAKE') 164 | 165 | 166 | # procedura di semplificazione mesh 167 | 168 | # ora mesh semplificata 169 | #------------------------------------------------------------------ 170 | 171 | 172 | bpy.ops.object.select_all(action='DESELECT') 173 | oggetto = bpy.data.objects[lod1name] 174 | oggetto.select = True 175 | print('Creating new texture atlas for BAKE....') 176 | 177 | tempimage = bpy.data.images.new(name=lod1name, width=2048, height=2048, alpha=False) 178 | tempimage.filepath_raw = "//"+subfolder+'/'+lod1name+".jpg" 179 | tempimage.file_format = 'JPEG' 180 | 181 | for uv_face in oggetto.data.uv_textures.active.data: 182 | uv_face.image = tempimage 183 | 184 | #-------------------------------------------------------------- 185 | print('Passing color data from original to BAKE...') 186 | bpy.context.scene.render.engine = 'BLENDER_RENDER' 187 | bpy.context.scene.render.use_bake_selected_to_active = True 188 | bpy.context.scene.render.bake_type = 'TEXTURE' 189 | 190 | object = bpy.data.objects[baseobjwithlod] 191 | object.select = True 192 | 193 | bpy.context.scene.objects.active = bpy.data.objects[lod1name] 194 | #-------------------------------------------------------------- 195 | 196 | bpy.ops.object.bake_image() 197 | tempimage.save() 198 | 199 | print('Creating custom material for BAKE...') 200 | bpy.ops.object.select_all(action='DESELECT') 201 | oggetto = bpy.data.objects[lod1name] 202 | oggetto.select = True 203 | bpy.context.scene.objects.active = oggetto 204 | bpy.ops.view3d.texface_to_material() 205 | oggetto.active_material.name = 'M_'+ oggetto.name 206 | oggetto.data.name = 'SM_' + oggetto.name 207 | # basedir = os.path.dirname(bpy.data.filepath) 208 | 209 | print('Saving on obj/mtl file for BAKE...') 210 | activename = bpy.path.clean_name(bpy.context.scene.objects.active.name) 211 | fn = os.path.join(basedir, subfolder, activename) 212 | bpy.ops.export_scene.obj(filepath=fn + ".obj", use_selection=True, axis_forward='Y', axis_up='Z', path_mode='RELATIVE') 213 | 214 | bpy.ops.object.move_to_layer(layers=(False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False)) 215 | print('>>> "'+obj.name+'" ('+str(ob_counter)+'/'+ str(ob_tot) +') object baked in '+str(time.time() - start_time_ob)+' seconds') 216 | ob_counter += 1 217 | 218 | bpy.context.scene.layers[11] = True 219 | bpy.context.scene.layers[0] = False 220 | end_time = time.time() - start_time 221 | print('<<<<<<< Process done >>>>>>') 222 | print('>>>'+str(ob_tot)+' objects processed in '+str(end_time)+' seconds') 223 | return {'FINISHED'} 224 | 225 | return {'FINISHED'} 226 | 227 | 228 | #_____________________________________________________________________________ 229 | 230 | 231 | 232 | class OBJECT_OT_LOD1(bpy.types.Operator): 233 | bl_idname = "lod1.b2osg" 234 | bl_label = "LOD1" 235 | bl_options = {"REGISTER", "UNDO"} 236 | 237 | def execute(self, context): 238 | context = bpy.context 239 | start_time = time.time() 240 | basedir = os.path.dirname(bpy.data.filepath) 241 | subfolder = 'LOD1' 242 | if not os.path.exists(os.path.join(basedir, subfolder)): 243 | os.mkdir(os.path.join(basedir, subfolder)) 244 | print('There is no LOD1 folder. Creating one...') 245 | else: 246 | print('Found previously created LOD1 folder. I will use it') 247 | if not basedir: 248 | raise Exception("Save the blend file") 249 | 250 | ob_counter = 1 251 | ob_tot = len(bpy.context.selected_objects) 252 | print('<<<<<<<<<<<<<< CREATION OF LOD 1 >>>>>>>>>>>>>>') 253 | print('>>>>>> '+str(ob_tot)+' objects will be processed') 254 | 255 | for obj in bpy.context.selected_objects: 256 | obj.data.uv_textures["MultiTex"].active_render = True 257 | start_time_ob = time.time() 258 | print('>>> LOD 1 >>>') 259 | print('>>>>>> processing the object ""'+ obj.name+'"" ('+str(ob_counter)+'/'+str(ob_tot)+')') 260 | bpy.ops.object.select_all(action='DESELECT') 261 | obj.select = True 262 | bpy.context.scene.objects.active = obj 263 | baseobjwithlod = obj.name 264 | if '_LOD0' in baseobjwithlod: 265 | baseobj = baseobjwithlod.replace("_LOD0", "") 266 | else: 267 | baseobj = baseobjwithlod 268 | print('Creating new LOD1 object..') 269 | bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False}) 270 | 271 | for obj in bpy.context.selected_objects: 272 | obj.name = baseobj + "_LOD1" 273 | newobj = obj 274 | for obj in bpy.context.selected_objects: 275 | lod1name = obj.name 276 | for i in range(0,len(bpy.data.objects[lod1name].material_slots)): 277 | bpy.ops.object.material_slot_remove() 278 | 279 | if obj.data.uv_textures[1] and obj.data.uv_textures[1].name =='Atlas': 280 | print('Found Atlas UV mapping layer. I will use it.') 281 | uv_textures = obj.data.uv_textures 282 | uv_textures = obj.data.uv_textures 283 | uv_textures.remove(uv_textures[0]) 284 | else: 285 | print('Creating new UV mapping layer.') 286 | bpy.ops.object.editmode_toggle() 287 | bpy.ops.mesh.select_all(action='SELECT') 288 | bpy.ops.mesh.remove_doubles() 289 | bpy.ops.uv.select_all(action='SELECT') 290 | bpy.ops.uv.pack_islands(margin=0.001) 291 | bpy.ops.object.editmode_toggle() 292 | 293 | decimate_mesh(context,obj,0.5,'LOD1') 294 | 295 | 296 | # procedura di semplificazione mesh 297 | 298 | # ora mesh semplificata 299 | #------------------------------------------------------------------ 300 | 301 | 302 | bpy.ops.object.select_all(action='DESELECT') 303 | oggetto = bpy.data.objects[lod1name] 304 | oggetto.select = True 305 | print('Creating new texture atlas for LOD1....') 306 | 307 | tempimage = bpy.data.images.new(name=lod1name, width=4096, height=4096, alpha=False) 308 | tempimage.filepath_raw = "//"+subfolder+'/'+lod1name+".jpg" 309 | tempimage.file_format = 'JPEG' 310 | 311 | for uv_face in oggetto.data.uv_textures.active.data: 312 | uv_face.image = tempimage 313 | 314 | #-------------------------------------------------------------- 315 | print('Passing color data from LOD0 to LOD1...') 316 | bpy.context.scene.render.engine = 'BLENDER_RENDER' 317 | bpy.context.scene.render.use_bake_selected_to_active = True 318 | bpy.context.scene.render.bake_type = 'TEXTURE' 319 | 320 | object = bpy.data.objects[baseobjwithlod] 321 | object.select = True 322 | 323 | bpy.context.scene.objects.active = bpy.data.objects[lod1name] 324 | #-------------------------------------------------------------- 325 | 326 | bpy.ops.object.bake_image() 327 | tempimage.save() 328 | 329 | print('Creating custom material for LOD1...') 330 | bpy.ops.object.select_all(action='DESELECT') 331 | oggetto = bpy.data.objects[lod1name] 332 | oggetto.select = True 333 | bpy.context.scene.objects.active = oggetto 334 | bpy.ops.view3d.texface_to_material() 335 | oggetto.active_material.name = 'M_'+ oggetto.name 336 | oggetto.data.name = 'SM_' + oggetto.name 337 | # basedir = os.path.dirname(bpy.data.filepath) 338 | 339 | print('Saving on obj/mtl file for LOD1...') 340 | activename = bpy.path.clean_name(bpy.context.scene.objects.active.name) 341 | fn = os.path.join(basedir, subfolder, activename) 342 | bpy.ops.export_scene.obj(filepath=fn + ".obj", use_selection=True, axis_forward='Y', axis_up='Z', path_mode='RELATIVE') 343 | 344 | bpy.ops.object.move_to_layer(layers=(False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False)) 345 | print('>>> "'+obj.name+'" ('+str(ob_counter)+'/'+ str(ob_tot) +') object baked in '+str(time.time() - start_time_ob)+' seconds') 346 | ob_counter += 1 347 | 348 | bpy.context.scene.layers[11] = True 349 | bpy.context.scene.layers[0] = False 350 | end_time = time.time() - start_time 351 | print('<<<<<<< Process done >>>>>>') 352 | print('>>>'+str(ob_tot)+' objects processed in '+str(end_time)+' seconds') 353 | return {'FINISHED'} 354 | 355 | 356 | #______________________________________ 357 | 358 | 359 | class OBJECT_OT_LOD2(bpy.types.Operator): 360 | bl_idname = "lod2.b2osg" 361 | bl_label = "LOD2" 362 | bl_options = {"REGISTER", "UNDO"} 363 | 364 | def execute(self, context): 365 | start_time = time.time() 366 | basedir = os.path.dirname(bpy.data.filepath) 367 | subfolder = 'LOD2' 368 | if not os.path.exists(os.path.join(basedir, subfolder)): 369 | os.mkdir(os.path.join(basedir, subfolder)) 370 | print('There is no LOD2 folder. Creating one...') 371 | else: 372 | print('Found previously created LOD1 folder. I will use it') 373 | if not basedir: 374 | raise Exception("Save the blend file") 375 | ob_counter = 1 376 | ob_tot = len(bpy.context.selected_objects) 377 | print('<<<<<<<<<<<<<< CREATION OF LOD 2 >>>>>>>>>>>>>>') 378 | print('>>>>>> '+str(ob_tot)+' objects will be processed') 379 | 380 | for obj in bpy.context.selected_objects: 381 | obj.data.uv_textures["MultiTex"].active_render = True 382 | print('>>> LOD 2 >>>') 383 | print('>>>>>> processing the object ""'+ obj.name+'"" ('+str(ob_counter)+'/'+str(ob_tot)+')') 384 | start_time_ob = time.time() 385 | 386 | bpy.ops.object.select_all(action='DESELECT') 387 | obj.select = True 388 | bpy.context.scene.objects.active = obj 389 | baseobjwithlod = obj.name 390 | if '_LOD0' in baseobjwithlod: 391 | baseobj = baseobjwithlod.replace("_LOD0", "") 392 | else: 393 | baseobj = baseobjwithlod 394 | print('Creating new LOD2 object..') 395 | bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False}) 396 | 397 | for obj in bpy.context.selected_objects: 398 | obj.name = baseobj + "_LOD2" 399 | newobj = obj 400 | for obj in bpy.context.selected_objects: 401 | lod2name = obj.name 402 | 403 | for i in range(0,len(bpy.data.objects[lod2name].material_slots)): 404 | bpy.ops.object.material_slot_remove() 405 | 406 | # se abbiamo precedente atlas, inutile rifarlo 407 | if obj.data.uv_textures[1] and obj.data.uv_textures[1].name =='Atlas': 408 | print('Found Atlas UV mapping layer. I will use it.') 409 | uv_textures = obj.data.uv_textures 410 | uv_textures.remove(uv_textures[0]) 411 | 412 | else: 413 | print('Creating new UV mapping layer.') 414 | bpy.ops.object.editmode_toggle() 415 | bpy.ops.mesh.select_all(action='SELECT') 416 | bpy.ops.mesh.remove_doubles() 417 | bpy.ops.uv.select_all(action='SELECT') 418 | bpy.ops.uv.pack_islands(margin=0.001) 419 | bpy.ops.object.editmode_toggle() 420 | 421 | # procedura di semplificazione mesh 422 | 423 | print('Decimating the original mesh to obtain the LOD2 mesh...') 424 | bpy.ops.object.editmode_toggle() 425 | bpy.ops.mesh.select_all(action='DESELECT') 426 | bpy.ops.mesh.select_non_manifold() 427 | bpy.ops.object.vertex_group_add() 428 | bpy.ops.object.vertex_group_assign() 429 | bpy.ops.object.editmode_toggle() 430 | bpy.data.objects[lod2name].modifiers.new("Decimate", type='DECIMATE') 431 | obj.modifiers["Decimate"].ratio = 0.1 432 | obj.modifiers["Decimate"].vertex_group = "Group" 433 | obj.modifiers["Decimate"].invert_vertex_group = True 434 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Decimate") 435 | # ora mesh semplificata 436 | #------------------------------------------------------------------ 437 | bpy.ops.object.select_all(action='DESELECT') 438 | oggetto = bpy.data.objects[lod2name] 439 | oggetto.select = True 440 | print('Creating new texture atlas for LOD2....') 441 | 442 | tempimage = bpy.data.images.new(name=lod2name, width=512, height=512, alpha=False) 443 | tempimage.filepath_raw = "//"+subfolder+'/'+lod2name+".jpg" 444 | tempimage.file_format = 'JPEG' 445 | 446 | for uv_face in oggetto.data.uv_textures.active.data: 447 | uv_face.image = tempimage 448 | 449 | #-------------------------------------------------------------- 450 | print('Passing color data from LOD0 to LOD2...') 451 | bpy.context.scene.render.engine = 'BLENDER_RENDER' 452 | bpy.context.scene.render.use_bake_selected_to_active = True 453 | bpy.context.scene.render.bake_type = 'TEXTURE' 454 | 455 | object = bpy.data.objects[baseobjwithlod] 456 | object.select = True 457 | 458 | bpy.context.scene.objects.active = bpy.data.objects[lod2name] 459 | #-------------------------------------------------------------- 460 | 461 | bpy.ops.object.bake_image() 462 | tempimage.save() 463 | 464 | print('Creating custom material for LOD2...') 465 | 466 | bpy.ops.object.select_all(action='DESELECT') 467 | oggetto = bpy.data.objects[lod2name] 468 | oggetto.select = True 469 | 470 | bpy.context.scene.objects.active = oggetto 471 | bpy.ops.view3d.texface_to_material() 472 | 473 | oggetto.active_material.name = 'M_'+ oggetto.name 474 | oggetto.data.name = 'SM_' + oggetto.name 475 | 476 | print('Saving on obj/mtl file for LOD2...') 477 | activename = bpy.path.clean_name(bpy.context.scene.objects.active.name) 478 | fn = os.path.join(basedir, subfolder, activename) 479 | bpy.ops.export_scene.obj(filepath=fn + ".obj", use_selection=True, axis_forward='Y', axis_up='Z', path_mode='RELATIVE') 480 | 481 | bpy.ops.object.move_to_layer(layers=(False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False)) 482 | print('>>> "'+obj.name+'" ('+str(ob_counter)+'/'+ str(ob_tot) +') object baked in '+str(time.time() - start_time_ob)+' seconds') 483 | ob_counter += 1 484 | 485 | bpy.context.scene.layers[10] = True 486 | bpy.context.scene.layers[0] = False 487 | end_time = time.time() - start_time 488 | print('<<<<<<< Process done >>>>>>') 489 | print('>>>'+str(ob_tot)+' objects processed in '+str(end_time)+' seconds') 490 | return {'FINISHED'} 491 | 492 | 493 | #_______________________________________________________________ 494 | 495 | class OBJECT_OT_ExportGroupsLOD(bpy.types.Operator): 496 | bl_idname = "exportfbx.grouplod" 497 | bl_label = "Export Group LOD" 498 | bl_options = {"REGISTER", "UNDO"} 499 | 500 | def execute(self, context): 501 | start_time = time.time() 502 | basedir = os.path.dirname(bpy.data.filepath) 503 | if not basedir: 504 | raise Exception("Blend file is not saved") 505 | ob_counter = 1 506 | scene = context.scene 507 | listobjects = bpy.context.selected_objects 508 | for obj in listobjects: 509 | if obj.type == 'EMPTY': 510 | if obj.get('fbx_type') is not None: 511 | print('Found LOD cluster to export: "'+obj.name+'", object') 512 | bpy.ops.object.select_all(action='DESELECT') 513 | obj.select = True 514 | bpy.context.scene.objects.active = obj 515 | for ob in getChildren(obj): 516 | ob.select = True 517 | name = bpy.path.clean_name(obj.name) 518 | fn = os.path.join(basedir, name) 519 | bpy.ops.export_scene.fbx(filepath= fn + ".fbx", check_existing=True, axis_forward='-Z', axis_up='Y', filter_glob="*.fbx", version='BIN7400', ui_tab='MAIN', use_selection=True, global_scale=1.0, apply_unit_scale=True, bake_space_transform=False, object_types={'ARMATURE', 'CAMERA', 'EMPTY', 'LAMP', 'MESH', 'OTHER'}, use_mesh_modifiers=True, mesh_smooth_type='EDGE', use_mesh_edges=False, use_tspace=False, use_custom_props=False, add_leaf_bones=True, primary_bone_axis='Y', secondary_bone_axis='X', use_armature_deform_only=False, bake_anim=True, bake_anim_use_all_bones=True, bake_anim_use_nla_strips=True, bake_anim_use_all_actions=True, bake_anim_force_startend_keying=True, bake_anim_step=1.0, bake_anim_simplify_factor=1.0, use_anim=True, use_anim_action_all=True, use_default_take=True, use_anim_optimize=True, anim_optimize_precision=6.0, path_mode='RELATIVE', embed_textures=False, batch_mode='OFF', use_batch_own_dir=True, use_metadata=True) 520 | else: 521 | print('The "' + obj.name + '" empty object has not the correct settings to export an FBX - LOD enabled file. I will skip it.') 522 | obj.select = False 523 | print('>>> Object number '+str(ob_counter)+' processed in '+str(time.time() - start_time)+' seconds') 524 | ob_counter += 1 525 | 526 | end_time = time.time() - start_time 527 | print('<<<<<<< Process done >>>>>>') 528 | print('>>>'+str(ob_counter)+' objects processed in '+str(end_time)+' seconds') 529 | 530 | return {'FINISHED'} 531 | 532 | #_______________________________________________________________ 533 | 534 | 535 | 536 | class OBJECT_OT_RemoveGroupsLOD(bpy.types.Operator): 537 | bl_idname = "remove.grouplod" 538 | bl_label = "Remove Group LOD" 539 | bl_options = {"REGISTER", "UNDO"} 540 | 541 | def execute(self, context): 542 | listobjects = bpy.context.selected_objects 543 | bpy.ops.object.select_all(action='DESELECT') 544 | for obj in listobjects: 545 | if obj.get('fbx_type') is not None: 546 | obj.select = True 547 | bpy.context.scene.objects.active = obj 548 | for ob in getChildren(obj): 549 | ob.select = True 550 | bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') 551 | bpy.ops.object.select_all(action='DESELECT') 552 | obj.select = True 553 | bpy.context.scene.objects.active = obj 554 | bpy.ops.object.delete() 555 | return {'FINISHED'} 556 | 557 | #_______________________________________________________________ 558 | 559 | 560 | class OBJECT_OT_CreateGroupsLOD(bpy.types.Operator): 561 | bl_idname = "create.grouplod" 562 | bl_label = "Create Group LOD" 563 | bl_options = {"REGISTER", "UNDO"} 564 | 565 | def execute(self, context): 566 | listobjects = bpy.context.selected_objects 567 | for obj in listobjects: 568 | bpy.ops.object.select_all(action='DESELECT') 569 | obj.select = True 570 | bpy.context.scene.objects.active = obj 571 | baseobjwithlod = obj.name 572 | 573 | if '_LOD0' in baseobjwithlod: 574 | baseobj = baseobjwithlod.replace("_LOD0", "") 575 | print('Found LOD0 object:' + baseobjwithlod) 576 | local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector()) 577 | global_bbox_center = obj.matrix_world * local_bbox_center 578 | emptyofname = 'GLOD_' + baseobj 579 | obempty = bpy.data.objects.new( emptyofname, None ) 580 | bpy.context.scene.objects.link( obempty ) 581 | obempty.empty_draw_size = 2 582 | obempty.empty_draw_type = 'PLAIN_AXES' 583 | obempty.location = global_bbox_center 584 | bpy.ops.object.select_all(action='DESELECT') 585 | obempty.select = True 586 | bpy.context.scene.objects.active = obempty 587 | obempty['fbx_type'] = 'LodGroup' 588 | # bpy.ops.wm.properties_edit(data_path="object", property="Fbx_Type", value="LodGroup", min=0, max=1, use_soft_limits=False, soft_min=0, soft_max=1, description="") 589 | 590 | num = 0 591 | child = selectLOD(listobjects, num, baseobj) 592 | while child is not None: 593 | bpy.ops.object.select_all(action='DESELECT') 594 | child.select = True 595 | obempty.select = True 596 | bpy.context.scene.objects.active = obempty 597 | bpy.ops.object.parent_set(type='OBJECT', keep_transform=False) 598 | # child.parent= obempty 599 | # child.location.x = child.location.x - obempty.location.x 600 | # child.location.y = child.location.y - obempty.location.y 601 | num += 1 602 | child = selectLOD(listobjects, num, baseobj) 603 | return {'FINISHED'} 604 | --------------------------------------------------------------------------------