import bpy, math, random, os, json
from datetime import datetime
from pathlib import Path
from math import cos, sin, pi, radians, degrees
from mathutils import Matrix, Vector
from ... ui_framework.master import Master
from ... ui_framework import form_ui as form
from ... utility import addon
from ... utility.base_modal_controls import Base_Modal_Controls
# Input system
from ...utility.screen import dpi_factor
from ... ui_framework.graphics.draw import render_quad, render_text, draw_border_lines
from ... ui_framework.utils.geo import get_blf_text_dims
# Cursor Warp imports
from ... utils.toggle_view3d_panels import collapse_3D_view_panels
from ... utils.modal_frame_drawing import draw_modal_frame
from ... utils.cursor_warp import mouse_warp
from ... utility import method_handler
# Save system
invalid = {'\\', '/', ':', '*', '?', '"', '<', '>', '|', '.'}
completed = {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
cancel = {'RIGHTMOUSE', 'ESC'}
class FLight:
def __init__(self, light):
self.light = light
self.name = light.name
self.increment_amount = .25
if light.data.type in {'POINT', 'AREA'}:
self.increment_amount = 100
@property
def energy(self):
if not self.light: return 1
return round(self.light.data.energy, 1)
@energy.setter
def energy(self, val):
if not self.light: return
self.light.data.energy = val
def toggle_vis(self):
self.light.hide_viewport = not self.light.hide_viewport
def vis_hook(self):
return bool(self.light.hide_viewport)
description = """Blank Light
Creates a randomized light rig
LMB - Blank Light Rig Scroll
Shift - Expanded Dot UI (Tab)
Ctrl - Non Destructive Scroll
(keeps previous light placements and count)
Press H for help
"""
class HOPS_OT_Blank_Light(bpy.types.Operator):
"""Blank Light Rig"""
bl_idname = "hops.blank_light"
bl_label = "Blank Light"
bl_options = {"REGISTER", "UNDO", "BLOCKING"}
bl_description = description
def invoke(self, context, event):
# Props
self.form_exit = False
self.empty = None
self.lights = []
self.active_light = None
self.non_destructive = True if event.ctrl else False
self.mouse_active = True
self.available_light_types = {'AREA', 'SUN'}
self.exit_to_viewport_adjust = False
self.ran_world_setup = False
self.can_bloom = hasattr(context.scene.eevee, 'use_bloom')
# Fog
if bpy.app.version[0] < 4:
self.initial_world_setup(context)
self.set_fog_off = self.is_fog_on()
self.__fog_z = 0
self.set_initial_fog_z()
self.__fog_opacity = 0
self.set_initial_fog_opacity()
# JSON Props
self.json_data = None # Current loaded json data
self.json_current_file = None # Current json file path
self.json_file_dirs = self.get_json_file_dirs() # The current list of file directories
self.json_files = [] # All the file names
self.json_current_file_name = "" # Used for UI
self.json_file_name = None # Last saved file name
self.json_getting_file_name = False # If inputing file name
self.setup_json_draw_elements(context) # Setup input system
# Setup
self.capture_initial_state(context=context)
self.get_light_empty(context)
self.get_lights()
if self.lights == []:
self.randomize_all_lights()
# Scene Settings
self.use_scene_lights = context.space_data.shading.use_scene_lights
context.space_data.shading.use_scene_lights = True
# Form
self.form_lights = []
# Base Systems
self.setup_form(context, event)
self.master = Master(context)
self.master.only_use_fast_ui = True
self.base_controls = Base_Modal_Controls(context, event)
self.original_tool_shelf, self.original_n_panel = collapse_3D_view_panels()
self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.safe_draw_shader, (context,), 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def modal(self, context, event):
# Getting JSON file name (Shift S sets this.)
if self.json_getting_file_name == True:
self.save_json_file_with_user_input(context, event)
context.area.tag_redraw()
return {'RUNNING_MODAL'}
self.master.receive_event(event)
self.base_controls.update(context, event)
self.form.update(context, event)
# Dot open / close
if event.type == 'TAB' and event.value == 'PRESS':
if self.form.is_dot_open():
self.form.close_dot()
else:
self.form.open_dot()
# Navigation
if self.base_controls.pass_through:
if not self.form.active():
return {'PASS_THROUGH'}
# Confirm
elif self.base_controls.confirm:
if not self.form.active():
self.confirm_exit(context)
return {'FINISHED'}
# Cancel
elif self.base_controls.cancel:
if not self.form.active():
self.cancel_exit(context)
return {'CANCELLED'}
# Form Close
if self.form_exit:
self.confirm_exit(context)
return {'FINISHED'}
# Actions
if not self.form.is_dot_open():
self.actions(context, event)
# Mouse Navigation
elif event.type in {'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
if not self.form.active():
return {'PASS_THROUGH'}
self.FAS_display(context=context)
context.area.tag_redraw()
return {'RUNNING_MODAL'}
def actions(self, context, event):
if self.mouse_active:
mouse_warp(context, event)
if not self.non_destructive:
angle = self.base_controls.mouse * 2
if abs(angle) > 0:
self.rotate_light_rig(angle)
# Randomize / Scale / Switch Lights
if event.type in {'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'LEFT_ARROW', 'RIGHT_ARROW'} and event.value == 'PRESS':
if not self.form.active():
if self.non_destructive:
self.randomize_all_lights_non_additive(context)
else:
if event.ctrl == True:
self.switch_light_types(context)
elif event.shift == False:
self.randomize_all_lights()
elif event.shift == True:
if event.type == 'WHEELUPMOUSE':
self.rig_sca += .25
else:
self.rig_sca -= .25
# Set all colors to white
elif event.type == 'W' and event.value == 'PRESS':
self.set_all_colors_to_white()
# Toggle Extra Lights
elif event.type == 'E' and event.value == 'PRESS':
if self.available_light_types == {'AREA'}:
self.available_light_types = {'AREA', 'SUN'}
elif self.available_light_types == {'AREA', 'SUN'}:
self.available_light_types = {'AREA', 'SUN', 'POINT'}
elif self.available_light_types == {'AREA', 'SUN', 'POINT'}:
self.available_light_types = {'AREA'}
types = ""
for light_type in self.available_light_types:
types += str(light_type) + " "
bpy.ops.hops.display_notification(info=f'{types}')
# Increase light energy
elif event.type in self.base_controls.keyboard_increment:
if event.value == 'PRESS':
self.adjust_light_energy(increase=True)
# Decrease light energy
elif event.type in self.base_controls.keyboard_decrement:
if event.value == 'PRESS':
self.adjust_light_energy(increase=False)
# Desaturate colors
elif event.type == 'D' and event.value == 'PRESS':
self.desaturate_light_colors()
# Saturate colors / Save
elif event.type == 'S' and event.value == 'PRESS':
# Save JSON
if event.shift: self.start_save_system()
# Adjust colors
else: self.saturate_light_colors()
# Randomize colors
elif event.type == 'C' and event.value == 'PRESS':
self.randomize_light_colors()
# Add Eevee Bloom
elif self.can_bloom and event.type == 'Q' and event.value == 'PRESS':
self.toggle_bloom()
# Exit to viewport adjust
elif event.type == 'V' and event.value == 'PRESS':
self.exit_to_viewport_adjust = not self.exit_to_viewport_adjust
# Toggle Ortho / Perspective
elif event.type in {'P', 'NUMPAD_5'} and event.value == 'PRESS':
bpy.ops.view3d.view_persportho()
# Toggle volumetrics
elif event.type == 'F' and event.value == 'PRESS':
if bpy.app.version[0] < 4:
self.toggle_world_fog(context)
else:
bpy.ops.hops.display_notification(info=F'Unavailable Post 4.0', subtext = "Due to changes with Blender itself in later versions")
def confirm_exit(self, context):
self.common_exit(context)
if self.exit_to_viewport_adjust == True:
bpy.context.space_data.shading.use_scene_world = False
bpy.ops.hops.adjust_viewport('INVOKE_DEFAULT')
def cancel_exit(self, context):
self.common_exit(context)
self.restore_initial_state(context=context)
def common_exit(self, context):
self.master.run_fade()
self.form.shut_down(context)
self.remove_shader()
collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
context.space_data.shading.use_scene_lights = self.use_scene_lights
# --- SETTERS --- #
@property
def rig_loc(self):
return round(self.empty.location.z, 1)
@rig_loc.setter
def rig_loc(self, val):
self.empty.location.z = val
@property
def rig_rot(self):
return int(math.degrees(self.empty.rotation_euler.z))
@rig_rot.setter
def rig_rot(self, val):
self.empty.rotation_euler.z = math.radians(val)
if abs(math.degrees(self.empty.rotation_euler.z)) > 360:
self.empty.rotation_euler.z = 0
@property
def rig_sca(self):
return round(self.empty.scale[0], 1)
@rig_sca.setter
def rig_sca(self, val):
self.empty.scale = Vector((val, val, val))
@property
def fog_z(self):
return round(self.__fog_z, 1)
@fog_z.setter
def fog_z(self, val):
val = 0 if abs(val) > 99.9 else val
self.__fog_z = val
self.z_location_world()
@property
def fog_opacity(self):
return round(self.__fog_opacity, 2)
@fog_opacity.setter
def fog_opacity(self, val):
self.__fog_opacity = val
if self.__fog_opacity > 1: self.__fog_opacity = 1
elif self.__fog_opacity < 0: self.__fog_opacity = 0
self.world_opacity()
# --- FORM FUNCS --- #
def FAS_display(self, context):
self.master.setup()
if self.master.should_build_fast_ui():
# Main
win_list = []
point_lights = 0
area_lights = 0
sun_lights = 0
for light in self.lights:
if light.data.type == 'POINT':
point_lights += 1
elif light.data.type == 'AREA':
area_lights += 1
elif light.data.type == 'SUN':
sun_lights += 1
if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1:
win_list.append(f'P : {point_lights}')
win_list.append(f'A : {area_lights}')
win_list.append(f'S : {sun_lights}')
else:
win_list.append(f'Point Lights: {point_lights}')
win_list.append(f'Area Lights: {area_lights}')
win_list.append(f'Sun Lights: {sun_lights}')
if self.exit_to_viewport_adjust:
win_list.append(f'[V] To_Viewport {self.exit_to_viewport_adjust}')
# Help
help_items = {"GLOBAL" : [], "STANDARD" : []}
help_items["GLOBAL"] = [
("M", "Toggle lights list"),
("H", "Toggle help"),
("~", "Toggle UI Display Type"),
("O", "Toggle viewport rendering")]
help_items["STANDARD"] = [
('V', f'To_Viewport - {self.exit_to_viewport_adjust}'),
("W", "Set all lights to White"),
("+ -", "Adjust energy"),
("D", "Desaturate colors"),
("S", "Saturate colors"),
('Q', 'Bloom'),
('Shift + S', 'Save Configuration'),
("C", "Randomize light colors"),
("E", "Toggle additional lights"),
("5 / P", "Toggle Ortho"),
#('F', "Toggle Fog"),
("Scroll", "Next Blank Rig"),
("Ctrl + Scroll", "Switch light types"),
("Shift + Scroll", "Adjust Scale")]
if not self.can_bloom:
help_items["STANDARD"].remove(('Q', 'Bloom'))
if bpy.app.version[0] < 4:
help_items["STANDARD"].insert(-3, ['F', "Toggle Fog"]),
# Mods
mods_list = []
for light in self.lights:
mods_list.append([light.name, ""])
self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="ArrayCircle", mods_list=mods_list)
self.master.finished()
def setup_form(self, context, event):
self.form = form.Form(context, event, dot_open=event.shift)
def spacer(height=10):
row = self.form.row()
row.add_element(form.Spacer(height=height))
self.form.row_insert(row)
def gap(row, width=5):
row.add_element(form.Spacer(width=width))
row = self.form.row()
row.add_element(form.Label(text="BlankLight", width=195))
row.add_element(form.Button(text="X", width=20, tips=["Finalize and Exit"], callback=self.exit_button))
self.form.row_insert(row)
spacer()
row = self.form.row()
row.add_element(form.Button(img='small_dice', width=32, tips=["Click / Scroll", "Generate a new light setup"], callback=self.randomize_all_lights))
gap(row)
row.add_element(form.Button(text='E', width=22, tips=["Click / Scroll", "Adjust the energy levels"], callback=self.adjust_light_energy, pos_args=(True,), neg_args=(False,)))
gap(row)
row.add_element(form.Button(text='C', width=22, tips=["Click / Scroll", "Generate new colors only"], callback=self.randomize_light_colors))
gap(row)
row.add_element(form.Button(text='W', width=22, tips=["Click", "Remove all colors from lights"], callback=self.set_all_colors_to_white))
gap(row)
if self.can_bloom:
row.add_element(form.Button(text='B', width=22, tips=["Click", "Toggle EEVEE Bloom"], callback=self.toggle_bloom))
self.form.row_insert(row)
spacer()
row = self.form.row()
row.add_element(form.Label(text="L", width=20))
row.add_element(form.Input(obj=self, attr="rig_loc", width=45, increment=.1, font_size=12, tips=["Location"]))
row.add_element(form.Label(text="R", width=20))
row.add_element(form.Input(obj=self, attr="rig_rot", width=45, increment=18, font_size=12, tips=["Rotation"]))
row.add_element(form.Label(text="S", width=20))
row.add_element(form.Input(obj=self, attr="rig_sca", width=45, increment=.1, font_size=12, tips=["Scale"]))
self.form.row_insert(row)
spacer()
row = self.form.row()
group = self.lights_group()
self.lights_box = form.Scroll_Box(width=215, height=20 * 3, scroll_group=group, view_scroll_enabled=False)
row.add_element(self.lights_box)
self.form.row_insert(row)
spacer()
row = self.form.row()
row.add_element(form.Button(text="Presets", tips=["Toggle Presets list."], callback=self.presets_toggle))
gap(row)
row.add_element(form.Button(text="Save", tips=["Save the current light config."], callback=self.start_save_system))
self.form.row_insert(row)
spacer()
row = self.form.row()
group = self.json_group(context)
self.json_box = form.Scroll_Box(width=215, height=20 * 5, scroll_group=group, view_scroll_enabled=True)
row.add_element(self.json_box)
self.form.row_insert(row, label='PRESETS', active=False)
spacer()
if bpy.app.version[0] < 4:
row = self.form.row()
row.add_element(form.Button(text="Fog", tips=["Toggle Fog."], callback=self.toggle_world_fog, pos_args=(context,), neg_args=(context,), highlight_hook=self.fog_update_hook))
self.form.row_insert(row, label='FOG_PARTIAL', active=not self.set_fog_off)
row = self.form.row()
row.add_element(form.Button(text="Fog", width=40, tips=["Toggle Fog."], callback=self.toggle_world_fog, pos_args=(context,), neg_args=(context,), highlight_hook=self.fog_update_hook))
gap(row)
row.add_element(form.Button(text="R", width=20, tips=["Randomize Fog"], callback=self.randomize_fog))
gap(row)
row.add_element(form.Button(text="C", width=20, tips=["Randomize Fog Color"], callback=self.randomize_world_color))
gap(row)
row.add_element(form.Input(obj=self, attr="fog_z", width=50, increment=.1, font_size=12, tips=["Fog Location"]))
gap(row)
row.add_element(form.Input(obj=self, attr="fog_opacity", width=45, increment=.05, font_size=12, tips=["Fog Opacity"]))
self.form.row_insert(row, label='FOG_FULL', active=self.set_fog_off)
self.form.build()
def lights_group(self):
self.form_lights = [FLight(light) for light in self.lights]
group = form.Scroll_Group()
for light in self.form_lights:
row = group.row()
text = form.shortened_text(light.name, width=105, font_size=12)
tip = [light.name, "Click : Change light color"]
row.add_element(
form.Button(
text='O', highlight_text='X', width=20, height=20, use_padding=False, scroll_enabled=False,
callback=light.toggle_vis, highlight_hook=light.vis_hook))
row.add_element(
form.Button(
text=text, tips=tip, width=115, height=20, use_padding=False, scroll_enabled=False,
callback=self.set_random_light_color, pos_args=(light.light,)))
row.add_element(
form.Input(obj=light, attr="energy", width=60, height=20, increment=light.increment_amount, font_size=12, tips=["Energy"]))
group.row_insert(row)
return group
def json_group(self, context):
group = form.Scroll_Group()
for json_file in self.get_json_file_names(with_msg=False):
row = group.row()
text = form.shortened_text(json_file, width=185, font_size=12)
tip = [] if text == json_file else [json_file]
row.add_element(
form.Button(
text=text, tips=tip, width=195, height=20, use_padding=False, scroll_enabled=False,
callback=self.load_specified_json_file, pos_args=(context, json_file), neg_args=(context, json_file)))
group.row_insert(row)
return group
def rebuild_lights_group(self):
if not hasattr(self, 'lights_box'): return
self.lights_box.scroll_group.clear(bpy.context)
group = self.lights_group()
self.lights_box.scroll_group = group
self.form.build()
def rebuild_json_group(self, context):
if not hasattr(self, 'json_box'): return
self.json_box.scroll_group.clear(context)
group = self.json_group(context)
self.json_box.scroll_group = group
self.form.build()
def fog_row_full_toggle(self, preset_label=''):
self.form.db.clicked = False
if self.set_fog_off:
self.form.row_activation(label='FOG_PARTIAL', active=False)
self.form.row_activation(label='FOG_FULL', active=True)
self.form.build()
else:
self.form.row_activation(label='FOG_PARTIAL', active=True)
self.form.row_activation(label='FOG_FULL', active=False)
self.form.build()
def presets_toggle(self):
active = self.form.get_row_status(label='PRESETS')
self.form.row_activation(label='PRESETS', active=not active)
self.form.db.clicked = False
self.form.build()
def exit_button(self):
self.form_exit = True
# --- SHADER --- #
def safe_draw_shader(self, context):
method_handler(self.draw_shader,
arguments=(context,),
identifier='Blank Lights',
exit_method=self.remove_shader)
def remove_shader(self):
if self.draw_handle:
self.draw_handle = bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW")
def draw_shader(self, context):
self.form.draw()
if not self.form.is_dot_open():
draw_modal_frame(context)
# --- WORLD FOG --- #
def initial_world_setup(self, context):
if bpy.context.scene.world == None:
world = bpy.data.worlds.new('HopsWorld')
bpy.context.scene.world = world
world = bpy.context.scene.world
world.use_nodes = True
types = [n.type for n in world.node_tree.nodes]
found_output = True if 'OUTPUT_WORLD' in types else False
if found_output == False:
node = world.node_tree.nodes.new('ShaderNodeOutputWorld')
node.location = (0,0)
def set_initial_fog_z(self):
for node in self.hops_world_nodes():
if node.name == 'Mapping':
self.__fog_z = node.inputs['Location'].default_value.z
def set_initial_fog_opacity(self):
for node in self.hops_world_nodes():
if node.name == 'Volume Scatter':
self.__fog_opacity = node.inputs['Color'].default_value[0]
break
def toggle_world_fog(self, context):
'''Toggle the world fog values.'''
world = bpy.context.scene.world
node_tree = world.node_tree
nodes = node_tree.nodes
if 'HOPS WORLD' in nodes:
hops_group = nodes['HOPS WORLD']
for node in hops_group.node_tree.nodes:
if node.name == 'Volume Scatter':
node.inputs['Color'].default_value[0] = 0 if self.set_fog_off else 1
node.inputs['Color'].default_value[1] = 0 if self.set_fog_off else 1
node.inputs['Color'].default_value[2] = 0 if self.set_fog_off else 1
break
else:
self.setup_world_edit_mode(context)
self.set_fog_off = False
self.set_fog_off = not self.set_fog_off
self.fog_row_full_toggle()
def is_fog_on(self):
if bpy.context.scene.eevee.use_volumetric_lights:
if bpy.context.scene.eevee.use_volumetric_shadows:
if not bpy.context.scene.world: return False
if not bpy.context.scene.world.use_nodes: return False
node_tree = bpy.context.scene.world.node_tree
nodes = node_tree.nodes
if 'HOPS WORLD' in nodes:
hops_group = nodes['HOPS WORLD']
for node in hops_group.node_tree.nodes:
if node.name == 'Volume Scatter':
if node.inputs['Color'].default_value[0] == 1:
return True
return False
def fog_update_hook(self):
return bool(self.set_fog_off)
def setup_world_edit_mode(self, context: bpy.context):
'''Creates the initial world setup.'''
if self.ran_world_setup: return
bpy.context.scene.eevee.use_volumetric_lights = True
bpy.context.scene.eevee.use_volumetric_shadows = True
bpy.context.scene.world.use_nodes = True
bpy.context.space_data.shading.use_scene_lights = True
bpy.context.space_data.shading.use_scene_world = True
# Data
group_name = "HOPS WORLD"
world_output = None
# Search for the node group and remove it if found
node_tree = context.scene.world.node_tree
nodes = node_tree.nodes
for node in nodes:
if node.type == 'GROUP':
if node.name == group_name:
node_tree.nodes.remove(node)
break
# Remove the node group from the blend data
for tree in bpy.data.node_groups:
if tree.name == group_name:
bpy.data.node_groups.remove(tree)
# Get the world output
for node in nodes:
if node.type == 'OUTPUT_WORLD':
world_output = node
break
# If the world output was not found
if world_output == None:
node_tree.nodes.new('ShaderNodeOutputWorld')
# Create the node group
self.create_volume_node_group(group_name=group_name)
# Insert node group into world node tree
volume_group = node_tree.nodes.new("ShaderNodeGroup")
volume_group.node_tree = bpy.data.node_groups[group_name]
volume_group.location = (world_output.location[0] - 250, world_output.location[1] - 100)
volume_group.name = group_name
# Link : Volume -> World
node_tree.links.new(volume_group.outputs['shader_out'], world_output.inputs['Volume'])
self.ran_world_setup = True
def create_volume_node_group(self, group_name=""):
'''Creates the volume node group and insert it into the blend data'''
# Create : Group
node_group = bpy.data.node_groups.new(group_name, 'ShaderNodeTree')
# Create : Group Outputs
group_outputs = node_group.nodes.new('NodeGroupOutput')
group_outputs.location = (700,0)
node_group.outputs.new('NodeSocketShader','shader_out')
# Create : Group Inputs
group_inputs = node_group.nodes.new('NodeGroupInput')
group_inputs.location = (-700,200)
node_group.inputs.new('NodeSocketFloat','density')
node_group.inputs.new('NodeSocketFloat','scale_one')
node_group.inputs.new('NodeSocketFloat','scale_two')
node_group.inputs['density'].min_value = 0
node_group.inputs['density'].max_value = 1
node_group.inputs['density'].default_value = 1
node_group.inputs['scale_one'].min_value = 0
node_group.inputs['scale_one'].max_value = 2
node_group.inputs['scale_one'].default_value = .05
node_group.inputs['scale_two'].min_value = 0
node_group.inputs['scale_two'].max_value = 2
node_group.inputs['scale_two'].default_value = .25
# Create : Texture Coords
text_coords_node = node_group.nodes.new('ShaderNodeTexCoord')
text_coords_node.location = (-700,0)
# Create : Mapping
mapping_node = node_group.nodes.new('ShaderNodeMapping')
mapping_node.inputs['Scale'].default_value = (.2, .2, 1)
self.__fog_z = mapping_node.inputs['Location'].default_value.z
mapping_node.location = (-500,0)
# Create : Musgrave One
musgrave_node_one = node_group.nodes.new('ShaderNodeTexMusgrave')
musgrave_node_one.musgrave_type = 'RIDGED_MULTIFRACTAL'
musgrave_node_one.inputs['Scale'].default_value = .05
musgrave_node_one.inputs['Offset'].default_value = .25
musgrave_node_one.location = (-300,-200)
# Create : Musgrave Two
musgrave_node_two = node_group.nodes.new('ShaderNodeTexMusgrave')
musgrave_node_two.musgrave_type = 'RIDGED_MULTIFRACTAL'
musgrave_node_two.inputs['Scale'].default_value = .25
musgrave_node_two.inputs['Offset'].default_value = .3
musgrave_node_two.location = (-300,200)
# Create : Mix
mix_node = node_group.nodes.new('ShaderNodeMixRGB')
mix_node.blend_type = 'MIX'
mix_node.inputs['Color2'].default_value = (.2,.2,.2,1)
mix_node.location = (0,0)
# Create : Mix Density
mix_density = node_group.nodes.new('ShaderNodeMixRGB')
mix_density.blend_type = 'MIX'
mix_density.inputs['Color1'].default_value = (0,0,0,1)
mix_density.location = (250,0)
# Create : Volume Scatter
volume_node = node_group.nodes.new('ShaderNodeVolumeScatter')
volume_node.location = (450,0)
# Links
node_group.links.new(text_coords_node.outputs['Generated'], mapping_node.inputs['Vector'])
node_group.links.new(mapping_node.outputs['Vector'], musgrave_node_two.inputs['Vector'])
node_group.links.new(mapping_node.outputs['Vector'], musgrave_node_one.inputs['Vector'])
node_group.links.new(musgrave_node_one.outputs['Fac'], mix_node.inputs['Color1'])
node_group.links.new(musgrave_node_two.outputs['Fac'], mix_node.inputs['Fac'])
node_group.links.new(mix_node.outputs['Color'], mix_density.inputs['Color2'])
node_group.links.new(mix_density.outputs['Color'], volume_node.inputs['Density'])
node_group.links.new(volume_node.outputs['Volume'], group_outputs.inputs['shader_out'])
# Inputs
node_group.links.new(group_inputs.outputs['density'], mix_density.inputs['Fac'])
node_group.links.new(group_inputs.outputs['scale_one'], musgrave_node_one.inputs['Scale'])
node_group.links.new(group_inputs.outputs['scale_two'], musgrave_node_two.inputs['Scale'])
def randomize_fog(self):
for node in self.hops_world_nodes():
if node.name == 'Mapping':
node.inputs['Scale'].default_value = (random.uniform(.25, 1), random.uniform(.25, 1), 1)
elif node.name == "Group Input":
node.outputs['scale_one'].default_value = random.uniform(.001, .1)
node.outputs['scale_two'].default_value = random.uniform(.001, .3)
elif node.name == 'Musgrave Texture':
node.inputs['Offset'].default_value = random.uniform(.05, .35)
elif node.name == 'Musgrave Texture.001':
node.inputs['Offset'].default_value = random.uniform(.001, .4)
elif node.name == 'Mix':
col = random.uniform(.05, .5)
node.inputs['Color2'].default_value = (col,col,col,1)
elif node.name == 'Volume Scatter':
col = random.uniform(.05, .5)
node.inputs['Color'].default_value = (col,col,col,1)
node.inputs['Anisotropy'].default_value = random.uniform(0, .8)
def randomize_world_color(self):
for node in self.hops_world_nodes():
if node.name == 'Volume Scatter':
node.inputs['Color'].default_value = (random.uniform(.5, 1), random.uniform(.5, 1), random.uniform(.5, 1) ,1)
break
def rotate_world(self, rot=18):
for node in self.hops_world_nodes():
if node.name == 'Mapping':
node.inputs['Rotation'].default_value.z += rot
break
def z_location_world(self, z_loc=0):
for node in self.hops_world_nodes():
if node.name == 'Mapping':
if z_loc:
node.inputs['Location'].default_value.z += z_loc
self.__fog_z = node.inputs['Location'].default_value.z
break
else:
node.inputs['Location'].default_value.z = self.__fog_z
def world_opacity(self, opacity=0):
for node in self.hops_world_nodes():
if node.name == 'Volume Scatter':
if opacity:
node.inputs['Color'].default_value[0] += opacity
node.inputs['Color'].default_value[1] += opacity
node.inputs['Color'].default_value[2] += opacity
break
else:
node.inputs['Color'].default_value[0] = self.__fog_opacity
node.inputs['Color'].default_value[1] = self.__fog_opacity
node.inputs['Color'].default_value[2] = self.__fog_opacity
break
def hops_world_nodes(self):
node_tree = bpy.context.scene.world.node_tree
nodes = node_tree.nodes
if 'HOPS WORLD' not in nodes: return []
return nodes['HOPS WORLD'].node_tree.nodes
def toggle_bloom(self):
bpy.context.scene.eevee.use_bloom = not bpy.context.scene.eevee.use_bloom
bpy.context.scene.eevee.bloom_intensity = 1.04762
bpy.context.scene.eevee.bloom_threshold = 1.94286
self.report({'INFO'}, F'Bloom: {bpy.context.scene.eevee.use_bloom}')
# --- RIG / LIGHT --- #
def get_light_empty(self, context, with_create_new=True, at_cursor=False):
'''Get the light collection or return the exsisting one.'''
empty_name = "HOPS Light Empty"
new_empty = None
collection = context.view_layer.active_layer_collection.collection
# Check if the empty exsist
for obj in collection.objects:
if obj.name[:16] == empty_name:
new_empty = obj
break
# Used for initial data storing
if with_create_new == False:
if new_empty == None:
return None
else:
return new_empty
# If no empty was in the collection make a new empty
if new_empty == None:
new_empty = bpy.data.objects.new(empty_name, None )
context.collection.objects.link(new_empty)
new_empty.empty_display_size = .5
new_empty.empty_display_type = 'SPHERE'
# Store it
self.empty = new_empty
# Locate it
if not self.non_destructive:
self.empty.location = context.scene.cursor.location
def get_lights(self):
'''Get all the light objects on parent object.'''
new_lights = []
for obj in self.empty.children:
if obj.type == 'LIGHT':
new_lights.append(obj)
self.lights = new_lights
def randomize_all_lights(self):
'''Delete all the lights and rebuild everything randomly.'''
for light in self.lights:
bpy.data.lights.remove(light.data)
self.lights = []
for light_type in self.available_light_types:
# Add random area lights
if light_type == 'AREA':
self.lights += self.add_random_area_lights()
# Add random sun lights
elif light_type == 'SUN':
self.lights += self.add_random_sun_lights()
# Add random point lights
elif light_type == 'POINT':
self.lights += self.add_random_point_lights()
self.rebuild_lights_group()
def randomize_all_lights_non_additive(self, context):
'''Randomize all the lights in non additive mode.'''
#self.switch_light_types(context, use_random=True)
self.randomize_light_energy()
for light in self.lights:
self.set_random_light_color(light)
def switch_light_types(self, context, use_random=False):
'''Keeps the lights in place but switches there type and resets the energy.'''
for light in self.lights:
types = list(self.available_light_types)
# Remove the current light from the list
if use_random == False:
if len(types) > 1:
if light.data.type in types:
index = types.index(light.data.type)
light.data.type = types[(index + 1) % len(types)]
else:
light.data.type = types[0]
else:
if len(types) > 1:
ran_index = random.randint(0, len(types) - 1)
# Make it harder for a sun to happen
if types[ran_index] == 'SUN':
pick = random.randint(0, 20)
if pick < 7:
light.data.type = 'SUN'
else:
light.data.type = 'AREA'
else:
light.data.type = types[ran_index]
elif len(types) == 1:
light.data.type = types[0]
if light.data.type == 'POINT':
light.name = "HOPS_LIGHT_POINT"
light.data.energy = random.uniform(50, 1000)
light.data.shadow_soft_size = .25
elif light.data.type == 'AREA':
light.name = "HOPS_LIGHT_AREA"
light.data.size = random.uniform(1, 10)
light.data.energy = random.uniform(50, 7000)
elif light.data.type == 'SUN':
light.name = "HOPS_LIGHT_SUN"
light.data.energy = random.uniform(1, 5)
def add_random_point_lights(self):
lights = []
light_count = random.randint(0, 6)
radius = random.uniform(10, 30)
# Place in circle
for i in range(light_count):
# Sweep angle
angle = i * math.pi * 2 / light_count
angle += random.uniform(-.5, .5)
# Location
x_loc = cos(angle) * radius
y_loc = sin(angle) * radius
z_loc = random.uniform(2, 6)
location = (x_loc, y_loc, z_loc)
# Create light
new_light = self.add_light(location=location, light_type='POINT')
# Color
self.set_random_light_color(new_light)
# Energy
new_light.data.energy = random.uniform(50, 1000)
# Store
lights.append(new_light)
return lights
def add_random_sun_lights(self):
lights = []
light_count = 0
if len(self.available_light_types) > 2:
light_count = random.randint(0, 3)
else:
light_count = random.randint(0, 1)
radius = random.uniform(15, 20)
# Place in circle
for i in range(light_count):
# Sweep angle
angle = i * math.pi * 2 / light_count
angle += random.uniform(-.5, .5)
# Location
x_loc = cos(angle) * radius
y_loc = sin(angle) * radius
z_loc = random.uniform(.5, 30)
location = (x_loc, y_loc, z_loc)
# Create light
new_light = self.add_light(location=location, light_type='SUN')
# Color
self.set_random_light_color(new_light)
# Energy
new_light.data.energy = random.uniform(1, 5)
# Rotation
up_vec = Vector((0, 0, 1))
euler_rot = up_vec.rotation_difference(location).to_euler()
new_light.rotation_euler = euler_rot
# Store
lights.append(new_light)
return lights
def add_random_area_lights(self):
lights = []
light_count = random.randint(1, 4)
radius = random.uniform(15, 20)
# Place in circle
for i in range(light_count):
# Sweep angle
angle = i * math.pi * 2 / light_count
angle += random.uniform(-.5, .5)
# Location
x_loc = cos(angle) * radius
y_loc = sin(angle) * radius
z_loc = random.uniform(.5, 30)
location = (x_loc, y_loc, z_loc)
# Create light
new_light = self.add_light(location=location, light_type='AREA')
# Color
self.set_random_light_color(new_light)
# Energy
new_light.data.energy = random.uniform(50, 7000)
# Size
new_light.data.size = random.uniform(1, 10)
# Rotation
up_vec = Vector((0, 0, 1))
euler_rot = up_vec.rotation_difference(location).to_euler()
new_light.rotation_euler = euler_rot
# Store
lights.append(new_light)
return lights
def rotate_light_rig(self, angle=0):
'''Rotate the light rig.'''
if self.empty != None:
self.empty.rotation_euler.z += angle
# --- LOWER LEVEL FUNCTIONS --- #
def add_light(self, location=(0,0,0), look_target=(0,0,0), light_type='POINT'):
'''Create a new light, return light ID.'''
light_data = bpy.data.lights.new(name='HOPS_Light_Data', type=light_type)
light_obj = bpy.data.objects.new(f'HOPS_{light_type}', object_data=light_data)
light_obj.location = location
bpy.context.collection.objects.link(light_obj)
light_obj.parent = self.empty
# doesn't exist in 4.3+
try:
light_obj.data.use_contact_shadow = True
except:
pass
if addon.preference().property.to_light_constraint:
con = light_obj.constraints.new(type='TRACK_TO')
con.target = self.empty
con.up_axis = 'UP_Y'
con.track_axis = 'TRACK_NEGATIVE_Z'
return light_obj
def set_random_light_color(self, light):
'''Set light color.'''
r = random.uniform(0, 1)
g = random.uniform(0, 1)
b = random.uniform(0, 1)
light.data.color = (r, g, b)
def set_all_colors_to_white(self):
'''Sets all the color values to white.'''
for light in self.lights:
light.data.color = (1, 1, 1)
def adjust_light_energy(self, increase=True):
'''Increase the energy of each light based on increase.'''
for light in self.lights:
if light.data.type == 'POINT':
value = 100 if increase else -(light.data.energy / 15)
light.data.energy += value
elif light.data.type == 'AREA':
value = 100 if increase else -(light.data.energy / 15)
light.data.energy += value
elif light.data.type == 'SUN':
value = .25 if increase else -(light.data.energy / 15)
light.data.energy += value
if light.data.energy < 0:
light.data.energy = 0
def randomize_light_energy(self):
'''Set random light energy.'''
for light in self.lights:
if light.data.type == 'POINT':
light.data.energy =random.uniform(50, 1000)
elif light.data.type == 'AREA':
light.data.energy =random.uniform(50, 7000)
elif light.data.type == 'SUN':
light.data.energy = random.uniform(1, 5)
def desaturate_light_colors(self):
'''Desaturate the light colors by moving values to white.'''
for light in self.lights:
for index, value in enumerate(light.data.color):
diff = 1 - value
if diff > 0:
light.data.color[index] += diff / 10
def saturate_light_colors(self):
'''Saturate the light colors by moving values to its closer side.'''
for light in self.lights:
if light.data.color.s < 1:
diff = 1 - light.data.color.s
light.data.color.s += diff / 10
def randomize_light_colors(self):
'''Randomize the light colors.'''
for light in self.lights:
self.set_random_light_color(light)
# --- RECOVERY FUNCTIONS --- #
def capture_initial_state(self, context):
'''Capture all the settings from the intitial build.'''
self.initial_light_data = []
self.initial_empty_data = None
empty = self.get_light_empty(context, with_create_new=False)
# No light empty was found : will remove every thing on cancel
if empty == None:
return
else:
class Empty_Data:
def __init__(self):
self.name = None
self.loc = None
self.scale = None
self.rot = None
self.initial_empty_data = Empty_Data()
self.initial_empty_data.name = empty.name
self.initial_empty_data.loc = (empty.location[0], empty.location[1], empty.location[2])
self.initial_empty_data.scale = (empty.scale[0], empty.scale[1], empty.scale[2])
self.initial_empty_data.rot = (empty.rotation_euler[1], empty.rotation_euler[1], empty.rotation_euler[2])
# Copy light data from original lights
for obj in empty.children:
if obj.type == 'LIGHT':
light_data = self.get_initial_light_data(obj)
if light_data != None:
self.initial_light_data.append(light_data)
def restore_initial_state(self, context):
'''Revert back all the changes made in the modal on cancel.'''
# Delete everything since it started with nothing
if self.initial_empty_data == None:
for light in self.lights:
bpy.data.lights.remove(light.data)
bpy.data.objects.remove(self.empty)
# Revert back to original state
else:
# Make sure no errors occured
if self.empty == None:
return
# Revert the empty
self.empty.name = self.initial_empty_data.name
self.empty.location = self.initial_empty_data.loc
self.empty.scale = self.initial_empty_data.scale
self.empty.rotation_euler = self.initial_empty_data.rot
# Remove all the modified lights
for light in self.lights:
bpy.data.lights.remove(light.data)
# Add back the original lights
for light_data in self.initial_light_data:
light = self.add_light(
location=light_data.loc,
look_target=self.empty.location,
light_type=light_data.type)
light.name = light_data.name
light.data.energy = light_data.energy
light.data.color = light_data.color
# create a location matrix
mat_loc = Matrix.Translation(light_data.loc)
# create an identitiy matrix
mat_sca = Matrix.Scale(1, 4, light_data.scale)
# create a rotation matrix
mat_rot = light_data.rot.to_matrix()
mat_rot = mat_rot.to_4x4()
# combine transformations
mat_out = mat_loc @ mat_rot @ mat_sca
light.matrix_local = mat_out
def get_initial_light_data(self, light):
'''Create a copy of all the light data for the light.'''
# Validate type and data members
validated = False
if hasattr(light, 'type'):
if light.type == 'LIGHT':
if hasattr(light, 'data'):
if hasattr(light.data, 'energy'):
if hasattr(light.data, 'color'):
validated = True
if validated == False:
return None
class Light_Data:
def __init__(self):
self.name = None
self.type = None
self.energy = None
self.color = None
self.loc = None
self.scale = None
self.rot = None
light_data = Light_Data()
light_data.name = light.name
light_data.type = light.data.type
light_data.energy = light.data.energy
light_data.color = (light.data.color[0], light.data.color[1], light.data.color[2])
light_data.loc = (light.location[0], light.location[1], light.location[2])
light_data.scale = (light.scale[0], light.scale[1], light.scale[2])
light_data.rot = light.matrix_local.decompose()[1]
return light_data
# --- JSON FUNCTIONS --- #
def start_save_system(self):
self.json_file_name = None
self.json_getting_file_name = True
def get_next_json_configuration(self, context, forward=True):
'''Get the next configuration from the json file.'''
# Validate file dirs
self.json_file_dirs = self.get_json_file_dirs()
if len(self.json_file_dirs) < 1:
bpy.ops.hops.display_notification(info='No Light files.')
return
# Set current file
if self.json_current_file == None:
self.json_current_file = self.json_file_dirs[0]
# Go to next file
else:
# Keep moving
if self.json_current_file in self.json_file_dirs:
index = self.json_file_dirs.index(self.json_current_file)
increment = 1 if forward else -1
self.json_current_file = self.json_file_dirs[(index + increment) % len(self.json_file_dirs)]
# File was not there restart
else:
self.json_current_file = self.json_file_dirs[0]
# Set the file name
file_with_extension = os.path.basename(self.json_current_file)
self.json_current_file_name = file_with_extension.split('.')[0]
# Open file for write
self.json_data = None
with open(self.json_current_file, 'r') as json_file:
self.json_data = json.load(json_file)
# Load data
if self.json_data != None:
self.setup_rig_to_match_json_data(context, self.json_data)
head, tail = file_name = os.path.split(self.json_current_file)
if self.master.should_build_fast_ui():
bpy.ops.hops.display_notification(info=F'{tail.split(".")[0]}')
else:
bpy.ops.hops.display_notification(info=F'ERROR: Bad Data @ {self.json_current_file}')
def setup_rig_to_match_json_data(self, context, data={}):
'''Setup the light rig to math the json data.'''
# Clear the lights out
for light in self.lights:
bpy.data.lights.remove(light.data)
self.lights = []
for light_name, props_dict in data.items():
if type(props_dict) != dict:
print("Props for key are invalid")
continue
# Validate
validated = False
if "type" in props_dict:
if "loc" in props_dict:
validated = True
if validated == False:
continue
light = self.add_light(
location=props_dict['loc'],
look_target=self.empty.location,
light_type=props_dict['type'])
# Validate
validated = False
if "rot" in props_dict:
if "scale" in props_dict:
if "color" in props_dict:
if "energy" in props_dict:
if "size" in props_dict:
validated = True
if validated == False:
continue
light.rotation_euler = props_dict['rot']
light.scale = props_dict['scale']
light.data.color = props_dict['color']
light.data.energy = props_dict['energy']
if hasattr(light.data, 'size'):
light.data.size = props_dict['size']
self.lights.append(light)
def save_json_file_with_user_input(self, context, event):
'''Freeze the modal and take input.'''
# Add draw handle
if not hasattr(self, 'json_draw_handle'):
self.json_draw_handle = None
if self.json_draw_handle == None:
self.json_draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.safe_draw_file_name, (context,), 'WINDOW', 'POST_PIXEL')
# Cancelled
if event.type in cancel and event.value == 'PRESS':
self.json_getting_file_name = False
# Remove shader
self.remove_file_name_shader()
bpy.ops.hops.display_notification(info="Cancelled")
return
# Finished
if event.type in completed and event.value == 'PRESS':
if self.json_file_name == None or self.json_file_name == "":
form = '%B %d %Y - %H %M %S %f'
self.json_file_name = datetime.now().strftime(form)
self.json_getting_file_name = False
# Append
elif event.ascii not in invalid and event.value == 'PRESS':
if self.json_file_name == None:
self.json_file_name = ""
self.json_file_name += event.ascii
# Backspace
if event.type == 'BACK_SPACE' and event.value == 'PRESS':
if self.json_file_name != None:
self.json_file_name = self.json_file_name[:len(self.json_file_name)-1]
else:
self.json_file_name = ""
# Completed Input : SAVE FILE
if self.json_getting_file_name == False:
# Remove shader
self.remove_file_name_shader()
# Save the file
self.save_rig_to_json(context)
bpy.ops.hops.display_notification(info="File Saved!")
self.rebuild_json_group(context)
# Set text to draw
if self.json_file_name == None:
self.json_shader_file_text = "Auto"
else:
self.json_shader_file_text = self.json_file_name
# Hack for the UI
self.master.receive_event(event)
self.FAS_display(context)
self.form.update(context, event)
def setup_json_draw_elements(self, context):
'''Setup json drawing data from invoke.'''
factor = dpi_factor(min=.25)
self.json_shader_file_text = ""
self.json_shader_help_text = "Type in file name or Return for Date Time formatting."
self.screen_width = context.area.width
self.screen_height = context.area.height
def safe_draw_file_name(self, context):
method_handler(self.draw_file_name_shader,
arguments=(context,),
identifier='File Save',
exit_method=self.remove_file_name_shader)
def remove_file_name_shader(self):
if self.json_draw_handle:
self.json_draw_handle = bpy.types.SpaceView3D.draw_handler_remove(self.json_draw_handle, "WINDOW")
def draw_file_name_shader(self, context):
'''Draw shader handle.'''
factor = dpi_factor()
help_text_size = 18
file_text_size = 24
sample_y = get_blf_text_dims("XyZ`Qq", file_text_size)[1]
help_text_dims = get_blf_text_dims(self.json_shader_help_text, help_text_size)
file_text_dims = get_blf_text_dims(self.json_shader_file_text, file_text_size)
center_x = self.screen_width * .5
center_y = self.screen_height * .5
text_padding_y = 30 * factor
text_padding_x = 20 * factor
total_height = text_padding_y * 3 + sample_y + sample_y
widest_text = help_text_dims[0] if help_text_dims[0] > file_text_dims[0] else file_text_dims[0]
total_width = text_padding_x * 2 + widest_text
# TL, BL, TR, BR
verts = [
(center_x - total_width * .5, center_y + total_height * .5),
(center_x - total_width * .5, center_y - total_height * .5),
(center_x + total_width * .5, center_y + total_height * .5),
(center_x + total_width * .5, center_y - total_height * .5)]
render_quad(
quad=verts,
color=(0,0,0,.5))
draw_border_lines(
vertices=verts,
width=2,
color=(0,0,0,.75))
x_loc = center_x - help_text_dims[0] * .5
y_loc = center_y - help_text_dims[1] * .5 + file_text_size * factor
render_text(
text=self.json_shader_help_text,
position=(x_loc, y_loc),
size=help_text_size,
color=(1,1,1,1))
x_loc = center_x - file_text_dims[0] * .5
y_loc = center_y - file_text_dims[1] * .5 - file_text_size * factor
render_text(
text=self.json_shader_file_text,
position=(x_loc, y_loc),
size=file_text_size,
color=(1,1,1,1))
def save_rig_to_json(self, context):
'''Save the rig to a json file.'''
# Get path to light folder
folder_path = self.get_json_folder_path()
if folder_path == None:
return
# Validate json file name
if self.json_file_name == "" or self.json_file_name == None:
return
# Get the rig data as a dict
rig_data = self.get_light_rig_data_for_json()
if rig_data == None:
return
# Save the file
self.save_json_file(folder_path=folder_path, rid_data=rig_data)
def get_json_folder_path(self):
'''Return the folder path for the JSON files.'''
prefs = addon.preference()
folder = Path(prefs.property.lights_folder).resolve()
if os.path.exists(folder):
return folder
try:
folder.mkdir(parents=True, exist_ok=True)
return folder
except:
print(f'Unable to create {folder}')
return None
def ensure_json_file_name(self, folder_path=""):
'''Makes sure no other file with the same name exsist.'''
if folder_path == "":
return None
file_path = os.path.join(folder_path, self.json_file_name + '.json')
if os.path.exists(file_path):
form = ' %B %d %Y - %H %M %S %f'
self.json_file_name += datetime.now().strftime(form)
def get_json_file_names(self, with_msg=True):
'''Get all the json file names.'''
# Check for json files
folder = self.get_json_folder_path()
if folder == None:
return []
all_files = [f for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))]
json_files = []
for f in all_files:
filename, file_extension = os.path.splitext(f)
if file_extension == '.json':
json_files.append(filename)
if json_files == [] and with_msg:
bpy.ops.hops.display_notification(info='Save a light first: Shift + S')
return json_files
def get_json_file_dirs(self):
'''Set the list of file directories.'''
# Check for json files
folder = self.get_json_folder_path()
if folder == None:
return
all_files = [f for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))]
json_files = []
for f in all_files:
filename, file_extension = os.path.splitext(f)
if file_extension == '.json':
json_files.append(os.path.join(folder, f))
return json_files
def get_light_rig_data_for_json(self):
'''Capture the light rig data as a json ready dictionary.'''
# Validate
if self.empty == None:
return None
config = {}
for obj in self.empty.children:
if obj.type == 'LIGHT':
config[obj.name] = {
'loc' : (obj.location[0], obj.location[1], obj.location[2]),
'rot' : (obj.rotation_euler[0], obj.rotation_euler[1], obj.rotation_euler[2]),
'scale' : (obj.scale[0], obj.scale[1], obj.scale[2]),
'color' : (obj.data.color[0], obj.data.color[1], obj.data.color[2]),
'energy' : obj.data.energy,
'type' : obj.data.type,
'size' : obj.data.size if hasattr(obj.data, 'size') else 0
}
return config
def save_json_file(self, folder_path="", rid_data={}):
'''Save the rig data to the file.'''
# Make sure the file wont write over and exsisting file
self.ensure_json_file_name(folder_path=folder_path)
# Create file and dump jason into it
file_path = os.path.join(folder_path, self.json_file_name + '.json')
with open(file_path, 'w') as json_file:
json.dump(rid_data, json_file, indent=4)
# Update the directories list
self.json_file_dirs = self.get_json_file_dirs()
# Update files
self.json_files = self.get_json_file_names()
def load_specified_json_file(self, context, file_name=""):
'''Load the specified file name.'''
# Check if the files are loaded
if self.json_file_dirs == [] or self.json_file_dirs == None:
bpy.ops.hops.display_notification(info="Could not load file.")
return
# Get file dir
load_dir = ""
for f_name in self.json_file_dirs:
if file_name in f_name:
load_dir = f_name
break
if load_dir == "":
bpy.ops.hops.display_notification(info="Could not load file.")
return
self.json_data = None
self.json_current_file = load_dir
# Open file for write
self.json_data = None
with open(self.json_current_file, 'r') as json_file:
self.json_data = json.load(json_file)
# Load data
if self.json_data != None:
self.setup_rig_to_match_json_data(context, self.json_data)
else:
bpy.ops.hops.display_notification(info=F'ERROR: Bad Data @ {self.json_current_file}')
self.json_current_file_name = file_name
self.rebuild_lights_group()