diff --git a/ForNEURON/README.txt b/README.txt similarity index 100% rename from ForNEURON/README.txt rename to README.txt diff --git a/blenderneuron/blender/properties/rootgroup.py b/blenderneuron/blender/properties/rootgroup.py index 2ca1334..c062a96 100755 --- a/blenderneuron/blender/properties/rootgroup.py +++ b/blenderneuron/blender/properties/rootgroup.py @@ -57,6 +57,21 @@ class CUSTOM_NEURON_LayerAlignment(PropertyGroup, BlenderNodeClass): " deviate from their original positions" ) + spring_stiffness = FloatProperty( + default=10, + min=0, + name="Joint Stiffness", + description="Joint stiffness coefficient" + ) + + spring_damping = FloatProperty( + default=0.5, + min=0, + max=1, + name="Joint Damping", + description="Joint damping coefficient" + ) + physics_steps_per_sec = FloatProperty( default=120, min=1, diff --git a/blenderneuron/blender/views/curvecontainer.py b/blenderneuron/blender/views/curvecontainer.py index 49b433a..b3d0851 100755 --- a/blenderneuron/blender/views/curvecontainer.py +++ b/blenderneuron/blender/views/curvecontainer.py @@ -359,7 +359,7 @@ def unlink(self): self.linked = False - def add_tip(self, tip_template, joint_template): + def add_tip(self, tip_template, empty_obj): ob = self.get_object() if ob.type != 'MESH': @@ -381,39 +381,13 @@ def add_tip(self, tip_template, joint_template): bpy.context.scene.objects.link(tip_object) self.tip_name = tip_object.name - self.create_joint_between(ob, tip_object, tip_loc, joint_template, max_bend_angle=0) - - def create_joint_with(self, child, joint_template, max_bend_angle): - self.create_joint_between(self.get_object(), child.get_object(), child.origin, joint_template, max_bend_angle) - - def create_joint_between(self, parent_object, child_object, joint_location, joint_template, max_bend_angle): - - # Create and link an "Empty" object - which serves as the joint - - # COPY METHOD - # empty_template = bpy.data.objects.get(joint_template) - # if empty_template is not None: - # empty = create_many_copies(empty_template, 1)[0] - # # This does not work - the copied constraints don't seem to be functional - # # Too tired to report a bug... ugh - # # empty = empty_template.copy() - # # bpy.context.scene.objects.link(empty) - # - # # CREATE NEW METHOD - # else: - - # This appears to be the only reliable way to add a constraint - # ... it's terribly slow. Profiler shows constraint_add() takes the most time - # I have not found any other way to add stable joints ... ugh - bpy.ops.object.empty_add(type='SPHERE') - bpy.ops.rigidbody.constraint_add() - empty = bpy.context.object - empty.name = joint_template + self.create_joint_between(ob, tip_object, tip_loc, empty_obj) + def create_joint_with(self, child, empty_obj): + self.create_joint_between(self.get_object(), child.get_object(), child.origin, empty_obj) + def create_joint_between(self, parent_object, child_object, joint_location, empty): empty.location = joint_location - empty.empty_draw_type = 'SPHERE' - empty.empty_draw_size = 0.5 # Create parent-child relationship between the parent section and the empty empty.parent = parent_object @@ -421,30 +395,6 @@ def create_joint_between(self, parent_object, child_object, joint_location, join # Set the joint params constraint = empty.rigid_body_constraint - constraint.type = 'GENERIC' - - constraint.use_limit_lin_x = \ - constraint.use_limit_lin_y = \ - constraint.use_limit_lin_z = \ - constraint.use_limit_ang_x = \ - constraint.use_limit_ang_y = \ - constraint.use_limit_ang_z = True - - constraint.limit_lin_x_lower = \ - constraint.limit_lin_y_lower = \ - constraint.limit_lin_z_lower = 0 - - constraint.limit_lin_x_upper = \ - constraint.limit_lin_y_upper = \ - constraint.limit_lin_z_upper = 0 - - constraint.limit_ang_x_lower = \ - constraint.limit_ang_y_lower = \ - constraint.limit_ang_z_lower = -pi / 180 * max_bend_angle - - constraint.limit_ang_x_upper = \ - constraint.limit_ang_y_upper = \ - constraint.limit_ang_z_upper = pi / 180 * max_bend_angle constraint.object1 = parent_object # parent constraint.object2 = child_object diff --git a/blenderneuron/blender/views/objectview.py b/blenderneuron/blender/views/objectview.py index b12e9c8..c049e62 100755 --- a/blenderneuron/blender/views/objectview.py +++ b/blenderneuron/blender/views/objectview.py @@ -169,7 +169,10 @@ def containers_to_mesh(self): self.select_containers() # Convert the selected container curves to mesh - bpy.context.scene.objects.active = bpy.context.selected_objects[0] + active_ob = next(o for o in bpy.context.scene.objects if o.select) + bpy.context.scene.objects.active = active_ob + + #context = get_operator_context_override(selected_object=active_ob) bpy.ops.object.convert(target='MESH', keep_original=False) def mesh_containers_to_curves(self): diff --git a/blenderneuron/blender/views/physicsmeshsectionobjectview.py b/blenderneuron/blender/views/physicsmeshsectionobjectview.py index 7ae5ab1..accce20 100755 --- a/blenderneuron/blender/views/physicsmeshsectionobjectview.py +++ b/blenderneuron/blender/views/physicsmeshsectionobjectview.py @@ -2,6 +2,9 @@ import bpy, math import numpy as np from blenderneuron.blender.utils import create_many_copies +from queue import Queue +from blenderneuron.blender.utils import get_operator_context_override +from math import sqrt, pi class PhysicsMeshSectionObjectView(SectionObjectView): def __init__(self, group): @@ -17,13 +20,23 @@ def __init__(self, group): # Don't add the extra end caps self.closed_ends = False + self.joint_count = 0 self.tip_template = self.create_tip_template() self.joint_template = None + self.joint_pool = None @property def max_bend_angle(self): return self.group.ui_group.layer_aligner_settings.max_bend_angle + @property + def spring_stiffness(self): + return self.group.ui_group.layer_aligner_settings.spring_stiffness + + @property + def spring_damping(self): + return self.group.ui_group.layer_aligner_settings.spring_damping + @property def max_section_length(self): return self.group.ui_group.layer_aligner_settings.max_section_length @@ -45,6 +58,8 @@ def show(self): self.make_containers_rigid_bodies() + self.create_joint_pool() + # Joints will allow sections to rotate around the branch point self.add_branch_joints() @@ -55,19 +70,106 @@ def show(self): self.setup_physics_sim() + def create_joint_pool(self): + + # Create one copy of the joint with the rigid body constraint + self.create_joint_template() + + self.count_joints() + + # Use the particle method to create many copies efficiently + joint_pool = create_many_copies(self.joint_template, self.joint_count) + + # Assign the newly created empties to the constraints group (used by rigid world) + bpy.context.scene.objects.active = self.joint_template + bpy.ops.group.objects_add_active() + + # Create a pool of pre-created joints + q = Queue() + + for j in joint_pool: + q.put(j) + + self.joint_pool = q + + def count_joints(self, section=None): + if section is None: + self.joint_count = 0 + + for root in self.group.roots.values(): + self.count_joints(root) + + return self.joint_count + + # Add contributions by split sections + if section.was_split: + self.joint_count += len(section.split_sections)-1 + + child_count = len(section.children) + + # Each child gets a joint + self.joint_count += child_count + + # Each tip gets a joint + if child_count == 0: + self.joint_count += 1 + + for child in section.children: + self.count_joints(child) + def create_joint_template(self): - # This does not work - the copy()s of the template do not seem to - # copy over constraint data (values do but not behavior) - # empty = bpy.data.objects.new("JointTemplate", None) # None creates "Empty" - # - # # Add rigid body constraint to the empty - only works when linked to scene - # bpy.context.scene.objects.link(empty) - # bpy.context.scene.objects.active = empty - # bpy.ops.rigidbody.constraint_add() - # - # return empty.name - - return "JointTemplate" + empty = bpy.data.objects.new("JointTemplate", None) # None creates "Empty" + + # Add rigid body constraint to the empty - only works when linked to scene + bpy.context.scene.objects.link(empty) + bpy.context.scene.objects.active = empty + bpy.ops.rigidbody.constraint_add() + + empty.empty_draw_type = 'SPHERE' + empty.empty_draw_size = 0.5 + + # Set the joint params + constraint = empty.rigid_body_constraint + constraint.type = 'GENERIC_SPRING' + + constraint.use_spring_ang_x = \ + constraint.use_spring_ang_y = \ + constraint.use_spring_ang_z = True + + constraint.spring_stiffness_ang_x = \ + constraint.spring_stiffness_ang_y = \ + constraint.spring_stiffness_ang_z = self.spring_stiffness + + constraint.spring_damping_ang_x = \ + constraint.spring_damping_ang_y = \ + constraint.spring_damping_ang_z = self.spring_damping + + constraint.use_limit_lin_x = \ + constraint.use_limit_lin_y = \ + constraint.use_limit_lin_z = \ + constraint.use_limit_ang_x = \ + constraint.use_limit_ang_y = \ + constraint.use_limit_ang_z = True + + constraint.limit_lin_x_lower = \ + constraint.limit_lin_y_lower = \ + constraint.limit_lin_z_lower = 0 + + constraint.limit_lin_x_upper = \ + constraint.limit_lin_y_upper = \ + constraint.limit_lin_z_upper = 0 + + constraint.limit_ang_x_lower = \ + constraint.limit_ang_y_lower = \ + constraint.limit_ang_z_lower = -pi / 180 * self.max_bend_angle + + constraint.limit_ang_x_upper = \ + constraint.limit_ang_y_upper = \ + constraint.limit_ang_z_upper = pi / 180 * self.max_bend_angle + + self.joint_template = empty + + return empty.name def create_tip_template(self): mesh = bpy.data.meshes.new('TipTemplateMesh') @@ -79,7 +181,6 @@ def create_tip_template(self): return object.name - def create_container_for_each_section(self, root, recursive=True, is_top_level=True): if is_top_level: origin_type = "center" @@ -145,8 +246,6 @@ def add_branch_tips(self): self.add_branch_tip_mesh(root) def add_branch_tip_mesh(self, root): - if self.joint_template is None: - self.joint_template = self.create_joint_template() if not any(root.children): if root.was_split: @@ -154,7 +253,7 @@ def add_branch_tip_mesh(self, root): else: root_cont = self.containers[root.hash] - root_cont.add_tip(self.tip_template, self.joint_template) + root_cont.add_tip(self.tip_template, self.joint_pool.get()) del root_cont # Recursively create the tip meshes for leaf sections @@ -163,9 +262,6 @@ def add_branch_tip_mesh(self, root): def add_branch_joints(self): - if self.joint_template is None: - self.joint_template = self.create_joint_template() - # Recursively add the joints between parent-child sections for root in self.group.roots.values(): self.add_joints_to_children(root) @@ -178,8 +274,9 @@ def add_joints_to_children(self, root): for i, split_sec in enumerate(root.split_sections[:-1]): start_cont = self.containers[split_sec.hash] end_cont = self.containers[root.split_sections[i+1].hash] - start_cont.create_joint_with(end_cont, self.joint_template, self.max_bend_angle) + start_cont.create_joint_with(end_cont, self.joint_pool.get()) del start_cont, end_cont + # Then link the last split section with original children root_cont = self.containers[root.split_sections[-1].hash] @@ -192,7 +289,7 @@ def add_joints_to_children(self, root): child_hash = (child.split_sections[0] if child.was_split else child).hash child_cont = self.containers[child_hash] - root_cont.create_joint_with(child_cont, self.joint_template, self.max_bend_angle) + root_cont.create_joint_with(child_cont, self.joint_pool.get()) del child_cont del root_cont @@ -222,7 +319,7 @@ def remove(self): bpy.data.objects.remove(tip_temp) if self.joint_template is not None: - tmpl = bpy.data.objects.get(self.joint_template) + tmpl = bpy.data.objects.get(self.joint_template.name) if tmpl: bpy.data.objects.remove(tmpl) diff --git a/blenderneuron/config.json b/blenderneuron/config.json index bb7f3b6..6abeecf 100755 --- a/blenderneuron/config.json +++ b/blenderneuron/config.json @@ -1,25 +1,25 @@ -[ - { - "NEURON_last_command": "import blenderneuron as bn; print(bn.bl_info);", - "NEURON_launch_command": "nrniv -python -c 'from blenderneuron import neuronstart'", - "default_ip": { - "Blender": "192.168.0.100", - "Control": "127.0.0.1", - "NEURON": "192.168.162.128" - }, - "default_port": { - "Blender": "5201", - "Control": "", - "NEURON": "5202" - }, - "end_types": [ - "NEURON", - "Blender", - "Control" - ], - "imports": { - "Blender": "import bpy, mathutils", - "NEURON": "from neuron import h" - } - } +[ + { + "NEURON_last_command": "import blenderneuron as bn; print(bn.bl_info);", + "NEURON_launch_command": "nrniv -python -c 'from blenderneuron import neuronstart'", + "default_ip": { + "Blender": "127.0.0.1", + "Control": "127.0.0.1", + "NEURON": "192.168.162.128" + }, + "default_port": { + "Blender": "", + "Control": "", + "NEURON": "5202" + }, + "end_types": [ + "NEURON", + "Blender", + "Control" + ], + "imports": { + "Blender": "import bpy, mathutils", + "NEURON": "from neuron import h" + } + } ] \ No newline at end of file diff --git a/ForNEURON/setup.py b/setup.py similarity index 100% rename from ForNEURON/setup.py rename to setup.py