From 2fccedbbddf1564a5853f237153bbfee5d31d785 Mon Sep 17 00:00:00 2001 From: u3dreal <48415446+u3dreal@users.noreply.github.com> Date: Thu, 2 Nov 2023 22:05:51 +0100 Subject: [PATCH] pack folder update --- molecularplus/__init__.py | 76 +++++ molecularplus/addon_prefrences.py | 23 ++ molecularplus/creators.py | 188 +++++++++++++ molecularplus/descriptions.py | 78 ++++++ molecularplus/names.py | 19 ++ molecularplus/operators.py | 439 +++++++++++++++++++++++++++++ molecularplus/properties.py | 389 ++++++++++++++++++++++++++ molecularplus/simulate.py | 160 +++++++++++ molecularplus/ui.py | 447 ++++++++++++++++++++++++++++++ molecularplus/utils.py | 21 ++ 10 files changed, 1840 insertions(+) create mode 100644 molecularplus/__init__.py create mode 100644 molecularplus/addon_prefrences.py create mode 100644 molecularplus/creators.py create mode 100644 molecularplus/descriptions.py create mode 100644 molecularplus/names.py create mode 100644 molecularplus/operators.py create mode 100644 molecularplus/properties.py create mode 100644 molecularplus/simulate.py create mode 100644 molecularplus/ui.py create mode 100644 molecularplus/utils.py diff --git a/molecularplus/__init__.py b/molecularplus/__init__.py new file mode 100644 index 0000000..b0aba44 --- /dev/null +++ b/molecularplus/__init__.py @@ -0,0 +1,76 @@ +# ====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ======================= END GPL LICENSE BLOCK ======================== +bl_info = { + "name": "Molecular+", + "author": + "Jean-Francois Gallant (PyroEvil), " + "Gregor Quade (u3dreal)", + "version": (1, 12, 1), + "blender": (2, 80, 0), + "location": "Properties editor > Physics Tab", + "description": + "Addon for calculating collisions " + "and for creating links between particles", + "warning": "", # used for warning icon and text in addons panel + "wiki_url": "http://q3de.com/research/molecular/", + "tracker_url": "https://discord.gg/tAwvNEAfA3", + "category": "Physics" +} + + +def register(): + + import bpy + from . import properties, ui, operators, creators, addon_prefrences + + properties.define_props() + + for operator in operators.operator_classes: + bpy.utils.register_class(operator) + + for panel in ui.panel_classes: + bpy.utils.register_class(panel) + + for panel in creators.create_classes: + bpy.utils.register_class(panel) + + bpy.utils.register_class(addon_prefrences.pref_classes) + + bpy.types.PHYSICS_PT_add.append(ui.append_to_PHYSICS_PT_add_panel) + + +def unregister(): + + import bpy + from . import ui, operators, creators, addon_prefrences + + bpy.types.PHYSICS_PT_add.remove(ui.append_to_PHYSICS_PT_add_panel) + + for operator in reversed(operators.operator_classes): + bpy.utils.unregister_class(operator) + + for panel in reversed(ui.panel_classes): + bpy.utils.unregister_class(panel) + + for panel in reversed(creators.create_classes): + bpy.utils.unregister_class(panel) + + bpy.utils.unregister_class(addon_prefrences.pref_classes) + +if __name__ == "__main__": + register() diff --git a/molecularplus/addon_prefrences.py b/molecularplus/addon_prefrences.py new file mode 100644 index 0000000..1654a2f --- /dev/null +++ b/molecularplus/addon_prefrences.py @@ -0,0 +1,23 @@ +import bpy +from os.path import basename, dirname +from bpy.types import AddonPreferences +from bpy.props import IntProperty + + +class MolecularAddonPreferences(AddonPreferences): + bl_idname = __package__ + + log_size: IntProperty( + name='Onscreen status size', + default=25, + min=1, + max=200 + ) + + def draw(self, context): + layout = self.layout + row = layout.row() + row.prop(self, "log_size") + + +pref_classes = (MolecularAddonPreferences) diff --git a/molecularplus/creators.py b/molecularplus/creators.py new file mode 100644 index 0000000..b7737b8 --- /dev/null +++ b/molecularplus/creators.py @@ -0,0 +1,188 @@ +import bpy + + +class MolecularGrid3d(bpy.types.Operator): + bl_idname = "molecular_operators.molecular_makegrid3d" + bl_label = "Create Molecular 3d grid" + bl_description = "Create / Set Gridobject 3d" + bl_options = {'REGISTER'} + + def execute(self, context): + + voxel_size = context.scene.mol_voxel_size + + for obj in context.view_layer.objects.selected: + init = False + if obj.particle_systems.active == None: + init = True + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.particle_system_add() + + psys = obj.particle_systems.active.settings + + # ParticlsSystemSettings + max_dim = max(obj.dimensions) + + psys.grid_resolution = int(max_dim/voxel_size) + psys.particle_size = max_dim/psys.grid_resolution/2 + psys.display_size = psys.particle_size/2 + psys.hexagonal_grid = context.scene.mol_hexgrid + psys.emit_from = 'VOLUME' + psys.distribution = 'GRID' + psys.normal_factor = 0.0 + + if init: + psys.frame_start = 1 + psys.frame_end = 1 + psys.lifetime = 500 + psys.grid_random = 0.02 + psys.use_size_deflect = True + psys.use_modifier_stack = True + + # Granular_Settings + if not psys.mol_active: + psys.mol_active = True + + psys.mol_selfcollision_active = True + psys.mol_friction = 0.15 + psys.mol_collision_damp = 0.25 + psys.mol_link_length = 3.5 + # update + bpy.ops.object.reset_pcache() + bpy.ops.object.mol_set_subs() + + return {'FINISHED'} + + +class MolecularGrid2d(bpy.types.Operator): + bl_idname = "molecular_operators.molecular_makegrid2d" + bl_label = "Create Molecular 2d grid" + bl_description = "Create / Set Gridobject 2d" + bl_options = {'REGISTER'} + + def execute(self, context): + + voxel_size = context.scene.mol_voxel_size + + for obj in context.view_layer.objects.selected: + init = False + if obj.particle_systems.active == None: + init = True + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.particle_system_add() + + # ParctilsSystemSettings + i = 1 + for parsys in obj.particle_systems: + parsys.name = "stack : " + str(i) + psys = parsys.settings + psys.name = "pstack : " + str(i) + max_dim = max(obj.dimensions) + psys.grid_resolution = int(max_dim/voxel_size) + psys.particle_size = max_dim/psys.grid_resolution/2 + psys.display_size = psys.particle_size/2 + psys.hexagonal_grid = context.scene.mol_hexgrid + psys.emit_from = 'FACE' + psys.distribution = 'GRID' + psys.normal_factor = 0.0 + + + if init: + psys.frame_start = i + psys.frame_end = i + psys.lifetime = 500 + psys.grid_random = 0.02 + psys.use_size_deflect = True + psys.use_modifier_stack = True + + # Granular_Settings + if psys.mol_active == False: + psys.mol_active = True + + psys.mol_selfcollision_active = True + psys.mol_othercollision_active = True + psys.mol_friction = 0.15 + psys.mol_collision_damp = 0.25 + psys.mol_link_length = 2.1 + + i += 20 + # update + bpy.ops.object.reset_pcache() + + bpy.ops.object.mol_set_subs() + + return {'FINISHED'} + +class MolecularEmitter(bpy.types.Operator): + bl_idname = "molecular_operators.molecular_makeemitter" + bl_label = "Create Molecular Emitter object" + bl_description = "Create / Set Emitter object" + bl_options = {'REGISTER'} + + def execute(self, context): + voxel_size = context.scene.mol_voxel_size + + for obj in context.view_layer.objects.selected: + init = False + if obj.particle_systems.active == None: + init = True + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.particle_system_add() + + psys = obj.particle_systems.active.settings + + # ParctilsSystemSettings + max_dim = max(obj.dimensions) + + psys.grid_resolution = int(max_dim/voxel_size) + psys.particle_size = voxel_size/2 + psys.display_size = voxel_size/4 + psys.hexagonal_grid = context.scene.mol_hexgrid + psys.emit_from = 'FACE' + psys.distribution = 'RAND' + psys.normal_factor = 2.0 + + if init: + psys.lifetime = 500 + psys.count = 10000 + psys.grid_random = 0.02 + psys.use_size_deflect = True + psys.use_modifier_stack = True + psys.use_emit_random = True + + # Molecularular_Settings + if psys.mol_active == False: + psys.mol_active = True + + psys.mol_selfcollision_active = True + psys.mol_othercollision_active = True + psys.mol_friction = 0.15 + psys.mol_collision_damp = 0.7 + psys.mol_link_length = 2.1 + + # update + bpy.ops.object.reset_pcache() + bpy.ops.object.mol_set_subs() + + return {'FINISHED'} + + +class MolecularCollider(bpy.types.Operator): + bl_idname = "molecular_operators.molecular_makecollider" + bl_label = "Create Molecular Collider object" + bl_description = "Create / Set Collider object" + bl_options = {'REGISTER'} + + def execute(self, context): + for obj in context.view_layer.objects.selected: + bpy.context.view_layer.objects.active = obj + bpy.ops.object.modifier_add(type='COLLISION') + obj.collision.damping_factor = 0.5 + obj.collision.friction_factor = 0.5 + + return {'FINISHED'} + +create_classes = (MolecularEmitter, MolecularCollider, MolecularGrid2d, MolecularGrid3d) diff --git a/molecularplus/descriptions.py b/molecularplus/descriptions.py new file mode 100644 index 0000000..c50483a --- /dev/null +++ b/molecularplus/descriptions.py @@ -0,0 +1,78 @@ +# Matter descriptions +MATTER_CUSTOM = 'put your parameter below' +MATTER_SAND = '1555kg per meter cu' +MATTER_WATER = '1000kg per meter cu' +MATTER_IRON = '7800kg per meter cu' + +# Properties descriptions +ACTIVE = 'Activate molecular script for this particles system' +REFRESH = 'Simple property used to refresh data in the process' + +DENSITY_ACTIVE = 'Control particle weight by density' +MATTER = 'Choose a matter preset for density' +DENSITY = 'Density of the matter kg/cube meter' + +SELF_COLLISION_ACTIVE = 'Activate self collsion between particles in the system' +OTHER_COLLISION_ACTIVE = 'Activate collision with particles from others systems' +FRICTION = 'Friction between particles at collision 0 = no friction , 1 = full friction' +COLLISION_DAMPING = 'Damping between particles at collision 0 = bouncy , 1 = no collision' +COLLISION_GROUP = 'Choose a collision group you want to collide with' + +LINKS_ACTIVE = 'Activate links between particles of this system' +LINK_OTHER_ACTIVE = '' +LINK_GROUP = '' +LINK_RELATIVE_LENGTH = 'Activate search distance relative to particles radius' +LINK_FRICTION = 'Friction in links , a kind of viscosity. Slow down tangent velocity. 0 = no friction , 1.0 = full of friction' +LINK_LENGTH = 'Searching range to make a link between particles' +LINK_TENSION = 'Make link bigger or smaller than it\'s created (1 = normal , 0.9 = 10% smaller , 1.15 = 15% bigger)' +LINK_TENSION_RANDOM = 'Tension random' +LINK_MAX = 'Maximum of links per particles' +LINK_STIFFNESS = 'Stiffness of links between particles' +LINK_STIFFNESS_RANDOM = 'Random variation for stiffness' +LINK_STIFFNESS_EXPONENT = 'Give a exponent force to the spring links' +LINK_DAMPING = 'Damping effect on spring links' +LINK_DAMPING_RANDOM = 'Random variation on damping' +LINK_BROKEN = 'How much link can stretch before they broken. 0.01 = 1% , 0.5 = 50% , 2.0 = 200% ...' +LINK_BROKEN_RANDOM = 'Give a random variation to the stretch limit' +LINK_SAME_VALUE = 'When active , expansion and compression of the spring have same value' +LINK_EXPENSION_STIFFNESS = 'Expension stiffness of links between particles' +LINK_EXPENSION_STIFFNESS_RANDOM = 'Random variation for expansion stiffness' +LINK_EXPENSION_STIFFNESS_EXPONENT = 'Give a exponent force to the expension spring links' +LINK_EXPENSION_DAMPING = 'Damping effect on expension spring links' +LINK_EXPENSION_DAMPING_RANDOM = 'Random variation on expension damping' +LINK_EXPENSION_BROKEN = 'How much link can expand before they broken. 0.01 = 1% , 0.5 = 50% , 2.0 = 200% ...' +LINK_EXPENSION_BROKEN_RANDOM = 'Give a random variation to the expension stretch limit' + +RELINK_GROUP = 'Choose a group that new link are possible' +RELINK_CHANCE = 'Chance of a new link are created on collision. 0 = off , 100 = 100% of chance' +RELINK_CHANCE_RANDOM = 'Give a random variation to the chance of new link' +RELINK_TENSION = 'Make link bigger or smaller than it\'s created (1 = normal , 0.9 = 10% smaller , 1.15 = 15% bigger)' +RELINK_TENSION_RANDOM = 'Tension random' +RELINK_MAX = 'Maximum of links per particles' +RELINK_STIFFNESS = 'Stiffness of links between particles' +RELINK_STIFFNESS_RANDOM = 'Random variation for stiffness' +RELINK_STIFFNESS_EXPONENT = 'Give a exponent force to the spring links' +RELINK_DAMPING = 'Damping effect on spring links' +RELINK_DAMPING_RANDOM = 'Random variation on damping' +RELINK_BROKEN = 'How much link can stretch before they broken. 0.01 = 1% , 0.5 = 50% , 2.0 = 200% ...' +RELINK_BROKEN_RANDOM = 'Give a random variation to the stretch limit' +RELINK_SAME_VALUE = 'When active , expansion and compression of the spring have same value' +RELINK_EXPENSION_STIFFNESS = 'Stiffness of links expension between particles' +RELINK_EXPENSION_STIFFNESS_RANDOM = 'Random variation for expension stiffness' +RELINK_EXPENSION_STIFFNESS_EXPONENT = 'Give a exponent force to the spring links' +RELINK_EXPENSION_DAMPING = 'Damping effect on expension spring links' +RELINK_EXPENSION_DAMPING_RANDOM = 'Random variation on damping' +RELINK_EXPENSION_BROKEN = 'How much link can stretch before they broken. 0.01 = 1% , 0.5 = 50% , 2.0 = 200% ...' +RELINK_EXPENSION_BROKEN_RANDOM = 'Give a random variation to the stretch limit' + +VAR_1 = 'Current number of particles to calculate substep' +VAR_2 = 'Current substep' +VAR_3 = 'Targeted number of particles you want to increase or decrease from current system to calculate substep you need to achieve similar effect' +BAKE_UV = 'Bake uv when finish' + +TIME_SCALE_ACTIVE = 'Activate TimeScaling' +TIME_SCALE = 'SpeedUp or Slow down the simulation with this multiplier' +SUBSTEP = 'mol_substep. Higher equal more stable and accurate but more slower' +BAKE = 'Bake simulation when finish' +RENDER = 'Start rendering animation when simulation is finish. WARNING: It\'s freeze blender until render is finish' +CPU = 'Numbers of cpu\'s included for process the simulation' diff --git a/molecularplus/names.py b/molecularplus/names.py new file mode 100644 index 0000000..77eda46 --- /dev/null +++ b/molecularplus/names.py @@ -0,0 +1,19 @@ +# Labels text +DENSITY = 'Density' +COLLISION = 'Collision' +LINKS = 'Links' +INITIAL_LINKING = 'Initial Linking (at birth)' +NEW_LINKING = 'New Linking (at collision)' +UVS = 'UV\'s' +SIMULATE = 'Simulate' +CPU_USED = 'CPU used' +MOLECULAR_TOOLS = 'Tools' +PARTICLE_UV = 'Particle UV' +SET_POSITION = 'Set current particles position' +UV_HELP = 'Has global or current uv in angular velocity' +CYCLES_HELP = 'Retrieve it with Cycles particle info node' +SUBSTEPS_CALCULATOR = 'Substeps Calculator' +THANKS = 'THANKS TO ALL DONATORS!' +SUPPORT_WORK = 'If you want donate to support my work' +VISIT = 'or visit:' +SITE = 'www.q3de.com' diff --git a/molecularplus/operators.py b/molecularplus/operators.py new file mode 100644 index 0000000..c166588 --- /dev/null +++ b/molecularplus/operators.py @@ -0,0 +1,439 @@ +import bpy +import blf + +from mathutils import Vector +from mathutils.geometry import barycentric_transform as barycentric +from time import sleep, strftime, gmtime, time + +from . import simulate, core +from .utils import get_object, destroy_caches, update_progress + +class MolRemoveCollider(bpy.types.Operator): + bl_idname = "object.mol_remove_collision" + bl_label = "Remove Collision" + + def execute(self, context): + bpy.ops.object.modifier_remove(modifier='Collision') + + return {'FINISHED'} + +class MolSet_Substeps(bpy.types.Operator): + bl_idname = "object.mol_set_subs" + bl_label = 'Set SubSteps' + + def execute(self, context): + parcount = 0 + for obj in bpy.data.objects: + if obj.particle_systems.active != None: + for psys in get_object(context, obj).particle_systems: + print(psys.name) + parcount += len(psys.particles) + + context.scene.mol_parnum = parcount + + if context.scene.mol_autosubsteps: + diff = (psys.settings.mol_var3 / psys.settings.mol_var1) + factor = (parcount**(1/3) / psys.settings.mol_var1**(1/3)) + newsubstep = int(round(factor * psys.settings.mol_var2)) + context.scene.mol_substep = newsubstep + + return {'FINISHED'} + + +class MolSimulate(bpy.types.Operator): + bl_idname = "object.mol_simulate" + bl_label = 'Simulate' + + def execute(self, context): + for ob in bpy.data.objects: + destroy_caches(ob) + + print('Molecular Sim Start' + '-' * 50) + mol_stime = time() + scene = context.scene + scene.mol_simrun = True + scene.mol_minsize = 1000000000.0 + scene.mol_newlink = 0 + scene.mol_deadlink = 0 + scene.mol_totallink = 0 + scene.mol_totaldeadlink = 0 + scene.mol_timeremain = "...Simulating..." + scene.frame_set(frame=scene.frame_start) + scene.mol_old_endframe = scene.frame_end + mol_substep = scene.mol_substep + scene.render.frame_map_old = 1 + scene.render.frame_map_new = mol_substep + 1 + scene.frame_end *= mol_substep + 1 + + if scene.mol_timescale_active == True: + fps = scene.render.fps * scene.timescale + else: + fps = scene.render.fps + + cpu = scene.mol_cpu + mol_exportdata = context.scene.mol_exportdata + mol_exportdata.clear() + mol_exportdata.append([fps, mol_substep, 0, 0, cpu]) + mol_stime = time() + simulate.pack_data(context, True) + etime = time() + print(" PackData took " + str(round(etime - mol_stime, 3)) + "sec") + mol_stime = time() + mol_report = core.init(mol_exportdata) + etime = time() + print(" Export time took " + str(round(etime - mol_stime, 3)) + "sec") + print(" total numbers of particles: " + str(mol_report)) + print(" start processing:") + bpy.ops.wm.mol_simulate_modal() + return {'FINISHED'} + +class MolSetGlobalUV(bpy.types.Operator): + bl_idname = "object.mol_set_global_uv" + bl_label = "Mol Set UV" + + objname : bpy.props.StringProperty() + + def execute(self, context): + scene = context.scene + obj = get_object(context, context.view_layer.objects[self.objname]) + + print('start bake global uv from:', obj.name) + + psys = obj.particle_systems.active + + par_uv = [] + for par in psys.particles: + + newuv = (par.location @ obj.matrix_world) - obj.location + par_uv.append(newuv[0]) + par_uv.append(newuv[1]) + par_uv.append(newuv[2]) + + psys.settings.use_rotations = True + psys.settings.angular_velocity_mode = 'RAND' + + psys.particles.foreach_set("angular_velocity", par_uv) + print('global uv baked on:', psys.settings.name) + + return {'FINISHED'} + + +class MolSetActiveUV(bpy.types.Operator): + bl_idname = "object.mol_set_active_uv" + bl_label = "Mol Set Active UV" + + objname : bpy.props.StringProperty() + + def execute(self, context): + + obj = get_object(context, context.view_layer.objects[self.objname]) + + if not obj.data.uv_layers.active: + return {'FINISHED'} + + print(' start bake uv from:', obj.name) + + obdata = context.object.data.copy() + obj2 = bpy.data.objects.new(name="mol_uv_temp", object_data=obdata) + obj2.matrix_world = obj.matrix_world + + context.scene.collection.objects.link(obj2) + mod = obj2.modifiers.new("tri_for_uv", "TRIANGULATE") + mod.ngon_method = 'BEAUTY' + mod.quad_method = 'BEAUTY' + + ctx = bpy.context.copy() + ctx["object"] = obj2 + bpy.ops.object.modifier_apply(ctx, modifier=mod.name) + + context.view_layer.update() + + psys = obj.particle_systems.active + par_uv = [] + me = obj2.data + + for par in psys.particles: + + parloc = (par.location @ obj2.matrix_world) - obj2.location + + point = obj2.closest_point_on_mesh(parloc) + vindex1 = me.polygons[point[3]].vertices[0] + vindex2 = me.polygons[point[3]].vertices[1] + vindex3 = me.polygons[point[3]].vertices[2] + + v1 = (obj2.matrix_world @ me.vertices[vindex1].co).to_tuple() + v2 = (obj2.matrix_world @ me.vertices[vindex2].co).to_tuple() + v3 = (obj2.matrix_world @ me.vertices[vindex3].co).to_tuple() + + uvindex1 = me.polygons[point[3]].loop_start + 0 + uvindex2 = me.polygons[point[3]].loop_start + 1 + uvindex3 = me.polygons[point[3]].loop_start + 2 + uv1 = me.uv_layers.active.data[uvindex1].uv.to_3d() + uv2 = me.uv_layers.active.data[uvindex2].uv.to_3d() + uv3 = me.uv_layers.active.data[uvindex3].uv.to_3d() + + p = obj2.matrix_world @ point[1] + + v1 = Vector(v1) + v2 = Vector(v2) + v3 = Vector(v3) + uv1 = Vector(uv1) + uv2 = Vector(uv2) + uv3 = Vector(uv3) + newuv = barycentric(p, v1, v2, v3, uv1, uv2, uv3) + + parloc = par.location @ obj2.matrix_world + + dist = (Vector(( + parloc[0] - p[0], + parloc[1] - p[1], + parloc[2] - p[2] + ))).length + + newuv[2] = dist + + par_uv.append(newuv[0]) + par_uv.append(newuv[1]) + par_uv.append(newuv[2]) + + context.scene.collection.objects.unlink(obj2) + bpy.data.objects.remove(obj2) + bpy.data.meshes.remove(obdata) + + print(' uv baked on:', psys.settings.name) + + psys.settings.use_rotations = True + psys.settings.angular_velocity_mode = 'RAND' + + psys.particles.foreach_set("angular_velocity", par_uv) + + return {'FINISHED'} + + +def convert_time_to_string(total_time): + HOUR_IN_SECONDS = 60 * 60 + MINUTE_IN_SCEONDS = 60 + time_string = '' + if total_time > 10.0: + total_time = int(total_time) + if total_time > MINUTE_IN_SCEONDS and total_time <= HOUR_IN_SECONDS: + minutes = total_time // MINUTE_IN_SCEONDS + seconds = total_time - minutes * MINUTE_IN_SCEONDS + time_string = '{0} min {1} sec'.format(minutes, seconds) + elif total_time <= MINUTE_IN_SCEONDS: + time_string = '{0} seconds'.format(total_time) + elif total_time > HOUR_IN_SECONDS: + hours = total_time // HOUR_IN_SECONDS + minutes = total_time - (total_time // HOUR_IN_SECONDS) * HOUR_IN_SECONDS + time_string = '{0} hours {1} min'.format(hours, minutes) + else: + seconds = round(total_time, 2) + time_string = '{0} seconds'.format(seconds) + return time_string + +def draw_callback_px(self, context): + """Draw on the viewports""" + # BLF drawing routine + + font_id = 0 + texts = bpy.context.scene.mol_progress.split('\n') + size = bpy.context.preferences.addons[__package__].preferences.log_size + blf.color(font_id, 1.0, 1.0, 1.0, 0.5) + for i, text in enumerate(texts): + blf.position(font_id, size, size*(i+1) , 0) + blf.size(font_id, size - 10, 50) + blf.draw(font_id, text) + +class MolSimulateModal(bpy.types.Operator): + """Operator which runs its self from a timer""" + bl_idname = "wm.mol_simulate_modal" + bl_label = "Simulate Molecular" + _timer = None + _draw_handler = None + + def check_bake_uv(self, context): + # bake the UV in the beginning + scene = context.scene + frame_old = scene.frame_current + for ob in bpy.data.objects: + obj = get_object(context, ob) + + for psys in obj.particle_systems: + if psys.settings.mol_bakeuv: + scene.frame_set(frame=int(psys.settings.frame_start)) + if psys.settings.mol_bakeuv_global: + bpy.ops.object.mol_set_global_uv("INVOKE_DEFAULT", objname = obj.name) + else: + bpy.ops.object.mol_set_active_uv("INVOKE_DEFAULT", objname = obj.name) + + scene.frame_set(frame=frame_old) + + def modal(self, context, event): + scene = context.scene + frame_end = scene.frame_end + frame_current = scene.frame_current + + if frame_current == frame_end-1: + update_progress("finished", 1) + + ###### ESC END ####### + if event.type == 'ESC' or frame_current == frame_end or scene.mol_cancel: + if scene.mol_bake: + fake_context = context.copy() + for ob in bpy.data.objects: + obj = get_object(context, ob) + for psys in obj.particle_systems: + if psys.settings.mol_active and len(psys.particles): + fake_context["point_cache"] = psys.point_cache + bpy.ops.ptcache.bake_from_cache(fake_context) + + + scene.render.frame_map_new = 1 + scene.frame_end = scene.mol_old_endframe + core.memfree() + scene.mol_simrun = False + mol_exportdata = scene.mol_exportdata + mol_exportdata.clear() + print('-' * 50 + 'Molecular Sim end') + + if frame_current == frame_end and scene.mol_render: + print("Rendering ..................") + bpy.ops.render.render(animation=True) + + scene.frame_set(frame=scene.frame_start) + sleep(0.1) + return self.cancel(context) + + ###### TIMER ####### + if event.type == 'TIMER': + + mol_substep = scene.mol_substep + framesubstep = frame_current / (mol_substep + 1) + + if framesubstep == int(framesubstep): + update_progress("Simulating", frame_current/frame_end) + stime = time() + + context.scene.mol_exportdata.clear() + simulate.pack_data(context, False) + if framesubstep == int(framesubstep): + etime = time() + packtime = etime - stime + stime2 = time() + + mol_importdata = core.simulate(context.scene.mol_exportdata) + if framesubstep == int(framesubstep): + etime2 = time() + moltime = (etime2-stime2) + stime3 = time() + i = 0 + for ob in bpy.data.objects: + obj = get_object(context, ob) + for psys in obj.particle_systems: + if psys.settings.mol_active and len(psys.particles): + psys.particles.foreach_set('velocity', mol_importdata[1][i]) + i += 1 + + scene.mol_newlink = 0 + scene.mol_deadlink = 0 + + scene.mol_newlink += mol_importdata[2] + scene.mol_deadlink += mol_importdata[3] + scene.mol_totallink = mol_importdata[4] + scene.mol_totaldeadlink = mol_importdata[5] + scene.frame_set(frame=frame_current + 1) + + if framesubstep == int(framesubstep): + print(" frame " + str(int(framesubstep) + 1) + ":") + print(" links created:", scene.mol_newlink) + if scene.mol_totallink: + print(" links broken :", scene.mol_deadlink) + print(" total links:", scene.mol_totallink - scene.mol_totaldeadlink ,"/", scene.mol_totallink," (",round((((scene.mol_totallink - scene.mol_totaldeadlink) / scene.mol_totallink) * 100), 2), "%)") +# + etime3 = time() + blendertime = etime3 - stime3 + print(" Pack : " + str(round(packtime * (mol_substep + 1), 3)) + " sec") + print(" Molecular : " + str(round(moltime * (mol_substep + 1), 3)) + " sec") + print(" Blender : " + str(round(blendertime * (mol_substep + 1), 3)) + " sec") + print(" Total Frame : " + str(round((blendertime + packtime + moltime) * (mol_substep + 1), 3)) + " sec") + + remain = (blendertime + packtime + moltime) * (mol_substep + 1) * (float(scene.mol_old_endframe) - (framesubstep + 1.0)) + #print(" Remaining estimated:", remain) + days = int(strftime('%d', gmtime(remain))) - 1 + scene.mol_timeremain = strftime(str(days) + ' days %H hours %M mins %S secs', gmtime(remain)) + print(" Remaining estimated:", scene.mol_timeremain) + + return {'PASS_THROUGH'} + + def execute(self, context): + # start time + self.st = time() + self.check_bake_uv(context) + self._timer = context.window_manager.event_timer_add(0.000000001, window=context.window) + update_progress("Initializing", 0.0001) + + if context.area.type == 'VIEW_3D': + self._handler = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), "WINDOW", "POST_PIXEL") + + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + def cancel(self, context): + # total time + tt = time() - self.st + tt_s = convert_time_to_string(tt) + update_progress("Finished", 1) + print("Total time : " + tt_s + " sec") + + self.report({'INFO'}, 'Total time: {0}'.format(tt_s)) + bpy.types.SpaceView3D.draw_handler_remove(self._handler, 'WINDOW') + context.window_manager.event_timer_remove(self._timer) + bpy.context.scene.mol_cancel = False + return {'CANCELLED'} + + +class MolClearCache(bpy.types.Operator): + """Clear Particle Cache""" + bl_idname = "object.clear_pcache" + bl_label = "Clear Particle Cache" + + def execute(self, context): + bpy.ops.ptcache.free_bake_all() + for ob in bpy.data.objects: + obj = get_object(context, ob) + for psys in obj.particle_systems: + if psys.settings.mol_active: + ccache = context.object.particle_systems.active.settings.use_modifier_stack + context.object.particle_systems.active.settings.use_modifier_stack = ccache + + context.scene.frame_current = 1 + + return {'FINISHED'} + +class MolResetCache(bpy.types.Operator): + """Clear Particle Cache""" + bl_idname = "object.reset_pcache" + bl_label = "Clear Particle Cache" + + def execute(self, context): + for ob in bpy.data.objects: + obj = get_object(context, ob) + for psys in obj.particle_systems: + if psys.settings.mol_active: + ccache = context.object.particle_systems.active.settings.use_modifier_stack + context.object.particle_systems.active.settings.use_modifier_stack = ccache + context.scene.frame_current = 1 + + return {'FINISHED'} + +class MolCancelSim(bpy.types.Operator): + """Cancel Particle Simulation""" + bl_idname = "object.cancel_sim" + bl_label = "Cancel Particle Simulation" + + def execute(self, context): + context.scene.mol_cancel = True + + return {'FINISHED'} + +operator_classes = (MolSimulateModal, MolSimulate, MolSetGlobalUV, MolSetActiveUV, MolSet_Substeps, MolClearCache, MolResetCache, MolCancelSim, MolRemoveCollider) diff --git a/molecularplus/properties.py b/molecularplus/properties.py new file mode 100644 index 0000000..07cfea0 --- /dev/null +++ b/molecularplus/properties.py @@ -0,0 +1,389 @@ +import multiprocessing +import bpy +from . import descriptions + +def define_props(): + parset = bpy.types.ParticleSettings + + parset.mol_active = bpy.props.BoolProperty( + name="mol_active", description=descriptions.ACTIVE, default=False + ) + parset.mol_refresh = bpy.props.BoolProperty( + name="mol_refresh", description=descriptions.REFRESH, default=True + ) + parset.mol_density_active = bpy.props.BoolProperty( + name="Calculate particles weight by density", + description=descriptions.DENSITY_ACTIVE, + default=False + ) + + matter_items = [ + ("-1", "custom", descriptions.MATTER_CUSTOM), + ("1555", "sand", descriptions.MATTER_SAND), + ("1000", "water", descriptions.MATTER_WATER), + ("7800", "iron", descriptions.MATTER_IRON) + ] + + parset.mol_matter = bpy.props.EnumProperty( + name='Preset', + items=matter_items, + description=descriptions.MATTER + ) + parset.mol_density = bpy.props.FloatProperty( + name="Kg per CubeMeter:", description=descriptions.DENSITY, + default=1000, min=0.001 + ) + + parset.mol_selfcollision_active = bpy.props.BoolProperty( + name="Activate Self Collision", + description=descriptions.SELF_COLLISION_ACTIVE, + default=False + ) + parset.mol_othercollision_active = bpy.props.BoolProperty( + name="Activate Collision with others", + description=descriptions.OTHER_COLLISION_ACTIVE, + default=False + ) + parset.mol_friction = bpy.props.FloatProperty( + name='Friction:', description=descriptions.FRICTION, + default=0.005, min=0, max=1, precision=6, subtype='FACTOR' + ) + parset.mol_collision_damp = bpy.props.FloatProperty( + name="Damping:", description=descriptions.COLLISION_DAMPING, + default=0.005, min=0, max=1, precision=6, subtype='FACTOR' + ) + + parset.mol_collision_group = bpy.props.IntProperty( + name='Collide only with:', default=1, min=1, + description=descriptions.COLLISION_GROUP + ) + + parset.mol_links_active = bpy.props.BoolProperty( + name="Activate Particles linking", + description=descriptions.LINKS_ACTIVE, + default=False + ) + parset.mol_other_link_active = bpy.props.BoolProperty( + name="Activate Particles linking with Others", + description=descriptions.LINK_OTHER_ACTIVE, default=False + ) + + parset.mol_link_group = bpy.props.IntProperty( + name='Linking only with:', default=1, min=1, + description=descriptions.LINK_GROUP + ) + + parset.mol_link_rellength = bpy.props.BoolProperty( + name="Relative", + description=descriptions.LINK_RELATIVE_LENGTH, + default=True + ) + parset.mol_link_friction = bpy.props.FloatProperty( + name="Link friction", description=descriptions.LINK_FRICTION, + min=0, max=1, default=0.005, precision=6, subtype='FACTOR' + ) + parset.mol_link_length = bpy.props.FloatProperty( + name="Search Length", description=descriptions.LINK_LENGTH, + min=0, precision=6, default=1 + ) + parset.mol_link_tension = bpy.props.FloatProperty( + name="Tension", description=descriptions.LINK_TENSION, + min=0, precision=6, default=1 + ) + parset.mol_link_tensionrand = bpy.props.FloatProperty( + name="Rand Tension", + description=descriptions.LINK_TENSION_RANDOM, + min=0, max=1, precision=6, default=0, subtype='FACTOR' + ) + parset.mol_link_max = bpy.props.IntProperty( + name="Max links", description=descriptions.LINK_MAX, + min=0, default=16 + ) + parset.mol_link_stiff = bpy.props.FloatProperty( + name="Stiff", description=descriptions.LINK_STIFFNESS, + min=0, max=1, default=1, precision=6, subtype='FACTOR' + ) + parset.mol_link_stiffrand = bpy.props.FloatProperty( + name="Rand Stiff", + description = descriptions.LINK_STIFFNESS_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_link_stiffexp = bpy.props.IntProperty( + name="Exponent", + description=descriptions.LINK_STIFFNESS_EXPONENT, + default=1, min=1, max=10 + ) + parset.mol_link_damp = bpy.props.FloatProperty( + name="Damping", description=descriptions.LINK_DAMPING, + min=0, max=1, default=1, precision=6, subtype='FACTOR' + ) + parset.mol_link_damprand = bpy.props.FloatProperty( + name="Rand Damping", description=descriptions.LINK_DAMPING_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_link_broken = bpy.props.FloatProperty( + name="Broken", description=descriptions.LINK_BROKEN, + min=0, default=0.5, precision=6 + ) + parset.mol_link_brokenrand = bpy.props.FloatProperty( + name="Rand Broken", + description=descriptions.LINK_BROKEN_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + + parset.mol_link_samevalue = bpy.props.BoolProperty( + name="Same values for compression/expansion", + description=descriptions.LINK_SAME_VALUE, + default=True + ) + + parset.mol_link_estiff = bpy.props.FloatProperty( + name="E Stiff", + description=descriptions.LINK_EXPENSION_STIFFNESS, + min=0, max=1, default=1, precision=6, subtype='FACTOR' + ) + parset.mol_link_estiffrand = bpy.props.FloatProperty( + name="Rand E Stiff", + description=descriptions.LINK_EXPENSION_STIFFNESS_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_link_estiffexp = bpy.props.IntProperty( + name="E Exponent", + description=descriptions.LINK_EXPENSION_STIFFNESS_EXPONENT, + default=1, min=1, max=10 + ) + parset.mol_link_edamp = bpy.props.FloatProperty( + name="E Damping", description=descriptions.LINK_EXPENSION_DAMPING, + min=0, max=1, default=1, precision=6, subtype='FACTOR' + ) + parset.mol_link_edamprand = bpy.props.FloatProperty( + name="Rand E Damping", + description=descriptions.LINK_EXPENSION_DAMPING_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_link_ebroken = bpy.props.FloatProperty( + name="E Broken", + description=descriptions.LINK_EXPENSION_BROKEN, + min=0, default=0.5, precision=6 + ) + parset.mol_link_ebrokenrand = bpy.props.FloatProperty( + name="Rand E Broken", + description=descriptions.LINK_EXPENSION_BROKEN_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + + parset.mol_relink_group = bpy.props.IntProperty( + name='Only links with:', + default=1, min=1, description=descriptions.RELINK_GROUP + ) + +# item = [] +# for i in range(1,12): +# item.append((str(i),"Relink Group " + str(i),"Relink only with group " + str(i) )) +# parset.mol_relink_group = bpy.props.EnumProperty( +# items = item, +# description = "Choose a group that new link are possible" +# ) + + parset.mol_relink_chance = bpy.props.FloatProperty( + name="% Linking", + description=descriptions.RELINK_CHANCE, + min=0, max=100, default=0, precision=1, subtype='FACTOR' + ) + parset.mol_relink_chancerand = bpy.props.FloatProperty( + name="Rand % Linking", + description=descriptions.RELINK_CHANCE_RANDOM, + default=0, min=0, max=1, precision=2, subtype='FACTOR' + ) + parset.mol_relink_tension = bpy.props.FloatProperty( + name="Tension", + description=descriptions.RELINK_TENSION, + min=0, precision=6, default=1 + ) + parset.mol_relink_tensionrand = bpy.props.FloatProperty( + name="Rand Tension", + description=descriptions.RELINK_TENSION_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_relink_max = bpy.props.IntProperty( + name="Max links", + description=descriptions.RELINK_MAX, + min=0, default=16 + ) + parset.mol_relink_stiff = bpy.props.FloatProperty( + name="Stiff", + description=descriptions.RELINK_STIFFNESS, + min=0, max=1, default=1, precision=6, subtype='FACTOR' + ) + parset.mol_relink_stiffrand = bpy.props.FloatProperty( + name="Rand Stiff", + description=descriptions.RELINK_STIFFNESS_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_relink_stiffexp = bpy.props.IntProperty( + name="Exponent", + description=descriptions.RELINK_STIFFNESS_EXPONENT, + min=1, max=10, default=1 + ) + parset.mol_relink_damp = bpy.props.FloatProperty( + name="Damping", + description=descriptions.RELINK_DAMPING, + min=0, max=1, default=1, precision=6, subtype='FACTOR' + ) + parset.mol_relink_damprand = bpy.props.FloatProperty( + name="Rand Damping", + description=descriptions.RELINK_DAMPING_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_relink_broken = bpy.props.FloatProperty( + name="Broken", + description=descriptions.RELINK_BROKEN, + min=0, default=0.5, precision=6 + ) + parset.mol_relink_brokenrand = bpy.props.FloatProperty( + name="Rand Broken", + description=descriptions.RELINK_BROKEN_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + + parset.mol_relink_samevalue = bpy.props.BoolProperty( + name="Same values for compression/expansion", + description=descriptions.RELINK_SAME_VALUE, + default=True + ) + + parset.mol_relink_estiff = bpy.props.FloatProperty( + name="E Stiff", + description=descriptions.RELINK_EXPENSION_STIFFNESS, + min=0, max=1, default=1, precision=6, subtype='FACTOR' + ) + parset.mol_relink_estiffrand = bpy.props.FloatProperty( + name="Rand E Stiff", + description=descriptions.RELINK_EXPENSION_STIFFNESS_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_relink_estiffexp = bpy.props.IntProperty( + name="Exponent", + description=descriptions.RELINK_EXPENSION_STIFFNESS_EXPONENT, + min=1, max=10, default=1 + ) + parset.mol_relink_edamp = bpy.props.FloatProperty( + name="E Damping", + description=descriptions.RELINK_EXPENSION_DAMPING, + min=0, max=1, default=1, precision=6, subtype='FACTOR' + ) + parset.mol_relink_edamprand = bpy.props.FloatProperty( + name="Rand E Damping", + description=descriptions.RELINK_EXPENSION_DAMPING_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + parset.mol_relink_ebroken = bpy.props.FloatProperty( + name="E Broken", + description=descriptions.RELINK_EXPENSION_BROKEN, + min=0, default=0.5, precision=6 + ) + parset.mol_relink_ebrokenrand = bpy.props.FloatProperty( + name="Rand E Broken", + description=descriptions.RELINK_EXPENSION_BROKEN_RANDOM, + min=0, max=1, default=0, precision=6, subtype='FACTOR' + ) + + parset.mol_var1 = bpy.props.IntProperty( + name="Current numbers of particles", + description=descriptions.VAR_1, + min=1, default=1000 + ) + parset.mol_var2 = bpy.props.IntProperty( + name="Current substep", + description=descriptions.VAR_2, + min=1, default=4 + ) + parset.mol_var3=bpy.props.IntProperty( + name="Targeted numbers of particles", + description=descriptions.VAR_3, + min=1, default=1000 + ) + parset.mol_bakeuv = bpy.props.BoolProperty( + name="mol_bakeuv", + description=descriptions.BAKE_UV, + default=False + ) + parset.mol_bakeuv_global = bpy.props.BoolProperty( + name="mol_bakeuv_global", + description="make global uv", + default=False + ) + parset.mol_bake_weak_map = bpy.props.BoolProperty( + name="mol_bake_weak_map", + description="bake weak_map", + default=False + ) + + bpy.types.Scene.mol_timescale_active = bpy.props.BoolProperty( + name="mol_timescale_active", + description=descriptions.TIME_SCALE_ACTIVE, + default=False + ) + bpy.types.Scene.timescale = bpy.props.FloatProperty( + name="timescale", + description=descriptions.TIME_SCALE, + default=1 + ) + bpy.types.Scene.mol_substep = bpy.props.IntProperty( + name="Substeps", + description=descriptions.SUBSTEP, + min=0, max=900, default=4 + ) + bpy.types.Scene.mol_autosubsteps = bpy.props.BoolProperty( + name="Auto Substeps", + description="auto substeps", + default=True + ) + bpy.types.Scene.mol_bake = bpy.props.BoolProperty( + name="Bake all at ending", + description=descriptions.BAKE, + default=True + ) + bpy.types.Scene.mol_render = bpy.props.BoolProperty( + name="Render at ending", + description=descriptions.RENDER, + default=False + ) + bpy.types.Scene.mol_cpu = bpy.props.IntProperty( + name="CPU", + description=descriptions.CPU, + default=multiprocessing.cpu_count(), + min=1, max =64 + ) + bpy.types.Scene.mol_parnum = bpy.props.IntProperty( + name="Particle Number", + description="Number of all particles in scene", + default=0 + ) + bpy.types.Scene.mol_voxel_size = bpy.props.FloatProperty( + name = "mol_voxel_size", + description = "Voxel Size for Grid", + min = 0.0001, max=1.0,precision = 4, step=0.001, + default = 0.1 + ) + bpy.types.Scene.mol_hexgrid = bpy.props.BoolProperty( + name = "mol_hexgrid", + description = "Create Hexagonal Grid", + default = False + ) + bpy.types.Scene.mol_progress = bpy.props.StringProperty( + name = "mol_progress", + description = "ProgressBar", + default = "" + ) + + bpy.types.Scene.mol_exportdata = [] + bpy.types.Scene.mol_minsize = bpy.props.FloatProperty() + bpy.types.Scene.mol_simrun = bpy.props.BoolProperty(default=False) + bpy.types.Scene.mol_timeremain = bpy.props.StringProperty() + bpy.types.Scene.mol_old_endframe = bpy.props.IntProperty() + bpy.types.Scene.mol_newlink = bpy.props.IntProperty() + bpy.types.Scene.mol_deadlink = bpy.props.IntProperty() + bpy.types.Scene.mol_totallink = bpy.props.IntProperty() + bpy.types.Scene.mol_totaldeadlink = bpy.props.IntProperty() + bpy.types.Scene.mol_cancel = bpy.props.BoolProperty(default=False) diff --git a/molecularplus/simulate.py b/molecularplus/simulate.py new file mode 100644 index 0000000..07ea003 --- /dev/null +++ b/molecularplus/simulate.py @@ -0,0 +1,160 @@ +import bpy +from .utils import get_object + +def get_weak_map(obj, psys): + print('start bake weak map from:', obj.name) + + par_weak = [] + tex = psys.settings.texture_slots[0].texture + texm_offset = psys.settings.texture_slots[0].offset + texm_scale = psys.settings.texture_slots[0].scale + for par in psys.particles: + newuv = (par.location + texm_offset) @ obj.matrix_world * texm_scale + par_weak.append(tex.evaluate(newuv).w) + + print('Weak Map baked on:', psys.settings.name) + + return par_weak + +def pack_data(context, initiate): + psyslen = 0 + parnum = 0 + scene = context.scene + + for ob in bpy.data.objects: + obj = get_object(context, ob) + + for psys in obj.particle_systems: + if psys.settings.mol_matter != "-1": + psys.settings.mol_density = float(psys.settings.mol_matter) + + parlen = len(psys.particles) + + if psys.settings.mol_active and parlen: + + par_loc = [0, 0, 0] * parlen + par_vel = [0, 0, 0] * parlen + par_size = [0] * parlen + par_alive = [0] * parlen + parnum += parlen + + psys.particles.foreach_get('location', par_loc) + psys.particles.foreach_get('velocity', par_vel) + psys.particles.foreach_get('alive_state', par_alive) + + if initiate: + par_mass = [0] * parlen + psys.particles.foreach_get('size', par_size) + + if psys.settings.mol_bake_weak_map: + par_weak = get_weak_map(obj, psys) + else: + par_weak = [1.0] * parlen + + if psys.settings.mol_density_active: + for i in range(len(par_size)): + par_mass[i] = psys.settings.mol_density * (4 / 3 * 3.141592653589793 * ((par_size[i] / 2) ** 3)) + else: + par_mass = [psys.settings.mass] * len(psys.particles) + + if scene.mol_timescale_active == True: + psys.settings.timestep = 1 / (scene.render.fps / scene.timescale) + else: + psys.settings.timestep = 1 / scene.render.fps + + psyslen += 1 + + if bpy.context.scene.mol_minsize > min(par_size): + bpy.context.scene.mol_minsize = min(par_size) + + if psys.settings.mol_link_samevalue: + psys.settings.mol_link_estiff = psys.settings.mol_link_stiff + psys.settings.mol_link_estiffrand = psys.settings.mol_link_stiffrand + psys.settings.mol_link_estiffexp = psys.settings.mol_link_stiffexp + psys.settings.mol_link_edamp = psys.settings.mol_link_damp + psys.settings.mol_link_edamprand = psys.settings.mol_link_damprand + psys.settings.mol_link_ebroken = psys.settings.mol_link_broken + psys.settings.mol_link_ebrokenrand = psys.settings.mol_link_brokenrand + + if psys.settings.mol_relink_samevalue: + psys.settings.mol_relink_estiff = psys.settings.mol_relink_stiff + psys.settings.mol_relink_estiffrand = psys.settings.mol_relink_stiffrand + psys.settings.mol_relink_estiffexp = psys.settings.mol_relink_stiffexp + psys.settings.mol_relink_edamp = psys.settings.mol_relink_damp + psys.settings.mol_relink_edamprand = psys.settings.mol_relink_damprand + psys.settings.mol_relink_ebroken = psys.settings.mol_relink_broken + psys.settings.mol_relink_ebrokenrand = psys.settings.mol_relink_brokenrand + + params = [0] * 47 + + params[0] = psys.settings.mol_selfcollision_active + params[1] = psys.settings.mol_othercollision_active + params[2] = psys.settings.mol_collision_group + params[3] = psys.settings.mol_friction + params[4] = psys.settings.mol_collision_damp + params[5] = psys.settings.mol_links_active + + if psys.settings.mol_link_rellength: + params[6] = psys.settings.particle_size * psys.settings.mol_link_length + else: + params[6] = psys.settings.mol_link_length + + params[7] = psys.settings.mol_link_max + params[8] = psys.settings.mol_link_tension + params[9] = psys.settings.mol_link_tensionrand + params[10] = psys.settings.mol_link_stiff + params[11] = psys.settings.mol_link_stiffrand + params[12] = psys.settings.mol_link_stiffexp + params[13] = psys.settings.mol_link_damp + params[14] = psys.settings.mol_link_damprand + params[15] = psys.settings.mol_link_broken + params[16] = psys.settings.mol_link_brokenrand + params[17] = psys.settings.mol_link_estiff + params[18] = psys.settings.mol_link_estiffrand + params[19] = psys.settings.mol_link_estiffexp + params[20] = psys.settings.mol_link_edamp + params[21] = psys.settings.mol_link_edamprand + params[22] = psys.settings.mol_link_ebroken + params[23] = psys.settings.mol_link_ebrokenrand + params[24] = psys.settings.mol_relink_group + params[25] = psys.settings.mol_relink_chance + params[26] = psys.settings.mol_relink_chancerand + params[27] = psys.settings.mol_relink_max + params[28] = psys.settings.mol_relink_tension + params[29] = psys.settings.mol_relink_tensionrand + params[30] = psys.settings.mol_relink_stiff + params[31] = psys.settings.mol_relink_stiffexp + params[32] = psys.settings.mol_relink_stiffrand + params[33] = psys.settings.mol_relink_damp + params[34] = psys.settings.mol_relink_damprand + params[35] = psys.settings.mol_relink_broken + params[36] = psys.settings.mol_relink_brokenrand + params[37] = psys.settings.mol_relink_estiff + params[38] = psys.settings.mol_relink_estiffexp + params[39] = psys.settings.mol_relink_estiffrand + params[40] = psys.settings.mol_relink_edamp + params[41] = psys.settings.mol_relink_edamprand + params[42] = psys.settings.mol_relink_ebroken + params[43] = psys.settings.mol_relink_ebrokenrand + params[44] = psys.settings.mol_link_friction + params[45] = psys.settings.mol_link_group + params[46] = psys.settings.mol_other_link_active + + mol_exportdata = bpy.context.scene.mol_exportdata + + if initiate: + mol_exportdata[0][2] = psyslen + mol_exportdata[0][3] = parnum + mol_exportdata.append(( + parlen, + par_loc, + par_vel, + par_size, + par_mass, + par_alive, + params, + par_weak + )) + else: + scoll = psys.settings.mol_selfcollision_active + mol_exportdata.append((par_loc, par_vel, par_alive, scoll)) diff --git a/molecularplus/ui.py b/molecularplus/ui.py new file mode 100644 index 0000000..9138198 --- /dev/null +++ b/molecularplus/ui.py @@ -0,0 +1,447 @@ +import bpy +from .utils import get_object +from . import bl_info + +class MS_PT_MolecularHelperPanel(bpy.types.Panel): + """Creates a Panel in the Tool properties window""" + bl_label = "Molecular+ " + str(bl_info['version']).replace('(','').replace(')','').replace(',','.') + bl_idname = "OBJECT_PT_molecular_helper" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Molecular+" + + @classmethod + def poll(cls, context): + return True #context.object != None #psys.settings.mol_active #context.object != None and context.object.type == 'MESH' + + def draw(self, context): + + scn = context.scene + obj = context.object + layout = self.layout + box = layout.box() + row = box.row() + + if obj != None: + row.scale_y = 0.5 + row.label(text = "Molecular Object : " + obj.name) + + if context.object.particle_systems.active: + psys = get_object(context, obj).particle_systems.active + parcount = len(psys.particles) + row = box.row() + row.scale_y = 0.5 + row.label(text = "System particles : " + str(parcount)) + row = box.row() + row.scale_y = 0.5 + row.label(text = "Total particles : " + str(scn.mol_parnum)) + row.operator("object.mol_set_subs", text="", icon="FILE_REFRESH") + + box = layout.box() + row = box.row() + if not scn.mol_simrun and not psys.point_cache.is_baked: + row.enabled = True + row.operator("object.mol_simulate", icon='RADIOBUT_ON', text="Start Simulation") + row = box.row() + row.enabled = False + row.operator("object.clear_pcache", text="Free All Bakes") + + + if psys.point_cache.is_baked and not scn.mol_simrun: + row.enabled = False + row.operator("object.mol_simulate", icon='RADIOBUT_ON', text="Simulation baked") + row = box.row() + row.enabled = True + row.operator("object.clear_pcache", text="Free All Bakes") + if scn.mol_simrun: + row.enabled = False + row.operator("object.mol_simulate", icon='RADIOBUT_ON', text="Process: " + scn.mol_timeremain + " left") + row = box.row() + row.operator("object.cancel_sim", icon='CANCEL', text="Cancel") + + row = box.row() + row.prop(scn,"mol_bake",text = "Bake") + row.prop(scn,"mol_render",text = "Render") + row.prop(scn, "mol_autosubsteps", text="auto") + row = box.row() + row.prop(scn,"mol_substep", text = "Steps") + row.prop(scn,"mol_cpu",text = "Threads") + + row = box.row() + row.prop(scn, "frame_start", text="start") + row.prop(scn, "frame_end", text="end") + row = box.row() + # row = box.row() + # row.enabled = scn.mol_timescale_active + row.prop(scn, "timescale", text="") + #row.alignment = 'RIGHT' + row.prop(scn, "mol_timescale_active", text="TimeScaling") + #row.label(text="") + #row = box.row() + + else: + row.label(text="No Object selected") + + +class MS_PT_MolecularInspectPanel(bpy.types.Panel): + """Creates a Panel in the Tool properties window""" + bl_label = "Inspect" + bl_idname = "OBJECT_PT_molecular_inspect" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Molecular+" + + @classmethod + def poll(cls, context): + return context.object and ('Collision' in context.object.modifiers) + + def draw(self, context): + layout = self.layout + obj = context.object + + row = layout.row() + row.label(text = "Collision: " + obj.name) + row.operator("object.mol_remove_collision",text="", icon='X') + row = layout.row() + row.prop(obj.collision, "damping_factor", text = "Damping") + row = layout.row() + row.prop(obj.collision, "friction_factor", text = "Friction") + row = layout.row() + row.prop(obj.collision, "stickiness", text = "Stickiness", slider=True) + + +class MS_PT_MolecularCreatePanel(bpy.types.Panel): + """Creates a Panel in the Tool properties window""" + bl_label = "Create" + bl_idname = "OBJECT_PT_molecular_create" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Molecular+" + + @classmethod + def poll(cls, context): + return context.object != None and context.object.type == 'MESH' and not ('Collision' in context.object.modifiers) + + def draw(self,context): + + layout = self.layout + scn = context.scene + obj = context.object + + row = layout.row() + row.alignment = 'RIGHT' + row.prop(scn,"mol_voxel_size", text = "Size") + row.prop(scn,"mol_hexgrid", text = "hexa") + row = layout.separator() + row = layout.row() + row.operator("molecular_operators.molecular_makeemitter", icon = 'MOD_PARTICLE_INSTANCE',text = "Emitter") + row.operator("molecular_operators.molecular_makegrid2d", icon = 'GRID',text = "2D Grid") + row = layout.row() + row.operator("molecular_operators.molecular_makegrid3d", icon = 'MOD_REMESH',text = "3D Grid") + row.operator("molecular_operators.molecular_makecollider", icon = 'MOD_PHYSICS', text = "Collider") + + + +class MS_PT_MolecularDonorPanel(bpy.types.Panel): + """Creates a Panel in the Tool properties window""" + bl_label = "q3de" + bl_idname = "OBJECT_PT_molecular_donations" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Molecular+" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + + layout = self.layout + box = layout.box() + row = box.row() + box.active = True + row.alignment = 'CENTER' + row.label(text = "If you like it") + row = box.row() + row.alignment = 'CENTER' + row.label(text = "Support the Development") + row = box.row() + row.alignment = 'CENTER' + row.operator("wm.url_open", text=" click here to Donate ", icon='URL').url = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=J7W7MNCKVBYAA" + row = box.row() + row.alignment = 'CENTER' + row.label(text = "or visit") + row = box.row() + row.alignment = 'CENTER' + row.operator("wm.url_open", text=" q3de.com ", icon='URL').url = "http://www.q3de.com/research/molecular" + #row.label(text = "www.q3de.com") + +class MS_PT_MolecularPanel(bpy.types.Panel): + """Creates a Panel in the Physics properties window""" + bl_label = "Molecular" + bl_idname = "OBJECT_PT_molecular" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "physics" + bl_category = "Molecular" + + @classmethod + def poll(cls, context): + if context.object.particle_systems.active and context.object.particle_systems.active.settings.mol_active == True: + return True + + def draw(self, context): + + layout = self.layout + scn = context.scene + obj = context.object + #obj = bpy.context.view_layer.depsgraph.objects.get(obj.name, None) + + psys = obj.particle_systems.active + if psys is None: + return + + #for the data + psys_eval = get_object(context, obj).particle_systems.active + + if psys.settings.mol_active == False: + return + layout.enabled = psys.settings.mol_active + #row = layout.row() + + ### Density by Mass ### + box = layout.box() + box.prop(psys.settings,"mol_density_active", icon = "PLUS",text = "Calculate particles weight by density") + + if psys.settings.mol_density_active: + + subbox = box.box() + row = subbox.row() + + row.prop(psys.settings,"mol_matter",text = "Preset:") + row = subbox.row() + if psys.settings.mol_matter != "-1": + pmass = (psys.settings.particle_size**3) * float(psys.settings.mol_matter) + density = float(psys.settings.mol_matter) + row.label(icon = "INFO",text = "Kg per CubeMeter:" + str(round(density,5))+ " kg") + else: + row.prop(psys.settings,"mol_density", text = "Kg per CubeMeter:") + row = subbox.row() + + pmass = (psys.settings.particle_size**3) * psys.settings.mol_density + + row = subbox.row() + row.label(icon = "INFO",text = "Mass per Particle: " + str(round(pmass,5)) + " kg") + row = subbox.row() + row.label(icon = "INFO",text = "Total system approx weight: " + str(round(len(psys_eval.particles) * pmass,4)) + " kg") + + + #row = layout.separator() + row = layout.row() + + ### Collision ### + + box = layout.box() + row = box.row() + row.label(text = "Collisions :") + row = box.row() + row.prop(psys.settings,"mol_selfcollision_active", icon = 'PHYSICS', text = "Self Collision") + row.prop(psys.settings,"mol_othercollision_active", icon = 'PHYSICS', text = "Collision with Others") + if psys.settings.mol_othercollision_active: + box.prop(psys.settings,"mol_collision_group",text = "only with:") + if psys.settings.mol_selfcollision_active or psys.settings.mol_othercollision_active: + row = box.row() + row.prop(psys.settings,"mol_friction",text = " Friction:") + row.prop(psys.settings,"mol_collision_damp",text = " Damping:") + + ### Links at Birth ### + + box = layout.box() + row = box.row() + row.label(text = "Links :") + row = box.row() + row.prop(psys.settings,"mol_links_active", icon = 'CONSTRAINT', text = "Link at Birth") + row.prop(psys.settings,"mol_other_link_active", icon = 'CONSTRAINT', text = "Link with Others at Birth") + if psys.settings.mol_other_link_active: + row = box.row() + row.prop(psys.settings,"mol_relink_group",text = "Only with:") + + + if psys.settings.mol_links_active : + row = box.row() + row.prop(psys.settings,"mol_link_length",text = "Search Distance") + row.prop(psys.settings,"mol_link_rellength",text = "Relative") + + row = box.row() + row.prop(psys.settings,"mol_link_max",text = "Max links") + row = box.row() + row.prop(psys.settings,"mol_link_friction",text = "Link friction") + row = box.row() + row.prop(psys.settings,"mol_link_tension",text = "Tension") + row.prop(psys.settings,"mol_link_tensionrand",text = "Rand Tension") + row = box.row() + row.prop(psys.settings,"mol_link_stiff",text = "Stiff") + row.prop(psys.settings,"mol_link_stiffrand",text = "Rand Stiff") + #row = subbox.row() + #row.prop(psys.settings,"mol_link_stiffexp",text = "Exponent") + #row.label(text = "") + row = box.row() + row.prop(psys.settings,"mol_link_damp",text = "Damping") + row.prop(psys.settings,"mol_link_damprand",text = "Rand Damping") + row = box.row() + row.prop(psys.settings,"mol_link_broken",text = "broken") + row.prop(psys.settings,"mol_link_brokenrand",text = "Rand Broken") + row = box.row() + if psys.settings.texture_slots[0]: + row.active = True + row.enabled = True + row.prop(psys.settings, "mol_bake_weak_map", text="WeakMap") + row = box.row() + row.label(text="Using Texture " + psys.settings.texture_slots[0].texture.name) + else: + row.label(text="No Texture found in Particle textures[0]") + row = box.row() + row.active = False + row.enabled = False + row.prop(psys.settings, "mol_bake_weak_map", text="WeakMap") + box = layout.box() + row = box.row() + row.prop(psys.settings,"mol_link_samevalue", text="Same values for compression/expansion") + row = box.row() + + if not psys.settings.mol_link_samevalue: + row.prop(psys.settings,"mol_link_estiff",text = "E Stiff") + row.prop(psys.settings,"mol_link_estiffrand",text = "Rand E Stiff") + row = box.row() + #row.enabled = not psys.settings.mol_link_samevalue + #row.prop(psys.settings,"mol_link_estiffexp",text = "E Exponent") + #row.label(text = "") + row = box.row() + row.prop(psys.settings,"mol_link_edamp",text = "E Damping") + row.prop(psys.settings,"mol_link_edamprand",text = "Rand E Damping") + row = box.row() + row.prop(psys.settings,"mol_link_ebroken",text = "E broken") + row.prop(psys.settings,"mol_link_ebrokenrand",text = "Rand E Broken") + + ### Relinking ### + + box = layout.box() + row = box.row() + row.label(text = "ReLinks :") + #row.prop(psys.settings,"mol_selfrelink_active", icon = 'CONSTRAINT', text = "Self Relink") + #row.prop(psys.settings,"mol_other_link_active", icon = 'CONSTRAINT', text = "Relink with Others") + #if psys.settings.mol_other_link_active: # or psys.settings.mol_selfrelink_active:# or psys.settings.mol_otherrelink_active: + row = box.row() + row.prop(psys.settings,"mol_relink_chance",text = "% linking") + row.prop(psys.settings,"mol_relink_chancerand",text = "Rand % linking") + + if psys.settings.mol_relink_chance > 0.0: + row = box.row() + row.prop(psys.settings,"mol_relink_max",text = "Max links") + row = box.row() + row.prop(psys.settings,"mol_relink_tension",text = "Tension") + row.prop(psys.settings,"mol_relink_tensionrand",text = "Rand Tension") + row = box.row() + row.prop(psys.settings,"mol_relink_stiff",text = "Stiff") + row.prop(psys.settings,"mol_relink_stiffrand",text = "Rand Stiff") + #row = subbox.row() + #row.prop(psys.settings,"mol_relink_stiffexp",text = "Exp") + #row.label(text = "") + row = box.row() + row.prop(psys.settings,"mol_relink_damp",text = "Damping") + row.prop(psys.settings,"mol_relink_damprand",text = "Rand Damping") + row = box.row() + row.prop(psys.settings,"mol_relink_broken",text = "broken") + row.prop(psys.settings,"mol_relink_brokenrand",text = "Rand broken") + row = box.row() + row.prop(psys.settings,"mol_relink_samevalue", text = "Same values for compression/expansion") + row = box.row() + + if not psys.settings.mol_relink_samevalue: + row.prop(psys.settings,"mol_relink_estiff",text = "E Stiff") + row.prop(psys.settings,"mol_relink_estiffrand",text = "Rand E Stiff") + row = box.row() + row.prop(psys.settings,"mol_relink_edamp",text = "E Damping") + row.prop(psys.settings,"mol_relink_edamprand",text = "Rand E Damping") + row = box.row() + row.prop(psys.settings,"mol_relink_ebroken",text = "E broken") + row.prop(psys.settings,"mol_relink_ebrokenrand",text = "Rand E broken") + + box = layout.box() + row = box.row() + if obj.data.uv_layers.active != None: + row.active = True + row.prop(psys.settings,"mol_bakeuv",text = "Bake UV (current: " + str(obj.data.uv_layers.active.name) + ")" ) + else: + row.active = False + if row.active == False: + row.prop(psys.settings,"mol_bakeuv",text = "Bake UV (current: None)" ) + row = box.row() + row.active = False #psys.settings.mol_bakeuv + row.prop(psys.settings,"mol_bakeuv_global",text = "Global") + row = box.row() + +class MolecularAdd(bpy.types.Operator): + bl_idname = "molecular_operators.molecular_add" + bl_label = "Add Molecular object" + bl_description = "Add active object as Molecular" + bl_options = {'REGISTER'} + + @classmethod + def poll(cls, context): + return context.object.particle_systems.active + + def execute(self, context): + obj = context.object + psys = obj.particle_systems.active + psys.settings.mol_active = True + + return {'FINISHED'} + +class MolecularRemove(bpy.types.Operator): + bl_idname = "molecular_operators.molecular_remove" + bl_label = "Remove Molecular object" + bl_description = "Remove Molecular settings from Object" + bl_options = {'REGISTER'} + + def execute(self, context): + obj = context.object + + psys = obj.particle_systems.active + psys.settings.mol_active = False + + return {'FINISHED'} + + +def append_to_PHYSICS_PT_add_panel(self, context): + obj = context.object + + if not obj.type == 'MESH': + return + + psys = obj.particle_systems.active + if not psys: + return + column = self.layout.column(align=True) + #split = column.split(factor=1.0) + #column_left = split.column() + #column_right = split.column() + col = column + + #for the data + psys_eval = get_object(context, obj).particle_systems.active + + if psys.settings.mol_active: + + col.operator( + "molecular_operators.molecular_remove", + text="Molecular", + icon='X' + ) + else: + + col.operator( + "molecular_operators.molecular_add", + text="Molecular", + icon='MOD_PARTICLES' + ) + + +panel_classes = (MS_PT_MolecularPanel,MolecularAdd, MolecularRemove, MS_PT_MolecularHelperPanel, MS_PT_MolecularCreatePanel, MS_PT_MolecularInspectPanel, MS_PT_MolecularDonorPanel) diff --git a/molecularplus/utils.py b/molecularplus/utils.py new file mode 100644 index 0000000..9db94a9 --- /dev/null +++ b/molecularplus/utils.py @@ -0,0 +1,21 @@ +import bpy +#import sys + +def get_object(context, obj): + depsgraph = context.evaluated_depsgraph_get() + return obj.evaluated_get(depsgraph) + + +def destroy_caches(obj): + for psys in obj.particle_systems: + # attempt to destroy cache prior to resimulation + # by provoking an internal RNA update call, this will also update the psys for get_object + if psys.settings.mol_active: + step = psys.point_cache.frame_step + psys.point_cache.frame_step = step + +def update_progress(job_title, progress): + mol = bpy.context.scene + length = 50 # modify this to change the length + block = int(round(length*progress)) + mol.mol_progress = "|" + (">"*block) + ("--"*(length-block)) + str(round(progress*100, 2)) + "%" + "|" + "\n" + "Time remain : " + str(mol.mol_timeremain) + "\n" + "Total links : " + str(mol.mol_totallink) + "\n" + "Dead links : " + str(mol.mol_deadlink) + "\n" + "New links : " + str(mol.mol_newlink) + "\n" + "Total dead links : " + str(mol.mol_totaldeadlink) + "\n" + "Status : " + job_title \ No newline at end of file