[
  {
    "path": ".github/FUNDING.yml",
    "content": "patreon: isathar\n"
  },
  {
    "path": "FGA_VectorFields/__init__.py",
    "content": "bl_info = {\n\t\"name\": \"Vector Field Tools\",\n\t\"author\": \"Andreas Wiehn (isathar)\",\n\t\"version\": (1, 2, 1),\n\t\"blender\": (2, 80, 0),\n\t\"location\": \"View3D > Add > VectorField\",\n\t\"description\": \"Create and edit 3D Vector Fields using the .fga format.\",\n\t\"warning\": \"\",\n\t\"tracker_url\": \"https://github.com/isathar/Blender_UE4_VectorFieldEditor/issues/\",\n\t\"category\": \"Object\"}\n\n\n\nimport bpy\nfrom bpy_extras.io_utils import (ImportHelper,ExportHelper,path_reference_mode)\nfrom bl_operators.presets import AddPresetBase\n\nfrom . import vf_editor, vf_io\n\n\n# UI Panel\nclass VFTOOLS_PT_menupanel(bpy.types.Panel):\n\tbl_idname = \"VFTOOLS_PT_menupanel\"\n\tbl_label = 'Vector Fields'\n\tbl_space_type = 'VIEW_3D'\n\tbl_region_type = 'UI'\n\tbl_category = \"Particle Simulation\"\n\n\tdef __init__(self):\n\t\tpass\n\n\t@classmethod\n\tdef poll(self, context):\n\t\treturn True\n\n\tdef draw(self, context):\n\t\tlayout = self.layout\n\t\t\n\t\t# Create\n\t\tbox = layout.box()\n\t\tshow_createpanel = box.prop(context.window_manager, 'show_createpanel', toggle=True, text=\"Create\")\n\t\tif context.window_manager.show_createpanel:\n\t\t\tbox.row().column().prop(context.window_manager, 'vf_density', text='Resolution')\n\t\t\tbox.row().column().prop(context.window_manager, 'vf_scale', text='Scale')\n\t\t\tbox.row().prop(context.window_manager, 'vf_gravity', text='Gravity')\n\t\t\tbox.row().prop(context.window_manager, 'vf_particleLifetime', text='Particle Lifetime')\n\t\t\t\n\t\t\trow = box.row()\n\t\t\tif context.active_object:\n\t\t\t\tif context.active_object.particle_systems:\n\t\t\t\t\tif context.active_object.particle_systems[0]:\n\t\t\t\t\t\tif context.active_object.particle_systems[0].settings.physics_type == 'FLUID':\n\t\t\t\t\t\t\trow.menu(\"Presets_VFCreate_Fluid\",text=bpy.types.Presets_VFCreate_Fluid.bl_label)\n\t\t\t\t\t\t\trow.operator(\"object.preset_vfcreate_fluid\", text=\"\", icon='ADD')\n\t\t\t\t\t\t\trow.operator(\"object.preset_vfcreate_fluid\", text=\"\", icon='REMOVE').remove_active = True\n\t\t\t\t\t\telif context.active_object.particle_systems[0].settings.physics_type == 'NEWTON':\n\t\t\t\t\t\t\trow.menu(\"Presets_VFCreate\",text=bpy.types.Presets_VFCreate.bl_label)\n\t\t\t\t\t\t\trow.operator(\"object.preset_vfcreate\", text=\"\", icon='ADD')\n\t\t\t\t\t\t\trow.operator(\"object.preset_vfcreate\", text=\"\", icon='REMOVE').remove_active = True\n\t\t\t\n\t\t\tbox.row().operator('vftools.create_vectorfield', text='Generate')\n\t\t\tnumObjects = context.window_manager.vf_density[0] * context.window_manager.vf_density[1] * context.window_manager.vf_density[2]\n\t\t\tbox.row().label(text=\"# of vectors: \" + str(numObjects))\n\t\t\n\t\t# Edit\n\t\tbox = layout.box()\n\t\tshow_editpanel = box.prop(context.window_manager, 'show_editpanel', toggle=True, text=\"Edit\")\n\t\tif context.window_manager.show_editpanel:\n\t\t\tbox2 = box.box()\n\t\t\tbox2.row().label(text=\" Velocity Type:\")\n\t\t\tbox2.row().prop(context.window_manager, 'pvelocity_veltype', text='')\n\t\t\tif context.window_manager.pvelocity_veltype == \"VECT\":\n\t\t\t\tbox2.row(align=True).column().prop(context.window_manager, 'pvelocity_dirvector',text='Custom Direction')\n\t\t\tbox2.row().label(text=\" Blend Method:\")\n\t\t\tbox2.row().prop(context.window_manager, 'pvelocity_genmode', text='')\n\t\t\tif context.window_manager.pvelocity_genmode == 'AVG':\n\t\t\t\tbox2.row().prop(context.window_manager, 'pvelocity_avgratio',text='Ratio')\n\t\t\tbox2.row(align=True).prop(context.window_manager, 'pvelocity_selection',text='Selected Only')\n\t\t\tbox2.row(align=True).prop(context.window_manager, 'pvelocity_invert',text='Invert Next')\n\t\t\tbox2.row(align=True).operator('object.calc_vectorfieldvelocities', text='Calculate')\n\t\t\tbox2 = box.box()\n\t\t\tbox2.row().operator('object.vf_normalizevelocities', text='Normalize')\n\t\t\tbox2.row().operator('object.vf_invertvelocities', text='Invert All')\n\t\t\n\t\t# Display\n\t\tbox = layout.box()\n\t\tbox.prop(context.window_manager, 'show_displaypanel', toggle=True, text=\"Display\")\n\t\tif context.window_manager.show_displaypanel:\t\t\n\t\t\tbox.prop(context.window_manager, 'vf_velocitylinescolor', toggle=True, text=\"Line Color\")\n\t\t\tif context.window_manager.vf_showingvelocitylines < 1:\n\t\t\t\tbox.row().operator('view3d.toggle_vectorfieldvelocities', text='Show')\n\t\t\telse:\n\t\t\t\tbox.row().operator('view3d.toggle_vectorfieldvelocities', text='Hide')\n\t\t\n\t\t# Tools:\n\t\tbox = layout.box()\n\t\tbox.prop(context.window_manager, 'show_toolspanel', toggle=True, text=\"Tools\")\n\t\tif context.window_manager.show_toolspanel:\n\t\t\t#\t# Wind Curve Force\n\t\t\tbox = box.box()\n\t\t\tbox.prop(context.window_manager, 'show_windcurvetool', toggle=True, text=\"Wind Curve Force\")\n\t\t\tif context.active_object:\n\t\t\t\tif context.active_object.type == 'CURVE' or 'CurveForce' in context.active_object.name:\n\t\t\t\t\tif context.window_manager.show_windcurvetool:\n\t\t\t\t\t\tif context.active_object:\n\t\t\t\t\t\t\tif context.active_object.type == 'CURVE':\n\t\t\t\t\t\t\t\tbox.row(align=True).prop(context.window_manager, 'curveForce_strength', text='Strength')\n\t\t\t\t\t\t\t\tbox.row(align=True).prop(context.window_manager, 'curveForce_maxDist', text='Distance')\n\t\t\t\t\t\t\t\tbox.row(align=True).prop(context.window_manager, 'curveForce_falloffPower', text='Power')\n\t\t\t\t\t\t\t\tbox.row(align=True).prop(context.window_manager, 'curveForce_trailout', text='Trail')\n\t\t\t\t\t\t\t\tbox.row(align=True).operator('object.calc_curvewindforce', text='Create New')\n\t\t\t\t\t\t\telif 'CurveForce' in context.active_object.name:\n\t\t\t\t\t\t\t\tbox.row(align=True).prop(context.window_manager, 'curveForce_strength', text='Strength')\n\t\t\t\t\t\t\t\tbox.row(align=True).prop(context.window_manager, 'curveForce_maxDist', text='Distance')\n\t\t\t\t\t\t\t\tbox.row(align=True).prop(context.window_manager, 'curveForce_falloffPower', text='Power')\n\t\t\t\t\t\t\t\tbox.row(align=True).operator('object.edit_curvewindforce', text='Edit Selected')\n\t\t\telse:\n\t\t\t\tbox.row().label(text='Select a curve or curve force')\n\t\t\t\tbox.enabled = False\n\t\t\n\n\n# Saved Data\nclass vector_field(bpy.types.PropertyGroup):\n\tvcoord : bpy.props.FloatVectorProperty(default=(0.0, 0.0, 0.0))\n\tvvelocity : bpy.props.FloatVectorProperty(default=(0.0, 0.0, 0.0))\n\n\n# Export\nclass export_vectorfieldfile(bpy.types.Operator, ExportHelper):\n\tbl_idname = \"object.export_vectorfieldfile\"\n\tbl_label = \"Export FGA\"\n\tbl_description = 'Export selected volume as a FGA file'\n\t\n\tfilename_ext = \".fga\"\n\tfilter_glob : bpy.props.StringProperty(default=\"*.fga\", options={'HIDDEN'})\n\t\n\texportvf_allowmanualbounds : bpy.props.BoolProperty(\n\t\tname=\"Manual Bounds\",default=False,\n\t\tdescription=\"Allow setting vector field bounds manually\"\n\t)\n\texportvf_manualboundsneg : bpy.props.IntVectorProperty(\n\t\tname=\"Bounds Scale -\",min=-10000,max=10000,default=(-100,-100,-100),\n\t\tsubtype='TRANSLATION',\n\t\tdescription=\"Minimum values for bounds in cm (have to be less than maximum values)\"\n\t)\n\texportvf_manualboundspos : bpy.props.IntVectorProperty(\n\t\tname=\"Bounds Scale +\",min=-10000,max=10000,default=(100,100,100),\n\t\tsubtype='TRANSLATION',\n\t\tdescription=\"Maximum values for bounds in cm (have to be greater than minimum values)\"\n\t)\n\texportvf_manualvelocityscale : bpy.props.FloatProperty(\n\t\tname=\"Velocity Scale\",min=1.0,max=10000.0,default=1.0,\n\t\tdescription=\"Multiplier for velocities when using manual bounds\"\n\t)\n\texportvf_scale : bpy.props.FloatProperty(\n\t\tname=\"Bounds Scale\",min=1.0,max=10000.0,default=100.0,\n\t\tdescription=(\"Scale the size of the volume's bounds by this on export\" + \n\t\t\t\" - actual size in UE4 = this * (the vector field's density) * 0.5 cm\")\n\t)\n\texportvf_velscale : bpy.props.BoolProperty(\n\t\tname=\"Scale Velocity\",default=True,\n\t\tdescription=\"Scale velocity with bounds scale\"\n\t)\n\texportvf_locoffset : bpy.props.BoolProperty(\n\t\tname=\"Export Offset\",default=True,\n\t\tdescription=\"Exports the location of the vector field's bounding volume as an offset to min/max bounds\"\n\t)\n\t\n\tdef check_extension(self):\n\t\treturn self.batch_mode == 'OFF'\n\t\n\tdef check(self, context):\n\t\tis_def_change = super().check(context)\n\t\treturn (is_def_change)\n\t\n\tdef draw(self,context):\n\t\tlayout = self.layout\n\t\tbox = layout.box()\n\t\tbox.row().prop(self, 'exportvf_allowmanualbounds', text='Manual Bounds')\n\t\tif self.exportvf_allowmanualbounds:\n\t\t\tbox.row().column().prop(self, 'exportvf_manualboundsneg', text='Minimum Bounds')\n\t\t\tbox.row().column().prop(self, 'exportvf_manualboundspos', text='Maximum Bounds')\n\t\t\tbox.row().prop(self, 'exportvf_manualvelocityscale', text='Velocity Scale:')\n\t\telse:\n\t\t\tbox.row().prop(self, 'exportvf_scale', text='Export Scale:')\n\t\t\tbox.row().prop(self, 'exportvf_velscale', text='Scale Velocity')\n\t\t\tbox.row().prop(self, 'exportvf_locoffset', text='Export Offset')\n\t\n\tdef execute(self, context):\n\t\tif not self.filepath:\n\t\t\traise Exception(\"filepath not set\")\n\t\telse:\n\t\t\tif context.active_object != None:\n\t\t\t\tif 'custom_vectorfield' in context.active_object:\n\t\t\t\t\tvf_io.write_fgafile(self, context.active_object)\n\t\t\t\telse:\n\t\t\t\t\tactiveobj = context.active_object\n\t\t\t\t\tfound = False\n\t\t\t\t\tfor obj in context.selectable_objects:\n\t\t\t\t\t\tif obj.parent == activeobj:\n\t\t\t\t\t\t\tif 'custom_vectorfield' in obj:\n\t\t\t\t\t\t\t\tvf_io.write_fgafile(self, obj)\n\t\t\t\t\t\t\t\tfound = True\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\n\t\t\t\t\tif not found:\n\t\t\t\t\t\traise Exception(\"No velocities\")\n\t\t\telse:\n\t\t\t\traise Exception(\"Nothing selected\")\n\t\t\n\t\treturn {'FINISHED'}\n\n\n# Import\nclass import_vectorfieldfile(bpy.types.Operator, ImportHelper):\n\tbl_idname = \"object.import_vectorfieldfile\"\n\tbl_label = \"Import FGA\"\n\tbl_description = 'Import FGA file as a vector field'\n\n\tfilename_ext = \".fga\"\n\tfilter_glob : bpy.props.StringProperty(default=\"*.fga\", options={'HIDDEN'})\n\t\n\timportvf_scalemult : bpy.props.FloatProperty(\n\t\tname=\"Size Multiplier\",min=0.0001,max=10000.0,step=0.0001,default=0.01,\n\t\tdescription=\"Multiplier to apply to the scale of the volume's bounds on import\"\n\t)\n\timportvf_velscale : bpy.props.BoolProperty(\n\t\tname=\"Scale Velocity\",default=True,\n\t\tdescription=\"Scale velocity on import\"\n\t)\n\timportvf_getoffset : bpy.props.BoolProperty(\n\t\tname=\"Get Offset\",default=True,\n\t\tdescription=\"Get location offset from file\"\n\t)\n\t\n\tdef draw(self,context):\n\t\tlayout = self.layout\n\t\t\n\t\tbox = layout.box()\n\t\trow = box.row()\n\t\t\n\t\trow.prop(self, 'importvf_scalemult', text='Import Scale')\n\t\trow = box.row()\n\t\trow.prop(self, 'importvf_velscale', text='Scale Velocity')\n\t\trow = box.row()\n\t\trow.prop(self, 'importvf_getoffset', text='Import Offset')\n\t\n\t\n\tdef execute(self, context):\n\t\tretmessage = vf_io.parse_fgafile(self, context)\n\t\tprint (\"FGA Import: \" + retmessage + \" (\" + self.filepath + \")\")\n\t\t\n\t\treturn {'FINISHED'}\n\n\nclass Preset_VFCreate(AddPresetBase, bpy.types.Operator):\n\tbl_idname = 'object.preset_vfcreate'\n\tbl_label = 'Physics Presets'\n\tbl_options = {'REGISTER', 'UNDO'}\n\tpreset_menu = 'Presets_VFCreate'\n\tpreset_subdir = 'VF_Default_Presets'\n\n\tpreset_defines = [\n\t\t\"PSystem  = bpy.context.active_object.particle_systems[0].settings\"\n\t\t]\n\n\tpreset_values = [\n\t\t# standard\n\t\t\"PSystem.effector_weights.gravity\",\n\t\t\"PSystem.factor_random\",\n\t\t\"PSystem.particle_size\",\n\t\t\"PSystem.size_random\",\n\t\t\"PSystem.mass\",\n\t\t\"PSystem.brownian_factor\",\n\t\t\"PSystem.drag_factor\",\n\t\t\"PSystem.damping\"\n\t\t]\n\n\nclass Preset_VFCreate_Fluid(AddPresetBase, bpy.types.Operator):\n\tbl_idname = 'object.preset_vfcreate_fluid'\n\tbl_label = 'Fluid Physics Presets'\n\tbl_options = {'REGISTER', 'UNDO'}\n\tpreset_menu = 'Presets_VFCreate_Fluid'\n\tpreset_subdir = 'VF_Fluid_Presets'\n\n\tpreset_defines = [\n\t\t\"PSystem  = bpy.context.active_object.particle_systems[0].settings\"\n\t\t]\n\n\tpreset_values = [\n\t\t# standard\n\t\t\"PSystem.effector_weights.gravity\",\n\t\t\"PSystem.factor_random\",\n\t\t\"PSystem.particle_size\",\n\t\t\"PSystem.size_random\",\n\t\t\"PSystem.mass\",\n\t\t\"PSystem.brownian_factor\",\n\t\t\"PSystem.drag_factor\",\n\t\t\"PSystem.damping\",\n\t\t# fluid\n\t\t\"PSystem.fluid.stiffness\",\n\t\t\"PSystem.fluid.linear_viscosity\",\n\t\t\"PSystem.fluid.buoyancy\",\n\t\t\"PSystem.fluid.stiff_viscosity\",\n\t\t\"PSystem.fluid.fluid_radius\",\n\t\t\"PSystem.fluid.rest_density\"\n\t\t]\n\n\nclass Presets_VFCreate(bpy.types.Menu):\n\tbl_label = \"Physics Presets\"\n\tbl_idname = \"Presets_VFCreate\"\n\tpreset_subdir = \"VF_Default_Presets\"\n\tpreset_operator = \"script.execute_preset\"\n\tdraw = bpy.types.Menu.draw_preset\n\n\nclass Presets_VFCreate_Fluid(bpy.types.Menu):\n\tbl_label = \"Fluid Presets\"\n\tbl_idname = \"Presets_VFCreate_Fluid\"\n\tpreset_subdir = \"VF_Fluid_Presets\"\n\tpreset_operator = \"script.execute_preset\"\n\tdraw = bpy.types.Menu.draw_preset\n\n\ndef exportmenu_func(self, context):\n\tself.layout.operator(export_vectorfieldfile.bl_idname,\n\t\t\t\t\t\ttext=\"UE4 Vector Field (.fga)\")\n\ndef importmenu_func(self, context):\n\tself.layout.operator(import_vectorfieldfile.bl_idname,\n\t\t\t\t\t\ttext=\"UE4 Vector Field (.fga)\")\n\n\ndef initdefaults():\n\tbpy.types.Object.custom_vectorfield = bpy.props.CollectionProperty(type=vector_field)\n\tbpy.types.Object.custom_vf_startlocs = bpy.props.CollectionProperty(type=vector_field)\n\tbpy.types.Object.vf_object_density = bpy.props.FloatVectorProperty(default=(0.0,0.0,0.0))\n\tbpy.types.Object.vf_object_scale = bpy.props.FloatVectorProperty(default=(1.0,1.0,1.0))\n\t\n\t# generate\n\tbpy.types.WindowManager.vf_density = bpy.props.IntVectorProperty(\n\t\tdefault=(16,16,16),min=1,max=128,\n\t\tdescription=\"The number of points in the vector field\"\n\t)\n\tbpy.types.WindowManager.vf_scale = bpy.props.FloatVectorProperty(\n\t\tdefault=(1.0,1.0,1.0),min=0.25,\n\t\tdescription=\"Distance between points in the vector field\"\n\t)\n\tbpy.types.WindowManager.vf_gravity = bpy.props.FloatProperty(\n\t\tdefault=0.0,min=0.0,description=\"Amount of influence gravity has on the volume's particles\"\n\t)\n\tbpy.types.WindowManager.vf_particleLifetime = bpy.props.IntProperty(default=32)\n\t\n\t# calculate/edit\n\tbpy.types.WindowManager.pvelocity_veltype = bpy.props.EnumProperty(\n\t\tname=\"Velocity Type\",\n\t\titems=(('VECT', \"Custom Vector\", \"Use direction vector as velocities\"),\n\t\t\t   ('ANGVEL', \"Angular Velocity\", \"Get particles' current angular velocities (spin)\"),\n\t\t\t   ('PNT', \"Point\", \"Get a direction vector pointing away from 3D cursor\"),\n\t\t\t   ('DIST', \"Distance\", \"Get particles' offsets from their initial locations\"),\n\t\t\t   ('PVEL', \"Velocity\", \"Get particles' current velocities\"),\n\t\t\t   ),\n\t\tdefault='PVEL',\n\t\tdescription=\"Method of obtaining velocities from the particle system\",\n\t)\n\tbpy.types.WindowManager.pvelocity_genmode = bpy.props.EnumProperty(\n\t\tname=\"Calculation Method\",\n\t\titems=(('REF', \"Reflection\", \"Get the reflection vector between old and new velocities\"),\n\t\t\t   ('CRS', \"Cross Product\", \"Get the cross product of old and current velocities\"),\n\t\t\t   ('AVG', \"Average\", \"Get the average of old and new velocities\"),\n\t\t\t   ('MULT', \"Multiply\", \"Multiply current velocities with old velocities\"),\n\t\t\t   ('ADD', \"Add\", \"Add new velocities to existing ones\"),\n\t\t\t   ('MATH', \"Formula\", \"Use a customizable function to calculate velocities\"),\n\t\t\t   ('REP', \"Replace\", \"Default - Overwrite old velocities\"),\n\t\t\t   ),\n\t\tdefault='REP',\n\t\tdescription=\"Method of combining current and saved velocities\",\n\t)\n\tbpy.types.WindowManager.pvelocity_invert = bpy.props.BoolProperty(\n\t\tdefault=False,description=\"Invert current velocities before saving\"\n\t)\n\tbpy.types.WindowManager.pvelocity_selection = bpy.props.BoolProperty(\n\t\tdefault=False,description=\"Replace selected particles' velocities only\"\n\t)\n\tbpy.types.WindowManager.pvelocity_avgratio = bpy.props.FloatProperty(\n\t\tdefault=0.5,min=0.0,max=1.0,\n\t\tdescription=\"The ratio between the current and new velocities\"\n\t)\n\tbpy.types.WindowManager.pvelocity_dirvector = bpy.props.FloatVectorProperty(\n\t\tdefault=(0.0,0.0,1.0), subtype='TRANSLATION', unit='NONE', min=-100.0, max=100.0, \n\t\tdescription=\"Vector to set all velocities to\"\n\t)\n\t# curve force\n\tbpy.types.WindowManager.curveForce_strength = bpy.props.FloatProperty(\n\t\tdefault=8.0,description=\"The power of each wind force along the curve\"\n\t)\n\tbpy.types.WindowManager.curveForce_maxDist = bpy.props.FloatProperty(\n\t\tdefault=4.0,description=\"Maximum influence distance for wind forces\"\n\t)\n\tbpy.types.WindowManager.curveForce_falloffPower = bpy.props.FloatProperty(\n\t\tdefault=2.0,description=\"Distance falloff for wind forces\"\n\t)\n\tbpy.types.WindowManager.curveForce_trailout = bpy.props.BoolProperty(\n\t\tdefault=False,description=\"Fade the size and influence of the wind forces along the curve\"\n\t)\n\t\n\t# display\n\tbpy.types.WindowManager.vf_showingvelocitylines = bpy.props.IntProperty(default=-1)\n\tbpy.types.WindowManager.vf_velocitylinescolor = bpy.props.FloatVectorProperty(\n\t\tdefault=(1.0,1.0,1.0),min=0.25,subtype='COLOR',\n\t\tdescription=\"Line Color\"\n\t)\n\t\n\t# toggle vars for panel\n\tbpy.types.WindowManager.show_createpanel = bpy.props.BoolProperty(\n\t\tdefault=False,description=\"Toggle Section\"\n\t)\n\tbpy.types.WindowManager.show_editpanel = bpy.props.BoolProperty(\n\t\tdefault=False,description=\"Toggle Section\"\n\t)\n\tbpy.types.WindowManager.show_displaypanel = bpy.props.BoolProperty(\n\t\tdefault=False,description=\"Toggle Section\"\n\t)\n\tbpy.types.WindowManager.show_toolspanel = bpy.props.BoolProperty(\n\t\tdefault=False,description=\"Toggle Section\"\n\t)\n\tbpy.types.WindowManager.show_windcurvetool = bpy.props.BoolProperty(\n\t\tdefault=False,description=\"Toggle Section\"\n\t)\n\t\n\ndef clearvars():\n\tprops = [\n\t\t'vf_density','vf_scale','vf_gravity','vf_particleLifetime','pvelocity_veltype','pvelocity_genmode',\n\t\t'pvelocity_invert','pvelocity_selection','pvelocity_avgratio','pvelocity_dirvector',\n\t\t'curveForce_strength','curveForce_maxDist','curveForce_falloffPower','curveForce_trailout','curveForce_dispSize'\n\t\t'vf_showingvelocitylines','vf_velocitylinescolor',\n\t\t'show_createpanel','show_editpanel','show_displaypanel','show_toolspanel','show_windcurvetool'\n\t]\n\t\n\tfor p in props:\n\t\tif bpy.context.window_manager.get(p) != None:\n\t\t\tdel bpy.context.window_manager[p]\n\t\ttry:\n\t\t\tx = getattr(bpy.types.WindowManager, p)\n\t\t\tdel x\n\t\texcept:\n\t\t\tpass\n\n\n\nclasses = (\n\tvector_field,\n\tPresets_VFCreate,\n\tPreset_VFCreate,\n\tPresets_VFCreate_Fluid,\n\tPreset_VFCreate_Fluid,\n\tvf_editor.calc_vectorfieldvelocities,\n\tvf_editor.VFTOOLS_OT_create_vectorfield,\n\tvf_editor.calc_curvewindforce,\n\tvf_editor.edit_curvewindforce,\n\tvf_editor.toggle_vectorfieldvelocities,\n\tvf_editor.vf_normalizevelocities,\n\tvf_editor.vf_invertvelocities,\n\texport_vectorfieldfile,\n\timport_vectorfieldfile,\n\tVFTOOLS_PT_menupanel,\n\t#exportmenu_func,\n\t#importmenu_func,\n)\n\n\ndef register():\n\tfrom bpy.utils import register_class\n\tfor cls in classes:\n\t\tregister_class(cls)\n\n\tinitdefaults()\n\tbpy.types.TOPBAR_MT_file_export.append(exportmenu_func)\n\tbpy.types.TOPBAR_MT_file_import.append(importmenu_func)\n\n\ndef unregister():\n\tbpy.types.TOPBAR_MT_file_export.remove(exportmenu_func)\n\tbpy.types.TOPBAR_MT_file_import.remove(importmenu_func)\n\t\n\tfrom bpy.utils import unregister_class\n\tfor cls in reversed(classes):\n\t\tunregister_class(cls)\n\n\tclearvars()\n\t\n\n\nif __name__ == '__main__':\n\tregister()"
  },
  {
    "path": "FGA_VectorFields/vf_editor.py",
    "content": "### Editor Functions\n\nimport bpy\nimport gpu\n\nfrom mathutils import Vector, Matrix\n\nfrom gpu_extras.batch import batch_for_shader\n\n\n### Create\n\n# Creates a new vector field from parameters\ndef build_vectorfield(context):\n\tzeroVect = Vector((0.0,0.0,0.0))\n\t\n\tdensityVal = Vector(context.window_manager.vf_density)\n\tscaleVal = Vector(context.window_manager.vf_scale)\n\tbaseLoc = -1.0 * (densityVal * 0.5) + Vector((0.5, 0.5, 0.5))\n\t\n\ttotalvertscount = densityVal[0] * densityVal[1] * densityVal[2]\n\t\n\tvf_startlocs = [zeroVect.copy() for v in range(int(totalvertscount))]\n\tvf_velocities = [zeroVect.copy() for v in range(int(totalvertscount))]\n\t\n\tvolcount = 0\n\tfor v in range(len(context.scene.objects)):\n\t\tif (\"VF_Volume\" in str(context.scene.objects[v].name)):\n\t\t\tvolcount += 1\n\t\n\t# create the volume\n\tme = bpy.data.meshes.new(\"Vert\")\n\tme.vertices.add(totalvertscount)\n\tfrom bpy_extras import object_utils\n\tobject_utils.object_data_add(context, me, operator=None)\n\t\n\tvolMesh = context.active_object\n\tvolMesh.name = 'VF_Volume_' + str(volcount)\n\tvolMesh.display.show_shadows = False\n\tvolMesh.location = zeroVect\n\t\n\t# add the particle system\n\tbpy.ops.object.particle_system_add()\n\tdegp = bpy.context.evaluated_depsgraph_get()\n\tparticle_systems = volMesh.evaluated_get(degp).particle_systems\n\tpsettings = particle_systems[0].settings\n\t\n\t#me = volMesh.data\n\t#me.update()\n\tmeshverts = [v for v in me.vertices]\n\t\n\tvolMesh.vf_object_density = densityVal\n\tvolMesh.vf_object_scale = scaleVal\n\t\n\t# create vertices + initialize velocities list\n\txval = int(densityVal[0])\n\tyval = int(densityVal[1])\n\tzval = int(densityVal[2])\n\ttempV = zeroVect.copy()\n\tcounter = 0\n\tfor i in range(zval):\n\t\tfor j in range(yval):\n\t\t\tfor k in range(xval):\n\t\t\t\ttempV[0] = (baseLoc[0] + (k)) * scaleVal[0]\n\t\t\t\ttempV[1] = (baseLoc[1] + (j)) * scaleVal[1]\n\t\t\t\ttempV[2] = (baseLoc[2] + (i)) * scaleVal[2]\n\t\t\t\tmeshverts[counter].co = tempV\n\t\t\t\tvf_startlocs[counter][0] = tempV[0]\n\t\t\t\tvf_startlocs[counter][1] = tempV[1]\n\t\t\t\tvf_startlocs[counter][2] = tempV[2]\n\t\t\t\tcounter += 1\n\t\n\tme.update()\n\t\n\tdel meshverts[:]\n\t\n\t# create the particle system\n\tpsettings.count = totalvertscount\n\tpsettings.emit_from = 'VERT'\n\tpsettings.normal_factor = 0.0\n\tpsettings.use_emit_random = False\n\tpsettings.frame_end = 1\n\tpsettings.lifetime = context.window_manager.vf_particleLifetime\n\tpsettings.grid_resolution = 1\n\tpsettings.use_rotations = True\n\tpsettings.use_dynamic_rotation = True\n\tpsettings.effector_weights.gravity = context.window_manager.vf_gravity\n\t\n\t# create the bounding box\n\tbpy.ops.mesh.primitive_cube_add(location=(0.0,0.0,0.0))\n\tboundsMesh = context.active_object\n\tboundsMesh.name = 'VF_Bounds_' + str(volcount)\n\tboundsMesh.display.show_shadows = False\n\t\n\t# match scale to the volume\n\tboundsMesh.scale[0] = (densityVal[0] * 0.5) * scaleVal[0]\n\tboundsMesh.scale[1] = (densityVal[1] * 0.5) * scaleVal[1]\n\tboundsMesh.scale[2] = (densityVal[2] * 0.5) * scaleVal[2]\n\tbpy.ops.object.transform_apply(scale=True)\n\t\n\tbpy.ops.object.mode_set(mode='EDIT')\n\tbpy.ops.mesh.delete(type='ONLY_FACE')\n\tbpy.ops.object.mode_set(mode='OBJECT')\n\t\n\tvolMesh.parent = boundsMesh\n\t\n\tif len(vf_velocities) == len (vf_startlocs):\n\t\tfor i in range(len(vf_velocities)):\n\t\t\ttempvertdata = volMesh.custom_vectorfield.add()\n\t\t\ttempvertdata.vcoord = Vector(vf_startlocs[i])\n\t\t\ttempvertdata.vvelocity = Vector(vf_velocities[i])\n\telse:\n\t\tprint (\"VectorField coords/velocities length mismatch!\")\n\t\n\tdel vf_velocities[:]\n\tdel vf_startlocs[:]\n\t\n\ttempconstraint = volMesh.constraints.new(type='COPY_TRANSFORMS')\n\ttempconstraint.target = volMesh.parent\n\t\n\treturn volMesh.name\n\n\nclass VFTOOLS_OT_create_vectorfield(bpy.types.Operator):\n\tbl_idname = 'vftools.create_vectorfield'\n\tbl_label = 'Create VectorField'\n\tbl_description = 'Create a new vector field from resolution and scale values'\n\tbl_options = {'REGISTER', 'UNDO'}\n\t\n\tdef execute(self, context):\n\t\tbuild_vectorfield(context)\n\t\treturn {'FINISHED'}\n\n\n\n\n# Performs vector math + writes results to data\nclass calc_vectorfieldvelocities(bpy.types.Operator):\n\tbl_idname = 'object.calc_vectorfieldvelocities'\n\tbl_label = 'Save VF EndLocations'\n\tbl_description = 'Calculate and save velocities'\n\tbl_options = {'REGISTER', 'UNDO'}\n\n\t@classmethod\n\tdef poll(cls, context):\n\t\treturn (context.mode == \"OBJECT\" and context.active_object != None) and 'VF_Volume_' in context.active_object.name\n\t\n\tdef execute(self, context):\n\t\tinvmult = -1.0 if context.window_manager.pvelocity_invert else 1.0\n\t\t\n\t\tuseselection = context.window_manager.pvelocity_selection\n\t\t\n\t\tparticleslist = []\n\t\t\n\t\tvolmesh = context.active_object\n\t\t\n\t\tvf_velocities = [Vector(v.vvelocity) for v in volmesh.custom_vectorfield]\n\t\t\n\t\t\n\t\tdegp = bpy.context.evaluated_depsgraph_get()\n\t\tparticle_systems = volmesh.evaluated_get(degp).particle_systems\n\t\t\n\t\t## Get velocities\n\t\tif context.window_manager.pvelocity_veltype == \"VECT\":\n\t\t\ttempvect = Vector(context.window_manager.pvelocity_dirvector)\n\t\t\tparticleslist = [tempvect.copy() for v in vf_velocities]\n\t\telif context.window_manager.pvelocity_veltype == \"DIST\":\n\t\t\tvf_startlocs = [Vector(v.vcoord) for v in volmesh.custom_vectorfield]\n\t\t\tvf_endLocs = [v.location for v in particle_systems[0].particles]\n\t\t\tparticleslist = [(vf_endLocs[i] - vf_startlocs[i]) for i in range(len(vf_endLocs))]\n\t\t\tdel vf_startlocs[:]\n\t\t\tdel vf_endLocs[:]\n\t\telif context.window_manager.pvelocity_veltype == \"ANGVEL\":\n\t\t\tparticleslist = [p.angular_velocity for p in particle_systems[0].particles]\n\t\telif context.window_manager.pvelocity_veltype == \"PNT\":\n\t\t\tcursorloc = context.scene.cursor.location\n\t\t\tparticleslist = [(Vector(v.vcoord) - cursorloc).normalized() for v in volmesh.custom_vectorfield]\n\t\telse:\n\t\t\tparticleslist = [p.velocity for p in particle_systems[0].particles]\n\t\t\n\t\tmvertslist = []\n\t\tif useselection:\n\t\t\tme = volmesh.data\n\t\t\tmvertslist = tuple(v.select for v in me.vertices)\n\t\t\n\t\t\n\t\t## Blend with List / calculate\n\t\t\n\t\t# multiply\n\t\tif context.window_manager.pvelocity_genmode == 'MULT':\n\t\t\tif useselection:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tif mvertslist[i]:\n\t\t\t\t\t\tvf_velocities[i] = Vector(\n\t\t\t\t\t\t\t(vf_velocities[i][0] * (particleslist[i][0] * invmult), \n\t\t\t\t\t\t\tvf_velocities[i][1] * (particleslist[i][1] * invmult), \n\t\t\t\t\t\t\tvf_velocities[i][2] * (particleslist[i][2] * invmult))\n\t\t\t\t\t\t)\n\t\t\telse:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tvf_velocities[i] = Vector(\n\t\t\t\t\t\t(vf_velocities[i][0] * (particleslist[i][0] * invmult), \n\t\t\t\t\t\tvf_velocities[i][1] * (particleslist[i][1] * invmult), \n\t\t\t\t\t\tvf_velocities[i][2] * (particleslist[i][2] * invmult))\n\t\t\t\t\t)\n\t\t\t\n\t\t# add\n\t\telif context.window_manager.pvelocity_genmode == 'ADD':\n\t\t\tif useselection:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tif mvertslist[i]:\n\t\t\t\t\t\tvf_velocities[i] = vf_velocities[i] + ((particleslist[i]) * invmult)\n\t\t\telse:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tvf_velocities[i] = vf_velocities[i] + ((particleslist[i]) * invmult)\n\t\t\t\n\t\t# average\n\t\telif context.window_manager.pvelocity_genmode == 'AVG':\n\t\t\tavgratio = context.window_manager.pvelocity_avgratio\n\t\t\tif useselection:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tif mvertslist[i]:\n\t\t\t\t\t\tvf_velocities[i] = ((vf_velocities[i] * (1.0 - avgratio)) + ((particleslist[i] * invmult) * avgratio))\n\t\t\telse:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tvf_velocities[i] = ((vf_velocities[i] * (1.0 - avgratio)) + ((particleslist[i] * invmult) * avgratio))\n\t\t\t\n\t\t# replace\n\t\telif context.window_manager.pvelocity_genmode == 'REP':\n\t\t\tif useselection:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tif mvertslist[i]:\n\t\t\t\t\t\tvf_velocities[i] = particleslist[i] * invmult\n\t\t\telse:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tvf_velocities[i] = particleslist[i] * invmult\n\t\t\t\n\t\t# cross product\n\t\telif context.window_manager.pvelocity_genmode == 'CRS':\n\t\t\tif useselection:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tif mvertslist[i]:\n\t\t\t\t\t\tvf_velocities[i] = vf_velocities[i].cross(particleslist[i])\n\t\t\telse:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tvf_velocities[i] = vf_velocities[i].cross(particleslist[i])\n\t\t\t\n\t\t# reflection\n\t\telif context.window_manager.pvelocity_genmode == 'REF':\n\t\t\tif useselection:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tif mvertslist[i]:\n\t\t\t\t\t\tvf_velocities[i] = vf_velocities[i].reflect(particleslist[i])\n\t\t\telse:\n\t\t\t\tfor i in range(len(particleslist)):\n\t\t\t\t\tvf_velocities[i] = vf_velocities[i].reflect(particleslist[i])\n\t\t\n\t\t\n\t\t# write new velocities\n\t\tfor i in range(len(vf_velocities)):\n\t\t\tvolmesh.custom_vectorfield[i].vvelocity = vf_velocities[i].copy()\n\t\t\n\t\t\n\t\tdel particleslist[:]\n\t\tdel vf_velocities[:]\n\t\tcontext.window_manager.vf_showingvelocitylines = -1\n\t\treturn {'FINISHED'}\n\n\n# Normalizes the list\nclass vf_normalizevelocities(bpy.types.Operator):\n\tbl_idname = 'object.vf_normalizevelocities'\n\tbl_label = 'Normalize'\n\tbl_description = 'Normalizes the currently saved velocity list'\n\tbl_options = {'REGISTER', 'UNDO'}\n\n\t@classmethod\n\tdef poll(cls, context):\n\t\treturn context.active_object != None and 'VF_Volume_' in context.active_object.name\n\t\n\tdef execute(self, context):\n\t\tvolmesh = context.active_object\n\t\tfor i in range(len(volmesh.custom_vectorfield)):\n\t\t\ttempVect = Vector(volmesh.custom_vectorfield[i].vvelocity)\n\t\t\tvolmesh.custom_vectorfield[i].vvelocity = tempVect.normalized()\n\t\tcontext.window_manager.vf_showingvelocitylines = -1\n\t\treturn {'FINISHED'}\n\n# Inverts the list\nclass vf_invertvelocities(bpy.types.Operator):\n\tbl_idname = 'object.vf_invertvelocities'\n\tbl_label = 'Invert All'\n\tbl_description = 'Inverts the currently saved velocity list'\n\tbl_options = {'REGISTER', 'UNDO'}\n\t\n\t@classmethod\n\tdef poll(cls, context):\n\t\treturn context.active_object != None and 'VF_Volume_' in context.active_object.name\n\t\n\tdef execute(self, context):\n\t\tvolmesh = context.active_object\n\t\tfor i in range(len(volmesh.custom_vectorfield)):\n\t\t\tvolmesh.custom_vectorfield[i].vvelocity[0] *= -1.0\n\t\t\tvolmesh.custom_vectorfield[i].vvelocity[1] *= -1.0\n\t\t\tvolmesh.custom_vectorfield[i].vvelocity[2] *= -1.0\n\t\tcontext.window_manager.vf_showingvelocitylines = -1\n\t\treturn {'FINISHED'}\n\n\n# Tools:\n\n# Curve Wind Force:\n\n# creates a wind tunnel from selected curve object\nclass calc_curvewindforce(bpy.types.Operator):\n\tbl_idname = 'object.calc_curvewindforce'\n\tbl_label = 'Curve Wind force'\n\tbl_description = 'create wind forces along a spline to direct velocities along it'\n\tbl_options = {'REGISTER', 'UNDO'}\n\t\n\t@classmethod\n\tdef poll(cls, context):\n\t\treturn (context.mode == \"OBJECT\" and context.active_object != None) and context.active_object.type == 'CURVE'\n\t\n\tdef execute(self, context):\n\t\tcurvepoints = []\n\t\tcurveobj = context.active_object\n\t\t\n\t\tbpy.ops.object.empty_add(type='PLAIN_AXES')\n\t\tparentobj = context.active_object\n\t\tparentobj.name = 'CurveForce'\n\t\t\n\t\tif len(curveobj.data.splines[0].bezier_points) > 1:\n\t\t\tcurvepoints = [v.co for v in curveobj.data.splines[0].bezier_points]\n\t\telse:\n\t\t\tcurvepoints = [v.co for v in curveobj.data.splines[0].points]\n\t\t\n\t\tcurveobj.parent = parentobj\n\t\tcontext.active_object.select_set(False)\n\t\tcurveobj.select_set(True)\n\t\t\n\t\tpreviousnormal = Vector((0.0,0.0,0.0))\n\t\t\n\t\tlastStrength = context.window_manager.curveForce_strength\n\t\tlastDistance = context.window_manager.curveForce_maxDist\n\t\t\n\t\tfor i in range(len(curvepoints)):\n\t\t\tcpoint = Vector((curvepoints[i][0],curvepoints[i][1],curvepoints[i][2]))\n\t\t\t\n\t\t\tbpy.ops.object.empty_add(type='SINGLE_ARROW',location=(cpoint))\n\t\t\tcontext.active_object.name = 'ForceObj'\n\t\t\t# turn into forcefield\n\t\t\tbpy.ops.object.forcefield_toggle()\n\t\t\tcontext.active_object.field.type = 'WIND'\n\t\t\t\n\t\t\tif context.window_manager.curveForce_trailout:\n\t\t\t\tif i > 0:\n\t\t\t\t\tlastStrength = lastStrength * 0.9\n\t\t\t\t\tlastDistance = lastDistance * 0.9\n\t\t\t\n\t\t\tcontext.active_object.field.strength = lastStrength\n\t\t\tcontext.active_object.field.use_max_distance = True\n\t\t\tcontext.active_object.field.distance_max = lastDistance\n\t\t\tcontext.active_object.field.falloff_power = context.window_manager.curveForce_falloffPower\n\t\t\t\n\t\t\t# get the curve's direction between points\n\t\t\ttempnorm = Vector((0,0,0))\n\t\t\tif (i < len(curvepoints) - 1):\n\t\t\t\tcpoint2 = Vector((curvepoints[i + 1][0],curvepoints[i + 1][1],curvepoints[i + 1][2]))\n\t\t\t\ttempnorm = cpoint - cpoint2\n\t\t\t\tif i > 0:\n\t\t\t\t\tif abs(previousnormal.length) > 0.0:\n\t\t\t\t\t\ttempnorm = (tempnorm + previousnormal) / 2.0\n\t\t\t\tpreviousnormal = tempnorm\n\t\t\telse:\n\t\t\t\tif curveobj.data.splines[0].use_cyclic_u or curveobj.data.splines[0].use_cyclic_u:\n\t\t\t\t\tcpoint2 = Vector((curvepoints[0][0],curvepoints[0][1],curvepoints[0][2]))\n\t\t\t\t\ttempnorm = cpoint - cpoint2\n\t\t\t\t\tif abs(previousnormal.length) > 0.0:\n\t\t\t\t\t\ttempnorm = (tempnorm + previousnormal) / 2.0\n\t\t\t\t\tpreviousnormal = tempnorm\n\t\t\t\telse:\n\t\t\t\t\tcpoint2 = Vector((curvepoints[i - 1][0],curvepoints[i - 1][1],curvepoints[i - 1][2]))\n\t\t\t\t\ttempnorm = cpoint2 - cpoint\n\t\t\t\t\tif abs(previousnormal.length) > 0.0:\n\t\t\t\t\t\ttempnorm = (tempnorm + previousnormal) / 2.0\n\t\t\t\t\tpreviousnormal = tempnorm\n\t\t\t\n\t\t\tif abs(tempnorm.length) > 0.0:\n\t\t\t\tz = Vector((0,0,1))\n\t\t\t\tangle = tempnorm.angle(z)\n\t\t\t\taxis = z.cross(tempnorm)\n\t\t\t\tmat = Matrix.Rotation(angle, 4, axis)\n\t\t\t\tmat_world = context.active_object.matrix_world @ mat\n\t\t\t\tcontext.active_object.matrix_world = mat_world\n\t\t\t\n\t\t\tcontext.active_object.parent = parentobj\n\t\t\t\n\t\t\n\t\treturn {'FINISHED'}\n\n# creates a wind tunnel from selected curve object\nclass edit_curvewindforce(bpy.types.Operator):\n\tbl_idname = 'object.edit_curvewindforce'\n\tbl_label = 'Curve Wind force'\n\tbl_description = 'Edit settings on the selected curve wind force object'\n\tbl_options = {'REGISTER', 'UNDO'}\n\t\n\t@classmethod\n\tdef poll(cls, context):\n\t\tif context.mode == \"OBJECT\" and context.active_object != None:\n\t\t\treturn 'CurveForce' in context.active_object.name\n\t\n\tdef execute(self, context):\n\t\tnewStrength = context.window_manager.curveForce_strength\n\t\tnewDistance = context.window_manager.curveForce_maxDist\n\t\tnewFalloff = context.window_manager.curveForce_falloffPower\n\t\t\n\t\tcurveforceobj = context.active_object\n\t\t\n\t\tobjlist = [obj for obj in context.scene.objects if obj.parent == curveforceobj]\n\t\t\n\t\tfor obj in objlist:\n\t\t\tif 'ForceObj' in obj.name:\n\t\t\t\tobj.field.strength = newStrength\n\t\t\t\tobj.field.distance_max = newDistance\n\t\t\t\tobj.field.falloff_power = newFalloff\n\t\t\n\t\treturn {'FINISHED'}\n\n\n\n\n### Display\n\nclass toggle_vectorfieldinfo(bpy.types.Operator):\n\tbl_idname = \"object.toggle_vectorfieldinfo\"\n\tbl_label = 'Show Current VF Info'\n\tbl_description = 'Display information about the currently selected vector field'\n\t\n\t@classmethod\n\tdef poll(cls, context):\n\t\treturn context.active_object != None and 'VF_Volume_' in context.active_object.name\n\t\n\tdef execute(self, context):\n\t\treturn {'FINISHED'}\n\n\n# Toggle velocities display as lines\nclass toggle_vectorfieldvelocities(bpy.types.Operator):\n\tbl_idname = \"view3d.toggle_vectorfieldvelocities\"\n\tbl_label = 'Show velocities'\n\tbl_description = 'Display velocities as 3D lines'\n\t\n\t_handle = None\n\t\n\t@classmethod\n\tdef poll(cls, context):\n\t\treturn context.mode == \"OBJECT\" and context.active_object != None and 'VF_Volume_' in context.active_object.name\n\t\n\tdef modal(self, context, event):\n\t\tif context.area:\n\t\t\tcontext.area.tag_redraw()\n\t\t\n\t\tif context.window_manager.vf_showingvelocitylines == -1:\n\t\t\tbpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')\n\t\t\tcontext.window_manager.vf_showingvelocitylines = 0\n\t\t\treturn {\"CANCELLED\"}\n\t\treturn {\"PASS_THROUGH\"}\n\n\tdef invoke(self, context, event):\n\t\t#print (\"test\")\n\t\tif context.area.type == \"VIEW_3D\":\n\t\t\tif context.window_manager.vf_showingvelocitylines < 1:\n\t\t\t\tvolmesh = context.active_object\n\t\t\t\ttemploc = Vector((0.0,0.0,0.0))\n\t\t\t\tif volmesh.parent:\n\t\t\t\t\ttemploc = volmesh.parent.location\n\t\t\t\t\n\t\t\t\tvf_coords = [(Vector(v.vcoord) + temploc) for v in volmesh.custom_vectorfield]\n\t\t\t\tvf_velocities = [Vector(v.vvelocity) for v in volmesh.custom_vectorfield]\n\t\t\t\tvf_DrawVelocities = [Vector((0.0,0.0,0.0)) for i in range(len(volmesh.custom_vectorfield) * 2)]\n\t\t\t\t\n\t\t\t\tvelcounter = 0\n\t\t\t\tfor i in range(len(vf_coords)):\n\t\t\t\t\tvf_DrawVelocities[velcounter] = vf_coords[i].copy()\n\t\t\t\t\tvelcounter += 1\n\t\t\t\t\tvf_DrawVelocities[velcounter] = vf_coords[i] + vf_velocities[i]\n\t\t\t\t\tvelcounter += 1\n\t\t\t\t\n\t\t\t\tshader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')\n\t\t\t\tbatch = batch_for_shader(shader, 'LINES', {\"pos\": vf_DrawVelocities})\n\t\t\t\t\n\t\t\t\tcontext.window_manager.vf_showingvelocitylines = 1\n\t\t\t\tcolor = Vector((context.window_manager.vf_velocitylinescolor[0], context.window_manager.vf_velocitylinescolor[1], context.window_manager.vf_velocitylinescolor[2], 1.0))\n\t\t\t\tself._handle = bpy.types.SpaceView3D.draw_handler_add(draw_vectorfield,\n\t\t\t\t\t(shader, batch, color), 'WINDOW', 'POST_VIEW')\n\t\t\t\t\n\t\t\t\tcontext.window_manager.modal_handler_add(self)\n\t\t\t\tcontext.area.tag_redraw()\n\t\t\t\treturn {\"RUNNING_MODAL\"}\n\t\t\telse:\n\t\t\t\tcontext.window_manager.vf_showingvelocitylines = -1\n\t\t\t\treturn {'RUNNING_MODAL'}\n\t\telse:\n\t\t\tself.report({\"WARNING\"}, \"View3D not found, can't run operator\")\n\t\t\treturn {\"CANCELLED\"}\n\n\n\n# draw lines\ndef draw_vectorfield(shader, batch, color):\n\tshader.bind()\n\tshader.uniform_float(\"color\", color)\n\tbatch.draw(shader)\n\n"
  },
  {
    "path": "FGA_VectorFields/vf_io.py",
    "content": "### Import/Export Functions\n\nimport bpy\nimport os.path\nfrom mathutils import Vector\n\n### Import\n\n# create new vector field from imported data\ndef build_importedVectorField(tempvelList, tempOffset):\n\t# create blank vf\n\tfrom . import vf_editor\n\tvolname = vf_editor.build_vectorfield(bpy.context)\n\tvolmesh = bpy.context.scene.objects[volname]\n\t# copy imported velocities\n\tfor i in range(len(tempvelList)):\n\t\tvolmesh.custom_vectorfield[i].vvelocity = tempvelList[i]\n\t\n\tif volmesh.parent:\n\t\tvolmesh.parent.location = volmesh.parent.location + tempOffset\n\telse:\n\t\tvolmesh.location = volmesh.location + tempOffset\n\n# read data from file\ndef parse_fgafile(self, context):\n\treturnmessage = \"\"\n\tfgafilepath = self.filepath\n\tif os.path.exists(fgafilepath):\n\t\tif os.path.isfile(fgafilepath):\n\t\t\tfile = open(fgafilepath, 'r')\n\t\t\timportvf_scalemult = self.importvf_scalemult\n\t\t\tlinecount = 0\n\t\t\ttempvelList = []\n\t\t\ttempMin = Vector((0.0,0.0,0.0))\n\t\t\ttempOffset = Vector((0.0,0.0,0.0))\n\t\t\ttempscalemult = Vector((0.0,0.0,0.0))\n\t\t\t\n\t\t\tfor line in file:\n\t\t\t\tslist = []\n\t\t\t\tslist = line.split(',')\n\t\t\t\tif len(slist) > 3:\n\t\t\t\t\tslist.remove(slist[3])\n\t\t\t\t\n\t\t\t\tflist = [float(s) for s in slist]\n\t\t\t\t\n\t\t\t\tif linecount <= 2:\n\t\t\t\t\tif linecount == 0:\n\t\t\t\t\t\t# Resolution\n\t\t\t\t\t\tcontext.window_manager.vf_density[0] = int(flist[0])\n\t\t\t\t\t\tcontext.window_manager.vf_density[1] = int(flist[1])\n\t\t\t\t\t\tcontext.window_manager.vf_density[2] = int(flist[2])\n\t\t\t\t\telif linecount == 1:\n\t\t\t\t\t\t# Min bounds\n\t\t\t\t\t\ttempMin[0] = flist[0]\n\t\t\t\t\t\ttempMin[1] = flist[1]\n\t\t\t\t\t\ttempMin[2] = flist[2]\n\t\t\t\t\telif linecount == 2:\n\t\t\t\t\t\t# Max bounds, calc offset + scale\n\t\t\t\t\t\ttempscalemult = Vector((0.0,0.0,0.0))\n\t\t\t\t\t\ttempscalemult[0] = abs(flist[0] - tempMin[0])\n\t\t\t\t\t\ttempscalemult[1] = abs(flist[1] - tempMin[1])\n\t\t\t\t\t\ttempscalemult[2] = abs(flist[2] - tempMin[2])\n\t\t\t\t\t\tcontext.window_manager.vf_scale[0] = (tempscalemult[0] / context.window_manager.vf_density[0]) * importvf_scalemult\n\t\t\t\t\t\tcontext.window_manager.vf_scale[1] = (tempscalemult[1] / context.window_manager.vf_density[1]) * importvf_scalemult\n\t\t\t\t\t\tcontext.window_manager.vf_scale[2] = (tempscalemult[2] / context.window_manager.vf_density[2]) * importvf_scalemult\n\t\t\t\t\t\tif self.importvf_getoffset:\n\t\t\t\t\t\t\ttempOffset[0] = (((tempMin[0] + (tempscalemult[0] * 0.5)) * importvf_scalemult))\n\t\t\t\t\t\t\ttempOffset[1] = (((tempMin[1] + (tempscalemult[1] * 0.5)) * importvf_scalemult))\n\t\t\t\t\t\t\ttempOffset[2] = (((tempMin[2] + (tempscalemult[2] * 0.5)) * importvf_scalemult))\n\t\t\t\telse:\n\t\t\t\t\t# Velocities\n\t\t\t\t\tif self.importvf_velscale:\n\t\t\t\t\t\ttempvelList.append(Vector((flist[0] * importvf_scalemult,flist[1] * importvf_scalemult,flist[2] * importvf_scalemult)))\n\t\t\t\t\telse:\n\t\t\t\t\t\ttempvelList.append(Vector(flist))\n\t\t\t\tlinecount += 1\n\t\t\t\n\t\t\tif linecount < 3:\n\t\t\t\treturnmessage = \"Import Failed: File is missing data\"\n\t\t\telse:\n\t\t\t\tif len(tempvelList) > 0:\n\t\t\t\t\treturnmessage = \"Import Successful\"\n\t\t\t\t\tbuild_importedVectorField(tempvelList, tempOffset)\n\t\t\tfile.close()\n\t\telse:\n\t\t\treturnmessage = \"Import Failed: File not found\"\n\telse:\n\t\treturnmessage = \"Import Failed: Path not found\"\n\t\n\treturn returnmessage\n\n\n\n### Export\n\ndef write_fgafile(self, exportvol):\n\tusevelscale = self.exportvf_velscale\n\tuseoffset = self.exportvf_locoffset\n\t\n\ttempDensity = Vector(exportvol.vf_object_density)\n\tfgascale = Vector(exportvol.vf_object_scale)\n\t\n\tfile = open(self.filepath, \"w\", encoding=\"utf8\", newline=\"\\n\")\n\tfw = file.write\n\t\n\t# Resolution:\n\tfw(\"%i,%i,%i,\" % (tempDensity[0],tempDensity[1],tempDensity[2]))\n\t\n\t# Minimum/Maximum Bounds:\n\tif self.exportvf_allowmanualbounds:\n\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\tself.exportvf_manualboundsneg[0],\n\t\t\tself.exportvf_manualboundsneg[1],\n\t\t\tself.exportvf_manualboundsneg[2])\n\t\t)\n\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\tself.exportvf_manualboundspos[0],\n\t\t\tself.exportvf_manualboundspos[1],\n\t\t\tself.exportvf_manualboundspos[2])\n\t\t)\n\telse:\n\t\tif useoffset:\n\t\t\toffsetvect = Vector((0.0,0.0,0.0))\n\t\t\tif exportvol.parent:\n\t\t\t\toffsetvect = exportvol.parent.location\n\t\t\t\n\t\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\t\t(((tempDensity[0] * -0.5) * fgascale[0]) + (offsetvect[0])) * self.exportvf_scale,\n\t\t\t\t(((tempDensity[1] * -0.5) * fgascale[1]) + (offsetvect[1])) * self.exportvf_scale,\n\t\t\t\t(((tempDensity[2] * -0.5) * fgascale[2]) + (offsetvect[2])) * self.exportvf_scale)\n\t\t\t)\n\t\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\t\t(((tempDensity[0] * 0.5) * fgascale[0]) + (offsetvect[0])) * self.exportvf_scale,\n\t\t\t\t(((tempDensity[1] * 0.5) * fgascale[1]) + (offsetvect[1])) * self.exportvf_scale,\n\t\t\t\t(((tempDensity[2] * 0.5) * fgascale[2]) + (offsetvect[2])) * self.exportvf_scale)\n\t\t\t)\n\t\telse: # centered\n\t\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\t\t((tempDensity[0] * -0.5) * fgascale[0]) * self.exportvf_scale,\n\t\t\t\t((tempDensity[1] * -0.5) * fgascale[1]) * self.exportvf_scale,\n\t\t\t\t((tempDensity[2] * -0.5) * fgascale[2]) * self.exportvf_scale)\n\t\t\t)\n\t\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\t\t((tempDensity[0] * 0.5) * fgascale[0]) * self.exportvf_scale,\n\t\t\t\t((tempDensity[1] * 0.5) * fgascale[1]) * self.exportvf_scale,\n\t\t\t\t((tempDensity[2] * 0.5) * fgascale[2]) * self.exportvf_scale)\n\t\t\t)\n\t\n\t# Velocities\n\tif usevelscale and not self.exportvf_allowmanualbounds:\n\t\tfor vec in exportvol.custom_vectorfield:\n\t\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\t\tvec.vvelocity[0] * self.exportvf_scale,\n\t\t\t\tvec.vvelocity[1] * self.exportvf_scale,\n\t\t\t\tvec.vvelocity[2] * self.exportvf_scale)\n\t\t\t)\n\telse:\n\t\tif self.exportvf_allowmanualbounds:\n\t\t\tfor vec in exportvol.custom_vectorfield:\n\t\t\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\t\t\tvec.vvelocity[0] * self.exportvf_manualvelocityscale,\n\t\t\t\t\tvec.vvelocity[1] * self.exportvf_manualvelocityscale,\n\t\t\t\t\tvec.vvelocity[2] * self.exportvf_manualvelocityscale)\n\t\t\t\t)\n\t\telse:\n\t\t\tfor vec in exportvol.custom_vectorfield:\n\t\t\t\tfw(\"\\n%f,%f,%f,\" % (\n\t\t\t\t\tvec.vvelocity[0],\n\t\t\t\t\tvec.vvelocity[1],\n\t\t\t\t\tvec.vvelocity[2])\n\t\t\t\t)\n\t\n\tfile.close()\n"
  },
  {
    "path": "README.md",
    "content": "Blender - FGA Vector Field Editor\n=======================================\n\nAllows creation and manipulation of vector fields using Blender particle simulations and vector math operations, as well as importing/exporting FGA files used by Unreal Engine 4. \n\n\n- Requires Blender 2.8 and up.\n\n- The menu panel can be found in the Panels section in the right part of the workspace. The documentation hasn't been updated to reflect this yet.\n  \n------------------------------------------------------------------------------------------------------- \n \n**Out-of-date Documentation is available here**: https://github.com/isathar/Blender_UE4_VectorFieldEditor/wiki (or the wiki link on the side) \n- Very out of date, will be updated as soon as time permits.\n\n \nSome example .fga files: http://www.mediafire.com/download/4x174fgf8lmec6g/VF_Examples.zip  \n  \n \n------------------------------------------------------------------------------------------------------- \n \n## Features  \n \n**Vector Field Editor:**\n- Saves current particle system velocities and blends them with saved results using one of the following methods:\n  - *Replace, Average, Add, Multiply, Cross Product, Vector Reflection*\n- Particle velocities used in these calculations can be obtained using the following methods:\n  - *Velocity, Offset Distance, Angular Velocity, Custom Vector, Point*\n- Curve Force tool that uses wind forces to move particles along a line. \n  \n**Importer + Exporter** for FGA files for use in Unreal Engine 4 \n \n------------------------------------------------------------------------------------------------------- \n \n## Installation  \n \n- Extract to your addons directory\n- Enable it in the addon manager (named *FGA Vector Field Tools*)\n- A new tab named *Vector Fields* should be visible in the tools panel\n \n--------------------------------------------------------------------------------------------------------- \n \n## Notes  \n \n###### Performance  \n- This addon's performance is heavily dependent on the speed of your CPU and memory.\n- Blender may stop responding during the Create and Calculate operations, but shouldn't crash.\n- On vector fields with a density of less than 128^3, operations should take less than a minute, with lower density fields (<64^3) taking a few seconds at most.\n- At maximum density (128^3), creating a new vector field takes about 20 seconds on my mid-range Core i5 based PC, and calculating velocities can take up to 2 minutes (after recent tweaks).\n- Performance while editing reasonably sized (< 1 million vertices) vector fields is good, while a 128^3 volume can be painfully slow under the right (wrong) circumstances.\n \n###### 128x128x128 and System Memory  \n- Editing a 128x128x128 vector field requires a 64-bit system and Blender version, as well as a large amount (> 6-8 GB) of system memory.\n- This is due to the amount of particles that need to have their dynamics cached (and the number of vertices in the volume mesh)\n- To avoid running out of memory while editing very high resolution vector fields, you may want to lower your undo history steps.\n- Using a disk cache for your particles may help, too. \n\n\n------------------------------------------------------------------------------------------------------- \n  \n## Changelog:  \n  \n***1.2.1***:\n- probably fixed noneType error messages *(Thank you to kkaja123 for reporting and looking into fixing it!)*\n- moved the menu to the panels at the right side of the workspace\n\n***1.2.0***:\n- initial update to support Blender 2.8.\n\n***1.1.5***:\n- changed sorting of the calculation/velocity dropdown lists\n- new calculation method: Vector Reflection\n- added constraint to vector field volume, only moving the bounding box now moves the volume  \n\n***1.1.4***:\n- fix for display lines not updating when new velocities are calculated\n- new calculation method: Cross Product  \n\n***1.1.3***:\n- removed the need for the update data/offsets buttons\n- slight performance optimizations for display lines  \n\n***1.1.2***:\n- new presets can now be added/removed\n- export should now work with any part of the vector field selected (or both)  \n\n***1.1.0***:\n- added support for selecting a physics preset to edit particle physics settings easily for selected vector field\n\n***1.0.1***:\n- renamed curve path tool to Wind Curve Force\n- added editor for created curve wind force strength, distance + falloff\n- the Ratio property should now work (apparently forgot to use the variable)  \n\n***1.0.0***:\n- *General:*\n  - tools panel category renamed to *Vector Fields*\n  - saved data only includes velocities now (removed position, index)\n    - files made with old versions are still compatible\n  - cleaned up ui panel to reduce clutter, added section toggles\n  - some code cleanup\n- *Import/Export:*\n  - moved import/export to standard menu\n  - made import/export properties local to their functions\n- *Editor:*\n  - performance tweaks for creating new vector fields + calculating velocities\n  - removed slice selection tool (redundant, easily done in edit mode)\n  - matched default scaling to (grid units x field density)\n    - distances were at half scale before\n    - density variable used for creation is now distance between particles\n  - added undo functionality to *Generate* function\n  - new velocity mode: Point\n- *Curve Force Tool:*\n  - changed curve force tool to create an object group to remove scene outliner clutter\n  - fixed curve force fields' parenting issue\n    - all transformations to the curve force object should now work\n  - curve forces now display an arrow pointing in the force's direction  \n\n***0.9.5***:\n- *Editor:*\n  - added new calculation method: Multiply\n  - added different methods for obtaining velocities\n  - reorganized the main editor\n  - added calculate for selection\n  - added invert all button\n  - seperated normalize function from calculation - it's a button again\n- *Import/Export:*\n  - moved density variable to object space for export script \n    - allows multiple vector fields in the scene during export (still exports one at a time)\n  - added ability to use object locations as offsets + import/export them\n  - scaling tweaks\n  - manual bounds option\n- *General:*\n  - added ability to undo slice selection, calculation, curve tool, and normalize\n  - select x,y,z slice\n  - created index by axis for velocities list for slice selection and upcoming features\n  - switched bpy.context to passed context where possible\n  - description text for all variables + operators (some may be vague)\n  - added bug reporting url to addon manager (Github)\n  - readme formatting  \n\n***0.9.1***:\n- added different generation modes: *Replace, Additive, Average*\n- added trail option for curve path (fade influence with curve position)\n- changed the way invert and normalize work\n- slight calculation performance tweak  \n\n***0.9***:\n- another performance tweak\n- added invert, normalize, disable gravity options  \n\n***0.8***:\n- added import functionality\n- massive speed improvement  \n\n***0.5***:\n- initial upload  \n"
  }
]