import bpy
from blenderneuron.blender.blenderroot import BlenderRoot
from blenderneuron.blender.blenderrootgroup import *
from blenderneuron.commnode import CommNode
[docs]class BlenderNode(CommNode):
def __init__(self, *args, **kwargs):
super(BlenderNode, self).__init__("Blender", *args, **kwargs)
@property
def offline_results(self):
from blenderneuron.blender.dataset import is_offline
return is_offline()
[docs] def try_setup_client(self, warn=True):
if self.offline_results:
self.client = None
self.client_address = None
return
old_peer = getattr(self, '_last_live_peer', None)
disconnected = getattr(self, 'client', None) is None
result = super().try_setup_client(warn=warn)
if self.client is not None:
transport = vars(self.client).get('_ServerProxy__transport')
peer = (self.client_address, getattr(transport, 'epoch', None))
imported = any(group.state == 'imported' for group in self.groups.values())
if old_peer is not None and imported and (disconnected or peer != old_peer):
self.export_requires_confirmation = True
self._last_live_peer = peer
return result
@property
def ui_properties(self):
return bpy.data.scenes[0].BlenderNEURON
[docs] def add_group(self, name=None, include_groupless_roots=True):
self.update_root_index()
if name is None:
name = self.find_unique_group_name()
group = BlenderRootGroup(name, self)
# Attach group to node
self.groups[name] = group
# Add group to the UI list
group.add_to_UI()
if include_groupless_roots:
group.add_groupless_roots()
return group
[docs] def update_root_index(self):
from blenderneuron.blender.dataset import require_live
require_live(self)
# Keep track which roots have been removed from NRN
roots_to_delete = set(self.root_index.keys())
# Get the list of root sections from NEURON
try:
root_data = self.client.get_roots()
# Update new or existing root entries
for i, root_info in enumerate(root_data):
name = root_info["name"]
existing_root = self.root_index.get(name)
# Update existing root
if existing_root is not None:
existing_root.index = root_info["index"]
existing_root.name = root_info["name"]
# Don't remove roots that previously existed and are present
roots_to_delete.remove(name)
# Add a new root
else:
new_root = self.root_index[name] = BlenderRoot(
root_info["index"],
root_info["name"]
)
# Make sure it's listed as selectable in all groups
for group in self.groups.values():
new_root.add_to_UI_group(group.ui_group)
except ConnectionRefusedError:
root_data = []
finally:
# Delete removed roots
for name_to_delete in roots_to_delete:
self.root_index[name_to_delete].remove(node=self)
[docs] def find_unique_group_name(self):
i_name = len(self.groups.values())
while True:
name = "Group." + str(i_name).zfill(3)
if name in self.groups:
i_name += 1
else:
break
return name
[docs] def get_group_data_from_neuron(self, group_list):
# Convert blender groups to skeletal dicts (needed for XML rcp with NRN)
# These dicts contain basic information (e.g. no 3D data, activity)
blender_groups = self.get_group_dicts(group_list)
# Send a request to NRN for the selected groups
compressed = self.client.initialize_groups(blender_groups)
# Decompress the result
nrn_groups = self.decompress(compressed)
return nrn_groups
[docs] def import_groups_from_neuron(self, group_list):
from blenderneuron.blender.dataset import require_live
require_live(self)
nrn_groups = self.get_group_data_from_neuron(group_list)
# Update each blender node group with the data received from NRN
for nrn_group in nrn_groups:
node_group = self.groups[nrn_group["name"]]
print('Importing group: ' + node_group.name + ' from NEURON...')
# Remove any views of the cells
if node_group.view is not None:
node_group.view.remove()
node_group.view = None
# Update blender node group with the data received from NRN
node_group.from_full_NEURON_group(nrn_group)
if all(group in group_list for group in self.groups.values() if group.state == 'imported'):
self.export_requires_confirmation = False
[docs] def get_selected_groups(self):
return [group for group in self.groups.values() if group.selected]
[docs] def get_group_dicts(self, group_list):
return [group.to_dict() for group in group_list]
@property
def synapse_sets(self):
return bpy.context.scene.BlenderNEURON.synapse_sets
[docs] def add_synapse_set(self, name=None):
new_set = self.synapse_sets.add()
if name is None:
i_name = len(self.synapse_sets.values())
while True:
name = "SynapseSet." + str(i_name).zfill(3)
if name in self.synapse_sets.keys():
i_name += 1
else:
break
new_set.name = name
return new_set
[docs] def display_groups(self):
for group in self.groups.values():
if group.selected:
print('Showing group ' + group.name + ' in Blender')
group.show()
else:
group.remove_view()
[docs] def add_neon_effect(self):
"""
Adds a neon-style glare effect via the compositor.
"""
scene = bpy.context.scene
# Eevee bloom was removed in newer Blender versions. Keep using it
# where it exists, and fall back to the compositor glare setup below.
eevee_settings = getattr(scene, "eevee", None)
if eevee_settings is not None and hasattr(eevee_settings, "use_bloom"): # pragma: no cover
eevee_settings.use_bloom = True
use_blender_5_compositor = bpy.app.version[0] >= 5
if use_blender_5_compositor:
nt = scene.compositing_node_group
if nt is None:
nt = bpy.data.node_groups.new("BlenderNEURON Neon Composite", "CompositorNodeTree")
scene.compositing_node_group = nt
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type != 'VIEW_3D':
continue
space = area.spaces.active
if hasattr(space, "shading") and hasattr(space.shading, "use_compositor"):
space.shading.use_compositor = 'ALWAYS'
else: # pragma: no cover
scene.use_nodes = True
nt = scene.node_tree
nodes = nt.nodes
links = nt.links
# ------------------------------------------------------------
# Get or create Render Layers node
# ------------------------------------------------------------
rl = nodes.get('Render Layers')
if rl is None:
rl = nodes.new('CompositorNodeRLayers')
rl.name = 'Render Layers'
# ------------------------------------------------------------
# Get or create Composite output node
# ------------------------------------------------------------
comp = nodes.get('Composite')
if comp is None:
comp = nodes.new('NodeGroupOutput' if use_blender_5_compositor else 'CompositorNodeComposite')
comp.name = 'Composite'
if use_blender_5_compositor and 'Image' not in comp.inputs:
nt.interface.new_socket(name="Image", in_out='OUTPUT', socket_type='NodeSocketColor')
# ------------------------------------------------------------
# Optional Viewer node for compositor preview
# ------------------------------------------------------------
viewer = None
if not use_blender_5_compositor: # pragma: no cover
viewer = nodes.get('Viewer')
if viewer is None:
viewer = nodes.new('CompositorNodeViewer')
viewer.name = 'Viewer'
viewer.use_alpha = False
# ------------------------------------------------------------
# Get or create Glare node (no duplication)
# ------------------------------------------------------------
glare = None
for node in nodes:
if node.type == 'GLARE':
glare = node
break
if glare is None:
glare = nodes.new('CompositorNodeGlare')
glare.name = 'NeonGlare'
def set_node_setting(node, prop_name, prop_value, input_name=None, input_value=None):
if hasattr(node, prop_name): # pragma: no cover
setattr(node, prop_name, prop_value)
return
if input_name is not None and input_name in node.inputs:
node.inputs[input_name].default_value = input_value if input_value is not None else prop_value
# ------------------------------------------------------------
# Configure glare for neon-style streak effect
# ------------------------------------------------------------
set_node_setting(glare, 'glare_type', 'STREAKS', 'Type', 'Streaks')
set_node_setting(glare, 'quality', 'HIGH', 'Quality', 'High')
set_node_setting(glare, 'iterations', 3, 'Iterations')
set_node_setting(glare, 'color_modulation', 0.2, 'Color Modulation')
set_node_setting(glare, 'threshold', 0.1, 'Threshold')
set_node_setting(glare, 'streaks', 7, 'Streaks')
set_node_setting(glare, 'fade', 0.75, 'Fade')
set_node_setting(glare, 'mix', 0, 'Mix')
# ------------------------------------------------------------
# Remove old links to Composite/Viewer to avoid stacking
# ------------------------------------------------------------
for link in list(links):
output_nodes = {comp}
if viewer is not None:
output_nodes.add(viewer)
if link.to_node in output_nodes:
links.remove(link)
# ------------------------------------------------------------
# Connect nodes cleanly:
# Render Layers -> Glare -> Composite (+ Viewer before Blender 5)
# ------------------------------------------------------------
links.new(rl.outputs['Image'], glare.inputs['Image'])
links.new(glare.outputs['Image'], comp.inputs['Image'])
if viewer is not None: # pragma: no cover
links.new(glare.outputs['Image'], viewer.inputs['Image'])