├── 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 | 
24 |
25 | ## Exporters
26 |
27 | The **Exporters** panel allows to
28 |
29 | 
30 |
31 | ## Quick Utils
32 |
33 | The **Quick Utils** panel allows to
34 |
35 | 
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 | 
49 |
50 | ## Color correction
51 |
52 | The **Color correction** panel allows to
53 |
54 | 
55 |
56 | ## LOD generator
57 |
58 | The **Color correction** panel allows to
59 |
60 | 
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 |
--------------------------------------------------------------------------------