├── datafiles └── colormanagement │ └── config.ocio └── scripts ├── addons ├── bpyqt │ ├── __init__.py │ └── example.py ├── custom_tools │ ├── __init__.py │ ├── animation.py │ ├── colorspace.py │ ├── image.py │ ├── movieclip.py │ ├── node.py │ ├── render.py │ ├── sequencer.py │ └── viewport.py ├── data_nodes │ ├── __init__.py │ ├── nodes │ │ ├── __init__.py │ │ ├── boolean.py │ │ ├── color.py │ │ ├── color_combine.py │ │ ├── color_palette.py │ │ ├── color_split.py │ │ ├── condition.py │ │ ├── data_input.py │ │ ├── data_output.py │ │ ├── debug.py │ │ ├── distance.py │ │ ├── expression.py │ │ ├── float_number.py │ │ ├── float_switch.py │ │ ├── float_to_int.py │ │ ├── float_to_string.py │ │ ├── int_to_float.py │ │ ├── integer.py │ │ ├── note.py │ │ ├── object_properties.py │ │ ├── render.py │ │ ├── render_engine.py │ │ ├── round_float.py │ │ ├── scene.py │ │ ├── time.py │ │ ├── vector.py │ │ ├── vector_split.py │ │ └── view_layer.py │ ├── operators.py │ └── utils.py ├── image_preview.py ├── pointcloud.py └── sonycamera.py ├── modules └── vgblender │ ├── __init__.py │ ├── alembic.py │ ├── area.py │ ├── argconfig.py │ ├── colorspace.py │ ├── datautils.py │ ├── glutils.py │ ├── image.py │ ├── path.py │ ├── render.py │ ├── sequencer.py │ ├── timeline.py │ └── ui │ ├── __init__.py │ └── color_management.py ├── presets └── interface_theme │ └── flat_dark.xml ├── startup ├── bl_app_templates_user │ ├── composite_image │ │ ├── __init__.py │ │ └── startup.blend │ ├── image_viewer │ │ ├── __init__.py │ │ └── startup.blend │ └── sequence_player │ │ ├── __init__.py │ │ └── startup.blend └── vg_startup │ └── __init__.py └── templates_py ├── convert_image.py ├── convert_to_movie.py ├── convert_to_stereo_movie.py └── set_preferences.py /datafiles/colormanagement/config.ocio: -------------------------------------------------------------------------------- 1 | # OpenColorIO configuration file for Blender 2 | # 3 | # Based on aces, nuke-default and spi configurations from OpenColorIO-Config 4 | # 5 | # Filmic Dynamic Range LUT configuration crafted by Troy James Sobotka with 6 | # special thanks and feedback from Guillermo, Claudio Rocha, Bassam Kurdali, 7 | # Eugenio Pignataro, Henri Hebeisen, Jason Clarke, Haarm-Peter Duiker, Thomas 8 | # Mansencal, and Timothy Lottes. 9 | # 10 | # See ocio-license.txt for details. 11 | 12 | ocio_profile_version: 1 13 | 14 | search_path: "luts:filmic" 15 | strictparsing: true 16 | luma: [0.2126, 0.7152, 0.0722] 17 | 18 | description: RRT version ut33 19 | 20 | roles: 21 | reference: Linear 22 | 23 | # Internal scene linear space 24 | scene_linear: Linear 25 | rendering: Linear 26 | 27 | # Default color space for byte image 28 | default_byte: sRGB 29 | 30 | # Default color space for float images 31 | default_float: Linear 32 | 33 | # Default color space sequencer is working in 34 | default_sequencer: sRGB 35 | 36 | # Color spaces for color picking and texture painting (not internally supported yet) 37 | color_picking: sRGB 38 | texture_paint: Raw 39 | 40 | # Non-color data 41 | data: Non-Color 42 | 43 | # CIE XYZ color space 44 | XYZ: XYZ 45 | 46 | displays: 47 | sRGB: 48 | - ! {name: Standard, colorspace: sRGB} 49 | - ! {name: Filmic, colorspace: Filmic sRGB} 50 | - ! {name: Filmic Log, colorspace: Filmic Log} 51 | - ! {name: Raw, colorspace: Raw} 52 | - ! {name: False Color, colorspace: False Color} 53 | XYZ: 54 | - ! {name: Standard, colorspace: XYZ} 55 | - ! {name: DCI, colorspace: dci_xyz} 56 | - ! {name: Raw, colorspace: Raw} 57 | None: 58 | - ! {name: Standard, colorspace: Raw} 59 | 60 | active_displays: [sRGB, XYZ, None] 61 | active_views: [Standard, Filmic, Filmic Log, Raw] 62 | 63 | colorspaces: 64 | - ! 65 | name: Linear 66 | family: linear 67 | equalitygroup: 68 | bitdepth: 32f 69 | description: | 70 | Rec. 709 (Full Range), Blender native linear space 71 | isdata: false 72 | allocation: lg2 73 | allocationvars: [-12.473931188, 12.526068812] 74 | 75 | - ! 76 | name: Raw 77 | family: raw 78 | equalitygroup: 79 | bitdepth: 32f 80 | isdata: true 81 | allocation: uniform 82 | allocationvars: [0, 1] 83 | 84 | - ! 85 | name: Linear ACES 86 | family: linear 87 | equalitygroup: 88 | bitdepth: 32f 89 | description: | 90 | ACES linear space 91 | isdata: false 92 | allocation: lg2 93 | allocationvars: [-8.5, 5] 94 | to_reference: ! {src: rec709_to_aces.spimtx, interpolation: linear, direction: inverse} 95 | 96 | - ! 97 | name: nuke_rec709 98 | family: display 99 | equalitygroup: 100 | bitdepth: 32f 101 | description: | 102 | Rec. 709 (Full Range) Display Space 103 | isdata: false 104 | allocation: uniform 105 | allocationvars: [-0.125, 1.125] 106 | to_reference: ! 107 | children: 108 | - ! {src: rec709.spi1d, interpolation: linear} 109 | 110 | - ! 111 | name: XYZ 112 | family: linear 113 | equalitygroup: 114 | bitdepth: 32f 115 | isdata: false 116 | allocation: lg2 117 | allocationvars: [-8.5, 5] 118 | from_reference: ! 119 | children: 120 | - ! {src: srgb_to_xyz.spimtx, interpolation: linear} 121 | 122 | - ! 123 | name: dci_xyz 124 | family: display 125 | equalitygroup: 126 | bitdepth: 16f 127 | description: | 128 | OpenDCP output LUT with DCI reference white and Gamma 2.6 129 | isdata: false 130 | allocation: uniform 131 | allocationvars: [0, 1] 132 | from_reference: ! 133 | children: 134 | - ! {src: srgb_to_xyz.spimtx, interpolation: linear} 135 | - ! {src: dci_xyz.spi1d, interpolation: linear} 136 | 137 | - ! 138 | name: lg10 139 | family: display 140 | equalitygroup: 141 | bitdepth: 10ui 142 | description: | 143 | conversion from film log 144 | isdata: false 145 | allocation: uniform 146 | to_reference: ! 147 | children: 148 | - ! {src: lg10.spi1d, interpolation: nearest} 149 | 150 | - ! 151 | name: sRGB 152 | family: 153 | equalitygroup: 154 | bitdepth: 32f 155 | description: | 156 | Standard RGB Display Space 157 | isdata: false 158 | allocation: uniform 159 | allocationvars: [-0.125, 4.875] 160 | to_reference: ! {src: srgb.spi1d, interpolation: linear} 161 | from_reference: ! {src: srgb_inv.spi1d, interpolation: linear} 162 | 163 | - ! 164 | name: Non-Color 165 | family: raw 166 | description: | 167 | Color space used for images which contains non-color data (i,e, normal maps) 168 | equalitygroup: 169 | bitdepth: 32f 170 | isdata: true 171 | allocation: uniform 172 | allocationvars: [0, 1] 173 | 174 | - ! 175 | name: Filmic Log 176 | family: log 177 | equalitygroup: 178 | bitdepth: 32f 179 | description: | 180 | Log based filmic shaper with 16.5 stops of latitude, and 25 stops of dynamic range 181 | isdata: false 182 | allocation: lg2 183 | allocationvars: [-12.473931188, 12.526068812] 184 | from_reference: ! 185 | children: 186 | - ! {allocation: lg2, vars: [-12.473931188, 12.526068812]} 187 | - ! {src: filmic_desat65cube.spi3d, interpolation: best} 188 | - ! {allocation: uniform, vars: [0, 0.66]} 189 | to_reference: ! {allocation: lg2, vars: [-12.473931188, 4.026068812], direction: inverse} 190 | 191 | - ! 192 | name: Filmic sRGB 193 | family: display 194 | equalitygroup: 195 | bitdepth: 32f 196 | description: | 197 | Filmic sRGB view transform 198 | isdata: false 199 | allocation: lg2 200 | allocationvars: [-12.473931188, 12.526068812] 201 | from_reference: ! 202 | children: 203 | - ! {src: Linear, dst: Filmic Log} 204 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear} 205 | 206 | - ! 207 | name: False Color 208 | family: display 209 | equalitygroup: 210 | bitdepth: 32f 211 | description: | 212 | Filmic false color view transform 213 | isdata: false 214 | allocation: lg2 215 | allocationvars: [-12.473931188, 12.526068812] 216 | from_reference: ! 217 | children: 218 | - ! {src: Linear, dst: Filmic Log} 219 | - ! {src: filmic_false_color.spi3d, interpolation: best} 220 | looks: 221 | - ! 222 | name: Luminance 223 | process_space: linear 224 | transform: ! {matrix: [0.2126, 0.7152, 0.0722, 0, 0.2126, 0.7152, 0.0722, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0, 0, 1]} 225 | 226 | - ! 227 | name: Filmic - Very High Contrast 228 | process_space: Filmic Log 229 | transform: ! 230 | children: 231 | - ! {src: filmic_to_1.20_1-00.spi1d, interpolation: linear} 232 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 233 | 234 | - ! 235 | name: Filmic - High Contrast 236 | process_space: Filmic Log 237 | transform: ! 238 | children: 239 | - ! {src: filmic_to_0.99_1-0075.spi1d, interpolation: linear} 240 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 241 | 242 | - ! 243 | name: Filmic - Medium High Contrast 244 | process_space: Filmic Log 245 | transform: ! 246 | children: 247 | - ! {src: filmic_to_0-85_1-011.spi1d, interpolation: best} 248 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 249 | 250 | - ! 251 | name: Filmic - Medium Contrast 252 | process_space: Filmic Log 253 | transform: ! 254 | children: 255 | 256 | - ! 257 | name: Filmic - Medium Low Contrast 258 | process_space: Filmic Log 259 | transform: ! 260 | children: 261 | - ! {src: filmic_to_0-60_1-04.spi1d, interpolation: linear} 262 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 263 | 264 | - ! 265 | name: Filmic - Low Contrast 266 | process_space: Filmic Log 267 | transform: ! 268 | children: 269 | - ! {src: filmic_to_0-48_1-09.spi1d, interpolation: linear} 270 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 271 | 272 | - ! 273 | name: Filmic - Very Low Contrast 274 | process_space: Filmic Log 275 | transform: ! 276 | children: 277 | - ! {src: filmic_to_0-35_1-30.spi1d, interpolation: linear} 278 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 279 | 280 | - ! 281 | name: Standard - Very High Contrast 282 | process_space: Filmic Log 283 | transform: ! 284 | children: 285 | - ! {src: filmic_to_1.20_1-00.spi1d, interpolation: linear} 286 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 287 | 288 | - ! 289 | name: Standard - High Contrast 290 | process_space: Filmic Log 291 | transform: ! 292 | children: 293 | - ! {src: filmic_to_0.99_1-0075.spi1d, interpolation: linear} 294 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 295 | 296 | - ! 297 | name: Standard - Medium High Contrast 298 | process_space: Filmic Log 299 | transform: ! 300 | children: 301 | - ! {src: filmic_to_0-85_1-011.spi1d, interpolation: best} 302 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 303 | 304 | - ! 305 | name: Standard - Base Contrast 306 | process_space: Filmic Log 307 | transform: ! 308 | children: 309 | 310 | - ! 311 | name: Standard - Medium Low Contrast 312 | process_space: Filmic Log 313 | transform: ! 314 | children: 315 | - ! {src: filmic_to_0-60_1-04.spi1d, interpolation: linear} 316 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 317 | 318 | - ! 319 | name: Standard - Low Contrast 320 | process_space: Filmic Log 321 | transform: ! 322 | children: 323 | - ! {src: filmic_to_0-48_1-09.spi1d, interpolation: linear} 324 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 325 | 326 | - ! 327 | name: Standard - Very Low Contrast 328 | process_space: Filmic Log 329 | transform: ! 330 | children: 331 | - ! {src: filmic_to_0-35_1-30.spi1d, interpolation: linear} 332 | - ! {src: filmic_to_0-70_1-03.spi1d, interpolation: linear, direction: inverse} 333 | -------------------------------------------------------------------------------- /scripts/addons/bpyqt/__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import sys 3 | import os 4 | import logging 5 | import importlib 6 | 7 | qt_binding = os.environ.get('QT_BINDING', 'PySide2') 8 | QtWidgets = importlib.import_module(f'{qt_binding}.QtWidgets') 9 | QtCore = importlib.import_module(f'{qt_binding}.QtCore') 10 | 11 | logger = logging.getLogger('bpyqt') 12 | 13 | 14 | bl_info = { 15 | 'name': 'bpyqt', 16 | 'author': 'Vincent Girès', 17 | 'description': 'Qt Integration', 18 | 'version': (0, 0, 1), 19 | 'blender': (2, 80, 0), 20 | 'category': 'Qt'} 21 | 22 | 23 | class QtWindowEventLoop(bpy.types.Operator): 24 | """Allows PyQt or PySide to run inside Blender""" 25 | 26 | bl_idname = 'screen.qt_event_loop' 27 | bl_label = 'Qt Event Loop' 28 | 29 | def __init__(self, widget, *args, **kwargs): 30 | self._widget = widget 31 | self._args = args 32 | self._kwargs = kwargs 33 | 34 | def modal(self, context, event): 35 | wm = context.window_manager 36 | 37 | if not self.widget.isVisible(): 38 | # if widget is closed 39 | logger.debug('finish modal operator') 40 | wm.event_timer_remove(self._timer) 41 | return {'FINISHED'} 42 | else: 43 | logger.debug('process the events for Qt window') 44 | self.event_loop.processEvents() 45 | self.app.sendPostedEvents(None, 0) 46 | 47 | return {'PASS_THROUGH'} 48 | 49 | def execute(self, context): 50 | logger.debug('execute operator') 51 | 52 | self.app = QtWidgets.QApplication.instance() 53 | # instance() gives the possibility to have multiple windows 54 | # and close it one by one 55 | 56 | if not self.app: 57 | # create the first instance 58 | self.app = QtWidgets.QApplication(sys.argv) 59 | 60 | if 'stylesheet' in self._kwargs: 61 | stylesheet = self._kwargs['stylesheet'] 62 | self.set_stylesheet(self.app, stylesheet) 63 | 64 | self.event_loop = QtCore.QEventLoop() 65 | self.widget = self._widget(*self._args, **self._kwargs) 66 | 67 | logger.debug(self.app) 68 | logger.debug(self.widget) 69 | 70 | # run modal 71 | wm = context.window_manager 72 | self._timer = wm.event_timer_add(1 / 120, window=context.window) 73 | context.window_manager.modal_handler_add(self) 74 | 75 | return {'RUNNING_MODAL'} 76 | 77 | def set_stylesheet(self, app, filepath): 78 | file_qss = QtCore.QFile(filepath) 79 | if file_qss.exists(): 80 | file_qss.open(QtCore.QFile.ReadOnly) 81 | stylesheet = QtCore.QTextStream(file_qss).readAll() 82 | app.setStyleSheet(stylesheet) 83 | file_qss.close() 84 | 85 | 86 | def register(): 87 | bpy.utils.register_class(QtWindowEventLoop) 88 | 89 | 90 | def unregister(): 91 | bpy.utils.unregister_class(QtWindowEventLoop) 92 | -------------------------------------------------------------------------------- /scripts/addons/bpyqt/example.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from PySide2 import QtWidgets, QtCore 3 | from bpyqt import QtWindowEventLoop 4 | 5 | 6 | class ExampleWidget(QtWidgets.QWidget): 7 | def __init__(self, label_name, text): 8 | super().__init__() 9 | self.resize(720, 300) 10 | self.setWindowTitle('Qt Window') 11 | self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) 12 | self.label = QtWidgets.QLabel(label_name) 13 | self.label2 = QtWidgets.QLabel(text) 14 | self.label3 = QtWidgets.QLabel() 15 | layout = QtWidgets.QVBoxLayout() 16 | layout.setContentsMargins(0, 0, 0, 0) 17 | layout.addWidget(self.label) 18 | layout.addWidget(self.label2) 19 | layout.addWidget(self.label3) 20 | self.setLayout(layout) 21 | self.show() 22 | 23 | def enterEvent(self, event): 24 | self.label3.setText(bpy.context.object.name) 25 | 26 | 27 | class CustomWindowOperator(QtWindowEventLoop): 28 | bl_idname = 'screen.custom_window' 29 | bl_label = 'Custom window' 30 | 31 | def __init__(self): 32 | super().__init__(ExampleWidget, 'Label name', text='A text') 33 | 34 | 35 | class QtPanelExample(bpy.types.Panel): 36 | bl_idname = 'BPYQT_PT_ExamplePanel' 37 | bl_label = 'Qt' 38 | bl_space_type = 'VIEW_3D' 39 | bl_region_type = 'UI' 40 | bl_category = 'bpyqt' 41 | 42 | def draw(self, context): 43 | layout = self.layout 44 | layout.operator('screen.custom_window') 45 | 46 | 47 | if __name__ == '__main__': 48 | bpy.utils.register_class(CustomWindowOperator) 49 | bpy.utils.register_class(QtPanelExample) 50 | bpy.ops.screen.custom_window() 51 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Operator 3 | from . import animation, image, movieclip, render, sequencer, node, viewport 4 | 5 | bl_info = { 6 | 'name': 'Custom tools', 7 | 'author': 'Vincent Girès', 8 | 'version': (0, 0, 1), 9 | 'blender': (2, 80, 0), 10 | 'category': '3D View'} 11 | 12 | 13 | def header_color_management(self, context): 14 | if context.region.alignment == 'RIGHT': 15 | scene = context.scene 16 | layout = self.layout 17 | row = layout.row(align=True) 18 | row.operator('scene.reset_exposure', text='Exp') 19 | row.prop(scene.view_settings, 'exposure', text='') 20 | row = layout.row(align=True) 21 | row.operator('scene.reset_gamma', text='Y') 22 | row.prop(scene.view_settings, 'gamma', text='') 23 | 24 | 25 | def image_image_menu_draw(self, context): 26 | self.layout.separator() 27 | self.layout.operator('scene.set_image_input_transform') 28 | 29 | 30 | def clip_clip_menu_draw(self, context): 31 | self.layout.separator() 32 | self.layout.operator('scene.set_movieclip_input_transform') 33 | 34 | 35 | def node_node_menu_draw(self, context): 36 | self.layout.separator() 37 | self.layout.operator('scene.set_image_node_input_transform') 38 | 39 | 40 | def render_menu_draw(self, context): 41 | self.layout.operator('render.render_gif') 42 | 43 | 44 | def sequencer_add_menu_draw(self, context): 45 | self.layout.separator() 46 | self.layout.operator('scene.add_multiple_movies') 47 | self.layout.operator( 48 | 'sequencer.create_adjustment_strip', 49 | text='Ajustment Layer from active') 50 | 51 | 52 | def sequencer_strip_menu_draw(self, context): 53 | self.layout.separator() 54 | self.layout.operator('scene.open_strip_as_movieclip') 55 | self.layout.operator('scene.add_strip_as_compositing') 56 | self.layout.operator('scene.set_strip_input_transform') 57 | self.layout.operator('scene.set_strip_proxy_quality') 58 | 59 | 60 | class ResetExposure(Operator): 61 | bl_idname = 'scene.reset_exposure' 62 | bl_label = 'Reset exposure' 63 | bl_description = 'Reset exposure to 0' 64 | 65 | def execute(self, context): 66 | context.scene.view_settings.exposure = 0 67 | return {'FINISHED'} 68 | 69 | 70 | class ResetGamma(Operator): 71 | bl_idname = 'scene.reset_gamma' 72 | bl_label = 'Reset gamma' 73 | bl_description = 'Reset gamma to 1' 74 | 75 | def execute(self, context): 76 | context.scene.view_settings.gamma = 1 77 | return {'FINISHED'} 78 | 79 | 80 | keymaps = list() 81 | classes = ( 82 | ResetExposure, 83 | ResetGamma, 84 | animation.PoseToEmpty, 85 | image.SetImageInputTransform, 86 | movieclip.SetMovieClipInputTransform, 87 | node.SetImageNodeInputTransform, 88 | render.RenderToGif, 89 | sequencer.SequencerCustomPanel, 90 | sequencer.OpenStripAsMovieclip, 91 | sequencer.AddStripAsCompositing, 92 | sequencer.DisableSceneStrips, 93 | sequencer.SetActiveSceneFromStrip, 94 | sequencer.CreateAdjustmentStrip, 95 | sequencer.AddMultipleMovies, 96 | sequencer.SetStripInputTransform, 97 | sequencer.SetStripProxyQuality, 98 | viewport.AimNormal, 99 | viewport.LookThroughSelected) 100 | 101 | 102 | def register(): 103 | for cls in classes: 104 | bpy.utils.register_class(cls) 105 | bpy.types.TOPBAR_HT_upper_bar.prepend(header_color_management) 106 | bpy.types.IMAGE_MT_image.append(image_image_menu_draw) 107 | bpy.types.CLIP_MT_clip.append(clip_clip_menu_draw) 108 | bpy.types.NODE_MT_node.append(node_node_menu_draw) 109 | bpy.types.TOPBAR_MT_render.append(render_menu_draw) 110 | bpy.types.SEQUENCER_MT_add.append(sequencer_add_menu_draw) 111 | bpy.types.SEQUENCER_MT_strip.append(sequencer_strip_menu_draw) 112 | 113 | # Keymaps 114 | #kc = bpy.context.window_manager.keyconfigs.addon 115 | 116 | #km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') 117 | #kmi = km.keymap_items.new('node.double_click', 118 | #'ACTIONMOUSE', 'DOUBLE_CLICK') 119 | #keymaps.append((km, kmi)) 120 | 121 | #km = kc.keymaps.new(name='Window', space_type='EMPTY') 122 | #kmi = km.keymap_items.new('compo_node_transform_grab.call', 'G', 'PRESS') 123 | #keymaps.append((km, kmi)) 124 | 125 | 126 | def unregister(): 127 | for cls in reversed(classes): 128 | bpy.utils.unregister_class(cls) 129 | bpy.types.TOPBAR_HT_upper_bar.remove(header_color_management) 130 | bpy.types.IMAGE_MT_image.remove(image_image_menu_draw) 131 | bpy.types.CLIP_MT_clip.remove(clip_clip_menu_draw) 132 | bpy.types.NODE_MT_node.remove(node_node_menu_draw) 133 | bpy.types.TOPBAR_MT_render.remove(render_menu_draw) 134 | bpy.types.SEQUENCER_MT_add.remove(sequencer_add_menu_draw) 135 | bpy.types.SEQUENCER_MT_strip.remove(sequencer_strip_menu_draw) 136 | 137 | # Keymaps 138 | for km, kmi in keymaps: 139 | km.keymap_items.remove(kmi) 140 | keymaps.clear() 141 | 142 | 143 | if __name__ == '__main__': 144 | register() 145 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/animation.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class PoseToEmpty(bpy.types.Operator): 5 | bl_idname = 'scene.pose_to_empty' 6 | bl_label = 'Pose To Empty' 7 | 8 | @classmethod 9 | def poll(cls, context): 10 | ob = context.object 11 | return ob and ob.mode == 'POSE' 12 | 13 | def execute(self, context): 14 | active_armature = context.active_object 15 | active_bone = context.active_pose_bone 16 | 17 | matrix = active_bone.matrix 18 | bone_rotation = matrix.to_euler() 19 | bone_location = matrix.translation 20 | object_location = active_armature.matrix_world.translation 21 | empty_location = bone_location + object_location 22 | 23 | empty_object = bpy.data.objects.new('Empty', None) 24 | context.scene.collection.objects.link(empty_object) 25 | empty_object.location = empty_location 26 | empty_object.rotation_euler = bone_rotation 27 | 28 | return {'FINISHED'} 29 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/colorspace.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Operator 2 | from bpy.props import StringProperty 3 | from vgblender.colorspace import set_input_transform 4 | 5 | 6 | class SetInputTransform(Operator): 7 | bl_idname = 'scene.set_input_transform' 8 | bl_label = 'Set Input Transform' 9 | 10 | input_transform: StringProperty(name='Input Transform') 11 | 12 | def get_datablocks(self, context): 13 | raise NotImplementedError 14 | 15 | def execute(self, context): 16 | for datablock in self.get_datablocks(context): 17 | set_input_transform(datablock, self.input_transform) 18 | return {'FINISHED'} 19 | 20 | def invoke(self, context, event): 21 | return context.window_manager.invoke_props_dialog(self) 22 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/image.py: -------------------------------------------------------------------------------- 1 | from .colorspace import SetInputTransform 2 | 3 | 4 | class SetImageInputTransform(SetInputTransform): 5 | bl_idname = 'scene.set_image_input_transform' 6 | bl_label = 'Set Image Input Transform' 7 | 8 | @classmethod 9 | def poll(cls, context): 10 | return context.space_data.image 11 | 12 | def get_datablocks(self, context): 13 | return [context.space_data.image] 14 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/movieclip.py: -------------------------------------------------------------------------------- 1 | from .colorspace import SetInputTransform 2 | 3 | 4 | class SetMovieClipInputTransform(SetInputTransform): 5 | bl_idname = 'scene.set_movieclip_input_transform' 6 | bl_label = 'Set Movieclip Input Transform' 7 | 8 | @classmethod 9 | def poll(cls, context): 10 | return context.space_data.clip 11 | 12 | def get_datablocks(self, context): 13 | return [context.space_data.clip] 14 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/node.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bgl 3 | import math 4 | from .colorspace import SetInputTransform 5 | 6 | 7 | class GetMaskFromNode(bpy.types.Operator): 8 | bl_idname = 'scene.get_mask_from_compositor_node' 9 | bl_label = 'Get Mask' 10 | bl_description = 'Get mask from active node' 11 | 12 | @classmethod 13 | def poll(cls, context): 14 | active_node = context.scene.node_tree.nodes.active 15 | return active_node and active_node.type == 'MASK' 16 | 17 | def execute(self, context): 18 | target_mask = context.scene.node_tree.nodes.active.mask 19 | context.space_data.mask = target_mask 20 | context.space_data.mode = 'MASK' 21 | return {'FINISHED'} 22 | 23 | 24 | class GetImageFromCompositorNode(bpy.types.Operator): 25 | bl_idname = 'scene.get_image_from_compositor_node' 26 | bl_label = 'Get Image' 27 | bl_description = 'Get image from active node' 28 | 29 | @classmethod 30 | def poll(cls, context): 31 | active_node = context.scene.node_tree.nodes.active 32 | return active_node and active_node.type == 'IMAGE' 33 | 34 | def execute(self, context): 35 | target_image = context.scene.node_tree.nodes.active.image 36 | context.space_data.image = target_image 37 | return {'FINISHED'} 38 | 39 | 40 | class GetImageFromMaterialNode(bpy.types.Operator): 41 | bl_idname = 'scene.get_image_from_material_node' 42 | bl_label = 'Get Image' 43 | bl_description = 'Get image from active node' 44 | 45 | @classmethod 46 | def poll(cls, context): 47 | if not context.object: 48 | return False 49 | 50 | active_material = context.object.active_material 51 | if context.object and active_material: 52 | if active_material.use_nodes: 53 | active_node = active_material.node_tree.nodes.active 54 | if active_node: 55 | return active_node.type == 'TEX_IMAGE' 56 | 57 | def execute(self, context): 58 | active_material = context.object.active_material 59 | target_image = active_material.node_tree.nodes.active.image 60 | context.space_data.image = target_image 61 | return {'FINISHED'} 62 | 63 | 64 | class CreateMaskNode(bpy.types.Operator): 65 | bl_idname = 'scene.create_mask_node' 66 | bl_label = 'Create Mask Node' 67 | bl_description = 'Create mask mode in the compositor' 68 | 69 | @classmethod 70 | def poll(cls, context): 71 | return (context.space_data.mode == 'MASK') 72 | 73 | def execute(self, context): 74 | mask_from_image_editor = context.area.spaces.active.mask 75 | node_tree = context.scene.node_tree 76 | mask_node = node_tree.nodes.new(type='CompositorNodeMask') 77 | mask_node.mask = mask_from_image_editor 78 | active_node = context.scene.node_tree.nodes.active 79 | if active_node: 80 | mask_node.location.x = active_node.location.x + 200 81 | mask_node.location.y = active_node.location.y - 100 82 | return {'FINISHED'} 83 | 84 | 85 | class DoubleClick(bpy.types.Operator): 86 | bl_idname = 'node.double_click' 87 | bl_label = 'Node Double Click' 88 | bl_options = {'UNDO'} 89 | 90 | def execute(self, context): 91 | active_node = context.active_node 92 | for area in context.screen.areas: 93 | if area.type == 'IMAGE_EDITOR': 94 | for space in area.spaces: 95 | if space.type == 'IMAGE_EDITOR': 96 | # Image 97 | if active_node.type in ['IMAGE', 'TEX_IMAGE']: 98 | space.image = active_node.image 99 | # Mask 100 | elif active_node.type == 'MASK': 101 | space.mask = active_node.mask 102 | space.mode = 'MASK' 103 | # Viewer 104 | elif active_node.type == 'VIEWER': 105 | space.image = bpy.data.images['Viewer Node'] 106 | # Composite 107 | elif active_node.type == 'COMPOSITE': 108 | space.image = bpy.data.images['Render Result'] 109 | return {'FINISHED'} 110 | 111 | 112 | class CompostorNodeColorPicker(bpy.types.Operator): 113 | bl_idname = 'scene.compositor_node_color_picker' 114 | bl_label = 'Color Picker' 115 | bl_description = 'Pick the color and set the value to the selected node' 116 | 117 | input_name: bpy.props.StringProperty() 118 | 119 | @classmethod 120 | def poll(cls, context): 121 | return (context.space_data.image) 122 | 123 | def modal(self, context, event): 124 | scene = context.scene 125 | area = context.area 126 | w = context.window_manager.windows[0] 127 | w.cursor_modal_set('EYEDROPPER') 128 | 129 | context.area.tag_redraw() 130 | 131 | if event.type == 'LEFTMOUSE': 132 | 133 | mouse_x = event.mouse_x - context.region.x 134 | mouse_y = event.mouse_y - context.region.y 135 | 136 | uv = area.regions[-1].view2d.region_to_view(mouse_x, mouse_y) 137 | 138 | if scene.color_picker_image: 139 | img = bpy.data.images[scene.color_picker_image] 140 | else: 141 | img = area.spaces[0].image 142 | 143 | size_x, size_y = img.size[:] 144 | 145 | x = int(size_x * uv[0]) % size_x 146 | y = int(size_y * uv[1]) % size_y 147 | 148 | offset = (y * size_x + x) * 4 149 | pixels = img.pixels[offset:offset + 4] 150 | pixels = [pixels[0], pixels[1], pixels[2]] 151 | 152 | selected_input = scene.node_tree.nodes.active.inputs 153 | 154 | if selected_input[self.input_name].type == 'VECTOR': 155 | selected_input[self.input_name].default_value = pixels 156 | 157 | elif selected_input[self.input_name].type == 'VALUE': 158 | selected_input[self.input_name].default_value = pixels[0] 159 | 160 | elif selected_input[self.input_name].type == 'RGBA': 161 | selected_input[self.input_name].default_value[0] = pixels[0] 162 | selected_input[self.input_name].default_value[1] = pixels[1] 163 | selected_input[self.input_name].default_value[2] = pixels[2] 164 | 165 | w.cursor_modal_restore() 166 | return {'FINISHED'} 167 | 168 | elif event.type in {'RIGHTMOUSE', 'ESC'}: 169 | w.cursor_modal_restore() 170 | return {'CANCELLED'} 171 | 172 | return {'RUNNING_MODAL'} 173 | 174 | def invoke(self, context, event): 175 | if context.area.type == 'IMAGE_EDITOR': 176 | context.window_manager.modal_handler_add(self) 177 | return {'RUNNING_MODAL'} 178 | else: 179 | self.report({'WARNING'}, 180 | 'UV/Image Editor not found, cannot run operator') 181 | return {'CANCELLED'} 182 | 183 | 184 | def _distance2d(A, B): 185 | distance = math.sqrt((B[0] - A[0]) ** 2 + (B[1] - A[1]) ** 2) 186 | return distance 187 | 188 | 189 | def draw_callback_axis(self, context): 190 | 191 | # X AXIS 192 | bgl.glEnable(bgl.GL_BLEND) 193 | if self.snap_axis_x: 194 | bgl.glColor4f(1.0, 0.0, 0.0, 0.7) 195 | bgl.glLineWidth(1) 196 | elif self.middlemouse: 197 | bgl.glColor4f(1.0, 0.0, 0.0, 0.3) 198 | bgl.glLineWidth(1) 199 | else: 200 | bgl.glColor4f(0.0, 0.0, 0.0, 0.0) 201 | bgl.glLineWidth(0) 202 | 203 | bgl.glBegin(bgl.GL_LINE_STRIP) 204 | for x in range(context.area.width): 205 | bgl.glVertex2i(x, self.init_pos_y) 206 | bgl.glEnd() 207 | 208 | # Y AXIS 209 | bgl.glEnable(bgl.GL_BLEND) 210 | if self.snap_axis_y: 211 | bgl.glColor4f(0.0, 1.0, 0.0, 0.7) 212 | bgl.glLineWidth(1) 213 | elif self.middlemouse: 214 | bgl.glColor4f(0.0, 1.0, 0.0, 0.3) 215 | bgl.glLineWidth(1) 216 | else: 217 | bgl.glColor4f(0.0, 0.0, 0.0, 0.0) 218 | bgl.glLineWidth(0) 219 | 220 | bgl.glBegin(bgl.GL_LINE_STRIP) 221 | for y in range(context.area.height): 222 | bgl.glVertex2i(self.init_pos_x, y) 223 | 224 | bgl.glEnd() 225 | 226 | # restore opengl defaults 227 | bgl.glLineWidth(1) 228 | bgl.glDisable(bgl.GL_BLEND) 229 | bgl.glColor4f(0.0, 0.0, 0.0, 1.0) 230 | 231 | 232 | class CompositorNodeTransformGrab(bpy.types.Operator): 233 | bl_idname = 'scene.compositor_node_transform_grab' 234 | bl_label = 'Transform Node Grab' 235 | 236 | region_x_init = None 237 | region_y_init = None 238 | value_x_init = None 239 | value_y_init = None 240 | 241 | @classmethod 242 | def poll(cls, context): 243 | scene = context.scene 244 | active_node = scene.node_tree.nodes.active 245 | return (scene.use_nodes and 246 | active_node.type in ['TRANSFORM', 'TRANSLATE']) 247 | 248 | def modal(self, context, event): 249 | active_node = context.scene.node_tree.nodes.active 250 | 251 | w = context.window_manager.windows[0] 252 | w.cursor_modal_set('SCROLL_XY') 253 | context.area.tag_redraw() 254 | 255 | self.mouse_pos_xy = (event.mouse_region_x, event.mouse_region_y) 256 | self.distance_x_axis = _distance2d( 257 | self.mouse_pos_xy, (self.mouse_pos_xy[0], self.init_pos_y)) 258 | self.distance_y_axis = _distance2d( 259 | self.mouse_pos_xy, (self.init_pos_x, self.mouse_pos_xy[1])) 260 | 261 | # Move X 262 | if self.region_x_init: 263 | if not self.snap_axis_y: 264 | target_value_x = self.value_x_init \ 265 | + (event.mouse_region_x - self.region_x_init) 266 | active_node.inputs['X'].default_value = target_value_x 267 | else: 268 | active_node.inputs['X'].default_value = self.value_x_init 269 | else: 270 | self.region_x_init = event.mouse_region_x 271 | self.value_x_init = active_node.inputs['X'].default_value 272 | 273 | # Move Y 274 | if self.region_y_init: 275 | if not self.snap_axis_x: 276 | target_value_y = self.value_y_init \ 277 | + (event.mouse_region_y - self.region_y_init) 278 | active_node.inputs['Y'].default_value = target_value_y 279 | else: 280 | active_node.inputs['Y'].default_value = self.value_y_init 281 | else: 282 | self.region_y_init = event.mouse_region_y 283 | self.value_y_init = active_node.inputs['Y'].default_value 284 | 285 | # Snap axis 286 | if self.middlemouse: 287 | # X 288 | if self.distance_x_axis < self.distance_y_axis: 289 | self.snap_axis_x = True 290 | self.snap_axis_y = False 291 | # Y 292 | elif self.distance_y_axis < self.distance_x_axis: 293 | self.snap_axis_y = True 294 | self.snap_axis_x = False 295 | 296 | # Event 297 | if event.type in ('RET', 'NUMPAD_ENTER', 'LEFTMOUSE'): 298 | w.cursor_modal_restore() 299 | context.area.header_text_set() 300 | bpy.types.SpaceImageEditor.draw_handler_remove( 301 | self._handle_axes, 'WINDOW') 302 | 303 | return {'FINISHED'} 304 | 305 | elif event.type in ('RIGHTMOUSE', 'ESC'): 306 | active_node.inputs['X'].default_value = self.value_x_init 307 | active_node.inputs['Y'].default_value = self.value_y_init 308 | w.cursor_modal_restore() 309 | context.area.header_text_set() 310 | bpy.types.SpaceImageEditor.draw_handler_remove( 311 | self._handle_axes, 'WINDOW') 312 | 313 | return {'CANCELLED'} 314 | 315 | elif event.type == 'MIDDLEMOUSE': 316 | if self.middlemouse: 317 | self.middlemouse = False 318 | else: 319 | self.middlemouse = True 320 | 321 | elif event.type == 'X': 322 | if self.x_key: 323 | self.x_key = False 324 | else: 325 | self.x_key = True 326 | if self.snap_axis_x: 327 | self.snap_axis_x = False 328 | self.snap_axis_y = False 329 | else: 330 | self.snap_axis_x = True 331 | self.snap_axis_y = False 332 | 333 | elif event.type == 'Y': 334 | if self.y_key: 335 | self.y_key = False 336 | else: 337 | self.y_key = True 338 | if self.snap_axis_y: 339 | self.snap_axis_x = False 340 | self.snap_axis_y = False 341 | else: 342 | self.snap_axis_x = False 343 | self.snap_axis_y = True 344 | 345 | return {'RUNNING_MODAL'} 346 | 347 | def invoke(self, context, event): 348 | active_node = context.scene.node_tree.nodes.active 349 | image_space = context.area.spaces.active 350 | img_size_x, img_size_y = image_space.image.size 351 | 352 | self.middlemouse = False 353 | self.x_key = False 354 | self.y_key = False 355 | 356 | view2d = context.region.view2d 357 | self.init_pos_x, self.init_pos_y = view2d.view_to_region( 358 | active_node.inputs['X'].default_value / img_size_x + 0.5, 359 | active_node.inputs['Y'].default_value / img_size_y + 0.5) 360 | 361 | self.snap_axis_x = False 362 | self.snap_axis_y = False 363 | 364 | args = (self, context) 365 | self._handle_axes = bpy.types.SpaceImageEditor.draw_handler_add( 366 | draw_callback_axis, args, 'WINDOW', 'POST_PIXEL') 367 | 368 | context.window_manager.modal_handler_add(self) 369 | return {'RUNNING_MODAL'} 370 | 371 | 372 | class SetImageNodeInputTransform(SetInputTransform): 373 | bl_idname = 'scene.set_image_node_input_transform' 374 | bl_label = 'Set Image Node Input Transform' 375 | 376 | @classmethod 377 | def poll(cls, context): 378 | return context.active_node 379 | 380 | def get_datablocks(self, context): 381 | return [context.active_node.image] 382 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/render.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | import shutil 4 | import tempfile 5 | from vgenc.convert import convert_to_gif 6 | 7 | 8 | class RenderToGif(bpy.types.Operator): 9 | bl_idname = 'render.render_gif' 10 | bl_label = 'Render to GIF' 11 | 12 | depth: bpy.props.IntProperty( 13 | name='Depth', 14 | default=8) 15 | optimize: bpy.props.BoolProperty( 16 | name='Optimize', 17 | default=True) 18 | bounce: bpy.props.BoolProperty( 19 | name='Bounce', 20 | default=False) 21 | 22 | def execute(self, context): 23 | scene = context.scene 24 | output = bpy.path.abspath(scene.render.filepath) 25 | 26 | # Set image sequence 27 | render_tmp = tempfile.mkdtemp() 28 | scene.render.filepath = os.path.join(render_tmp, 'render.####.png') 29 | scene.render.image_settings.file_format = 'PNG' 30 | 31 | bpy.ops.render.render(animation=True) 32 | convert_to_gif( 33 | render_tmp, output, fps=scene.render.fps, 34 | optimize=self.optimize, depth=self.depth, bounce=self.bounce) 35 | 36 | # Set back settings and clean temporary files 37 | shutil.rmtree(render_tmp) 38 | scene.render.filepath = output 39 | return {'FINISHED'} 40 | 41 | def invoke(self, context, event): 42 | return context.window_manager.invoke_props_dialog(self) 43 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/sequencer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Operator, Panel, OperatorFileListElement 3 | from bpy.props import ( 4 | StringProperty, CollectionProperty, BoolProperty, IntProperty) 5 | from bpy_extras.io_utils import ImportHelper 6 | import os 7 | from vgblender.sequencer import ( 8 | create_adjustment_strip, load_multiple_movie_strips, 9 | set_strip_proxy_quality) 10 | from .colorspace import SetInputTransform 11 | 12 | 13 | class SequencerCustomPanel(Panel): 14 | bl_idname = 'CUSTOMTOOLS_PT_sequencer_custom_panel' 15 | bl_label = 'Custom' 16 | bl_space_type = 'SEQUENCE_EDITOR' 17 | bl_region_type = 'UI' 18 | bl_category = 'Custom' 19 | 20 | def draw(self, context): 21 | layout = self.layout 22 | col = layout.column(align=True) 23 | col.label(text='Scene strips') 24 | row = col.row(align=True) 25 | row.operator('scene.disable_scene_strips', text='Mute').mute = True 26 | row.operator('scene.disable_scene_strips', text='Show').mute = False 27 | col.operator('scene.set_active_scene_from_strip', text='Set active') 28 | 29 | 30 | class OpenStripAsMovieclip(Operator): 31 | bl_idname = 'scene.open_strip_as_movieclip' 32 | bl_label = 'Open Strip As Movieclip' 33 | 34 | @classmethod 35 | def poll(cls, context): 36 | scene = context.scene 37 | if scene.sequence_editor and scene.sequence_editor.active_strip: 38 | return scene.sequence_editor.active_strip.type == 'MOVIE' 39 | 40 | def execute(self, context): 41 | scene = context.scene 42 | data = bpy.data 43 | strip = scene.sequence_editor.active_strip 44 | movie = data.movieclips.load(filepath=strip.filepath) 45 | movie.frame_start = strip.frame_start 46 | return {'FINISHED'} 47 | 48 | 49 | class AddStripAsCompositing(Operator): 50 | bl_idname = 'scene.add_strip_as_compositing' 51 | bl_label = 'Add Strip As Compositing Scene' 52 | 53 | @classmethod 54 | def poll(cls, context): 55 | scene = context.scene 56 | if scene.sequence_editor and scene.sequence_editor.active_strip: 57 | return scene.sequence_editor.active_strip.type == 'MOVIE' 58 | 59 | def execute(self, context): 60 | data = bpy.data 61 | sequence_editor = context.scene.sequence_editor 62 | strip = sequence_editor.active_strip 63 | 64 | movieclip = data.movieclips.load(filepath=strip.filepath) 65 | movieclip.colorspace_settings.name = strip.colorspace_settings.name 66 | x, y = movieclip.size 67 | scene = data.scenes.new(movieclip.name) 68 | scene.render.resolution_x = x 69 | scene.render.resolution_y = y 70 | scene.render.resolution_percentage = 100 71 | scene.frame_start = 1 72 | scene.frame_end = movieclip.frame_duration 73 | 74 | scene.use_nodes = True 75 | node_tree = scene.node_tree 76 | for node in node_tree.nodes: 77 | node_tree.nodes.remove(node) 78 | movieclip_node = node_tree.nodes.new('CompositorNodeMovieClip') 79 | movieclip_node.clip = movieclip 80 | output_node = node_tree.nodes.new('CompositorNodeComposite') 81 | node_tree.links.new(movieclip_node.outputs[0], output_node.inputs[0]) 82 | 83 | # Create scene on sequencer 84 | sequence_editor.sequences.new_scene( 85 | scene.name, scene, strip.channel + 1, strip.frame_start) 86 | 87 | return {'FINISHED'} 88 | 89 | 90 | class DisableSceneStrips(Operator): 91 | bl_idname = 'scene.disable_scene_strips' 92 | bl_label = 'Disable Scene Strips' 93 | 94 | mute: BoolProperty(name='Mute', default=True) 95 | 96 | def execute(self, context): 97 | scene = context.scene 98 | for strip in scene.sequence_editor.sequences_all: 99 | if strip.type == 'SCENE': 100 | strip.mute = self.mute 101 | return {'FINISHED'} 102 | 103 | 104 | class SetActiveSceneFromStrip(Operator): 105 | bl_idname = 'scene.set_active_scene_from_strip' 106 | bl_label = 'Set Active Scene From Selected Strip' 107 | 108 | @classmethod 109 | def poll(cls, context): 110 | scene = context.scene 111 | if scene.sequence_editor and scene.sequence_editor.active_strip: 112 | return scene.sequence_editor.active_strip.type == 'SCENE' 113 | 114 | def execute(self, context): 115 | scene = context.scene 116 | strip = scene.sequence_editor.active_strip 117 | context.window.scene = strip.scene 118 | return {'FINISHED'} 119 | 120 | 121 | class CreateAdjustmentStrip(Operator): 122 | bl_idname = 'sequencer.create_adjustment_strip' 123 | bl_label = 'Create Adjustment Effect With Active Strip Range' 124 | 125 | @classmethod 126 | def poll(cls, context): 127 | if context.scene.sequence_editor: 128 | return context.scene.sequence_editor.active_strip 129 | 130 | def execute(self, context): 131 | create_adjustment_strip(context.scene) 132 | return {'FINISHED'} 133 | 134 | 135 | class AddMultipleMovies(Operator, ImportHelper): 136 | bl_idname = 'scene.add_multiple_movies' 137 | bl_label = 'Add Multiple Movies' 138 | 139 | filter_glob: StringProperty(default='*.mp4;*.mov;*.mkv;*.ogg;*.ogv') 140 | files: CollectionProperty(type=OperatorFileListElement) 141 | directory: StringProperty(subtype='DIR_PATH') 142 | content_file: BoolProperty( 143 | name='Content File', default=False, 144 | description='Using a text file that contains the paths of all videos') 145 | 146 | def execute(self, context): 147 | if self.content_file: 148 | with open(self.filepath, 'r') as f: 149 | filepaths = f.read().split('\n') 150 | else: 151 | filepaths = [ 152 | os.path.join(self.directory, f.name) for f in self.files] 153 | load_multiple_movie_strips(context.scene, filepaths) 154 | return {'FINISHED'} 155 | 156 | 157 | class SetStripInputTransform(SetInputTransform): 158 | bl_idname = 'scene.set_strip_input_transform' 159 | bl_label = 'Set Strip Input Transform' 160 | 161 | @classmethod 162 | def poll(cls, context): 163 | sequences = context.scene.sequence_editor.sequences 164 | return [s for s in sequences if s.select] 165 | 166 | def get_datablocks(self, context): 167 | sequences = context.scene.sequence_editor.sequences 168 | selected_strips = [s for s in sequences if s.select] 169 | for strip in selected_strips: 170 | yield strip 171 | 172 | 173 | class SetStripProxyQuality(Operator): 174 | bl_idname = 'scene.set_strip_proxy_quality' 175 | bl_label = 'Set Strip Proxy Quality' 176 | 177 | quality: IntProperty(name='Quality', min=1, max=100, default=100) 178 | 179 | @classmethod 180 | def poll(cls, context): 181 | sequences = context.scene.sequence_editor.sequences 182 | return [s for s in sequences if s.select] 183 | 184 | def execute(self, context): 185 | sequences = context.scene.sequence_editor.sequences 186 | selected_strips = [s for s in sequences if s.select] 187 | for strip in selected_strips: 188 | set_strip_proxy_quality(strip, self.quality) 189 | return {'FINISHED'} 190 | 191 | def invoke(self, context, event): 192 | return context.window_manager.invoke_props_dialog(self) 193 | -------------------------------------------------------------------------------- /scripts/addons/custom_tools/viewport.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import mathutils 3 | import math 4 | from bpy_extras import view3d_utils 5 | 6 | 7 | def aim_normal(context, event, active_object, ray_max=1000.0, offset=-1): 8 | """Run this function on left mouse, execute the ray cast""" 9 | 10 | # scene = context.scene 11 | region = context.region 12 | rv3d = context.region_data 13 | coord = event.mouse_region_x, event.mouse_region_y 14 | 15 | # get the ray from the viewport and mouse 16 | view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord) 17 | ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord) 18 | 19 | ray_target = ray_origin + (view_vector * ray_max) 20 | 21 | def visible_objects_and_duplis(): 22 | """Loop over (object, matrix) pairs (mesh only)""" 23 | 24 | for obj in context.visible_objects: 25 | if obj.type == 'MESH': 26 | yield (obj, obj.matrix_world.copy()) 27 | 28 | # TODO: update it for recent Blender API 29 | # if obj.dupli_type != 'NONE': 30 | # obj.dupli_list_create(scene) 31 | # for dob in obj.dupli_list: 32 | # obj_dupli = dob.object 33 | # if obj_dupli.type == 'MESH': 34 | # yield (obj_dupli, dob.matrix.copy()) 35 | # obj.dupli_list_clear() 36 | 37 | def obj_ray_cast(obj, matrix): 38 | """Wrapper for ray casting that moves the ray into object space""" 39 | 40 | # get the ray relative to the object 41 | matrix_inv = matrix.inverted() 42 | ray_origin_obj = matrix_inv @ ray_origin 43 | ray_target_obj = matrix_inv @ ray_target 44 | ray_direction_obj = ray_target_obj - ray_origin_obj 45 | '''# cast the ray 46 | hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj) 47 | 48 | if face_index != -1: 49 | return hit, normal, face_index 50 | else: 51 | return None, None, None''' 52 | # cast the ray 53 | success, location, normal, face_index = obj.ray_cast( 54 | ray_origin_obj, ray_direction_obj) 55 | 56 | if success: 57 | return location, normal, face_index 58 | else: 59 | return None, None, None 60 | 61 | # cast rays and find the closest object 62 | # best_length_squared = ray_max * ray_max 63 | best_length_squared = -1.0 64 | best_obj = None 65 | 66 | for obj, matrix in visible_objects_and_duplis(): 67 | 68 | if obj.type != 'MESH': 69 | continue 70 | 71 | hit, normal, face_index = obj_ray_cast(obj, matrix) 72 | if not hit: 73 | continue 74 | 75 | hit_world = matrix @ hit 76 | length_squared = (hit_world - ray_origin).length_squared 77 | if not best_obj or length_squared < best_length_squared: 78 | best_length_squared = length_squared 79 | best_obj = obj 80 | 81 | origin_scale = active_object.scale.copy() 82 | 83 | # apply target local coordinate 84 | normal = best_obj.matrix_world.to_3x3() @ normal 85 | normal = normal * -1 86 | normal = normal.copy() 87 | 88 | # rotation 89 | vect_x = 1 90 | vect_y = 1 91 | vect_z = 1 92 | 93 | y_vect_x = (normal.y * vect_z) - (normal.z * vect_y) 94 | y_vect_y = (normal.z * vect_x) - (normal.x * vect_z) 95 | y_vect_z = (normal.x * vect_y) - (normal.y * vect_x) 96 | 97 | x_vect_x = (normal.y * y_vect_z) - (normal.z * y_vect_y) 98 | x_vect_y = (normal.z * y_vect_x) - (normal.x * y_vect_z) 99 | x_vect_z = (normal.x * y_vect_y) - (normal.y * y_vect_x) 100 | 101 | y_vect_normalize = math.sqrt( 102 | (y_vect_x * y_vect_x) 103 | + (y_vect_y * y_vect_y) 104 | + (y_vect_z * y_vect_z)) 105 | x_vect_normalize = math.sqrt( 106 | (x_vect_x * x_vect_x) 107 | + (x_vect_y * x_vect_y) 108 | + (x_vect_z * x_vect_z)) 109 | 110 | matrix = mathutils.Matrix().to_3x3() 111 | matrix.row[0] = ( 112 | (x_vect_x / x_vect_normalize, 113 | y_vect_x / y_vect_normalize, 114 | normal.x)) 115 | matrix.row[1] = ( 116 | (x_vect_y / x_vect_normalize, 117 | y_vect_y / y_vect_normalize, 118 | normal.y)) 119 | matrix.row[2] = ( 120 | (x_vect_z / x_vect_normalize, 121 | y_vect_z / y_vect_normalize, 122 | normal.z)) 123 | 124 | active_object.matrix_world = matrix.to_4x4() 125 | 126 | # position 127 | active_object.location = hit_world 128 | for i in range(0, 3): 129 | active_object.location[i] = active_object.location[i] \ 130 | + (offset * normal[i]) 131 | 132 | # scale 133 | active_object.scale = origin_scale 134 | 135 | 136 | class AimNormal(bpy.types.Operator): 137 | bl_idname = 'scene.aim_normal' 138 | bl_label = 'Aim Normal' 139 | 140 | @classmethod 141 | def poll(cls, context): 142 | return context.object 143 | 144 | def modal(self, context, event): 145 | 146 | w = context.window_manager.windows[0] 147 | w.cursor_modal_set('CROSSHAIR') 148 | 149 | context.area.tag_redraw() 150 | 151 | if event.type == 'MOUSEMOVE': 152 | aim_normal(context, event, context.object, offset=self.offset) 153 | self.set_header(context, self.offset) 154 | 155 | elif event.type == 'WHEELUPMOUSE': 156 | self.offset += 0.5 157 | aim_normal(context, event, context.object, offset=self.offset) 158 | self.set_header(context, self.offset) 159 | 160 | elif event.type == 'WHEELDOWNMOUSE': 161 | self.offset -= 0.5 162 | aim_normal(context, event, context.object, offset=self.offset) 163 | self.set_header(context, self.offset) 164 | 165 | elif event.type in ('LEFTMOUSE', 'RET', 'NUMPAD_ENTER'): 166 | w.cursor_modal_restore() 167 | return {'FINISHED'} 168 | 169 | elif event.type in ('RIGHTMOUSE', 'ESC'): 170 | context.object.matrix_world = self.matrix_save 171 | w.cursor_modal_restore() 172 | return {'CANCELLED'} 173 | 174 | return {'RUNNING_MODAL'} 175 | 176 | def invoke(self, context, event): 177 | self.matrix_save = context.object.matrix_world.copy() 178 | self.offset = -5 179 | if context.area.type == 'VIEW_3D': 180 | context.window_manager.modal_handler_add(self) 181 | return {'RUNNING_MODAL'} 182 | else: 183 | self.report({'WARNING'}, 184 | 'VIEW_3D Editor not found, cannot run operator') 185 | return {'CANCELLED'} 186 | 187 | def set_header(self, context, offset): 188 | context.area.header_text_set( 189 | text=('Aim tool | ' 190 | 'Move the mouse to align to the normal | ' 191 | 'Offset with Wheel Up-Down : {}').format(offset)) 192 | 193 | 194 | class LookThroughSelected(bpy.types.Operator): 195 | bl_idname = 'scene.look_through_selected' 196 | bl_label = 'Look Through Selected' 197 | bl_description = 'Look Through Selected' 198 | 199 | @classmethod 200 | def poll(cls, context): 201 | return context.object 202 | 203 | def execute(self, context): 204 | context.space_data.use_local_camera = True 205 | context.space_data.camera = context.object 206 | bpy.ops.view3d.view_camera() 207 | return {'FINISHED'} 208 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeTree 3 | from bpy.props import StringProperty, CollectionProperty, FloatVectorProperty 4 | import nodeitems_utils 5 | from nodeitems_utils import NodeCategory, NodeItem 6 | from .nodes import ( 7 | boolean, color, color_combine, color_palette, color_split, condition, 8 | data_input, data_output, debug, distance, expression, float_number, 9 | float_switch, float_to_int, float_to_string, integer, int_to_float, note, 10 | object_properties, render, render_engine, round_float, scene, time, 11 | vector_split, vector, view_layer, NODES_TYPES) 12 | from . import operators 13 | from .utils import ( 14 | frame_change, scene_update, render_pre_update, render_post_update, 15 | AVAILABLE_NTREES) 16 | 17 | 18 | bl_info = { 19 | 'name': 'Data Nodes', 20 | 'author': 'Vincent Girès', 21 | 'description': ( 22 | 'Node utils for Cycles, the compositor' 23 | 'and a custom Data Node Tree'), 24 | 'version': (0, 0, 3), 25 | 'blender': (2, 80, 0), 26 | 'location': 'Node Editor', 27 | 'category': 'Node'} 28 | 29 | 30 | class ColorPaletteColorProperty(bpy.types.PropertyGroup): 31 | color: FloatVectorProperty( 32 | name='Color', 33 | subtype='COLOR', 34 | size=4, # RGBA 35 | soft_min=0.0, soft_max=1.0, 36 | default=(0.5, 0.5, 0.5, 1.0)) 37 | 38 | 39 | class ColorPaletteProperties(bpy.types.PropertyGroup): 40 | name: StringProperty(name='Color Palette name', default='Palette') 41 | colors: CollectionProperty(type=ColorPaletteColorProperty) 42 | 43 | 44 | class NodeEditorDataTree(NodeTree): 45 | bl_idname = 'DataNodeTree' 46 | bl_label = 'Data Node Tree' 47 | bl_icon = 'NODETREE' 48 | 49 | 50 | class NodeEditorDataNodesPanel(bpy.types.Panel): 51 | bl_idname = 'DATANODES_PT_node_editor' 52 | bl_label = 'Tools' 53 | bl_space_type = 'NODE_EDITOR' 54 | bl_region_type = 'UI' 55 | bl_category = 'Data' 56 | 57 | @classmethod 58 | def poll(cls, context): 59 | return context.space_data.tree_type in AVAILABLE_NTREES 60 | 61 | def draw(self, context): 62 | layout = self.layout 63 | layout.operator('scene.update_data_node', icon='FILE_REFRESH') 64 | 65 | 66 | class SocketsPanel(bpy.types.Panel): 67 | bl_idname = 'DATANODES_PT_sockets' 68 | bl_label = 'Sockets' 69 | bl_space_type = 'NODE_EDITOR' 70 | bl_region_type = 'UI' 71 | bl_category = 'Data' 72 | 73 | @classmethod 74 | def poll(cls, context): 75 | return context.space_data.tree_type in AVAILABLE_NTREES 76 | 77 | def draw(self, context): 78 | layout = self.layout 79 | col = layout.column(align=True) 80 | fac = 0.3 81 | sub = col.split(factor=fac) 82 | sub.label(text='Remove') 83 | sub.operator_menu_enum( 84 | 'scene.remove_input_socket', 'socket', text='Input') 85 | sub = col.split(factor=fac) 86 | sub.separator() 87 | sub.operator_menu_enum( 88 | 'scene.remove_output_socket', 'socket', text='Output') 89 | col = layout.column(align=True) 90 | sub = col.split(factor=fac) 91 | sub.label(text='Clear') 92 | input_op = sub.operator('scene.remove_sockets', text='Input') 93 | input_op.socket_type = 'INPUT' 94 | sub = col.split(factor=fac) 95 | sub.separator() 96 | output_op = sub.operator('scene.remove_sockets', text='Output') 97 | output_op.socket_type = 'OUTPUT' 98 | 99 | 100 | class DataNodeCategory(NodeCategory): 101 | @classmethod 102 | def poll(cls, context): 103 | return context.space_data.tree_type in AVAILABLE_NTREES 104 | 105 | 106 | classes = ( 107 | operators.DataNodesUpdate, 108 | operators.DataNodesGetObject, 109 | operators.DataNodesRemoveSockets, 110 | operators.DataNodesRemoveSocket, 111 | operators.DataNodesRemoveInputSocket, 112 | operators.DataNodesRemoveOutputSocket, 113 | operators.DataNodesAddSocket, 114 | boolean.Boolean, 115 | color.Color, 116 | color_combine.ColorCombine, 117 | color_palette.TemplateColorPaletteCollectionUL, 118 | color_palette.ColorPalette, 119 | color_palette.ColorPaletteAdd, 120 | color_palette.ColorPaletteRemove, 121 | color_palette.ColorPaletteAddColor, 122 | color_palette.ColorPaletteRemoveColor, 123 | color_split.ColorSplit, 124 | condition.Condition, 125 | data_input.DataInputNode, 126 | data_output.DataOutputNode, 127 | debug.DebugNode, 128 | distance.DistanceNode, 129 | expression.Expression, 130 | expression.ExpressionNodeAddInputSocket, 131 | float_number.FloatNumber, 132 | float_switch.FloatSwitch, 133 | float_to_int.FloatToInt, 134 | float_to_string.FloatToString, 135 | integer.Integer, 136 | int_to_float.IntToFloat, 137 | note.NoteNode, 138 | object_properties.ObjectPropertiesNode, 139 | render.RenderNode, 140 | render_engine.RenderEngineNode, 141 | view_layer.ViewLayerNode, 142 | round_float.RoundFloat, 143 | scene.SceneNode, 144 | time.Time, 145 | vector.Vector, 146 | vector_split.VectorSplit, 147 | ColorPaletteColorProperty, 148 | ColorPaletteProperties, 149 | NodeEditorDataTree, 150 | NodeEditorDataNodesPanel, 151 | SocketsPanel) 152 | 153 | node_categories = [ 154 | # identifier, label, items list 155 | DataNodeCategory('DATA', 'Data', items=[NodeItem(n) for n in NODES_TYPES])] 156 | 157 | 158 | def register(): 159 | for cls in classes: 160 | bpy.utils.register_class(cls) 161 | nodeitems_utils.register_node_categories('DATA_NODES', node_categories) 162 | bpy.types.Scene.color_palettes = CollectionProperty( 163 | type=ColorPaletteProperties) 164 | 165 | bpy.app.handlers.frame_change_post.append(frame_change) 166 | bpy.app.handlers.depsgraph_update_post.append(scene_update) 167 | bpy.app.handlers.render_pre.append(render_pre_update) 168 | bpy.app.handlers.render_post.append(render_post_update) 169 | 170 | 171 | def unregister(): 172 | for cls in reversed(classes): 173 | bpy.utils.unregister_class(cls) 174 | nodeitems_utils.unregister_node_categories('DATA_NODES') 175 | del bpy.types.Scene.color_palettes 176 | 177 | bpy.app.handlers.frame_change_post.remove(frame_change) 178 | bpy.app.handlers.depsgraph_update_post.remove(scene_update) 179 | bpy.app.handlers.render_pre.remove(render_pre_update) 180 | bpy.app.handlers.render_post.remove(render_post_update) 181 | 182 | 183 | if __name__ == '__main__': 184 | register() 185 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/__init__.py: -------------------------------------------------------------------------------- 1 | NODES_TYPES = [ 2 | 'BooleanNodeType', 3 | 'ColorNodeType', 4 | 'ColorCombineNodeType', 5 | 'ColorPaletteNodeType', 6 | 'ColorSplitNodeType', 7 | 'ConditionNodeType', 8 | 'DataInputNodeType', 9 | 'DataOutputNodeType', 10 | 'DebugNodeType', 11 | 'DistanceNodeType', 12 | 'ExpressionNodeType', 13 | 'FloatNumberNodeType', 14 | 'FloatSwitchNodeType', 15 | 'FloatToIntNodeType', 16 | 'FloatToStringNodeType', 17 | 'IntToFloatNodeType', 18 | 'IntegerNodeType', 19 | 'NoteNodeType', 20 | 'ObjectPropertiesNodeType', 21 | 'RenderNodeType', 22 | 'RenderEngineNodeType', 23 | 'RoundFloatNodeType', 24 | 'SceneNodeType', 25 | 'TimeNodeType', 26 | 'VectorNodeType', 27 | 'VectorSplitNodeType', 28 | 'ViewLayerNodeType'] 29 | 30 | DATA_ITEMS = ( 31 | ('actions', 'actions', ''), 32 | ('armatures', 'armatures', ''), 33 | ('brushes', 'brushes', ''), 34 | ('cameras', 'cameras', ''), 35 | ('curves', 'curves', ''), 36 | ('fonts', 'fonts', ''), 37 | ('groups', 'groups', ''), 38 | ('images', 'images', ''), 39 | ('lamps', 'lamps', ''), 40 | ('lattices', 'lattices', ''), 41 | ('linestyles', 'linestyles', ''), 42 | ('masks', 'masks', ''), 43 | ('materials', 'materials', ''), 44 | ('meshes', 'meshes', ''), 45 | ('metaballs', 'metaballs', ''), 46 | ('movieclips', 'movieclips', ''), 47 | ('node_groups', 'node_groups', ''), 48 | ('objects', 'objects', ''), 49 | ('particles', 'particles', ''), 50 | ('scenes', 'scenes', ''), 51 | ('shape_keys', 'shape_keys', ''), 52 | ('sounds', 'sounds', ''), 53 | ('speakers', 'speakers', ''), 54 | ('texts', 'texts', ''), 55 | ('textures', 'textures', ''), 56 | ('worlds', 'worlds', '')) 57 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/boolean.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class Boolean(Node): 7 | """Boolean node""" 8 | bl_idname = 'BooleanNodeType' 9 | bl_label = 'Boolean' 10 | 11 | def update_props(self, context): 12 | self.update() 13 | 14 | value: bpy.props.BoolProperty( 15 | name='Bool', 16 | default=True, 17 | update=update_props) 18 | 19 | def init(self, context): 20 | self.outputs.new('NodeSocketBool', 'Boolean') 21 | 22 | def update(self): 23 | for output in self.outputs: 24 | set_sockets(output, self.value) 25 | 26 | def draw_buttons(self, context, layout): 27 | layout.prop(self, 'value') 28 | 29 | def draw_label(self): 30 | return 'Boolean' 31 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/color.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class Color(Node): 7 | """Color node""" 8 | bl_idname = 'ColorNodeType' 9 | bl_label = 'Color' 10 | 11 | def update_props(self, context): 12 | self.update() 13 | 14 | value: bpy.props.FloatVectorProperty( 15 | name='Color', 16 | subtype='COLOR', 17 | size=4, # RGBA 18 | soft_min=0.0, soft_max=1.0, 19 | default=(1, 1, 1, 1), 20 | update=update_props) 21 | 22 | def init(self, context): 23 | self.outputs.new('NodeSocketColor', 'Color') 24 | 25 | def update(self): 26 | for output in self.outputs: 27 | set_sockets(output, self.value) 28 | 29 | def draw_buttons(self, context, layout): 30 | layout.prop(self, 'value') 31 | 32 | def draw_label(self): 33 | return 'Color' 34 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/color_combine.py: -------------------------------------------------------------------------------- 1 | import mathutils 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class ColorCombine(Node): 7 | """Color Split node""" 8 | bl_idname = 'ColorCombineNodeType' 9 | bl_label = 'Color Combine' 10 | 11 | def init(self, context): 12 | for socket_name in ('R', 'G', 'B', 'A'): 13 | self.inputs.new('NodeSocketFloat', socket_name) 14 | self.outputs.new('NodeSocketColor', 'Color') 15 | 16 | def update(self): 17 | if len(self.inputs) >= 4: 18 | color = mathutils.Vector((0.0, 0.0, 0.0, 0.0)) 19 | color[0] = self.inputs[0].default_value 20 | color[1] = self.inputs[1].default_value 21 | color[2] = self.inputs[2].default_value 22 | color[3] = self.inputs[3].default_value 23 | for output in self.outputs: 24 | set_sockets(output, color) 25 | 26 | def draw_label(self): 27 | return 'Color Combine' 28 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/color_palette.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets, AVAILABLE_NTREES 4 | 5 | 6 | class TemplateColorPaletteCollectionUL(bpy.types.UIList): 7 | bl_idname = 'DATANODES_UL_template_color_palette_collection' 8 | 9 | def draw_item(self, context, layout, data, item, 10 | icon, active_data, active_propname, index): 11 | layout.prop(item, 'name', text='', emboss=False) 12 | row = layout.row(align=True) 13 | for color_item in item.colors: 14 | row.prop(color_item, 'color', text='') 15 | 16 | 17 | def _find_maximum_length(items): 18 | list_len = [len(i) for i in items] 19 | return max(list_len) 20 | 21 | 22 | class ColorPalette(Node): 23 | """Color Palette node""" 24 | bl_idname = 'ColorPaletteNodeType' 25 | bl_label = 'Color Palette' 26 | 27 | def update_props(self, context): 28 | self.update() 29 | 30 | settings: bpy.props.BoolProperty( 31 | name='Settings', 32 | default=True) 33 | palette_index: bpy.props.IntProperty( 34 | name='Palette Index', 35 | default=0, 36 | update=update_props) 37 | 38 | def add_outputs(self): 39 | items = [p.colors for p in bpy.context.scene.color_palettes] 40 | length = _find_maximum_length(items) 41 | for i in range(length): 42 | if len(self.outputs) >= length: 43 | break 44 | self.outputs.new('NodeSocketColor', 'Color') 45 | 46 | def init(self, context): 47 | self.add_outputs() 48 | 49 | def update(self): 50 | scene = bpy.context.scene 51 | if not scene.color_palettes: 52 | return 53 | palette = scene.color_palettes[self.palette_index] 54 | for index, output in enumerate(self.outputs): 55 | if index >= len(palette.colors): 56 | continue 57 | value = palette.colors[index].color 58 | set_sockets(output, value) 59 | 60 | def draw_buttons(self, context, layout): 61 | if self.settings: 62 | row = layout.row() 63 | row.prop(self, 'settings', text='', icon='TRIA_DOWN', emboss=False) 64 | row = layout.row() 65 | row.template_list( 66 | TemplateColorPaletteCollectionUL.bl_idname, '', 67 | context.scene, 'color_palettes', self, 'palette_index') 68 | col = row.column(align=True) 69 | col.operator('scene.add_color_palette', icon='ADD', text='') 70 | col.operator('scene.remove_color_palette', icon='REMOVE', text='') 71 | else: 72 | row = layout.row() 73 | row.prop( 74 | self, 'settings', text='', icon='TRIA_RIGHT', emboss=False) 75 | 76 | if context.scene.color_palettes: 77 | palette = context.scene.color_palettes[self.palette_index] 78 | row = layout.row(align=True) 79 | row.operator('scene.add_color_palette_color', text='', icon='ADD') 80 | for color_item in palette.colors: 81 | row.prop(color_item, 'color', text='') 82 | row.operator( 83 | 'scene.remove_color_palette_color', text='', icon='REMOVE') 84 | 85 | def draw_label(self): 86 | return 'Color Palette' 87 | 88 | 89 | class ColorPaletteAdd(bpy.types.Operator): 90 | bl_idname = 'scene.add_color_palette' 91 | bl_label = 'Add Color Palette' 92 | 93 | @classmethod 94 | def poll(cls, context): 95 | return context.space_data.tree_type in AVAILABLE_NTREES 96 | 97 | def execute(self, context): 98 | node = context.node 99 | color_palettes = context.scene.color_palettes 100 | p = color_palettes.add() 101 | p.name = str(len(color_palettes)) 102 | for i in range(3): 103 | p.colors.add() 104 | node.palette_index = len(color_palettes) - 1 105 | node.add_outputs() 106 | return {'FINISHED'} 107 | 108 | 109 | class ColorPaletteRemove(bpy.types.Operator): 110 | bl_idname = 'scene.remove_color_palette' 111 | bl_label = 'Remove Color Palette' 112 | 113 | @classmethod 114 | def poll(cls, context): 115 | return context.space_data.tree_type in AVAILABLE_NTREES 116 | 117 | def execute(self, context): 118 | node = context.node 119 | color_palettes = context.scene.color_palettes 120 | color_palettes.remove(node.palette_index) 121 | if node.palette_index > 0: 122 | node.palette_index -= 1 123 | return {'FINISHED'} 124 | 125 | 126 | class ColorPaletteAddColor(bpy.types.Operator): 127 | bl_idname = 'scene.add_color_palette_color' 128 | bl_label = 'Add Color' 129 | 130 | @classmethod 131 | def poll(cls, context): 132 | return context.space_data.tree_type in AVAILABLE_NTREES 133 | 134 | def execute(self, context): 135 | node = context.node 136 | palette = context.scene.color_palettes[node.palette_index] 137 | palette.colors.add() 138 | # Sockets 139 | if (len(node.outputs) < len(palette.colors)): 140 | node.outputs.new('NodeSocketColor', 'Color') 141 | return {'FINISHED'} 142 | 143 | 144 | class ColorPaletteRemoveColor(bpy.types.Operator): 145 | bl_idname = 'scene.remove_color_palette_color' 146 | bl_label = 'Remove Color' 147 | 148 | @classmethod 149 | def poll(cls, context): 150 | return context.space_data.tree_type in AVAILABLE_NTREES 151 | 152 | def execute(self, context): 153 | node = context.node 154 | palette = context.scene.color_palettes[node.palette_index] 155 | palette.colors.remove(len(palette.colors) - 1) 156 | return {'FINISHED'} 157 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/color_split.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Node 2 | from ..utils import set_sockets 3 | 4 | 5 | class ColorSplit(Node): 6 | """Color Split node""" 7 | bl_idname = 'ColorSplitNodeType' 8 | bl_label = 'Color Split' 9 | 10 | def init(self, context): 11 | self.inputs.new('NodeSocketColor', 'Color') 12 | for socket_name in ('R', 'G', 'B', 'A'): 13 | self.outputs.new('NodeSocketFloat', socket_name) 14 | 15 | def update(self): 16 | for output in self.outputs: 17 | if output.name == 'R': 18 | set_sockets(output, self.inputs['Color'].default_value[0]) 19 | elif output.name == 'G': 20 | set_sockets(output, self.inputs['Color'].default_value[1]) 21 | elif output.name == 'B': 22 | set_sockets(output, self.inputs['Color'].default_value[2]) 23 | elif output.name == 'A': 24 | set_sockets(output, self.inputs['Color'].default_value[3]) 25 | 26 | def draw_label(self): 27 | return 'Color Split' 28 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/condition.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | operation_items = ( 6 | ('==', '==', ''), 7 | ('!=', '!=', ''), 8 | ('>', '>', ''), 9 | ('>=', '>=', ''), 10 | ('<', '<', ''), 11 | ('<=', '<=', ''), 12 | ('AND', 'and', ''), 13 | ('OR', 'or', ''), 14 | ('NOT', 'not', '')) 15 | 16 | 17 | class Condition(Node): 18 | """Expression node""" 19 | bl_idname = 'ConditionNodeType' 20 | bl_label = 'Condition' 21 | 22 | def update_props(self, context): 23 | self.update() 24 | 25 | operation: bpy.props.EnumProperty( 26 | name='Operation', 27 | items=operation_items, 28 | update=update_props) 29 | 30 | def init(self, context): 31 | for socket_name in ('A', 'B'): 32 | self.inputs.new('NodeSocketFloat', socket_name) 33 | self.outputs.new('NodeSocketFloat', 'Value') 34 | 35 | def update(self): 36 | if len(self.inputs) >= 2: 37 | a = self.inputs['A'].default_value 38 | b = self.inputs['B'].default_value 39 | for output in self.outputs: 40 | if self.operation == '==': 41 | set_sockets(output, a == b) 42 | elif self.operation == '!=': 43 | set_sockets(output, a != b) 44 | elif self.operation == '>': 45 | set_sockets(output, a > b) 46 | elif self.operation == '>=': 47 | set_sockets(output, a >= b) 48 | elif self.operation == '<': 49 | set_sockets(output, a < b) 50 | elif self.operation == '<=': 51 | set_sockets(output, a <= b) 52 | elif self.operation == 'AND': 53 | set_sockets(output, a and b) 54 | elif self.operation == 'OR': 55 | set_sockets(output, a or b) 56 | elif self.operation == 'NOT': 57 | set_sockets(output, not a) 58 | 59 | def draw_buttons(self, context, layout): 60 | layout.prop(self, 'operation') 61 | 62 | def draw_label(self): 63 | return 'Condition' 64 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/data_input.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from . import DATA_ITEMS 4 | from ..utils import set_sockets 5 | from operator import attrgetter 6 | 7 | 8 | class DataInputNode(Node): 9 | """Data Input""" 10 | bl_idname = 'DataInputNodeType' 11 | bl_label = 'Data Input' 12 | 13 | def update_attribute(self, context): 14 | self.update() 15 | 16 | settings: bpy.props.BoolProperty( 17 | name='Settings', default=True) 18 | data: bpy.props.EnumProperty( 19 | name='Data', items=DATA_ITEMS, default='objects') 20 | item: bpy.props.StringProperty( 21 | name='Item') 22 | attribute: bpy.props.StringProperty( 23 | name='Attribute', update=update_attribute) 24 | 25 | def update(self): 26 | if not self.item: 27 | return 28 | data_collection = getattr(bpy.data, self.data) 29 | item = data_collection.get(self.item) 30 | if item is None: 31 | return 32 | for output in self.outputs: 33 | value = attrgetter(output.name)(item) 34 | set_sockets(output, value) 35 | 36 | def _draw_settings(self, layout, display_settings_prop=False): 37 | col = layout.column(align=True) 38 | row = col.row(align=True) 39 | if display_settings_prop: 40 | row.prop(self, 'settings', text='', icon='TRIA_DOWN', emboss=False) 41 | row.prop(self, 'data') 42 | row = col.row(align=True) 43 | row.prop_search(self, 'item', bpy.data, self.data, text='') 44 | row.operator( 45 | 'scene.get_object_to_data_node', text='', icon='EYEDROPPER') 46 | row = col.row(align=True) 47 | row.prop(self, 'attribute', text='') 48 | add_socket = row.operator( 49 | 'scene.add_socket_to_data_node', text='', icon='ADD') 50 | add_socket.socket_type = 'OUTPUT' 51 | 52 | def draw_buttons(self, context, layout): 53 | if self.settings: 54 | self._draw_settings(layout, display_settings_prop=True) 55 | else: 56 | row = layout.row(align=True) 57 | row.prop( 58 | self, 'settings', text='', icon='TRIA_RIGHT', emboss=False) 59 | row.label(text=self.item) 60 | 61 | def draw_buttons_ext(self, context, layout): 62 | self._draw_settings(layout) 63 | 64 | def draw_label(self): 65 | return 'Data Input' 66 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/data_output.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from . import DATA_ITEMS 4 | from functools import reduce 5 | 6 | 7 | class DataOutputNode(Node): 8 | """Data Output""" 9 | bl_idname = 'DataOutputNodeType' 10 | bl_label = 'Data Output' 11 | 12 | def update_attribute(self, context): 13 | self.update() 14 | 15 | settings: bpy.props.BoolProperty( 16 | name='Settings', default=True) 17 | data: bpy.props.EnumProperty( 18 | name='Data', items=DATA_ITEMS, default='objects') 19 | item: bpy.props.StringProperty( 20 | name='Item') 21 | attribute: bpy.props.StringProperty( 22 | name='Attribute', update=update_attribute) 23 | 24 | def update(self): 25 | if not self.item: 26 | return 27 | data_collection = getattr(bpy.data, self.data) 28 | item = data_collection.get(self.item) 29 | for input in self.inputs: 30 | for link in input.links: 31 | if not link.is_valid: 32 | continue 33 | value = input.default_value 34 | attrs = input.name.split('.') 35 | setattr(reduce(getattr, attrs[:-1], item), attrs[-1], value) 36 | 37 | def _draw_settings(self, layout, display_settings_prop=False): 38 | col = layout.column(align=True) 39 | row = col.row(align=True) 40 | if display_settings_prop: 41 | row.prop(self, 'settings', text='', icon='TRIA_DOWN', emboss=False) 42 | row.prop(self, 'data') 43 | row = col.row(align=True) 44 | row.prop_search(self, 'item', bpy.data, self.data, text='') 45 | row.operator( 46 | 'scene.get_object_to_data_node', text='', icon='EYEDROPPER') 47 | row = col.row(align=True) 48 | row.prop(self, 'attribute', text='') 49 | add_socket = row.operator( 50 | 'scene.add_socket_to_data_node', text='', icon='ADD') 51 | add_socket.socket_type = 'INPUT' 52 | 53 | def draw_buttons(self, context, layout): 54 | if self.settings: 55 | self._draw_settings(layout, display_settings_prop=True) 56 | else: 57 | row = layout.row(align=True) 58 | row.prop( 59 | self, 'settings', text='', icon='TRIA_RIGHT', emboss=False) 60 | row.label(text=self.item) 61 | 62 | def draw_buttons_ext(self, context, layout): 63 | self._draw_settings(layout) 64 | 65 | def draw_label(self): 66 | return 'Data Output' 67 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/debug.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Node 2 | 3 | 4 | class DebugNode(Node): 5 | """Debug node""" 6 | bl_idname = 'DebugNodeType' 7 | bl_label = 'Debug' 8 | 9 | def init(self, context): 10 | self.inputs.new('NodeSocketInt', 'Integer') 11 | self.inputs.new('NodeSocketFloat', 'Float') 12 | self.inputs.new('NodeSocketVector', 'Vector') 13 | self.inputs.new('NodeSocketColor', 'Color') 14 | self.inputs.new('NodeSocketString', 'String') 15 | self.inputs.new('NodeSocketBool', 'Boolean') 16 | 17 | def draw_buttons(self, context, layout): 18 | if self.inputs['Integer'].links: 19 | layout.label( 20 | text=f"Integer : {self.inputs['Integer'].default_value}") 21 | if self.inputs['Float'].links: 22 | layout.label(text=f"Float : {self.inputs['Float'].default_value}") 23 | if self.inputs['Vector'].links: 24 | layout.label( 25 | text=f"Vector X : {self.inputs['Vector'].default_value[0]}") 26 | layout.label( 27 | text=f"Vector Y : {self.inputs['Vector'].default_value[1]}") 28 | layout.label( 29 | text=f"Vector Z : {self.inputs['Vector'].default_value[2]}") 30 | if self.inputs['Color'].links: 31 | layout.label( 32 | text=f"Color R : {self.inputs['Color'].default_value[0]}") 33 | layout.label( 34 | text=f"Color G : {self.inputs['Color'].default_value[1]}") 35 | layout.label( 36 | text=f"Color B : {self.inputs['Color'].default_value[2]}") 37 | layout.label( 38 | text=f"Color A : {self.inputs['Color'].default_value[3]}") 39 | if self.inputs['String'].links: 40 | layout.label( 41 | text=f"String : {self.inputs['String'].default_value}") 42 | if self.inputs['Boolean'].links: 43 | layout.label( 44 | text=f"Boolean : {self.inputs['Boolean'].default_value}") 45 | 46 | def draw_label(self): 47 | return 'Debug' 48 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/distance.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bgl 3 | import math 4 | from bpy.types import Node, SpaceView3D 5 | from ..utils import set_sockets 6 | import gpu 7 | from gpu_extras.batch import batch_for_shader 8 | 9 | shader_3d_uniform = ( 10 | gpu.shader.from_builtin('3D_UNIFORM_COLOR') 11 | if not bpy.app.background else None) 12 | draw_handler = {} 13 | 14 | 15 | def draw_line(v1, v2, color, width=1): 16 | if shader_3d_uniform is None: 17 | return 18 | bgl.glEnable(bgl.GL_BLEND) 19 | bgl.glLineWidth(width) 20 | coords = [(v1[0], v1[1], v1[2]), (v2[0], v2[1], v2[2])] 21 | batch = batch_for_shader(shader_3d_uniform, 'LINES', {'pos': coords}) 22 | shader_3d_uniform.bind() 23 | shader_3d_uniform.uniform_float('color', color) 24 | batch.draw(shader_3d_uniform) 25 | bgl.glLineWidth(1) 26 | bgl.glDisable(bgl.GL_BLEND) 27 | 28 | 29 | def draw_distance_opengl(node, context): 30 | if not node.display: 31 | return 32 | a = tuple(node.inputs['VectorA'].default_value) 33 | b = tuple(node.inputs['VectorB'].default_value) 34 | draw_line(a, b, node.color, node.width) 35 | 36 | 37 | def _force_redraw_view3d(): 38 | for area in bpy.context.screen.areas: 39 | if area.type in 'VIEW_3D': 40 | area.tag_redraw() 41 | 42 | 43 | class DistanceNode(Node): 44 | """Distance node""" 45 | bl_idname = 'DistanceNodeType' 46 | bl_label = 'Distance' 47 | 48 | def update_props(self, context): 49 | self.update() 50 | 51 | display: bpy.props.BoolProperty( 52 | name='Display', default=False) 53 | color: bpy.props.FloatVectorProperty( 54 | name='Color', 55 | subtype='COLOR', 56 | size=4, # RGBA 57 | soft_min=0.0, soft_max=1.0, 58 | default=(1.0, 1.0, 1.0, 1.0), 59 | update=update_props) 60 | width: bpy.props.IntProperty( 61 | name='Width', default=1, update=update_props) 62 | 63 | def _set_draw_handler(self): 64 | # Use memory address to get unique key used in draw handler 65 | if self.as_pointer() in draw_handler: 66 | return 67 | draw_handler[self.as_pointer()] = SpaceView3D.draw_handler_add( 68 | draw_distance_opengl, (self, bpy.context), 'WINDOW', 'POST_VIEW') 69 | 70 | def init(self, context): 71 | for socket_name in ('VectorA', 'VectorB'): 72 | self.inputs.new('NodeSocketVector', socket_name) 73 | self.outputs.new('NodeSocketFloat', 'Distance') 74 | self._set_draw_handler() 75 | 76 | def update(self): 77 | self._set_draw_handler() 78 | if len(self.inputs) >= 2: 79 | a = self.inputs['VectorA'].default_value 80 | b = self.inputs['VectorB'].default_value 81 | distance = math.sqrt( 82 | (b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2 + (b[2] - a[2]) ** 2) 83 | for output in self.outputs: 84 | set_sockets(output, distance) 85 | _force_redraw_view3d() 86 | 87 | def copy(self, node): 88 | self._set_draw_handler() 89 | 90 | def free(self): 91 | SpaceView3D.draw_handler_remove( 92 | draw_handler[self.as_pointer()], 'WINDOW') 93 | _force_redraw_view3d() 94 | 95 | def draw_buttons_ext(self, context, layout): 96 | col = layout.column(align=True) 97 | col.prop(self, 'display') 98 | col.prop(self, 'color') 99 | col.prop(self, 'width') 100 | 101 | def draw_label(self): 102 | return 'Distance' 103 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/expression.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | import string 4 | from ..utils import set_sockets, AVAILABLE_NTREES 5 | import numpy 6 | 7 | operation_items = ( 8 | ('EXPRESSION', 'Expression', ''), 9 | ('ADD', 'Add', ''), 10 | ('SUBTRACT', 'Subtract', ''), 11 | ('MULTIPLY', 'Multiply', ''), 12 | ('DIVIDE', 'Divide', '')) 13 | 14 | 15 | class Expression(Node): 16 | """Expression node""" 17 | bl_idname = 'ExpressionNodeType' 18 | bl_label = 'Expression' 19 | 20 | def update_props(self, context): 21 | self.update() 22 | 23 | operation: bpy.props.EnumProperty( 24 | name='Operation', items=operation_items, update=update_props) 25 | expression: bpy.props.StringProperty( 26 | name='Expression', update=update_props) 27 | 28 | def init(self, context): 29 | for socket_name in ('A', 'B'): 30 | self.inputs.new('NodeSocketFloat', socket_name) 31 | self.outputs.new('NodeSocketFloat', 'Value') 32 | 33 | def update(self): 34 | if not self.inputs: 35 | return 36 | values = {i.name: i.default_value for i in self.inputs} 37 | for output in self.outputs: 38 | if self.operation == 'EXPRESSION': 39 | if not self.expression: 40 | return 41 | expr = list(self.expression) 42 | for index, i in enumerate(expr): 43 | v = values.get(i) 44 | if v is not None: 45 | expr[index] = str(v) 46 | set_sockets(output, eval(''.join(expr))) 47 | elif self.operation == 'ADD': 48 | set_sockets(output, numpy.sum(list(values.values()))) 49 | elif self.operation == 'SUBTRACT': 50 | vv = list(values.values()) 51 | v = [vv[0]] + [v * -1 for v in vv[1:]] 52 | set_sockets(output, numpy.sum(v)) 53 | elif self.operation == 'MULTIPLY': 54 | set_sockets(output, numpy.prod(list(values.values()))) 55 | elif self.operation == 'DIVIDE': 56 | vv = list(values.values()) 57 | v = [vv[0]] + [1 / v for v in vv[1:]] 58 | set_sockets(output, numpy.prod(v)) 59 | 60 | def draw_buttons(self, context, layout): 61 | layout.prop(self, 'operation') 62 | if self.operation == 'EXPRESSION': 63 | layout.prop(self, 'expression') 64 | row = layout.row(align=True) 65 | row.operator('scene.add_input_socket_to_expression_node', icon='ADD') 66 | remove_sockets = row.operator( 67 | 'scene.remove_sockets', text='', icon='X') 68 | remove_sockets.socket_type = 'INPUT' 69 | 70 | def draw_label(self): 71 | return 'Expression' 72 | 73 | 74 | class ExpressionNodeAddInputSocket(bpy.types.Operator): 75 | bl_idname = 'scene.add_input_socket_to_expression_node' 76 | bl_label = 'Add Input' 77 | bl_description = 'Add input socket to the node' 78 | 79 | @classmethod 80 | def poll(cls, context): 81 | return context.space_data.tree_type in AVAILABLE_NTREES 82 | 83 | def execute(self, context): 84 | node = context.node 85 | alphabet = list(string.ascii_uppercase) 86 | node.inputs.new('NodeSocketFloat', alphabet[len(node.inputs)]) 87 | return {'FINISHED'} 88 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/float_number.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class FloatNumber(Node): 7 | """Float number node""" 8 | bl_idname = 'FloatNumberNodeType' 9 | bl_label = 'Float Number' 10 | 11 | def update_props(self, context): 12 | self.update() 13 | 14 | value: bpy.props.FloatProperty( 15 | name='Float', default=1.0, update=update_props) 16 | 17 | def init(self, context): 18 | self.outputs.new('NodeSocketFloat', 'Float') 19 | 20 | def update(self): 21 | for output in self.outputs: 22 | set_sockets(output, self.value) 23 | 24 | def draw_buttons(self, context, layout): 25 | layout.prop(self, 'value') 26 | 27 | def draw_label(self): 28 | return 'Float Number' 29 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/float_switch.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Node 2 | from ..utils import set_sockets 3 | 4 | 5 | class FloatSwitch(Node): 6 | """Float To String node""" 7 | bl_idname = 'FloatSwitchNodeType' 8 | bl_label = 'Float Switch' 9 | 10 | def init(self, context): 11 | for socket_name in ('A', 'B', 'Switch'): 12 | self.inputs.new('NodeSocketFloat', socket_name) 13 | self.outputs.new('NodeSocketFloat', 'Value') 14 | 15 | def update(self): 16 | if len(self.inputs) >= 3: 17 | if self.inputs['Switch'].default_value > 0.0: 18 | value = self.inputs['B'].default_value 19 | else: 20 | value = self.inputs['A'].default_value 21 | for output in self.outputs: 22 | set_sockets(output, value) 23 | 24 | def draw_label(self): 25 | return 'Float Switch' 26 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/float_to_int.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Node 2 | from ..utils import set_sockets 3 | 4 | 5 | class FloatToInt(Node): 6 | """Float To String node""" 7 | bl_idname = 'FloatToIntNodeType' 8 | bl_label = 'Float To Int' 9 | 10 | def init(self, context): 11 | self.inputs.new('NodeSocketFloat', 'Float') 12 | self.outputs.new('NodeSocketInt', 'Int') 13 | 14 | def update(self): 15 | value = int(round(self.inputs['Float'].default_value)) 16 | for output in self.outputs: 17 | set_sockets(output, value) 18 | 19 | def draw_label(self): 20 | return 'Float To Int' 21 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/float_to_string.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class FloatToString(Node): 7 | """Float To String node""" 8 | bl_idname = 'FloatToStringNodeType' 9 | bl_label = 'Float To String' 10 | 11 | def update_props(self, context): 12 | self.update() 13 | 14 | round: bpy.props.BoolProperty( 15 | name='Round', update=update_props) 16 | 17 | def init(self, context): 18 | self.inputs.new('NodeSocketFloat', 'Float') 19 | self.outputs.new('NodeSocketString', 'String') 20 | 21 | def update(self): 22 | value = self.inputs['Float'].default_value 23 | if self.round: 24 | value = round(value) 25 | for output in self.outputs: 26 | set_sockets(output, str(value)) 27 | 28 | def draw_buttons(self, context, layout): 29 | layout.prop(self, 'value') 30 | 31 | def draw_label(self): 32 | return 'Float To String' 33 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/int_to_float.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Node 2 | from ..utils import set_sockets 3 | 4 | 5 | class IntToFloat(Node): 6 | """Int to float""" 7 | bl_idname = 'IntToFloatNodeType' 8 | bl_label = 'Int To Float' 9 | 10 | def init(self, context): 11 | self.inputs.new('NodeSocketInt', 'Int') 12 | self.outputs.new('NodeSocketFloat', 'Float') 13 | 14 | def update(self): 15 | value = self.inputs['Int'].default_value 16 | for output in self.outputs: 17 | set_sockets(output, float(value)) 18 | 19 | def draw_label(self): 20 | return 'Int To Float' 21 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/integer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class Integer(Node): 7 | """Integer node""" 8 | bl_idname = 'IntegerNodeType' 9 | bl_label = 'Integer' 10 | 11 | def update_props(self, context): 12 | self.update() 13 | 14 | value: bpy.props.IntProperty( 15 | name='Int', default=1, update=update_props) 16 | 17 | def init(self, context): 18 | self.outputs.new('NodeSocketInt', 'Int') 19 | 20 | def update(self): 21 | for output in self.outputs: 22 | set_sockets(output, self.value) 23 | 24 | def draw_buttons(self, context, layout): 25 | layout.prop(self, 'value') 26 | 27 | def draw_label(self): 28 | return 'Int' 29 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/note.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class NoteNode(Node): 7 | """Note node""" 8 | bl_idname = 'NoteNodeType' 9 | bl_label = 'Note' 10 | 11 | def update_attribute(self, context): 12 | self.update() 13 | 14 | note: bpy.props.StringProperty( 15 | name='Note', update=update_attribute) 16 | 17 | def init(self, context): 18 | self.outputs.new('NodeSocketString', 'String') 19 | 20 | def update(self): 21 | for output in self.outputs: 22 | set_sockets(output, self.note) 23 | 24 | def draw_buttons(self, context, layout): 25 | layout.prop(self, 'note', text='') 26 | 27 | def draw_buttons_ext(self, context, layout): 28 | layout.prop(self, 'note') 29 | 30 | def draw_label(self): 31 | return 'Note' 32 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/object_properties.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class ObjectPropertiesNode(Node): 7 | """Object Properties node""" 8 | bl_idname = 'ObjectPropertiesNodeType' 9 | bl_label = 'Object Properties' 10 | 11 | item: bpy.props.StringProperty( 12 | name='Object') 13 | invert_matrix: bpy.props.BoolProperty( 14 | name='Invert matrix', 15 | default=False) 16 | 17 | def get_object(self): 18 | if not self.item: 19 | return 20 | ob = bpy.context.scene.objects[self.item] 21 | return ob 22 | 23 | def get_location(self): 24 | ob = self.get_object() 25 | if ob is not None: 26 | return ob.location 27 | 28 | def get_rotation(self): 29 | ob = self.get_object() 30 | if ob is not None: 31 | return ob.rotation_euler 32 | 33 | def get_scale(self): 34 | ob = self.get_object() 35 | if ob is not None: 36 | return ob.scale 37 | 38 | def get_matrix(self, row=None): 39 | ob = self.get_object() 40 | if ob is None: 41 | return 42 | matrix = ob.matrix_world.to_3x3() 43 | if self.invert_matrix: 44 | matrix = matrix.inverted() 45 | if row is None: 46 | return matrix 47 | else: 48 | return matrix[row] 49 | 50 | def init(self, context): 51 | for socket_name in ( 52 | 'Location', 'Rotation', 'Scale', 53 | 'Matrix Row1', 'Matrix Row2', 'Matrix Row3'): 54 | self.outputs.new('NodeSocketVector', socket_name) 55 | 56 | def update(self): 57 | if self.get_object() is None: 58 | return 59 | for output in self.outputs: 60 | if output.name == 'Location': 61 | set_sockets(output, self.get_location()) 62 | elif output.name == 'Rotation': 63 | set_sockets(output, self.get_rotation()) 64 | elif output.name == 'Scale': 65 | set_sockets(output, self.get_scale()) 66 | elif output.name == 'Matrix Row1': 67 | set_sockets(output, self.get_matrix(row=0)) 68 | elif output.name == 'Matrix Row2': 69 | set_sockets(output, self.get_matrix(row=1)) 70 | elif output.name == 'Matrix Row3': 71 | set_sockets(output, self.get_matrix(row=2)) 72 | 73 | def draw_buttons(self, context, layout): 74 | row = layout.row(align=True) 75 | row.prop_search( 76 | self, 'item', bpy.data, 'objects', 77 | icon='OBJECT_DATA', text='') 78 | row.operator( 79 | 'scene.get_object_to_data_node', text='', icon='EYEDROPPER') 80 | layout.prop(self, 'invert_matrix') 81 | 82 | def draw_buttons_ext(self, context, layout): 83 | row = layout.row(align=True) 84 | row.prop_search(self, 'item', bpy.data, 'objects', icon='OBJECT_DATA') 85 | row.operator( 86 | 'scene.get_object_to_data_node', text='', icon='EYEDROPPER') 87 | box = layout.box() 88 | box.prop(self, 'invert_matrix') 89 | if self.get_object() is not None: 90 | row = box.row() 91 | row.label(text=str(self.get_matrix(row=0)[0])) 92 | row.label(text=str(self.get_matrix(row=0)[1])) 93 | row.label(text=str(self.get_matrix(row=0)[2])) 94 | row = box.row() 95 | row.label(text=str(self.get_matrix(row=1)[0])) 96 | row.label(text=str(self.get_matrix(row=1)[1])) 97 | row.label(text=str(self.get_matrix(row=1)[2])) 98 | row = box.row() 99 | row.label(text=str(self.get_matrix(row=2)[0])) 100 | row.label(text=str(self.get_matrix(row=2)[1])) 101 | row.label(text=str(self.get_matrix(row=2)[2])) 102 | 103 | def draw_label(self): 104 | return 'Object Properties' 105 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/render.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class RenderNode(Node): 7 | """Render node""" 8 | bl_idname = 'RenderNodeType' 9 | bl_label = 'Render' 10 | 11 | on_render: bpy.props.FloatProperty( 12 | name='On Render', default=0) 13 | 14 | def init(self, context): 15 | self.outputs.new('NodeSocketFloat', 'On Render') 16 | 17 | def update(self): 18 | for output in self.outputs: 19 | set_sockets(output, self.on_render) 20 | 21 | def draw_label(self): 22 | return 'Render' 23 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/render_engine.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class RenderEngineNode(Node): 7 | """Render Engine""" 8 | bl_idname = 'RenderEngineNodeType' 9 | bl_label = 'Render Engine' 10 | 11 | render_engine: bpy.props.EnumProperty( 12 | items=(('CYCLES', 'Cycles', 'Engine to use: Cycles'), 13 | ('BLENDER_EEVEE', 'Eevee', 'Engine to use: Eevee'), 14 | ('BLENDER_WORKBENCH', 'Workbench', 'Engine to use: Workbench')), 15 | name='Engine') 16 | 17 | def init(self, context): 18 | self.outputs.new('NodeSocketFloat', 'Active') 19 | 20 | def update(self): 21 | scene = bpy.context.scene 22 | for output in self.outputs: 23 | if self.render_engine == scene.render.engine: 24 | set_sockets(output, 1) 25 | else: 26 | set_sockets(output, 0) 27 | 28 | def draw_buttons(self, context, layout): 29 | layout.prop(self, 'render_engine') 30 | 31 | def draw_label(self): 32 | return 'Render Engine' 33 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/round_float.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Node 2 | from ..utils import set_sockets 3 | 4 | 5 | class RoundFloat(Node): 6 | """Round float""" 7 | bl_idname = 'RoundFloatNodeType' 8 | bl_label = 'Round Float' 9 | 10 | def init(self, context): 11 | self.inputs.new('NodeSocketFloat', 'Float') 12 | self.outputs.new('NodeSocketFloat', 'Float') 13 | self.outputs.new('NodeSocketInt', 'Int') 14 | 15 | def update(self): 16 | value = round(self.inputs['Float'].default_value) 17 | for output in self.outputs: 18 | set_sockets(output, value) 19 | 20 | def draw_label(self): 21 | return 'Round' 22 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/scene.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class SceneNode(Node): 7 | """Scene""" 8 | bl_idname = 'SceneNodeType' 9 | bl_label = 'Scene' 10 | 11 | def scene_enum(self, context): 12 | data = bpy.data 13 | items = [(scene.name, scene.name, '') for scene in data.scenes] 14 | return items 15 | 16 | scene: bpy.props.EnumProperty( 17 | items=scene_enum, name='Scene') 18 | 19 | def init(self, context): 20 | self.outputs.new('NodeSocketFloat', 'Active') 21 | 22 | def update(self): 23 | context = bpy.context 24 | for output in self.outputs: 25 | if self.scene == context.scene.name: 26 | set_sockets(output, 1) 27 | else: 28 | set_sockets(output, 0) 29 | 30 | def draw_buttons(self, context, layout): 31 | layout.prop(self, 'scene') 32 | 33 | def draw_label(self): 34 | return 'Scene' 35 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/time.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class Time(Node): 7 | """Time node""" 8 | bl_idname = 'TimeNodeType' 9 | bl_label = 'Time Info' 10 | 11 | def init(self, context): 12 | self.outputs.new('NodeSocketFloat', 'Frame') 13 | 14 | def update(self): 15 | for output in self.outputs: 16 | set_sockets(output, bpy.context.scene.frame_current) 17 | 18 | def draw_label(self): 19 | return 'Time' 20 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/vector.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import mathutils 3 | from bpy.types import Node 4 | from ..utils import set_sockets 5 | 6 | 7 | class Vector(Node): 8 | """Vector node""" 9 | bl_idname = 'VectorNodeType' 10 | bl_label = 'Vector' 11 | 12 | def update_props(self, context): 13 | self.update() 14 | 15 | def init(self, context): 16 | for socket_name in ('X', 'Y', 'Z'): 17 | self.inputs.new('NodeSocketFloat', socket_name) 18 | self.outputs.new('NodeSocketVector', 'Vector') 19 | 20 | def update(self): 21 | if len(self.inputs) >= 3: 22 | x, y, z = (self.inputs[i].default_value for i in ('X', 'Y', 'Z')) 23 | vector = mathutils.Vector((x, y, z)) 24 | for output in self.outputs: 25 | set_sockets(output, vector) 26 | 27 | def draw_label(self): 28 | return 'Vector' 29 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/vector_split.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Node 2 | from ..utils import set_sockets 3 | 4 | 5 | class VectorSplit(Node): 6 | """Vector Split node""" 7 | bl_idname = 'VectorSplitNodeType' 8 | bl_label = 'Vector Split' 9 | 10 | def init(self, context): 11 | self.inputs.new('NodeSocketVector', 'Vector') 12 | for socket_name in ('X', 'Y', 'Z'): 13 | self.outputs.new('NodeSocketFloat', socket_name) 14 | 15 | def update(self): 16 | for output in self.outputs: 17 | if output.name == 'X': 18 | set_sockets(output, self.inputs['Vector'].default_value[0]) 19 | elif output.name == 'Y': 20 | set_sockets(output, self.inputs['Vector'].default_value[1]) 21 | elif output.name == 'Z': 22 | set_sockets(output, self.inputs['Vector'].default_value[2]) 23 | 24 | def draw_label(self): 25 | return 'Vector Split' 26 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/nodes/view_layer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | from ..utils import set_sockets 4 | 5 | 6 | class ViewLayerNode(Node): 7 | """View Layer""" 8 | bl_idname = 'ViewLayerNodeType' 9 | bl_label = 'View Layer' 10 | 11 | def view_layer_enum(self, context): 12 | scene = context.scene 13 | items = [(layer.name, layer.name, '') for layer in scene.view_layers] 14 | return items 15 | 16 | view_layer: bpy.props.EnumProperty( 17 | items=view_layer_enum, name='Layer') 18 | 19 | def init(self, context): 20 | self.outputs.new('NodeSocketFloat', 'Active') 21 | 22 | def update(self): 23 | context = bpy.context 24 | for output in self.outputs: 25 | if self.view_layer == context.view_layer.name: 26 | set_sockets(output, 1) 27 | else: 28 | set_sockets(output, 0) 29 | 30 | def draw_buttons(self, context, layout): 31 | layout.prop(self, 'view_layer') 32 | 33 | def draw_label(self): 34 | return 'View Layer' 35 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/operators.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import mathutils 3 | from .utils import update_nodes, AVAILABLE_NTREES 4 | from operator import attrgetter 5 | 6 | SOCKET_TYPE_ITEMS = ( 7 | ('OUTPUT', 'Output', ''), 8 | ('INPUT', 'Input', '')) 9 | 10 | 11 | class DataNodesUpdate(bpy.types.Operator): 12 | bl_idname = 'scene.update_data_node' 13 | bl_label = 'Update Nodes' 14 | bl_description = 'Force update data nodes' 15 | 16 | @classmethod 17 | def poll(cls, context): 18 | return context.space_data.tree_type in AVAILABLE_NTREES 19 | 20 | def execute(self, context): 21 | update_nodes() 22 | return {'FINISHED'} 23 | 24 | 25 | class DataNodesGetObject(bpy.types.Operator): 26 | bl_idname = 'scene.get_object_to_data_node' 27 | bl_label = 'Get Object' 28 | bl_description = 'Get selected object from scene' 29 | 30 | @classmethod 31 | def poll(cls, context): 32 | return context.space_data.tree_type in AVAILABLE_NTREES 33 | 34 | def execute(self, context): 35 | node = context.node 36 | selected_object = context.object 37 | node.item = selected_object.name 38 | node.update() 39 | return {'FINISHED'} 40 | 41 | 42 | class DataNodesRemoveSockets(bpy.types.Operator): 43 | bl_idname = 'scene.remove_sockets' 44 | bl_label = 'Remove Sockets' 45 | 46 | socket_type: bpy.props.EnumProperty( 47 | name='Socket Type', 48 | items=SOCKET_TYPE_ITEMS, 49 | default='OUTPUT') 50 | 51 | @classmethod 52 | def poll(cls, context): 53 | return context.space_data.tree_type in AVAILABLE_NTREES 54 | 55 | def execute(self, context): 56 | if hasattr(context, 'node'): 57 | node = context.node 58 | else: 59 | node = context.active_node 60 | if self.socket_type == 'OUTPUT': 61 | node.outputs.clear() 62 | elif self.socket_type == 'INPUT': 63 | node.inputs.clear() 64 | return {'FINISHED'} 65 | 66 | 67 | class DataNodesRemoveSocket(bpy.types.Operator): 68 | bl_idname = 'scene.remove_socket' 69 | bl_label = 'Remove Socket' 70 | 71 | def _get_socket_items(self, context): 72 | node = context.active_node 73 | if self.socket_type == 'OUTPUT': 74 | sockets = node.outputs 75 | elif self.socket_type == 'INPUT': 76 | sockets = node.inputs 77 | return ((s.name, s.name, '') for s in sockets) 78 | 79 | socket_type: bpy.props.EnumProperty( 80 | name='Socket Type', 81 | items=SOCKET_TYPE_ITEMS, 82 | default='OUTPUT') 83 | socket: bpy.props.EnumProperty( 84 | items=_get_socket_items, 85 | name='Node Sockets') 86 | 87 | @classmethod 88 | def poll(cls, context): 89 | return context.space_data.tree_type in AVAILABLE_NTREES 90 | 91 | def execute(self, context): 92 | node = context.active_node 93 | if self.socket_type == 'OUTPUT': 94 | sockets = node.outputs 95 | elif self.socket_type == 'INPUT': 96 | sockets = node.inputs 97 | sockets.remove(sockets[self.socket]) 98 | return {'FINISHED'} 99 | 100 | 101 | class DataNodesRemoveInputSocket(DataNodesRemoveSocket): 102 | bl_idname = 'scene.remove_input_socket' 103 | bl_label = 'Remove Input Socket' 104 | socket_type: bpy.props.StringProperty(name='Socket Type', default='INPUT') 105 | 106 | 107 | class DataNodesRemoveOutputSocket(DataNodesRemoveSocket): 108 | bl_idname = 'scene.remove_output_socket' 109 | bl_label = 'Remove Output Socket' 110 | socket_type: bpy.props.StringProperty(name='Socket Type', default='OUTPUT') 111 | 112 | 113 | class DataNodesAddSocket(bpy.types.Operator): 114 | bl_idname = 'scene.add_socket_to_data_node' 115 | bl_label = 'Add Socket' 116 | bl_description = 'Add socket to the node' 117 | 118 | socket_type: bpy.props.EnumProperty( 119 | name='Socket Type', 120 | items=SOCKET_TYPE_ITEMS, 121 | default='OUTPUT') 122 | 123 | @classmethod 124 | def poll(cls, context): 125 | return context.space_data.tree_type in AVAILABLE_NTREES 126 | 127 | def execute(self, context): 128 | node = context.node 129 | if not node.item: 130 | return 131 | 132 | data_collection = getattr(bpy.data, node.data) 133 | item = data_collection.get(node.item) 134 | 135 | if item is None: 136 | return {'CANCELLED'} 137 | 138 | attribute = attrgetter(node.attribute)(item) 139 | vector_sockets = (mathutils.Vector, mathutils.Euler, mathutils.Euler) 140 | if self.socket_type == 'OUTPUT': 141 | sockets = node.outputs 142 | elif self.socket_type == 'INPUT': 143 | sockets = node.inputs 144 | 145 | if node.attribute in sockets: 146 | return {'CANCELLED'} 147 | 148 | if isinstance(attribute, str): 149 | sockets.new('NodeSocketString', node.attribute) 150 | elif isinstance(attribute, bool): 151 | sockets.new('NodeSocketBool', node.attribute) 152 | elif isinstance(attribute, int): 153 | sockets.new('NodeSocketInt', node.attribute) 154 | elif isinstance(attribute, float): 155 | sockets.new('NodeSocketFloat', node.attribute) 156 | elif isinstance(attribute, mathutils.Color): 157 | sockets.new('NodeSocketColor', node.attribute) 158 | elif isinstance(attribute, vector_sockets): 159 | sockets.new('NodeSocketVector', node.attribute) 160 | elif len(attribute) == 4: # RGBA 161 | sockets.new('NodeSocketColor', node.attribute) 162 | return {'FINISHED'} 163 | -------------------------------------------------------------------------------- /scripts/addons/data_nodes/utils.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.app.handlers import persistent 3 | from .nodes import NODES_TYPES 4 | 5 | AVAILABLE_NTREES = [ 6 | 'DataNodeTree', 'ShaderNodeTree', 'CompositorNodeTree', 'GeometryNodeTree'] 7 | 8 | 9 | def get_all_data_nodes(): 10 | nodes = [] 11 | data = bpy.data 12 | # Compositing tree 13 | for scene in data.scenes: 14 | if scene.node_tree is None: 15 | continue 16 | for node in scene.node_tree.nodes: 17 | nodes.append(node) 18 | # Material trees 19 | for material in data.materials: 20 | if material.node_tree is None: 21 | continue 22 | for node in material.node_tree.nodes: 23 | nodes.append(node) 24 | # Node groups trees 25 | for tree in data.node_groups: 26 | for node in tree.nodes: 27 | nodes.append(node) 28 | nodes = [n for n in nodes if n.bl_idname in NODES_TYPES] 29 | return nodes 30 | 31 | 32 | def is_updatable(node): 33 | if hasattr(node, 'update') and callable(getattr(node, 'update')): 34 | return True 35 | return False 36 | 37 | 38 | def update_nodes(): 39 | nodes = get_all_data_nodes() 40 | for node in nodes: 41 | if is_updatable(node): 42 | node.update() 43 | 44 | 45 | @persistent 46 | def frame_change(scene): 47 | update_nodes() 48 | 49 | 50 | @persistent 51 | def scene_update(scene): 52 | update_nodes() 53 | 54 | 55 | @persistent 56 | def render_pre_update(scene): 57 | node_types = ['RenderNodeType'] 58 | nodes = get_all_data_nodes() 59 | for node in nodes: 60 | if node.bl_idname in node_types: 61 | node.on_render = 1 62 | node.update() 63 | 64 | 65 | @persistent 66 | def render_post_update(scene): 67 | node_types = ['RenderNodeType'] 68 | nodes = get_all_data_nodes() 69 | for node in nodes: 70 | if node.bl_idname in node_types: 71 | node.on_render = 0 72 | node.update() 73 | 74 | 75 | def set_sockets(output, value): 76 | for link in output.links: 77 | if not link.is_valid: 78 | continue 79 | if link.to_node.type == 'REROUTE': 80 | reroute = link.to_node 81 | set_sockets(reroute.outputs[0], value) 82 | elif output.type == link.to_socket.type: 83 | # Assign value to connected socket 84 | link.to_socket.default_value = value 85 | # Update connected target nodes 86 | if is_updatable(link.to_node): 87 | link.to_node.update() 88 | else: 89 | value_types = ('VALUE', 'BOOLEAN', 'INT') 90 | if (output.type in value_types 91 | and link.to_socket.type in value_types): 92 | link.to_socket.default_value = value 93 | if is_updatable(link.to_node): 94 | link.to_node.update() 95 | -------------------------------------------------------------------------------- /scripts/addons/image_preview.py: -------------------------------------------------------------------------------- 1 | import os 2 | import bpy 3 | from bpy.utils import previews 4 | 5 | bl_info = { 6 | 'name': 'Image Preview', 7 | 'author': 'Vincent Girès', 8 | 'description': 'Data image preview', 9 | 'version': (0, 0, 1), 10 | 'blender': (3, 4, 0), 11 | 'location': 'Property panel (Image Editor)', 12 | 'category': 'Images'} 13 | 14 | preview_images = {} 15 | 16 | 17 | def update_image_preview(self, context): 18 | data = bpy.data 19 | space = context.space_data 20 | wm = context.window_manager 21 | image_path = wm.image_preview 22 | image_path = os.path.normpath(image_path) 23 | 24 | if space.type == 'IMAGE_EDITOR': 25 | image = data.images.load(filepath=image_path, check_existing=True) 26 | space.image = image 27 | else: 28 | bpy.ops.image_viewer.qt_event_loop( 29 | 'INVOKE_DEFAULT', 30 | filepath=image_path) 31 | 32 | 33 | def get_enum_previews_from_file(self, context): 34 | """EnumProperty callback""" 35 | 36 | enum_items = [] 37 | if not context: 38 | return enum_items 39 | pcoll = preview_images['images'] 40 | images = [ 41 | i for i in bpy.data.images 42 | if i.type == 'IMAGE' and i.preview is not None] 43 | for index, image in enumerate(images): 44 | enum_items.append(( 45 | image.filepath, 46 | image.filepath, 47 | '', 48 | image.preview.icon_id, 49 | index)) 50 | pcoll.my_previews = enum_items 51 | return pcoll.my_previews 52 | 53 | 54 | class ImageEditorPanel(bpy.types.Panel): 55 | bl_idname = 'IMAGEPREVIEW_PT_ImageEditorPanel' 56 | bl_label = 'Images' 57 | bl_space_type = 'IMAGE_EDITOR' 58 | bl_region_type = 'UI' 59 | bl_category = 'Images' 60 | 61 | def draw(self, context): 62 | wm = context.window_manager 63 | layout = self.layout 64 | layout.template_icon_view(wm, 'image_preview', show_labels=False) 65 | 66 | 67 | class SequencerPanel(bpy.types.Panel): 68 | bl_idname = 'IMAGEPREVIEW_PT_SequencerPanel' 69 | bl_label = 'Images' 70 | bl_space_type = 'SEQUENCE_EDITOR' 71 | bl_region_type = 'UI' 72 | bl_category = 'Images' 73 | 74 | def draw(self, context): 75 | wm = context.window_manager 76 | layout = self.layout 77 | layout.template_icon_view(wm, 'image_preview', show_labels=False) 78 | 79 | 80 | class View3dPanel(bpy.types.Panel): 81 | bl_idname = 'IMAGEPREVIEW_PT_View3dPanel' 82 | bl_label = 'Images' 83 | bl_space_type = 'VIEW_3D' 84 | bl_region_type = 'UI' 85 | bl_category = 'Images' 86 | 87 | def draw(self, context): 88 | wm = context.window_manager 89 | layout = self.layout 90 | layout.template_icon_view(wm, 'image_preview', show_labels=False) 91 | 92 | 93 | classes = (ImageEditorPanel, SequencerPanel, View3dPanel) 94 | 95 | 96 | def register(): 97 | for cls in classes: 98 | bpy.utils.register_class(cls) 99 | preview_images['images'] = previews.new() 100 | bpy.types.WindowManager.image_preview = bpy.props.EnumProperty( 101 | items=get_enum_previews_from_file, 102 | name='Image preview', 103 | update=update_image_preview) 104 | 105 | 106 | def unregister(): 107 | for cls in reversed(classes): 108 | bpy.utils.unregister_class(cls) 109 | for pcoll in preview_images.values(): 110 | previews.remove(pcoll) 111 | preview_images.clear() 112 | del bpy.types.WindowManager.image_preview 113 | -------------------------------------------------------------------------------- /scripts/addons/pointcloud.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bgl 3 | import gpu 4 | from gpu_extras.batch import batch_for_shader 5 | 6 | bl_info = { 7 | 'name': 'Point Cloud', 8 | 'author': 'Vincent Girès', 9 | 'description': 'Generate cloud of vertices based on the position pass', 10 | 'version': (0, 0, 1), 11 | 'blender': (2, 80, 0), 12 | 'location': 'Tool shelves (3D View, Image Editor)', 13 | 'category': '3D View'} 14 | 15 | pointcloud = { 16 | 'coords': [], # reduced pixels for faster display 17 | 'colors': [], 18 | 'coords_full': [], # full pixels from the image 19 | 'colors_full': [], 20 | 'shader': gpu.shader.from_builtin('3D_FLAT_COLOR'), 21 | 'batch': None} 22 | 23 | RGBA_STEP = 4 # image pixels gives 4 values for each pixel (r, g, b, a) 24 | XYZ_STEP = 3 # mesh creation needs only position values (x, y, z) 25 | 26 | 27 | def redraw_view_3d(): 28 | for screen in bpy.data.screens: 29 | for area in screen.areas: 30 | if area.type == 'VIEW_3D': 31 | area.tag_redraw() 32 | 33 | 34 | def batch_pointcloud(): 35 | pointcloud['batch'] = batch_for_shader( 36 | pointcloud['shader'], 'POINTS', 37 | {'pos': pointcloud['coords'], 'color': pointcloud['colors']}) 38 | 39 | 40 | def callback_point_detail(self, context): 41 | detail = self.point_detail 42 | length_full = len(pointcloud['coords_full']) 43 | length_detail = length_full * detail 44 | step = length_full / length_detail 45 | step = int(step) 46 | pointcloud['coords'] = pointcloud['coords_full'][::step] 47 | pointcloud['colors'] = pointcloud['colors_full'][::step] 48 | batch_pointcloud() 49 | 50 | 51 | class PointCloudProperties(bpy.types.PropertyGroup): 52 | color_pass: bpy.props.StringProperty( 53 | name='Color pass') 54 | position_pass: bpy.props.StringProperty( 55 | name='Position pass') 56 | point_detail: bpy.props.FloatProperty( 57 | name='Detail', 58 | default=0.25, 59 | min=0.001, 60 | max=1.0, 61 | update=callback_point_detail) 62 | point_size: bpy.props.IntProperty( 63 | name='Size', 64 | default=1, 65 | min=1) 66 | 67 | 68 | def draw_cloud(): 69 | context = bpy.context 70 | scene = context.scene 71 | if pointcloud['batch']: 72 | pointcloud['shader'].bind() 73 | size = scene.point_cloud.point_size 74 | bgl.glPointSize(size) 75 | pointcloud['batch'].draw(pointcloud['shader']) 76 | 77 | 78 | def get_positions(context): 79 | scene = context.scene 80 | data = bpy.data 81 | position_src = scene.point_cloud.position_pass 82 | position_data = data.images[position_src] 83 | position_pixels = list(position_data.pixels) 84 | 85 | context.window.cursor_set('WAIT') 86 | coordinates = [] 87 | for i in range(0, len(position_pixels), RGBA_STEP): 88 | pixel_rgb = [] 89 | for j in range(XYZ_STEP): 90 | pixel_rgb.append(position_pixels[i + j]) 91 | coordinates.append(pixel_rgb) 92 | context.window.cursor_set('DEFAULT') 93 | 94 | return coordinates 95 | 96 | 97 | def set_coordinates_and_colors(context): 98 | scene = context.scene 99 | data = bpy.data 100 | position_src = scene.point_cloud.position_pass 101 | color_src = scene.point_cloud.color_pass 102 | position_data = data.images[position_src] 103 | color_data = data.images[color_src] 104 | position_pixels = list(position_data.pixels) 105 | color_pixels = list(color_data.pixels) 106 | 107 | context.window.cursor_set('WAIT') 108 | pixels = list(zip(position_pixels, color_pixels)) 109 | for i in range(0, len(pixels), RGBA_STEP): 110 | pixel_position = [] 111 | pixel_color = [] 112 | for j in range(RGBA_STEP): 113 | position, color = pixels[i + j] 114 | if j < XYZ_STEP: 115 | pixel_position.append(position) 116 | pixel_color.append(color) 117 | pointcloud['coords_full'].append(pixel_position) 118 | pointcloud['colors_full'].append(pixel_color) 119 | 120 | pointcloud['coords'] = pointcloud['coords_full'] 121 | pointcloud['colors'] = pointcloud['colors_full'] 122 | context.window.cursor_set('DEFAULT') 123 | 124 | redraw_view_3d() 125 | 126 | 127 | def create_mesh(name, origin, verts, edges, faces): 128 | me = bpy.data.meshes.new(name + 'Mesh') 129 | ob = bpy.data.objects.new(name, me) 130 | ob.location = origin 131 | ob.show_name = True 132 | bpy.context.scene.collection.objects.link(ob) 133 | me.from_pydata(verts, edges, faces) 134 | me.update(calc_edges=True) 135 | return ob 136 | 137 | 138 | class ScenePropertiesPanel(bpy.types.Panel): 139 | bl_idname = 'POINTCLOUD_PT_ScenePropertiesPanel' 140 | bl_label = 'Point Cloud' 141 | bl_space_type = 'PROPERTIES' 142 | bl_region_type = 'WINDOW' 143 | bl_context = 'scene' 144 | bl_options = {'DEFAULT_CLOSED'} 145 | 146 | def draw(self, context): 147 | scene = context.scene 148 | data = bpy.data 149 | layout = self.layout 150 | 151 | col = layout.column(align=True) 152 | col.prop_search( 153 | scene.point_cloud, 'position_pass', 154 | data, 'images', icon='FILE_IMAGE') 155 | col.prop_search( 156 | scene.point_cloud, 'color_pass', 157 | data, 'images', icon='FILE_IMAGE') 158 | 159 | col = layout.column(align=True) 160 | row = col.row(align=True) 161 | text = 'Update cloud' if pointcloud['coords'] else 'Generate cloud' 162 | row.operator('scene.pointcloud_generate', text=text) 163 | row.operator('scene.pointcloud_clear', text='', icon='PANEL_CLOSE') 164 | col.prop(scene.point_cloud, 'point_detail', slider=True) 165 | col.prop(scene.point_cloud, 'point_size') 166 | col.operator('scene.pointcloud_generate_mesh') 167 | 168 | 169 | class PointCloudGenerateOpenGl(bpy.types.Operator): 170 | bl_idname = 'scene.pointcloud_generate' 171 | bl_label = 'Generate cloud' 172 | bl_description = 'Generate cloud of point based on the position pass' 173 | 174 | @classmethod 175 | def poll(cls, context): 176 | position = context.scene.point_cloud.position_pass 177 | color = context.scene.point_cloud.color_pass 178 | return position and color 179 | 180 | def execute(self, context): 181 | set_coordinates_and_colors(context) 182 | batch_pointcloud() 183 | return {'FINISHED'} 184 | 185 | 186 | class PointCloudClearOpenGl(bpy.types.Operator): 187 | bl_idname = 'scene.pointcloud_clear' 188 | bl_label = 'Clear' 189 | bl_description = 'Remove cloud from the viewport' 190 | 191 | @classmethod 192 | def poll(cls, context): 193 | return pointcloud['coords'] 194 | 195 | def execute(self, context): 196 | pointcloud['coords'].clear() 197 | pointcloud['colors'].clear() 198 | pointcloud['batch'] = None 199 | redraw_view_3d() 200 | return {'FINISHED'} 201 | 202 | 203 | class PointCloudGenerateMesh(bpy.types.Operator): 204 | bl_idname = 'scene.pointcloud_generate_mesh' 205 | bl_label = 'Create mesh' 206 | bl_description = 'Generate cloud of vertices based on the position pass' 207 | 208 | @classmethod 209 | def poll(cls, context): 210 | position = context.scene.point_cloud.position_pass 211 | return position 212 | 213 | def execute(self, context): 214 | coordinates = get_positions(context) 215 | origin = (0, 0, 0) 216 | create_mesh('Position_Cloud', origin, coordinates, [], []) 217 | return {'FINISHED'} 218 | 219 | 220 | draw_handler = {} 221 | classes = ( 222 | PointCloudProperties, 223 | ScenePropertiesPanel, 224 | PointCloudGenerateOpenGl, 225 | PointCloudClearOpenGl, 226 | PointCloudGenerateMesh) 227 | 228 | 229 | def register(): 230 | for cls in classes: 231 | bpy.utils.register_class(cls) 232 | bpy.types.Scene.point_cloud = \ 233 | bpy.props.PointerProperty(type=PointCloudProperties) 234 | draw_handler['cloud'] = bpy.types.SpaceView3D.draw_handler_add( 235 | draw_cloud, (), 'WINDOW', 'POST_VIEW') 236 | 237 | 238 | def unregister(): 239 | for cls in reversed(classes): 240 | bpy.utils.unregister_class(cls) 241 | del bpy.types.Scene.point_cloud 242 | bpy.types.SpaceView3D.draw_handler_remove( 243 | draw_handler['cloud'], 'WINDOW') 244 | 245 | 246 | if __name__ == '__main__': 247 | register() 248 | -------------------------------------------------------------------------------- /scripts/addons/sonycamera.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Panel, Operator 3 | import os 4 | import xml.etree.ElementTree as ET 5 | 6 | bl_info = { 7 | 'name': 'Sony Camera', 8 | 'author': 'Vincent Girès', 9 | 'description': '', 10 | 'version': (0, 0, 1), 11 | 'blender': (2, 80, 0), 12 | 'location': 'Property panel', 13 | 'category': 'Sequencer'} 14 | 15 | 16 | def find_xml(filepath): 17 | SUFFIX = 'M01' 18 | XML_EXTS = ['.xml', '.XML'] 19 | filepath_base = bpy.path.abspath(filepath).rsplit('.', 1)[0] 20 | for suffix in ['', SUFFIX]: # first look for filename without suffix 21 | for ext in XML_EXTS: 22 | xml_path = filepath_base + suffix + ext 23 | if os.path.exists(xml_path): 24 | return xml_path 25 | 26 | 27 | def get_info_from_xml(xml_path): 28 | tree = ET.parse(xml_path) 29 | root = tree.getroot() 30 | info = {} 31 | model_attrib = root[6].attrib 32 | info[model_attrib['manufacturer']] = model_attrib['modelName'] 33 | videoformat = root[4] 34 | info['fps'] = videoformat[1].attrib['captureFps'] 35 | info['resolution'] = '{}x{}'.format( 36 | videoformat[2].attrib['pixel'], 37 | videoformat[2].attrib['numOfVerticalLine']) 38 | colorspace = root[8][0] 39 | for c in colorspace: 40 | info[c.attrib['name']] = c.attrib['value'] 41 | return info 42 | 43 | 44 | def draw_info(layout, xml_info): 45 | for k, v in xml_info.items(): 46 | col = layout.column(align=True) 47 | row = col.row(align=True) 48 | row.label(text=k) 49 | row.label(text=v) 50 | 51 | 52 | def set_colorspace(strip, xml_path): 53 | """Match camera colorspace to ACES OCIO config""" 54 | gamma = { 55 | 'rec2100-hlg': 'HLG', 56 | 's-log2': 'S-Log2', 57 | 's-log3': 'S-Log3', 58 | 's-log3-cine': 'S-Log3'} 59 | primaries = { 60 | 'rec709': 'Rec.709', 61 | 'rec2020': 'Rec.2020', 62 | 's-gamut': 'S-Gamut', 63 | 's-gamut3': 'S-Gamut3', 64 | 's-gamut3-cine': 'S-Gamut.Cine'} 65 | tree = ET.parse(xml_path) 66 | root = tree.getroot() 67 | colorspace = {} 68 | for c in root[8][0]: 69 | if c.attrib['name'] == 'CaptureGammaEquation': 70 | colorspace['gamma'] = c.attrib['value'] 71 | if c.attrib['name'] == 'CaptureColorPrimaries': 72 | colorspace['primaries'] = c.attrib['value'] 73 | ocio_colorspace = '{} {}'.format( 74 | gamma[colorspace['gamma']], primaries[colorspace['primaries']]) 75 | strip.colorspace_settings.name = ocio_colorspace 76 | 77 | 78 | class SetColorspace(Operator): 79 | bl_idname = 'scene.sonycamera_set_colorspace' 80 | bl_label = 'Set colorspace' 81 | bl_options = {'REGISTER', 'UNDO'} 82 | 83 | @classmethod 84 | def poll(cls, context): 85 | return context.scene.sequence_editor 86 | 87 | def execute(self, context): 88 | scene = context.scene 89 | sequences = scene.sequence_editor.sequences_all 90 | for strip in sequences: 91 | if strip.select: 92 | xml_path = find_xml(strip.filepath) 93 | if not xml_path: 94 | continue 95 | set_colorspace(strip, xml_path) 96 | return {'FINISHED'} 97 | 98 | 99 | class SequencerPropertiesPanel(Panel): 100 | bl_idname = 'SONYCAMERA_PT_SequencerPropertiesPanel' 101 | bl_label = 'Sony' 102 | bl_space_type = 'SEQUENCE_EDITOR' 103 | bl_region_type = 'UI' 104 | bl_category = 'Strip' 105 | bl_options = {'DEFAULT_CLOSED'} 106 | 107 | @classmethod 108 | def poll(cls, context): 109 | scene = context.scene 110 | if scene.sequence_editor and scene.sequence_editor.active_strip: 111 | return scene.sequence_editor.active_strip.type == 'MOVIE' 112 | 113 | def draw(self, context): 114 | scene = context.scene 115 | strip = scene.sequence_editor.active_strip 116 | layout = self.layout 117 | xml_path = find_xml(strip.filepath) 118 | if not xml_path: 119 | return 120 | draw_info(layout, get_info_from_xml(xml_path)) 121 | layout.operator('scene.sonycamera_set_colorspace') 122 | 123 | 124 | class FilebrowserPropertiesPanel(Panel): 125 | bl_idname = 'SONYCAMERA_PT_FilebrowserPropertiesPanel' 126 | bl_label = 'Sony' 127 | bl_space_type = 'FILE_BROWSER' 128 | bl_region_type = 'TOOLS' 129 | bl_options = {'DEFAULT_CLOSED'} 130 | 131 | def draw(self, context): 132 | layout = self.layout 133 | space = context.space_data 134 | directory = space.params.directory 135 | if isinstance(directory, bytes): # directory is a byte since 2.81 136 | directory = directory.decode() 137 | filepath = os.path.join(directory, space.params.filename) 138 | xml_path = find_xml(filepath) 139 | if not xml_path: 140 | return 141 | draw_info(layout, get_info_from_xml(xml_path)) 142 | 143 | 144 | classes = [ 145 | SetColorspace, 146 | SequencerPropertiesPanel, 147 | FilebrowserPropertiesPanel] 148 | 149 | 150 | def register(): 151 | for cls in classes: 152 | bpy.utils.register_class(cls) 153 | 154 | 155 | def unregister(): 156 | for cls in reversed(classes): 157 | bpy.utils.unregister_class(cls) 158 | 159 | 160 | if __name__ == '__main__': 161 | register() 162 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/alembic.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .path import normpath 3 | 4 | 5 | def load_objects_from_alembic(path, filter_type=None): 6 | context = bpy.context 7 | data = bpy.data 8 | current_scene = context.scene 9 | 10 | temporary_scene = data.scenes.new(name='Temp Scene') 11 | context.window.scene = temporary_scene 12 | bpy.ops.wm.alembic_import(filepath=normpath(path)) 13 | if filter_type is None: 14 | objects = [ob for ob in temporary_scene.objects] 15 | else: 16 | objects = [ob for ob in temporary_scene.objects 17 | if ob.type == filter_type] 18 | 19 | context.window.scene = current_scene 20 | data.scenes.remove(temporary_scene) 21 | return objects 22 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/area.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | def redraw_area(context, area_types=None): 5 | if area_types is None: 6 | context.area.tag_redraw() 7 | return 8 | for screen in bpy.data.screens: 9 | for area in screen.areas: 10 | if area.type in area_types: 11 | area.tag_redraw() 12 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/argconfig.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def get_args(parser): 5 | # remove Blender specific arguments from sys.argv 6 | # to be able to use argparse 7 | if '--' in sys.argv: 8 | index = sys.argv.index('--') + 1 9 | arguments = sys.argv[index:] 10 | args = parser.parse_args(arguments) 11 | else: 12 | args = parser.parse_args() 13 | 14 | return args 15 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/colorspace.py: -------------------------------------------------------------------------------- 1 | def set_input_transform(datablock, value): 2 | colorspace_settings = getattr(datablock, 'colorspace_settings', None) 3 | if colorspace_settings is not None: 4 | colorspace_settings.name = value 5 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/datautils.py: -------------------------------------------------------------------------------- 1 | def get_enum_values(property, name): 2 | enum_items = property.bl_rna.properties[name].enum_items 3 | return [item.identifier for item in enum_items] 4 | 5 | 6 | def get_enum_index(property, name, identifier): 7 | enum_items = property.bl_rna.properties[name].enum_items 8 | result = [ 9 | item.value for item in enum_items if item.identifier == identifier] 10 | if result: 11 | return result[0] 12 | 13 | 14 | def remove_item_from_collection(collection, properties, index_name): 15 | if index_name in properties: 16 | index = properties[index_name] 17 | # Adapt index of note collection's items 18 | if (len(collection) - 1) >= index: 19 | if properties[index_name] > 0: 20 | properties[index_name] -= 1 21 | # Remove item 22 | collection.remove(index) 23 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/glutils.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import blf 3 | import gpu 4 | from gpu_extras.batch import batch_for_shader 5 | 6 | 7 | shader_uniform_color = ( 8 | gpu.shader.from_builtin('UNIFORM_COLOR') 9 | if not bpy.app.background else None) 10 | shader_polyline_uniform_color = ( 11 | gpu.shader.from_builtin('POLYLINE_UNIFORM_COLOR') 12 | if not bpy.app.background else None) 13 | 14 | 15 | def draw_poly(coords, color, width=1): 16 | shader = shader_polyline_uniform_color 17 | if shader is not None: 18 | batch = batch_for_shader(shader, 'LINE_STRIP', {'pos': coords}) 19 | shader.bind() 20 | shader.uniform_float('lineWidth', width) 21 | shader.uniform_float('viewportSize', gpu.state.viewport_get()[2:]) 22 | shader.uniform_float('color', color) 23 | batch.draw(shader) 24 | 25 | 26 | def draw_line(v1, v2, color, width=1): 27 | shader = shader_polyline_uniform_color 28 | if shader is not None: 29 | coords = [(v1[0], v1[1]), (v2[0], v2[1])] 30 | batch = batch_for_shader(shader, 'LINES', {'pos': coords}) 31 | shader.bind() 32 | shader.uniform_float('lineWidth', width) 33 | shader.uniform_float('viewportSize', gpu.state.viewport_get()[2:]) 34 | shader.uniform_float('color', color) 35 | batch.draw(shader) 36 | 37 | 38 | def draw_rectangle(v1, v2, color): 39 | """v1, v2 are corners: bottom left, top right""" 40 | shader = shader_uniform_color 41 | if shader is not None: 42 | vertices = ( 43 | (v1[0], v1[1]), (v2[0], v1[1]), # bottom left, bottom right 44 | (v1[0], v2[1]), (v2[0], v2[1])) # top left, top right 45 | indices = ((0, 1, 2), (2, 1, 3)) 46 | batch = batch_for_shader( 47 | shader, 'TRIS', {'pos': vertices}, indices=indices) 48 | shader.bind() 49 | shader.uniform_float('color', color) 50 | batch.draw(shader) 51 | 52 | 53 | def draw_text(text, position, color, size, font_id): 54 | blf.size(font_id, int(size)) 55 | blf.position(font_id, *position) 56 | blf.color(font_id, *color) 57 | blf.draw(font_id, text) 58 | 59 | 60 | def draw_text_line(packed_strings, x, y, size, font_id): 61 | blf.size(font_id, size) 62 | x_offset = 0 63 | for pstr, pcol in packed_strings: 64 | text_width, text_height = blf.dimensions(font_id, pstr) 65 | position = (x + x_offset, y, 0) 66 | draw_text(pstr, position, pcol, size, font_id) 67 | x_offset += text_width 68 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/image.py: -------------------------------------------------------------------------------- 1 | from bpy import data 2 | 3 | 4 | def get_image_resolution(image): 5 | if image.type == 'MULTILAYER': 6 | # HACK to get the resolution of a multilayer EXR through movieclip 7 | movieclip = data.movieclips.load(image.filepath) 8 | x, y = movieclip.size 9 | data.movieclips.remove(movieclip) 10 | else: 11 | x, y = image.size 12 | return x, y 13 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/path.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import bpy 4 | 5 | 6 | def normpath(path): 7 | """Set path to correct OS and Blender convention""" 8 | 9 | # Remove double slash to be able to use for absolute path 10 | if sys.platform.startswith('linux'): 11 | path = path.replace('//', '/') 12 | # Result: /my/path 13 | 14 | # Make linux path compatible with windows 15 | elif sys.platform.startswith('win'): 16 | if path.startswith('/') and not path.startswith('//'): 17 | path = '/{}'.format(path) 18 | # Result after normpath: \\my\path 19 | 20 | path = os.path.normpath(path) 21 | return path 22 | 23 | 24 | def convert_os_path(path): 25 | if sys.platform.startswith('linux'): 26 | if path.startswith(r'\\'): 27 | path = '/' + path[2:] 28 | elif sys.platform.startswith('win'): 29 | if path.startswith('/') and not path.startswith('//'): 30 | path = r'\\' + path[1:] 31 | return path 32 | 33 | 34 | def conform_data_paths(): 35 | """Adapt paths for Linux or Windows""" 36 | 37 | for scene in bpy.data.scenes: 38 | scene.render.filepath = convert_os_path(scene.render.filepath) 39 | if not scene.sequence_editor: 40 | continue 41 | sequences = scene.sequence_editor.sequences_all 42 | for strip in sequences: 43 | match strip.type: 44 | case 'MOVIE': 45 | strip.filepath = convert_os_path(strip.filepath) 46 | case 'IMAGE': 47 | strip.directory = convert_os_path(strip.directory) 48 | for image in bpy.data.images: 49 | image.filepath = convert_os_path(image.filepath) 50 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/render.py: -------------------------------------------------------------------------------- 1 | import os 2 | import bpy 3 | import tempfile 4 | import shutil 5 | import subprocess 6 | 7 | 8 | def render_movie(scene, codec=None, qscale=None, metadata=None): 9 | """Wrapper around bpy.ops.render.render() 10 | 11 | Always render as image sequence and convert the result with FFmpeg 12 | that gives more control (codecs and metadata). 13 | """ 14 | codec = codec or 'mjpeg' 15 | 16 | tmp_format = 'JPEG' 17 | tmp_ext = '.jpg' 18 | 19 | output = scene.render.filepath 20 | render_tmp = tempfile.mkdtemp() 21 | scene.render.filepath = os.path.join( 22 | render_tmp, 'render.####' + tmp_ext) 23 | scene.render.image_settings.file_format = tmp_format 24 | scene.render.image_settings.quality = 100 25 | 26 | bpy.ops.render.render(animation=True) 27 | 28 | # Build command to convert image seq to movie 29 | command = [ 30 | 'ffmpeg', 31 | '-framerate', str(scene.render.fps), 32 | '-start_number', str(scene.frame_start), 33 | '-i', '{}/render.%04d'.format(render_tmp) + tmp_ext, 34 | '-c:v', codec] 35 | if qscale is not None: 36 | command.extend(['-q:v', qscale]) 37 | if metadata is not None: 38 | for md in metadata: 39 | command.extend(['-metadata', md]) 40 | command.extend([output, '-y']) 41 | 42 | subprocess.call(command) 43 | 44 | # Remove temp folder 45 | shutil.rmtree(render_tmp) 46 | scene.render.filepath = output 47 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/sequencer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | import queue 4 | from functools import partial 5 | from .path import normpath 6 | 7 | DEFAULT_CHANNEL = 1 8 | DEFAULT_BLEND_TYPE = 'REPLACE' 9 | DEFAULT_LENGTH = 24 10 | 11 | 12 | def is_available_sequences(scene): 13 | if not scene.sequence_editor: 14 | return False 15 | sequences = scene.sequence_editor.sequences 16 | if sequences: 17 | return True 18 | 19 | 20 | def enable_sequence_editor(scene): 21 | if not scene.sequence_editor: 22 | scene.sequence_editor_create() 23 | 24 | 25 | def clean_sequencer(scene): 26 | if not scene.sequence_editor: 27 | return 28 | sequences = scene.sequence_editor.sequences 29 | for seq in sequences: 30 | sequences.remove(seq) 31 | 32 | 33 | def get_current_strip(scene, channel=DEFAULT_CHANNEL, exclude_meta=True): 34 | if not scene.sequence_editor: 35 | return None 36 | frame_current = scene.frame_current 37 | space_data = bpy.context.space_data 38 | if space_data is not None and space_data.type == 'SEQUENCE_EDITOR': 39 | display_channel = space_data.display_channel 40 | if display_channel != 0: 41 | channel = display_channel 42 | for strip in scene.sequence_editor.sequences_all: 43 | if strip.type == 'META' and exclude_meta: 44 | continue 45 | frame_end = strip.frame_start + strip.frame_final_duration 46 | if strip.frame_start <= frame_current < frame_end: 47 | if strip.channel == channel: 48 | return strip 49 | 50 | 51 | def get_current_strips(scene): 52 | def frame_end(strip): 53 | return strip.frame_start + strip.frame_final_duration 54 | frame_current = scene.frame_current 55 | if not scene.sequence_editor: 56 | return None 57 | strips = [ 58 | strip for strip in scene.sequence_editor.sequences_all 59 | if strip.frame_start <= frame_current < frame_end(strip)] 60 | return strips 61 | 62 | 63 | def get_first_strip(scene): 64 | if not is_available_sequences(scene): 65 | return 66 | sequences = scene.sequence_editor.sequences 67 | first_strip = sequences[0] 68 | for strip in sequences: 69 | if strip.frame_start < first_strip.frame_start: 70 | first_strip = strip 71 | return first_strip 72 | 73 | 74 | def get_last_strip(scene): 75 | if not is_available_sequences(scene): 76 | return 77 | sequences = scene.sequence_editor.sequences 78 | last_strip = sequences[0] 79 | for strip in sequences: 80 | if strip.frame_start > last_strip.frame_start: 81 | last_strip = strip 82 | return last_strip 83 | 84 | 85 | def set_frame_range(scene): 86 | first_strip = get_first_strip(scene) 87 | last_strip = get_last_strip(scene) 88 | if first_strip and last_strip: 89 | scene.frame_start = int(first_strip.frame_start) 90 | scene.frame_end = last_strip.frame_final_end - 1 91 | 92 | 93 | def _get_next_frame_start(scene): 94 | last_strip = get_last_strip(scene) 95 | frame_start = ( 96 | last_strip.frame_start + last_strip.frame_final_duration 97 | if last_strip else 1) 98 | return int(frame_start) 99 | 100 | 101 | def load_image_strip( 102 | scene, image, channel=DEFAULT_CHANNEL, blend_type=DEFAULT_BLEND_TYPE, 103 | length=DEFAULT_LENGTH, frame_start=None): 104 | sequences = scene.sequence_editor.sequences 105 | frame_start = frame_start or _get_next_frame_start(scene) 106 | strip = sequences.new_image( 107 | name=os.path.basename(image), 108 | filepath=normpath(image), 109 | channel=channel, 110 | frame_start=int(frame_start)) 111 | strip.select = False 112 | strip.blend_type = blend_type 113 | strip.frame_final_duration = length 114 | return strip 115 | 116 | 117 | def load_image_sequence_strip( 118 | scene, images, channel=DEFAULT_CHANNEL, blend_type=DEFAULT_BLEND_TYPE, 119 | frame_start=None, colorspace=None): 120 | sequences = scene.sequence_editor.sequences 121 | frame_start = frame_start or _get_next_frame_start(scene) 122 | first_frame = images[0] 123 | strip = sequences.new_image( 124 | name=os.path.basename(first_frame), 125 | filepath=normpath(first_frame), 126 | channel=channel, 127 | frame_start=int(frame_start)) 128 | for image in images[1:]: 129 | name = os.path.basename(image) 130 | strip.elements.append(name) 131 | strip.select = False 132 | strip.blend_type = blend_type 133 | if colorspace is not None: 134 | strip.colorspace_settings.name = colorspace 135 | return strip 136 | 137 | 138 | def load_movie_strip( 139 | scene, moviepath, channel=DEFAULT_CHANNEL, 140 | blend_type=DEFAULT_BLEND_TYPE, frame_start=None): 141 | sequences = scene.sequence_editor.sequences 142 | frame_start = frame_start or _get_next_frame_start(scene) 143 | strip = sequences.new_movie( 144 | name=os.path.basename(moviepath), 145 | filepath=normpath(moviepath), 146 | channel=channel, 147 | frame_start=int(frame_start)) 148 | strip.select = False 149 | strip.blend_type = blend_type 150 | return strip 151 | 152 | 153 | def load_sound_strip( 154 | scene, soundpath, channel=DEFAULT_CHANNEL, frame_start=None, 155 | show_waveform=False): 156 | sequences = scene.sequence_editor.sequences 157 | frame_start = frame_start or _get_next_frame_start(scene) 158 | strip = sequences.new_sound( 159 | name=os.path.basename(soundpath), 160 | filepath=normpath(soundpath), 161 | channel=channel, 162 | frame_start=int(frame_start)) 163 | strip.select = False 164 | strip.show_waveform = show_waveform 165 | return strip 166 | 167 | 168 | def load_scene_strip( 169 | scene, strip_scene, channel=DEFAULT_CHANNEL, 170 | blend_type=DEFAULT_BLEND_TYPE, length=DEFAULT_LENGTH, 171 | frame_start=None): 172 | sequences = scene.sequence_editor.sequences 173 | frame_start = frame_start or _get_next_frame_start(scene) 174 | strip = sequences.new_scene( 175 | name=strip_scene.name, 176 | scene=strip_scene, 177 | channel=channel, 178 | frame_start=int(frame_start)) 179 | strip.select = False 180 | strip.blend_type = blend_type 181 | strip.frame_final_duration = length 182 | return strip 183 | 184 | 185 | def load_multiple_movie_strips(scene, filepaths): 186 | for path in filepaths: 187 | if not os.path.exists(path): 188 | continue 189 | movie_strip = load_movie_strip(scene, path) 190 | sound_channel = movie_strip.channel + 1 191 | sound_frame_start = movie_strip.frame_start 192 | load_sound_strip( 193 | scene, path, channel=sound_channel, frame_start=sound_frame_start) 194 | 195 | 196 | def create_adjustment_strip(scene): 197 | active_strip = scene.sequence_editor.active_strip 198 | active_start = int(active_strip.frame_start) 199 | if not active_strip: 200 | return 201 | sequences = scene.sequence_editor.sequences 202 | strip = sequences.new_effect( 203 | name='Adjustment', 204 | type='ADJUSTMENT', 205 | channel=active_strip.channel + 1, 206 | frame_start=active_start, 207 | frame_end=active_start + active_strip.frame_final_duration) 208 | strip.select = True 209 | scene.sequence_editor.active_strip = strip 210 | return strip 211 | 212 | 213 | def set_strip_proxy_quality(strip, quality): 214 | if getattr(strip, 'proxy', None): 215 | strip.proxy.quality = quality 216 | 217 | 218 | def view_zoom_preview(context): 219 | scene = context.scene 220 | for region in context.area.regions: 221 | if region.type == 'PREVIEW': 222 | width = region.width 223 | height = region.height 224 | rv1 = region.view2d.region_to_view(0, 0) 225 | rv2 = region.view2d.region_to_view(width - 1, height - 1) 226 | res_percentage = scene.render.resolution_percentage 227 | zoom = (1 / (width / (rv2[0] - rv1[0]))) / (res_percentage / 100) 228 | return zoom 229 | 230 | 231 | def normalise_mouse_position(context, position): 232 | """Normalise coordinates between 0 and 1""" 233 | scene = context.scene 234 | region = context.region 235 | position_x, position_y = position 236 | mouse_x, mouse_y = region.view2d.region_to_view( 237 | position_x - region.x, 238 | position_y - region.y) 239 | mouse_x += (scene.render.resolution_x / 2) 240 | mouse_y += (scene.render.resolution_y / 2) 241 | x = mouse_x / scene.render.resolution_x 242 | y = mouse_y / scene.render.resolution_y 243 | return (x, y) 244 | 245 | 246 | def iterate_over_selected_strips(scene): 247 | if not scene.sequence_editor: 248 | return 249 | sequences = scene.sequence_editor.sequences 250 | sequences = sorted(sequences, key=lambda x: x.frame_start) 251 | for strip in sequences: 252 | if strip.select: 253 | yield strip 254 | 255 | 256 | def replace_path(strip, find_text, replace_text, exists_only=True): 257 | if strip is None: 258 | return 259 | match strip.type: 260 | case 'MOVIE': 261 | dirname, basename = os.path.split(strip.filepath) 262 | basename = basename.replace(find_text, replace_text) 263 | filepath = os.path.join(dirname, basename) 264 | if not os.path.isfile(filepath) and exists_only: 265 | return 266 | strip.filepath = filepath 267 | strip.name = basename 268 | return filepath 269 | case 'IMAGE': 270 | filepath = os.path.join( 271 | strip.directory, 272 | strip.elements[0].filename.replace(find_text, replace_text)) 273 | if not os.path.isfile(filepath) and exists_only: 274 | return 275 | for e in strip.elements: 276 | elem_filename = e.filename.replace(find_text, replace_text) 277 | e.filename = elem_filename 278 | strip.name = strip.elements[0].filename 279 | return filepath 280 | 281 | 282 | def get_strip_filepath(strip, image_index=0): 283 | if strip is None: 284 | return 285 | if strip.type == 'MOVIE': 286 | return strip.filepath 287 | elif strip.type == 'IMAGE': 288 | dirname = strip.directory 289 | basename = strip.elements[image_index].filename 290 | filepath = os.path.join(dirname, basename) 291 | return filepath 292 | 293 | 294 | def mute_channel(scene, channel, unmute=False): 295 | sequences = scene.sequence_editor.sequences 296 | for strip in sequences: 297 | if strip.channel == channel: 298 | strip.mute = not unmute 299 | 300 | 301 | class AsyncMovieLoader: 302 | def __init__(self, files): 303 | self.files_queue = queue.Queue() 304 | for file in files: 305 | self.files_queue.put(file) 306 | 307 | def load_next_movie(self, scene): 308 | if self.files_queue.empty(): 309 | return 310 | 311 | file = self.files_queue.get() 312 | load_movie_strip(scene, file) 313 | 314 | # Set timer for next file 315 | if not self.files_queue.empty(): 316 | return 0.1 317 | 318 | 319 | def start_async_movie_loading(scene, files): 320 | loader = AsyncMovieLoader(files) 321 | bpy.app.timers.register(partial(loader.load_next_movie, scene=scene)) 322 | 323 | 324 | if __name__ == '__main__': 325 | def test_loading(): 326 | files = [ 327 | '/path.mkv', 328 | '/path.mkv', 329 | '/path.mkv'] 330 | start_async_movie_loading(scene=bpy.context.scene, files=files) 331 | 332 | test_loading() 333 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/timeline.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Operator, WorkSpaceTool 3 | 4 | 5 | def go_to_next_frame(scene, frames, reverse=False): 6 | for frame in frames: 7 | if reverse: 8 | if frame < scene.frame_current: 9 | scene.frame_current = int(frame) 10 | break 11 | else: 12 | if frame > scene.frame_current: 13 | scene.frame_current = int(frame) 14 | break 15 | 16 | 17 | def view_all(context=None): 18 | context = context or bpy.context 19 | if not context.screen: 20 | return None 21 | for area in context.screen.areas: 22 | if area.type == 'DOPESHEET_EDITOR': 23 | for region in area.regions: 24 | if region.type == 'WINDOW': 25 | with context.temp_override(area=area, region=region): 26 | bpy.ops.action.view_all() 27 | 28 | 29 | class InteractiveTimeline(Operator): 30 | bl_idname = 'screen.interactive_timeline' 31 | bl_label = 'Interactive timeline' 32 | 33 | @classmethod 34 | def poll(cls, context): 35 | return context.region.type == 'PREVIEW' 36 | 37 | def modal(self, context, event): 38 | w = context.window_manager.windows[0] 39 | w.cursor_modal_set('SCROLL_X') 40 | 41 | context.area.header_text_set( 42 | 'Left/right to change frame | ' 43 | 'enter key or left click to valid | ' 44 | 'escape or right clic to exit') 45 | 46 | scene = context.scene 47 | frame_start = context.scene.frame_start 48 | frame_end = context.scene.frame_end 49 | 50 | match event.type: 51 | case 'RET' | 'NUMPAD_ENTER' | 'SPACE' | 'LEFTMOUSE': 52 | w.cursor_modal_restore() 53 | context.area.header_text_set(text='') 54 | return {'FINISHED'} 55 | case 'ESC' | 'RIGHTMOUSE': 56 | context.scene.frame_current = self.frame_init 57 | w.cursor_modal_restore() 58 | context.area.header_text_set(text='') 59 | return {'CANCELLED'} 60 | case 'MOUSEMOVE': 61 | target_frame = self.frame_init + ( 62 | event.mouse_region_x - self.region_x_init) 63 | if target_frame > frame_start and target_frame < frame_end: 64 | scene.frame_current = target_frame 65 | elif target_frame <= frame_start: 66 | scene.frame_current = frame_start 67 | elif target_frame >= frame_end: 68 | scene.frame_current = frame_end 69 | 70 | return {'RUNNING_MODAL'} 71 | 72 | def invoke(self, context, event): 73 | self.frame_init = context.scene.frame_current 74 | self.region_x_init = event.mouse_region_x 75 | context.window_manager.modal_handler_add(self) 76 | return {'RUNNING_MODAL'} 77 | 78 | 79 | class InteractiveTimelineTool(WorkSpaceTool): 80 | bl_space_type = 'SEQUENCE_EDITOR' 81 | bl_context_mode = None 82 | bl_idname = 'timeline.interactive_move' 83 | bl_label = 'Interactive timeline' 84 | bl_icon = 'ops.transform.transform' 85 | bl_widget = None 86 | bl_keymap = ( 87 | ('screen.interactive_timeline', 88 | {'type': 'LEFTMOUSE', 'value': 'PRESS'}, None), 89 | ) 90 | 91 | 92 | def register(): 93 | bpy.utils.register_class(InteractiveTimeline) 94 | bpy.utils.register_tool(InteractiveTimelineTool) 95 | 96 | 97 | def unregister(): 98 | bpy.utils.unregister_class(InteractiveTimeline) 99 | bpy.utils.unregister_tool(InteractiveTimelineTool) 100 | -------------------------------------------------------------------------------- /scripts/modules/vgblender/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentgires/blender-scripts/384f6d931f24c28a08648a5e60aae02764463973/scripts/modules/vgblender/ui/__init__.py -------------------------------------------------------------------------------- /scripts/modules/vgblender/ui/color_management.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Panel 2 | 3 | 4 | def draw_color_management_display(layout, context): 5 | layout.use_property_split = True 6 | layout.use_property_decorate = False 7 | scene = context.scene 8 | view = scene.view_settings 9 | flow = layout.grid_flow( 10 | row_major=True, 11 | columns=0, 12 | even_columns=False, 13 | even_rows=False, 14 | align=True) 15 | col = flow.column(align=True) 16 | col.prop(scene.display_settings, 'display_device') 17 | col.separator() 18 | col.prop(view, 'view_transform') 19 | col.prop(view, 'look') 20 | col = flow.column(align=True) 21 | col.prop(view, 'exposure') 22 | col.prop(view, 'gamma') 23 | 24 | 25 | class ImageEditorColorManagement(): 26 | bl_space_type = 'IMAGE_EDITOR' 27 | bl_region_type = 'UI' 28 | bl_category = 'View' 29 | bl_label = 'Color Management' 30 | 31 | 32 | class SequencerColorManagement(): 33 | bl_space_type = 'SEQUENCE_EDITOR' 34 | bl_region_type = 'UI' 35 | bl_category = 'View' 36 | bl_label = 'Color Management' 37 | 38 | 39 | class SequencerColorManagementDisplay(SequencerColorManagement, Panel): 40 | bl_idname = 'SEQUENCER_PT_color_management_display' 41 | 42 | def draw(self, context): 43 | layout = self.layout 44 | scene = context.scene 45 | draw_color_management_display(layout, context) 46 | layout.prop( 47 | scene.sequencer_colorspace_settings, 'name', text='Sequencer') 48 | 49 | 50 | class ImageEditorColorManagementDisplay(ImageEditorColorManagement, Panel): 51 | bl_idname = 'IMAGE_EDITOR_PT_color_management_display' 52 | 53 | def draw(self, context): 54 | draw_color_management_display(self.layout, context) 55 | 56 | 57 | class ColorManagementCurves(): 58 | bl_label = 'Use Curves' 59 | bl_options = {'DEFAULT_CLOSED'} 60 | 61 | def draw_header(self, context): 62 | scene = context.scene 63 | view = scene.view_settings 64 | self.layout.prop(view, 'use_curve_mapping', text='') 65 | 66 | def draw(self, context): 67 | layout = self.layout 68 | scene = context.scene 69 | view = scene.view_settings 70 | layout.use_property_split = False 71 | layout.use_property_decorate = False 72 | layout.enabled = view.use_curve_mapping 73 | layout.template_curve_mapping( 74 | view, 75 | 'curve_mapping', 76 | type='COLOR', 77 | levels=True) 78 | 79 | 80 | class SequencerColorManagementCurves( 81 | ColorManagementCurves, SequencerColorManagement, Panel): 82 | bl_idname = 'SEQUENCER_PT_color_management_curves' 83 | bl_parent_id = 'SEQUENCER_PT_color_management_display' 84 | 85 | 86 | class ImageEditorColorManagementCurves( 87 | ColorManagementCurves, ImageEditorColorManagement, Panel): 88 | bl_idname = 'IMAGE_EDITOR_PT_color_management_curves' 89 | bl_parent_id = 'IMAGE_EDITOR_PT_color_management_display' 90 | -------------------------------------------------------------------------------- /scripts/startup/bl_app_templates_user/composite_image/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import bpy 4 | from bpy.app.handlers import persistent 5 | from vgblender.path import normpath 6 | from vgblender.timeline import view_all 7 | 8 | filepath = normpath(sys.argv[-1]) 9 | 10 | 11 | def load_file_as_image(): 12 | if not os.path.exists(filepath): 13 | return 14 | context = bpy.context 15 | scene = context.scene 16 | image = bpy.data.images.load(filepath) 17 | image.use_view_as_render = True 18 | 19 | x, y = image.size 20 | scene.render.resolution_x = x 21 | scene.render.resolution_y = y 22 | scene.render.resolution_percentage = 100 23 | scene.frame_end = image.frame_duration 24 | 25 | scene.use_nodes = True 26 | node_tree = scene.node_tree 27 | for node in node_tree.nodes: 28 | node_tree.nodes.remove(node) 29 | image_node = node_tree.nodes.new('CompositorNodeImage') 30 | image_node.frame_duration = image.frame_duration 31 | viewer_node = node_tree.nodes.new('CompositorNodeViewer') 32 | viewer_node.location.x += 300 33 | image_node.image = image 34 | node_tree.links.new(image_node.outputs[0], viewer_node.inputs[0]) 35 | 36 | screen = context.screen 37 | for area in screen.areas: 38 | if area.type == 'IMAGE_EDITOR': 39 | render_image = bpy.data.images['Viewer Node'] 40 | space = area.spaces.active 41 | space.image = render_image 42 | 43 | 44 | @persistent 45 | def load_handler(dummy): 46 | load_file_as_image() 47 | view_all() 48 | 49 | 50 | def register(): 51 | bpy.app.handlers.load_factory_startup_post.append(load_handler) 52 | 53 | 54 | def unregister(): 55 | bpy.app.handlers.load_factory_startup_post.remove(load_handler) 56 | -------------------------------------------------------------------------------- /scripts/startup/bl_app_templates_user/composite_image/startup.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentgires/blender-scripts/384f6d931f24c28a08648a5e60aae02764463973/scripts/startup/bl_app_templates_user/composite_image/startup.blend -------------------------------------------------------------------------------- /scripts/startup/bl_app_templates_user/image_viewer/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import bpy 4 | from bpy.app.handlers import persistent 5 | from vgblender.path import normpath 6 | from vgblender.timeline import view_all 7 | 8 | filepath = normpath(sys.argv[-1]) 9 | 10 | 11 | def load_file_as_image(): 12 | if not os.path.exists(filepath): 13 | return 14 | image = bpy.data.images.load(filepath) 15 | image.use_view_as_render = True 16 | screen = bpy.context.screen 17 | for area in screen.areas: 18 | if area.type == 'IMAGE_EDITOR': 19 | space = area.spaces.active 20 | space.image = image 21 | space.image_user.frame_duration = image.frame_duration 22 | bpy.context.scene.frame_end = image.frame_duration 23 | 24 | 25 | @persistent 26 | def load_handler(dummy): 27 | load_file_as_image() 28 | view_all() 29 | 30 | 31 | def register(): 32 | bpy.app.handlers.load_factory_startup_post.append(load_handler) 33 | 34 | 35 | def unregister(): 36 | bpy.app.handlers.load_factory_startup_post.remove(load_handler) 37 | -------------------------------------------------------------------------------- /scripts/startup/bl_app_templates_user/image_viewer/startup.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentgires/blender-scripts/384f6d931f24c28a08648a5e60aae02764463973/scripts/startup/bl_app_templates_user/image_viewer/startup.blend -------------------------------------------------------------------------------- /scripts/startup/bl_app_templates_user/sequence_player/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import bpy 4 | from bpy.app.handlers import persistent 5 | from vgblender.path import normpath 6 | from vgblender.render import render_movie 7 | from vgblender.timeline import view_all 8 | 9 | filepath = normpath(sys.argv[-1]) 10 | 11 | 12 | class ExportMovie(bpy.types.Operator): 13 | bl_idname = 'render.export_movie' 14 | bl_label = 'Export movie' 15 | 16 | codec: bpy.props.StringProperty( 17 | name='codec', 18 | default='mjpeg') 19 | qscale: bpy.props.StringProperty( 20 | name='qscale', 21 | default='1') 22 | container: bpy.props.StringProperty( 23 | name='container', 24 | default='mkv') 25 | 26 | def execute(self, context): 27 | scene = context.scene 28 | dirpath = os.path.dirname(filepath) 29 | output = os.path.join(dirpath, f'export.{self.container}') 30 | scene.render.filepath = output 31 | render_movie(scene, codec=self.codec, qscale=self.qscale) 32 | return {'FINISHED'} 33 | 34 | def invoke(self, context, event): 35 | return context.window_manager.invoke_props_dialog(self) 36 | 37 | 38 | def render_menu_draw(self, context): 39 | self.layout.operator('render.export_movie') 40 | 41 | 42 | def load_file_as_clip(): 43 | if not os.path.exists(filepath): 44 | return 45 | scene = bpy.context.scene 46 | clip = bpy.data.movieclips.load(filepath) 47 | sequences = scene.sequence_editor.sequences 48 | sequences.new_clip( 49 | name=os.path.basename(filepath), 50 | clip=clip, 51 | channel=1, 52 | frame_start=1) 53 | scene.frame_start = 1 54 | scene.frame_end = clip.frame_duration 55 | x, y = clip.size 56 | scene.render.resolution_x = x 57 | scene.render.resolution_y = y 58 | scene.render.resolution_percentage = 100 59 | 60 | 61 | @persistent 62 | def load_handler(dummy): 63 | load_file_as_clip() 64 | view_all() 65 | 66 | 67 | def register(): 68 | bpy.utils.register_class(ExportMovie) 69 | bpy.app.handlers.load_factory_startup_post.append(load_handler) 70 | bpy.types.TOPBAR_MT_render.append(render_menu_draw) 71 | 72 | 73 | def unregister(): 74 | bpy.utils.unregister_class(ExportMovie) 75 | bpy.app.handlers.load_factory_startup_post.remove(load_handler) 76 | bpy.types.TOPBAR_MT_render.remove(render_menu_draw) 77 | -------------------------------------------------------------------------------- /scripts/startup/bl_app_templates_user/sequence_player/startup.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincentgires/blender-scripts/384f6d931f24c28a08648a5e60aae02764463973/scripts/startup/bl_app_templates_user/sequence_player/startup.blend -------------------------------------------------------------------------------- /scripts/startup/vg_startup/__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from vgblender.ui.color_management import ( 3 | SequencerColorManagementDisplay, SequencerColorManagementCurves, 4 | ImageEditorColorManagementDisplay, ImageEditorColorManagementCurves) 5 | 6 | 7 | classes = [ 8 | SequencerColorManagementDisplay, 9 | SequencerColorManagementCurves, 10 | ImageEditorColorManagementDisplay, 11 | ImageEditorColorManagementCurves,] 12 | 13 | 14 | def register(): 15 | for cls in classes: 16 | bpy.utils.register_class(cls) 17 | 18 | 19 | def unregister(): 20 | for cls in reversed(classes): 21 | bpy.utils.unregister_class(cls) 22 | -------------------------------------------------------------------------------- /scripts/templates_py/convert_image.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import bpy 4 | from vgblender.argconfig import get_args 5 | 6 | FILE_EXT = '.jpg' 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument( 10 | '-inputs', 11 | nargs='+', 12 | help='File inputs', 13 | required=False) 14 | parser.add_argument( 15 | '-resolution', 16 | nargs='+', 17 | type=int, 18 | help='Resolution X Y', 19 | required=False) 20 | parser.add_argument( 21 | '-colorspace', 22 | help='Footage colorspace', 23 | required=False) 24 | parser.add_argument( 25 | '-displaydevice', 26 | help='OCIO Display Device', 27 | required=False) 28 | parser.add_argument( 29 | '-viewtransform', 30 | help='OCIO View Transform', 31 | required=False) 32 | parser.add_argument( 33 | '-output', 34 | help='File output', 35 | required=False) 36 | 37 | args = get_args(parser) 38 | 39 | data = bpy.data 40 | context = bpy.context 41 | scene = context.scene 42 | 43 | # Remove all objects 44 | for obj in data.objects: 45 | data.objects.remove(obj) 46 | 47 | # Scene setup 48 | scene.use_nodes = True 49 | 50 | # Clear default nodes 51 | node_tree = scene.node_tree 52 | for node in node_tree.nodes: 53 | node_tree.nodes.remove(node) 54 | 55 | output_node = node_tree.nodes.new('CompositorNodeComposite') 56 | for f in args.inputs: 57 | image_node = node_tree.nodes.new('CompositorNodeImage') 58 | image = data.images.load(f) 59 | if args.colorspace: 60 | image.colorspace_settings.name = args.colorspace 61 | image_node.image = image 62 | node_tree.links.new(image_node.outputs[0], output_node.inputs[0]) 63 | 64 | if args.resolution is not None: 65 | x, y = args.resolution 66 | else: 67 | x, y = image.size 68 | scene.render.resolution_x = x 69 | scene.render.resolution_y = y 70 | scene.render.resolution_percentage = 100 71 | scene.render.image_settings.file_format = 'JPEG' 72 | scene.render.image_settings.color_mode = 'RGB' 73 | scene.render.image_settings.quality = 95 74 | if args.displaydevice: 75 | scene.display_settings.display_device = args.displaydevice 76 | if args.viewtransform: 77 | scene.view_settings.view_transform = args.viewtransform 78 | 79 | if args.output: 80 | if args.output.endswith(FILE_EXT): 81 | outputpath = args.output 82 | else: 83 | # assume that output path is a directory, the filename extension 84 | # of each files is then replaced 85 | _, filename = os.path.split(f) 86 | outputpath = os.path.join( 87 | args.output, os.path.splitext(filename)[0] + FILE_EXT) 88 | else: 89 | root, _ = os.path.splitext(f) 90 | outputpath = root + FILE_EXT 91 | scene.render.filepath = outputpath 92 | bpy.ops.render.render(write_still=True) 93 | -------------------------------------------------------------------------------- /scripts/templates_py/convert_to_movie.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import bpy 3 | from vgblender.path import normpath 4 | from vgblender.argconfig import get_args 5 | from vgblender.render.render import render_movie 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument( 9 | '-input', 10 | help='File input', 11 | required=False) 12 | parser.add_argument( 13 | '-startframe', 14 | type=int, 15 | help='Start frame to begin the clip', 16 | required=False) 17 | parser.add_argument( 18 | '-endframe', 19 | type=int, 20 | help='End frame', 21 | required=False) 22 | parser.add_argument( 23 | '-fps', 24 | type=int, 25 | help='FPS', 26 | required=False) 27 | parser.add_argument( 28 | '-resolution', 29 | nargs='+', 30 | type=int, 31 | help='Resolution X Y', 32 | required=False) 33 | parser.add_argument( 34 | '-colordepth', 35 | help='Color depth', 36 | required=False) 37 | parser.add_argument( 38 | '-colorspace', 39 | help='Footage colorspace', 40 | required=False) 41 | parser.add_argument( 42 | '-displaydevice', 43 | help='OCIO Display Device', 44 | required=False) 45 | parser.add_argument( 46 | '-viewtransform', 47 | help='OCIO View Transform', 48 | required=False) 49 | parser.add_argument( 50 | '-output', 51 | help='File output', 52 | required=False) 53 | parser.add_argument( 54 | '-metadata', 55 | nargs='+', 56 | required=False) 57 | parser.add_argument( 58 | '-codec', 59 | required=False) 60 | parser.add_argument( 61 | '-qscale', 62 | required=False) 63 | 64 | args = get_args(parser) 65 | 66 | data = bpy.data 67 | context = bpy.context 68 | scene = context.scene 69 | 70 | 71 | def process(): 72 | # Remove all objects 73 | for obj in data.objects: 74 | data.objects.remove(obj) 75 | 76 | # Scene setup 77 | scene.use_nodes = True 78 | 79 | # Clear default nodes 80 | node_tree = scene.node_tree 81 | for node in node_tree.nodes: 82 | node_tree.nodes.remove(node) 83 | 84 | start_frame = args.startframe or 1 85 | end_frame = args.endframe or start_frame 86 | 87 | image = data.images.load(normpath(args.input)) 88 | image.source = 'SEQUENCE' 89 | if args.colorspace: 90 | image.colorspace_settings.name = args.colorspace 91 | image_node = node_tree.nodes.new('CompositorNodeImage') 92 | image_node.image = image 93 | image_node.frame_duration = end_frame 94 | scale_node = node_tree.nodes.new('CompositorNodeScale') 95 | scale_node.space = 'RENDER_SIZE' 96 | scale_node.frame_method = 'FIT' 97 | output_node = node_tree.nodes.new('CompositorNodeComposite') 98 | node_tree.links.new(image_node.outputs[0], scale_node.inputs[0]) 99 | node_tree.links.new(scale_node.outputs[0], output_node.inputs[0]) 100 | 101 | scene.frame_start = start_frame 102 | scene.frame_end = end_frame 103 | if args.resolution: 104 | x, y = args.resolution 105 | else: 106 | x, y = image.size 107 | scene.render.resolution_x = x 108 | scene.render.resolution_y = y 109 | scene.render.resolution_percentage = 100 110 | if args.fps: 111 | scene.render.fps = args.fps 112 | if args.displaydevice: 113 | scene.display_settings.display_device = args.displaydevice 114 | if args.viewtransform: 115 | scene.view_settings.view_transform = args.viewtransform 116 | if args.colordepth: 117 | scene.render.image_settings.color_depth = args.colordepth 118 | if args.output: 119 | scene.render.filepath = args.output 120 | 121 | render_movie( 122 | scene, metadata=args.metadata, codec=args.codec, qscale=args.qscale) 123 | 124 | # Debug file 125 | # bpy.ops.wm.save_as_mainfile( 126 | # filepath='{}.blend'.format(scene.render.filepath), 127 | # check_existing=True, 128 | # relative_remap=False) 129 | 130 | 131 | process() 132 | -------------------------------------------------------------------------------- /scripts/templates_py/convert_to_stereo_movie.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import bpy 3 | from vgblender.path import normpath 4 | from vgblender.argconfig import get_args 5 | from vgblender.render.render import render_movie 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument( 9 | '-inputs', 10 | nargs='+', 11 | help='File inputs', 12 | required=False) 13 | parser.add_argument( 14 | '-startframe', 15 | type=int, 16 | help='Start frame to begin the clip', 17 | required=False) 18 | parser.add_argument( 19 | '-endframe', 20 | type=int, 21 | help='End frame', 22 | required=False) 23 | parser.add_argument( 24 | '-fps', 25 | type=int, 26 | help='FPS', 27 | required=False) 28 | parser.add_argument( 29 | '-resolution', 30 | nargs='+', 31 | type=int, 32 | help='Resolution X Y', 33 | required=False) 34 | parser.add_argument( 35 | '-colordepth', 36 | help='Color depth', 37 | required=False) 38 | parser.add_argument( 39 | '-colorspace', 40 | help='Footage colorspace', 41 | required=False) 42 | parser.add_argument( 43 | '-displaydevice', 44 | help='OCIO Display Device', 45 | required=False) 46 | parser.add_argument( 47 | '-viewtransform', 48 | help='OCIO View Transform', 49 | required=False) 50 | parser.add_argument( 51 | '-output', 52 | help='File output', 53 | required=False) 54 | parser.add_argument( 55 | '-metadata', 56 | nargs='+', 57 | required=False) 58 | parser.add_argument( 59 | '-codec', 60 | required=False) 61 | parser.add_argument( 62 | '-qscale', 63 | required=False) 64 | 65 | args = get_args(parser) 66 | 67 | data = bpy.data 68 | context = bpy.context 69 | scene = context.scene 70 | 71 | 72 | def process(): 73 | # Remove all objects 74 | for obj in data.objects: 75 | data.objects.remove(obj) 76 | 77 | # Scene setup 78 | scene.use_nodes = True 79 | scene.render.use_multiview = True 80 | scene.render.views_format = 'STEREO_3D' 81 | scene.render.image_settings.views_format = 'STEREO_3D' 82 | scene.render.image_settings.stereo_3d_format.display_mode = 'ANAGLYPH' 83 | scene.render.image_settings.stereo_3d_format.anaglyph_type = 'RED_CYAN' 84 | 85 | # Clear default nodes 86 | node_tree = scene.node_tree 87 | for node in node_tree.nodes: 88 | node_tree.nodes.remove(node) 89 | 90 | start_frame = args.startframe or 1 91 | end_frame = args.endframe or start_frame 92 | 93 | # Create nodes 94 | switchview_node = node_tree.nodes.new('CompositorNodeSwitchView') 95 | images = [] 96 | view_inputs = (args.inputs[0], args.inputs[1]) 97 | for index, inputpath in enumerate(view_inputs): 98 | image = data.images.load(normpath(inputpath)) 99 | image.source = 'SEQUENCE' 100 | if args.colorspace: 101 | image.colorspace_settings.name = args.colorspace 102 | images.append(image) 103 | node = node_tree.nodes.new('CompositorNodeImage') 104 | node.image = image 105 | node.frame_duration = end_frame 106 | node_tree.links.new(node.outputs[0], switchview_node.inputs[index]) 107 | bw_node = node_tree.nodes.new('CompositorNodeRGBToBW') 108 | scale_node = node_tree.nodes.new('CompositorNodeScale') 109 | scale_node.space = 'RENDER_SIZE' 110 | scale_node.frame_method = 'FIT' 111 | output_node = node_tree.nodes.new('CompositorNodeComposite') 112 | node_tree.links.new(switchview_node.outputs[0], bw_node.inputs[0]) 113 | node_tree.links.new(bw_node.outputs[0], scale_node.inputs[0]) 114 | node_tree.links.new(scale_node.outputs[0], output_node.inputs[0]) 115 | 116 | scene.frame_start = start_frame 117 | scene.frame_end = end_frame 118 | x, y = images[0].size 119 | scene.render.resolution_x = x 120 | scene.render.resolution_y = y 121 | scene.render.resolution_percentage = 100 122 | 123 | if args.fps: 124 | scene.render.fps = args.fps 125 | if args.resolution: 126 | x, y = args.resolution 127 | scene.render.resolution_x = x 128 | scene.render.resolution_y = y 129 | if args.displaydevice: 130 | scene.display_settings.display_device = args.displaydevice 131 | if args.viewtransform: 132 | scene.view_settings.view_transform = args.viewtransform 133 | if args.colordepth: 134 | scene.render.image_settings.color_depth = args.colordepth 135 | if args.output: 136 | scene.render.filepath = args.output 137 | 138 | render_movie( 139 | scene, metadata=args.metadata, codec=args.codec, qscale=args.qscale) 140 | 141 | # Debug file 142 | # bpy.ops.wm.save_as_mainfile( 143 | # filepath='{}.blend'.format(scene.render.filepath), 144 | # check_existing=True, 145 | # relative_remap=False) 146 | 147 | 148 | process() 149 | -------------------------------------------------------------------------------- /scripts/templates_py/set_preferences.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from operator import attrgetter 3 | 4 | preferences_assignation = { 5 | 'use_preferences_save': False, # Auto save preferences 6 | 'view': { 7 | 'show_splash': False, 8 | 'show_developer_ui': True, 9 | 'show_tooltips_python': True 10 | }, 11 | 'inputs': { 12 | 'use_emulate_numpad': True, 13 | 'use_mouse_continuous': True, # Continuous grab 14 | 'use_drag_immediately': False # Release confirms 15 | }, 16 | 'system': { 17 | 'memory_cache_limit': 12288, 18 | 'sequencer_proxy_setup': 'MANUAL' 19 | }, 20 | 'filepaths': { 21 | 'save_version': 0 22 | } 23 | } 24 | 25 | keyconfigs_assignation = { 26 | 'select_mouse': 'RIGHT', 27 | 'use_file_single_click': True # Open folder on single click 28 | } 29 | 30 | 31 | def set_preferences(prefs, assignation): 32 | for k, v in assignation.items(): 33 | if not isinstance(v, dict): 34 | setattr(prefs, k, v) 35 | continue 36 | for attr_name, attr_value in v.items(): 37 | obj = attrgetter(k)(prefs) 38 | setattr(obj, attr_name, attr_value) 39 | 40 | 41 | preferences = bpy.context.preferences 42 | wm = bpy.context.window_manager 43 | kc_preferences = wm.keyconfigs['Blender'].preferences 44 | 45 | set_preferences(preferences, preferences_assignation) 46 | set_preferences(kc_preferences, keyconfigs_assignation) 47 | 48 | bpy.ops.wm.save_userpref() 49 | --------------------------------------------------------------------------------