Repository: isathar/Blender_UE4_VectorFieldEditor
Branch: master
Commit: b24f207c9142
Files: 5
Total size: 47.2 KB
Directory structure:
gitextract_6jzh4irb/
├── .github/
│ └── FUNDING.yml
├── FGA_VectorFields/
│ ├── __init__.py
│ ├── vf_editor.py
│ └── vf_io.py
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
patreon: isathar
================================================
FILE: FGA_VectorFields/__init__.py
================================================
bl_info = {
"name": "Vector Field Tools",
"author": "Andreas Wiehn (isathar)",
"version": (1, 2, 1),
"blender": (2, 80, 0),
"location": "View3D > Add > VectorField",
"description": "Create and edit 3D Vector Fields using the .fga format.",
"warning": "",
"tracker_url": "https://github.com/isathar/Blender_UE4_VectorFieldEditor/issues/",
"category": "Object"}
import bpy
from bpy_extras.io_utils import (ImportHelper,ExportHelper,path_reference_mode)
from bl_operators.presets import AddPresetBase
from . import vf_editor, vf_io
# UI Panel
class VFTOOLS_PT_menupanel(bpy.types.Panel):
bl_idname = "VFTOOLS_PT_menupanel"
bl_label = 'Vector Fields'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Particle Simulation"
def __init__(self):
pass
@classmethod
def poll(self, context):
return True
def draw(self, context):
layout = self.layout
# Create
box = layout.box()
show_createpanel = box.prop(context.window_manager, 'show_createpanel', toggle=True, text="Create")
if context.window_manager.show_createpanel:
box.row().column().prop(context.window_manager, 'vf_density', text='Resolution')
box.row().column().prop(context.window_manager, 'vf_scale', text='Scale')
box.row().prop(context.window_manager, 'vf_gravity', text='Gravity')
box.row().prop(context.window_manager, 'vf_particleLifetime', text='Particle Lifetime')
row = box.row()
if context.active_object:
if context.active_object.particle_systems:
if context.active_object.particle_systems[0]:
if context.active_object.particle_systems[0].settings.physics_type == 'FLUID':
row.menu("Presets_VFCreate_Fluid",text=bpy.types.Presets_VFCreate_Fluid.bl_label)
row.operator("object.preset_vfcreate_fluid", text="", icon='ADD')
row.operator("object.preset_vfcreate_fluid", text="", icon='REMOVE').remove_active = True
elif context.active_object.particle_systems[0].settings.physics_type == 'NEWTON':
row.menu("Presets_VFCreate",text=bpy.types.Presets_VFCreate.bl_label)
row.operator("object.preset_vfcreate", text="", icon='ADD')
row.operator("object.preset_vfcreate", text="", icon='REMOVE').remove_active = True
box.row().operator('vftools.create_vectorfield', text='Generate')
numObjects = context.window_manager.vf_density[0] * context.window_manager.vf_density[1] * context.window_manager.vf_density[2]
box.row().label(text="# of vectors: " + str(numObjects))
# Edit
box = layout.box()
show_editpanel = box.prop(context.window_manager, 'show_editpanel', toggle=True, text="Edit")
if context.window_manager.show_editpanel:
box2 = box.box()
box2.row().label(text=" Velocity Type:")
box2.row().prop(context.window_manager, 'pvelocity_veltype', text='')
if context.window_manager.pvelocity_veltype == "VECT":
box2.row(align=True).column().prop(context.window_manager, 'pvelocity_dirvector',text='Custom Direction')
box2.row().label(text=" Blend Method:")
box2.row().prop(context.window_manager, 'pvelocity_genmode', text='')
if context.window_manager.pvelocity_genmode == 'AVG':
box2.row().prop(context.window_manager, 'pvelocity_avgratio',text='Ratio')
box2.row(align=True).prop(context.window_manager, 'pvelocity_selection',text='Selected Only')
box2.row(align=True).prop(context.window_manager, 'pvelocity_invert',text='Invert Next')
box2.row(align=True).operator('object.calc_vectorfieldvelocities', text='Calculate')
box2 = box.box()
box2.row().operator('object.vf_normalizevelocities', text='Normalize')
box2.row().operator('object.vf_invertvelocities', text='Invert All')
# Display
box = layout.box()
box.prop(context.window_manager, 'show_displaypanel', toggle=True, text="Display")
if context.window_manager.show_displaypanel:
box.prop(context.window_manager, 'vf_velocitylinescolor', toggle=True, text="Line Color")
if context.window_manager.vf_showingvelocitylines < 1:
box.row().operator('view3d.toggle_vectorfieldvelocities', text='Show')
else:
box.row().operator('view3d.toggle_vectorfieldvelocities', text='Hide')
# Tools:
box = layout.box()
box.prop(context.window_manager, 'show_toolspanel', toggle=True, text="Tools")
if context.window_manager.show_toolspanel:
# # Wind Curve Force
box = box.box()
box.prop(context.window_manager, 'show_windcurvetool', toggle=True, text="Wind Curve Force")
if context.active_object:
if context.active_object.type == 'CURVE' or 'CurveForce' in context.active_object.name:
if context.window_manager.show_windcurvetool:
if context.active_object:
if context.active_object.type == 'CURVE':
box.row(align=True).prop(context.window_manager, 'curveForce_strength', text='Strength')
box.row(align=True).prop(context.window_manager, 'curveForce_maxDist', text='Distance')
box.row(align=True).prop(context.window_manager, 'curveForce_falloffPower', text='Power')
box.row(align=True).prop(context.window_manager, 'curveForce_trailout', text='Trail')
box.row(align=True).operator('object.calc_curvewindforce', text='Create New')
elif 'CurveForce' in context.active_object.name:
box.row(align=True).prop(context.window_manager, 'curveForce_strength', text='Strength')
box.row(align=True).prop(context.window_manager, 'curveForce_maxDist', text='Distance')
box.row(align=True).prop(context.window_manager, 'curveForce_falloffPower', text='Power')
box.row(align=True).operator('object.edit_curvewindforce', text='Edit Selected')
else:
box.row().label(text='Select a curve or curve force')
box.enabled = False
# Saved Data
class vector_field(bpy.types.PropertyGroup):
vcoord : bpy.props.FloatVectorProperty(default=(0.0, 0.0, 0.0))
vvelocity : bpy.props.FloatVectorProperty(default=(0.0, 0.0, 0.0))
# Export
class export_vectorfieldfile(bpy.types.Operator, ExportHelper):
bl_idname = "object.export_vectorfieldfile"
bl_label = "Export FGA"
bl_description = 'Export selected volume as a FGA file'
filename_ext = ".fga"
filter_glob : bpy.props.StringProperty(default="*.fga", options={'HIDDEN'})
exportvf_allowmanualbounds : bpy.props.BoolProperty(
name="Manual Bounds",default=False,
description="Allow setting vector field bounds manually"
)
exportvf_manualboundsneg : bpy.props.IntVectorProperty(
name="Bounds Scale -",min=-10000,max=10000,default=(-100,-100,-100),
subtype='TRANSLATION',
description="Minimum values for bounds in cm (have to be less than maximum values)"
)
exportvf_manualboundspos : bpy.props.IntVectorProperty(
name="Bounds Scale +",min=-10000,max=10000,default=(100,100,100),
subtype='TRANSLATION',
description="Maximum values for bounds in cm (have to be greater than minimum values)"
)
exportvf_manualvelocityscale : bpy.props.FloatProperty(
name="Velocity Scale",min=1.0,max=10000.0,default=1.0,
description="Multiplier for velocities when using manual bounds"
)
exportvf_scale : bpy.props.FloatProperty(
name="Bounds Scale",min=1.0,max=10000.0,default=100.0,
description=("Scale the size of the volume's bounds by this on export" +
" - actual size in UE4 = this * (the vector field's density) * 0.5 cm")
)
exportvf_velscale : bpy.props.BoolProperty(
name="Scale Velocity",default=True,
description="Scale velocity with bounds scale"
)
exportvf_locoffset : bpy.props.BoolProperty(
name="Export Offset",default=True,
description="Exports the location of the vector field's bounding volume as an offset to min/max bounds"
)
def check_extension(self):
return self.batch_mode == 'OFF'
def check(self, context):
is_def_change = super().check(context)
return (is_def_change)
def draw(self,context):
layout = self.layout
box = layout.box()
box.row().prop(self, 'exportvf_allowmanualbounds', text='Manual Bounds')
if self.exportvf_allowmanualbounds:
box.row().column().prop(self, 'exportvf_manualboundsneg', text='Minimum Bounds')
box.row().column().prop(self, 'exportvf_manualboundspos', text='Maximum Bounds')
box.row().prop(self, 'exportvf_manualvelocityscale', text='Velocity Scale:')
else:
box.row().prop(self, 'exportvf_scale', text='Export Scale:')
box.row().prop(self, 'exportvf_velscale', text='Scale Velocity')
box.row().prop(self, 'exportvf_locoffset', text='Export Offset')
def execute(self, context):
if not self.filepath:
raise Exception("filepath not set")
else:
if context.active_object != None:
if 'custom_vectorfield' in context.active_object:
vf_io.write_fgafile(self, context.active_object)
else:
activeobj = context.active_object
found = False
for obj in context.selectable_objects:
if obj.parent == activeobj:
if 'custom_vectorfield' in obj:
vf_io.write_fgafile(self, obj)
found = True
break
if not found:
raise Exception("No velocities")
else:
raise Exception("Nothing selected")
return {'FINISHED'}
# Import
class import_vectorfieldfile(bpy.types.Operator, ImportHelper):
bl_idname = "object.import_vectorfieldfile"
bl_label = "Import FGA"
bl_description = 'Import FGA file as a vector field'
filename_ext = ".fga"
filter_glob : bpy.props.StringProperty(default="*.fga", options={'HIDDEN'})
importvf_scalemult : bpy.props.FloatProperty(
name="Size Multiplier",min=0.0001,max=10000.0,step=0.0001,default=0.01,
description="Multiplier to apply to the scale of the volume's bounds on import"
)
importvf_velscale : bpy.props.BoolProperty(
name="Scale Velocity",default=True,
description="Scale velocity on import"
)
importvf_getoffset : bpy.props.BoolProperty(
name="Get Offset",default=True,
description="Get location offset from file"
)
def draw(self,context):
layout = self.layout
box = layout.box()
row = box.row()
row.prop(self, 'importvf_scalemult', text='Import Scale')
row = box.row()
row.prop(self, 'importvf_velscale', text='Scale Velocity')
row = box.row()
row.prop(self, 'importvf_getoffset', text='Import Offset')
def execute(self, context):
retmessage = vf_io.parse_fgafile(self, context)
print ("FGA Import: " + retmessage + " (" + self.filepath + ")")
return {'FINISHED'}
class Preset_VFCreate(AddPresetBase, bpy.types.Operator):
bl_idname = 'object.preset_vfcreate'
bl_label = 'Physics Presets'
bl_options = {'REGISTER', 'UNDO'}
preset_menu = 'Presets_VFCreate'
preset_subdir = 'VF_Default_Presets'
preset_defines = [
"PSystem = bpy.context.active_object.particle_systems[0].settings"
]
preset_values = [
# standard
"PSystem.effector_weights.gravity",
"PSystem.factor_random",
"PSystem.particle_size",
"PSystem.size_random",
"PSystem.mass",
"PSystem.brownian_factor",
"PSystem.drag_factor",
"PSystem.damping"
]
class Preset_VFCreate_Fluid(AddPresetBase, bpy.types.Operator):
bl_idname = 'object.preset_vfcreate_fluid'
bl_label = 'Fluid Physics Presets'
bl_options = {'REGISTER', 'UNDO'}
preset_menu = 'Presets_VFCreate_Fluid'
preset_subdir = 'VF_Fluid_Presets'
preset_defines = [
"PSystem = bpy.context.active_object.particle_systems[0].settings"
]
preset_values = [
# standard
"PSystem.effector_weights.gravity",
"PSystem.factor_random",
"PSystem.particle_size",
"PSystem.size_random",
"PSystem.mass",
"PSystem.brownian_factor",
"PSystem.drag_factor",
"PSystem.damping",
# fluid
"PSystem.fluid.stiffness",
"PSystem.fluid.linear_viscosity",
"PSystem.fluid.buoyancy",
"PSystem.fluid.stiff_viscosity",
"PSystem.fluid.fluid_radius",
"PSystem.fluid.rest_density"
]
class Presets_VFCreate(bpy.types.Menu):
bl_label = "Physics Presets"
bl_idname = "Presets_VFCreate"
preset_subdir = "VF_Default_Presets"
preset_operator = "script.execute_preset"
draw = bpy.types.Menu.draw_preset
class Presets_VFCreate_Fluid(bpy.types.Menu):
bl_label = "Fluid Presets"
bl_idname = "Presets_VFCreate_Fluid"
preset_subdir = "VF_Fluid_Presets"
preset_operator = "script.execute_preset"
draw = bpy.types.Menu.draw_preset
def exportmenu_func(self, context):
self.layout.operator(export_vectorfieldfile.bl_idname,
text="UE4 Vector Field (.fga)")
def importmenu_func(self, context):
self.layout.operator(import_vectorfieldfile.bl_idname,
text="UE4 Vector Field (.fga)")
def initdefaults():
bpy.types.Object.custom_vectorfield = bpy.props.CollectionProperty(type=vector_field)
bpy.types.Object.custom_vf_startlocs = bpy.props.CollectionProperty(type=vector_field)
bpy.types.Object.vf_object_density = bpy.props.FloatVectorProperty(default=(0.0,0.0,0.0))
bpy.types.Object.vf_object_scale = bpy.props.FloatVectorProperty(default=(1.0,1.0,1.0))
# generate
bpy.types.WindowManager.vf_density = bpy.props.IntVectorProperty(
default=(16,16,16),min=1,max=128,
description="The number of points in the vector field"
)
bpy.types.WindowManager.vf_scale = bpy.props.FloatVectorProperty(
default=(1.0,1.0,1.0),min=0.25,
description="Distance between points in the vector field"
)
bpy.types.WindowManager.vf_gravity = bpy.props.FloatProperty(
default=0.0,min=0.0,description="Amount of influence gravity has on the volume's particles"
)
bpy.types.WindowManager.vf_particleLifetime = bpy.props.IntProperty(default=32)
# calculate/edit
bpy.types.WindowManager.pvelocity_veltype = bpy.props.EnumProperty(
name="Velocity Type",
items=(('VECT', "Custom Vector", "Use direction vector as velocities"),
('ANGVEL', "Angular Velocity", "Get particles' current angular velocities (spin)"),
('PNT', "Point", "Get a direction vector pointing away from 3D cursor"),
('DIST', "Distance", "Get particles' offsets from their initial locations"),
('PVEL', "Velocity", "Get particles' current velocities"),
),
default='PVEL',
description="Method of obtaining velocities from the particle system",
)
bpy.types.WindowManager.pvelocity_genmode = bpy.props.EnumProperty(
name="Calculation Method",
items=(('REF', "Reflection", "Get the reflection vector between old and new velocities"),
('CRS', "Cross Product", "Get the cross product of old and current velocities"),
('AVG', "Average", "Get the average of old and new velocities"),
('MULT', "Multiply", "Multiply current velocities with old velocities"),
('ADD', "Add", "Add new velocities to existing ones"),
('MATH', "Formula", "Use a customizable function to calculate velocities"),
('REP', "Replace", "Default - Overwrite old velocities"),
),
default='REP',
description="Method of combining current and saved velocities",
)
bpy.types.WindowManager.pvelocity_invert = bpy.props.BoolProperty(
default=False,description="Invert current velocities before saving"
)
bpy.types.WindowManager.pvelocity_selection = bpy.props.BoolProperty(
default=False,description="Replace selected particles' velocities only"
)
bpy.types.WindowManager.pvelocity_avgratio = bpy.props.FloatProperty(
default=0.5,min=0.0,max=1.0,
description="The ratio between the current and new velocities"
)
bpy.types.WindowManager.pvelocity_dirvector = bpy.props.FloatVectorProperty(
default=(0.0,0.0,1.0), subtype='TRANSLATION', unit='NONE', min=-100.0, max=100.0,
description="Vector to set all velocities to"
)
# curve force
bpy.types.WindowManager.curveForce_strength = bpy.props.FloatProperty(
default=8.0,description="The power of each wind force along the curve"
)
bpy.types.WindowManager.curveForce_maxDist = bpy.props.FloatProperty(
default=4.0,description="Maximum influence distance for wind forces"
)
bpy.types.WindowManager.curveForce_falloffPower = bpy.props.FloatProperty(
default=2.0,description="Distance falloff for wind forces"
)
bpy.types.WindowManager.curveForce_trailout = bpy.props.BoolProperty(
default=False,description="Fade the size and influence of the wind forces along the curve"
)
# display
bpy.types.WindowManager.vf_showingvelocitylines = bpy.props.IntProperty(default=-1)
bpy.types.WindowManager.vf_velocitylinescolor = bpy.props.FloatVectorProperty(
default=(1.0,1.0,1.0),min=0.25,subtype='COLOR',
description="Line Color"
)
# toggle vars for panel
bpy.types.WindowManager.show_createpanel = bpy.props.BoolProperty(
default=False,description="Toggle Section"
)
bpy.types.WindowManager.show_editpanel = bpy.props.BoolProperty(
default=False,description="Toggle Section"
)
bpy.types.WindowManager.show_displaypanel = bpy.props.BoolProperty(
default=False,description="Toggle Section"
)
bpy.types.WindowManager.show_toolspanel = bpy.props.BoolProperty(
default=False,description="Toggle Section"
)
bpy.types.WindowManager.show_windcurvetool = bpy.props.BoolProperty(
default=False,description="Toggle Section"
)
def clearvars():
props = [
'vf_density','vf_scale','vf_gravity','vf_particleLifetime','pvelocity_veltype','pvelocity_genmode',
'pvelocity_invert','pvelocity_selection','pvelocity_avgratio','pvelocity_dirvector',
'curveForce_strength','curveForce_maxDist','curveForce_falloffPower','curveForce_trailout','curveForce_dispSize'
'vf_showingvelocitylines','vf_velocitylinescolor',
'show_createpanel','show_editpanel','show_displaypanel','show_toolspanel','show_windcurvetool'
]
for p in props:
if bpy.context.window_manager.get(p) != None:
del bpy.context.window_manager[p]
try:
x = getattr(bpy.types.WindowManager, p)
del x
except:
pass
classes = (
vector_field,
Presets_VFCreate,
Preset_VFCreate,
Presets_VFCreate_Fluid,
Preset_VFCreate_Fluid,
vf_editor.calc_vectorfieldvelocities,
vf_editor.VFTOOLS_OT_create_vectorfield,
vf_editor.calc_curvewindforce,
vf_editor.edit_curvewindforce,
vf_editor.toggle_vectorfieldvelocities,
vf_editor.vf_normalizevelocities,
vf_editor.vf_invertvelocities,
export_vectorfieldfile,
import_vectorfieldfile,
VFTOOLS_PT_menupanel,
#exportmenu_func,
#importmenu_func,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
initdefaults()
bpy.types.TOPBAR_MT_file_export.append(exportmenu_func)
bpy.types.TOPBAR_MT_file_import.append(importmenu_func)
def unregister():
bpy.types.TOPBAR_MT_file_export.remove(exportmenu_func)
bpy.types.TOPBAR_MT_file_import.remove(importmenu_func)
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
clearvars()
if __name__ == '__main__':
register()
================================================
FILE: FGA_VectorFields/vf_editor.py
================================================
### Editor Functions
import bpy
import gpu
from mathutils import Vector, Matrix
from gpu_extras.batch import batch_for_shader
### Create
# Creates a new vector field from parameters
def build_vectorfield(context):
zeroVect = Vector((0.0,0.0,0.0))
densityVal = Vector(context.window_manager.vf_density)
scaleVal = Vector(context.window_manager.vf_scale)
baseLoc = -1.0 * (densityVal * 0.5) + Vector((0.5, 0.5, 0.5))
totalvertscount = densityVal[0] * densityVal[1] * densityVal[2]
vf_startlocs = [zeroVect.copy() for v in range(int(totalvertscount))]
vf_velocities = [zeroVect.copy() for v in range(int(totalvertscount))]
volcount = 0
for v in range(len(context.scene.objects)):
if ("VF_Volume" in str(context.scene.objects[v].name)):
volcount += 1
# create the volume
me = bpy.data.meshes.new("Vert")
me.vertices.add(totalvertscount)
from bpy_extras import object_utils
object_utils.object_data_add(context, me, operator=None)
volMesh = context.active_object
volMesh.name = 'VF_Volume_' + str(volcount)
volMesh.display.show_shadows = False
volMesh.location = zeroVect
# add the particle system
bpy.ops.object.particle_system_add()
degp = bpy.context.evaluated_depsgraph_get()
particle_systems = volMesh.evaluated_get(degp).particle_systems
psettings = particle_systems[0].settings
#me = volMesh.data
#me.update()
meshverts = [v for v in me.vertices]
volMesh.vf_object_density = densityVal
volMesh.vf_object_scale = scaleVal
# create vertices + initialize velocities list
xval = int(densityVal[0])
yval = int(densityVal[1])
zval = int(densityVal[2])
tempV = zeroVect.copy()
counter = 0
for i in range(zval):
for j in range(yval):
for k in range(xval):
tempV[0] = (baseLoc[0] + (k)) * scaleVal[0]
tempV[1] = (baseLoc[1] + (j)) * scaleVal[1]
tempV[2] = (baseLoc[2] + (i)) * scaleVal[2]
meshverts[counter].co = tempV
vf_startlocs[counter][0] = tempV[0]
vf_startlocs[counter][1] = tempV[1]
vf_startlocs[counter][2] = tempV[2]
counter += 1
me.update()
del meshverts[:]
# create the particle system
psettings.count = totalvertscount
psettings.emit_from = 'VERT'
psettings.normal_factor = 0.0
psettings.use_emit_random = False
psettings.frame_end = 1
psettings.lifetime = context.window_manager.vf_particleLifetime
psettings.grid_resolution = 1
psettings.use_rotations = True
psettings.use_dynamic_rotation = True
psettings.effector_weights.gravity = context.window_manager.vf_gravity
# create the bounding box
bpy.ops.mesh.primitive_cube_add(location=(0.0,0.0,0.0))
boundsMesh = context.active_object
boundsMesh.name = 'VF_Bounds_' + str(volcount)
boundsMesh.display.show_shadows = False
# match scale to the volume
boundsMesh.scale[0] = (densityVal[0] * 0.5) * scaleVal[0]
boundsMesh.scale[1] = (densityVal[1] * 0.5) * scaleVal[1]
boundsMesh.scale[2] = (densityVal[2] * 0.5) * scaleVal[2]
bpy.ops.object.transform_apply(scale=True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.delete(type='ONLY_FACE')
bpy.ops.object.mode_set(mode='OBJECT')
volMesh.parent = boundsMesh
if len(vf_velocities) == len (vf_startlocs):
for i in range(len(vf_velocities)):
tempvertdata = volMesh.custom_vectorfield.add()
tempvertdata.vcoord = Vector(vf_startlocs[i])
tempvertdata.vvelocity = Vector(vf_velocities[i])
else:
print ("VectorField coords/velocities length mismatch!")
del vf_velocities[:]
del vf_startlocs[:]
tempconstraint = volMesh.constraints.new(type='COPY_TRANSFORMS')
tempconstraint.target = volMesh.parent
return volMesh.name
class VFTOOLS_OT_create_vectorfield(bpy.types.Operator):
bl_idname = 'vftools.create_vectorfield'
bl_label = 'Create VectorField'
bl_description = 'Create a new vector field from resolution and scale values'
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
build_vectorfield(context)
return {'FINISHED'}
# Performs vector math + writes results to data
class calc_vectorfieldvelocities(bpy.types.Operator):
bl_idname = 'object.calc_vectorfieldvelocities'
bl_label = 'Save VF EndLocations'
bl_description = 'Calculate and save velocities'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.mode == "OBJECT" and context.active_object != None) and 'VF_Volume_' in context.active_object.name
def execute(self, context):
invmult = -1.0 if context.window_manager.pvelocity_invert else 1.0
useselection = context.window_manager.pvelocity_selection
particleslist = []
volmesh = context.active_object
vf_velocities = [Vector(v.vvelocity) for v in volmesh.custom_vectorfield]
degp = bpy.context.evaluated_depsgraph_get()
particle_systems = volmesh.evaluated_get(degp).particle_systems
## Get velocities
if context.window_manager.pvelocity_veltype == "VECT":
tempvect = Vector(context.window_manager.pvelocity_dirvector)
particleslist = [tempvect.copy() for v in vf_velocities]
elif context.window_manager.pvelocity_veltype == "DIST":
vf_startlocs = [Vector(v.vcoord) for v in volmesh.custom_vectorfield]
vf_endLocs = [v.location for v in particle_systems[0].particles]
particleslist = [(vf_endLocs[i] - vf_startlocs[i]) for i in range(len(vf_endLocs))]
del vf_startlocs[:]
del vf_endLocs[:]
elif context.window_manager.pvelocity_veltype == "ANGVEL":
particleslist = [p.angular_velocity for p in particle_systems[0].particles]
elif context.window_manager.pvelocity_veltype == "PNT":
cursorloc = context.scene.cursor.location
particleslist = [(Vector(v.vcoord) - cursorloc).normalized() for v in volmesh.custom_vectorfield]
else:
particleslist = [p.velocity for p in particle_systems[0].particles]
mvertslist = []
if useselection:
me = volmesh.data
mvertslist = tuple(v.select for v in me.vertices)
## Blend with List / calculate
# multiply
if context.window_manager.pvelocity_genmode == 'MULT':
if useselection:
for i in range(len(particleslist)):
if mvertslist[i]:
vf_velocities[i] = Vector(
(vf_velocities[i][0] * (particleslist[i][0] * invmult),
vf_velocities[i][1] * (particleslist[i][1] * invmult),
vf_velocities[i][2] * (particleslist[i][2] * invmult))
)
else:
for i in range(len(particleslist)):
vf_velocities[i] = Vector(
(vf_velocities[i][0] * (particleslist[i][0] * invmult),
vf_velocities[i][1] * (particleslist[i][1] * invmult),
vf_velocities[i][2] * (particleslist[i][2] * invmult))
)
# add
elif context.window_manager.pvelocity_genmode == 'ADD':
if useselection:
for i in range(len(particleslist)):
if mvertslist[i]:
vf_velocities[i] = vf_velocities[i] + ((particleslist[i]) * invmult)
else:
for i in range(len(particleslist)):
vf_velocities[i] = vf_velocities[i] + ((particleslist[i]) * invmult)
# average
elif context.window_manager.pvelocity_genmode == 'AVG':
avgratio = context.window_manager.pvelocity_avgratio
if useselection:
for i in range(len(particleslist)):
if mvertslist[i]:
vf_velocities[i] = ((vf_velocities[i] * (1.0 - avgratio)) + ((particleslist[i] * invmult) * avgratio))
else:
for i in range(len(particleslist)):
vf_velocities[i] = ((vf_velocities[i] * (1.0 - avgratio)) + ((particleslist[i] * invmult) * avgratio))
# replace
elif context.window_manager.pvelocity_genmode == 'REP':
if useselection:
for i in range(len(particleslist)):
if mvertslist[i]:
vf_velocities[i] = particleslist[i] * invmult
else:
for i in range(len(particleslist)):
vf_velocities[i] = particleslist[i] * invmult
# cross product
elif context.window_manager.pvelocity_genmode == 'CRS':
if useselection:
for i in range(len(particleslist)):
if mvertslist[i]:
vf_velocities[i] = vf_velocities[i].cross(particleslist[i])
else:
for i in range(len(particleslist)):
vf_velocities[i] = vf_velocities[i].cross(particleslist[i])
# reflection
elif context.window_manager.pvelocity_genmode == 'REF':
if useselection:
for i in range(len(particleslist)):
if mvertslist[i]:
vf_velocities[i] = vf_velocities[i].reflect(particleslist[i])
else:
for i in range(len(particleslist)):
vf_velocities[i] = vf_velocities[i].reflect(particleslist[i])
# write new velocities
for i in range(len(vf_velocities)):
volmesh.custom_vectorfield[i].vvelocity = vf_velocities[i].copy()
del particleslist[:]
del vf_velocities[:]
context.window_manager.vf_showingvelocitylines = -1
return {'FINISHED'}
# Normalizes the list
class vf_normalizevelocities(bpy.types.Operator):
bl_idname = 'object.vf_normalizevelocities'
bl_label = 'Normalize'
bl_description = 'Normalizes the currently saved velocity list'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return context.active_object != None and 'VF_Volume_' in context.active_object.name
def execute(self, context):
volmesh = context.active_object
for i in range(len(volmesh.custom_vectorfield)):
tempVect = Vector(volmesh.custom_vectorfield[i].vvelocity)
volmesh.custom_vectorfield[i].vvelocity = tempVect.normalized()
context.window_manager.vf_showingvelocitylines = -1
return {'FINISHED'}
# Inverts the list
class vf_invertvelocities(bpy.types.Operator):
bl_idname = 'object.vf_invertvelocities'
bl_label = 'Invert All'
bl_description = 'Inverts the currently saved velocity list'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return context.active_object != None and 'VF_Volume_' in context.active_object.name
def execute(self, context):
volmesh = context.active_object
for i in range(len(volmesh.custom_vectorfield)):
volmesh.custom_vectorfield[i].vvelocity[0] *= -1.0
volmesh.custom_vectorfield[i].vvelocity[1] *= -1.0
volmesh.custom_vectorfield[i].vvelocity[2] *= -1.0
context.window_manager.vf_showingvelocitylines = -1
return {'FINISHED'}
# Tools:
# Curve Wind Force:
# creates a wind tunnel from selected curve object
class calc_curvewindforce(bpy.types.Operator):
bl_idname = 'object.calc_curvewindforce'
bl_label = 'Curve Wind force'
bl_description = 'create wind forces along a spline to direct velocities along it'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return (context.mode == "OBJECT" and context.active_object != None) and context.active_object.type == 'CURVE'
def execute(self, context):
curvepoints = []
curveobj = context.active_object
bpy.ops.object.empty_add(type='PLAIN_AXES')
parentobj = context.active_object
parentobj.name = 'CurveForce'
if len(curveobj.data.splines[0].bezier_points) > 1:
curvepoints = [v.co for v in curveobj.data.splines[0].bezier_points]
else:
curvepoints = [v.co for v in curveobj.data.splines[0].points]
curveobj.parent = parentobj
context.active_object.select_set(False)
curveobj.select_set(True)
previousnormal = Vector((0.0,0.0,0.0))
lastStrength = context.window_manager.curveForce_strength
lastDistance = context.window_manager.curveForce_maxDist
for i in range(len(curvepoints)):
cpoint = Vector((curvepoints[i][0],curvepoints[i][1],curvepoints[i][2]))
bpy.ops.object.empty_add(type='SINGLE_ARROW',location=(cpoint))
context.active_object.name = 'ForceObj'
# turn into forcefield
bpy.ops.object.forcefield_toggle()
context.active_object.field.type = 'WIND'
if context.window_manager.curveForce_trailout:
if i > 0:
lastStrength = lastStrength * 0.9
lastDistance = lastDistance * 0.9
context.active_object.field.strength = lastStrength
context.active_object.field.use_max_distance = True
context.active_object.field.distance_max = lastDistance
context.active_object.field.falloff_power = context.window_manager.curveForce_falloffPower
# get the curve's direction between points
tempnorm = Vector((0,0,0))
if (i < len(curvepoints) - 1):
cpoint2 = Vector((curvepoints[i + 1][0],curvepoints[i + 1][1],curvepoints[i + 1][2]))
tempnorm = cpoint - cpoint2
if i > 0:
if abs(previousnormal.length) > 0.0:
tempnorm = (tempnorm + previousnormal) / 2.0
previousnormal = tempnorm
else:
if curveobj.data.splines[0].use_cyclic_u or curveobj.data.splines[0].use_cyclic_u:
cpoint2 = Vector((curvepoints[0][0],curvepoints[0][1],curvepoints[0][2]))
tempnorm = cpoint - cpoint2
if abs(previousnormal.length) > 0.0:
tempnorm = (tempnorm + previousnormal) / 2.0
previousnormal = tempnorm
else:
cpoint2 = Vector((curvepoints[i - 1][0],curvepoints[i - 1][1],curvepoints[i - 1][2]))
tempnorm = cpoint2 - cpoint
if abs(previousnormal.length) > 0.0:
tempnorm = (tempnorm + previousnormal) / 2.0
previousnormal = tempnorm
if abs(tempnorm.length) > 0.0:
z = Vector((0,0,1))
angle = tempnorm.angle(z)
axis = z.cross(tempnorm)
mat = Matrix.Rotation(angle, 4, axis)
mat_world = context.active_object.matrix_world @ mat
context.active_object.matrix_world = mat_world
context.active_object.parent = parentobj
return {'FINISHED'}
# creates a wind tunnel from selected curve object
class edit_curvewindforce(bpy.types.Operator):
bl_idname = 'object.edit_curvewindforce'
bl_label = 'Curve Wind force'
bl_description = 'Edit settings on the selected curve wind force object'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if context.mode == "OBJECT" and context.active_object != None:
return 'CurveForce' in context.active_object.name
def execute(self, context):
newStrength = context.window_manager.curveForce_strength
newDistance = context.window_manager.curveForce_maxDist
newFalloff = context.window_manager.curveForce_falloffPower
curveforceobj = context.active_object
objlist = [obj for obj in context.scene.objects if obj.parent == curveforceobj]
for obj in objlist:
if 'ForceObj' in obj.name:
obj.field.strength = newStrength
obj.field.distance_max = newDistance
obj.field.falloff_power = newFalloff
return {'FINISHED'}
### Display
class toggle_vectorfieldinfo(bpy.types.Operator):
bl_idname = "object.toggle_vectorfieldinfo"
bl_label = 'Show Current VF Info'
bl_description = 'Display information about the currently selected vector field'
@classmethod
def poll(cls, context):
return context.active_object != None and 'VF_Volume_' in context.active_object.name
def execute(self, context):
return {'FINISHED'}
# Toggle velocities display as lines
class toggle_vectorfieldvelocities(bpy.types.Operator):
bl_idname = "view3d.toggle_vectorfieldvelocities"
bl_label = 'Show velocities'
bl_description = 'Display velocities as 3D lines'
_handle = None
@classmethod
def poll(cls, context):
return context.mode == "OBJECT" and context.active_object != None and 'VF_Volume_' in context.active_object.name
def modal(self, context, event):
if context.area:
context.area.tag_redraw()
if context.window_manager.vf_showingvelocitylines == -1:
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
context.window_manager.vf_showingvelocitylines = 0
return {"CANCELLED"}
return {"PASS_THROUGH"}
def invoke(self, context, event):
#print ("test")
if context.area.type == "VIEW_3D":
if context.window_manager.vf_showingvelocitylines < 1:
volmesh = context.active_object
temploc = Vector((0.0,0.0,0.0))
if volmesh.parent:
temploc = volmesh.parent.location
vf_coords = [(Vector(v.vcoord) + temploc) for v in volmesh.custom_vectorfield]
vf_velocities = [Vector(v.vvelocity) for v in volmesh.custom_vectorfield]
vf_DrawVelocities = [Vector((0.0,0.0,0.0)) for i in range(len(volmesh.custom_vectorfield) * 2)]
velcounter = 0
for i in range(len(vf_coords)):
vf_DrawVelocities[velcounter] = vf_coords[i].copy()
velcounter += 1
vf_DrawVelocities[velcounter] = vf_coords[i] + vf_velocities[i]
velcounter += 1
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
batch = batch_for_shader(shader, 'LINES', {"pos": vf_DrawVelocities})
context.window_manager.vf_showingvelocitylines = 1
color = Vector((context.window_manager.vf_velocitylinescolor[0], context.window_manager.vf_velocitylinescolor[1], context.window_manager.vf_velocitylinescolor[2], 1.0))
self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_vectorfield,
(shader, batch, color), 'WINDOW', 'POST_VIEW')
context.window_manager.modal_handler_add(self)
context.area.tag_redraw()
return {"RUNNING_MODAL"}
else:
context.window_manager.vf_showingvelocitylines = -1
return {'RUNNING_MODAL'}
else:
self.report({"WARNING"}, "View3D not found, can't run operator")
return {"CANCELLED"}
# draw lines
def draw_vectorfield(shader, batch, color):
shader.bind()
shader.uniform_float("color", color)
batch.draw(shader)
================================================
FILE: FGA_VectorFields/vf_io.py
================================================
### Import/Export Functions
import bpy
import os.path
from mathutils import Vector
### Import
# create new vector field from imported data
def build_importedVectorField(tempvelList, tempOffset):
# create blank vf
from . import vf_editor
volname = vf_editor.build_vectorfield(bpy.context)
volmesh = bpy.context.scene.objects[volname]
# copy imported velocities
for i in range(len(tempvelList)):
volmesh.custom_vectorfield[i].vvelocity = tempvelList[i]
if volmesh.parent:
volmesh.parent.location = volmesh.parent.location + tempOffset
else:
volmesh.location = volmesh.location + tempOffset
# read data from file
def parse_fgafile(self, context):
returnmessage = ""
fgafilepath = self.filepath
if os.path.exists(fgafilepath):
if os.path.isfile(fgafilepath):
file = open(fgafilepath, 'r')
importvf_scalemult = self.importvf_scalemult
linecount = 0
tempvelList = []
tempMin = Vector((0.0,0.0,0.0))
tempOffset = Vector((0.0,0.0,0.0))
tempscalemult = Vector((0.0,0.0,0.0))
for line in file:
slist = []
slist = line.split(',')
if len(slist) > 3:
slist.remove(slist[3])
flist = [float(s) for s in slist]
if linecount <= 2:
if linecount == 0:
# Resolution
context.window_manager.vf_density[0] = int(flist[0])
context.window_manager.vf_density[1] = int(flist[1])
context.window_manager.vf_density[2] = int(flist[2])
elif linecount == 1:
# Min bounds
tempMin[0] = flist[0]
tempMin[1] = flist[1]
tempMin[2] = flist[2]
elif linecount == 2:
# Max bounds, calc offset + scale
tempscalemult = Vector((0.0,0.0,0.0))
tempscalemult[0] = abs(flist[0] - tempMin[0])
tempscalemult[1] = abs(flist[1] - tempMin[1])
tempscalemult[2] = abs(flist[2] - tempMin[2])
context.window_manager.vf_scale[0] = (tempscalemult[0] / context.window_manager.vf_density[0]) * importvf_scalemult
context.window_manager.vf_scale[1] = (tempscalemult[1] / context.window_manager.vf_density[1]) * importvf_scalemult
context.window_manager.vf_scale[2] = (tempscalemult[2] / context.window_manager.vf_density[2]) * importvf_scalemult
if self.importvf_getoffset:
tempOffset[0] = (((tempMin[0] + (tempscalemult[0] * 0.5)) * importvf_scalemult))
tempOffset[1] = (((tempMin[1] + (tempscalemult[1] * 0.5)) * importvf_scalemult))
tempOffset[2] = (((tempMin[2] + (tempscalemult[2] * 0.5)) * importvf_scalemult))
else:
# Velocities
if self.importvf_velscale:
tempvelList.append(Vector((flist[0] * importvf_scalemult,flist[1] * importvf_scalemult,flist[2] * importvf_scalemult)))
else:
tempvelList.append(Vector(flist))
linecount += 1
if linecount < 3:
returnmessage = "Import Failed: File is missing data"
else:
if len(tempvelList) > 0:
returnmessage = "Import Successful"
build_importedVectorField(tempvelList, tempOffset)
file.close()
else:
returnmessage = "Import Failed: File not found"
else:
returnmessage = "Import Failed: Path not found"
return returnmessage
### Export
def write_fgafile(self, exportvol):
usevelscale = self.exportvf_velscale
useoffset = self.exportvf_locoffset
tempDensity = Vector(exportvol.vf_object_density)
fgascale = Vector(exportvol.vf_object_scale)
file = open(self.filepath, "w", encoding="utf8", newline="\n")
fw = file.write
# Resolution:
fw("%i,%i,%i," % (tempDensity[0],tempDensity[1],tempDensity[2]))
# Minimum/Maximum Bounds:
if self.exportvf_allowmanualbounds:
fw("\n%f,%f,%f," % (
self.exportvf_manualboundsneg[0],
self.exportvf_manualboundsneg[1],
self.exportvf_manualboundsneg[2])
)
fw("\n%f,%f,%f," % (
self.exportvf_manualboundspos[0],
self.exportvf_manualboundspos[1],
self.exportvf_manualboundspos[2])
)
else:
if useoffset:
offsetvect = Vector((0.0,0.0,0.0))
if exportvol.parent:
offsetvect = exportvol.parent.location
fw("\n%f,%f,%f," % (
(((tempDensity[0] * -0.5) * fgascale[0]) + (offsetvect[0])) * self.exportvf_scale,
(((tempDensity[1] * -0.5) * fgascale[1]) + (offsetvect[1])) * self.exportvf_scale,
(((tempDensity[2] * -0.5) * fgascale[2]) + (offsetvect[2])) * self.exportvf_scale)
)
fw("\n%f,%f,%f," % (
(((tempDensity[0] * 0.5) * fgascale[0]) + (offsetvect[0])) * self.exportvf_scale,
(((tempDensity[1] * 0.5) * fgascale[1]) + (offsetvect[1])) * self.exportvf_scale,
(((tempDensity[2] * 0.5) * fgascale[2]) + (offsetvect[2])) * self.exportvf_scale)
)
else: # centered
fw("\n%f,%f,%f," % (
((tempDensity[0] * -0.5) * fgascale[0]) * self.exportvf_scale,
((tempDensity[1] * -0.5) * fgascale[1]) * self.exportvf_scale,
((tempDensity[2] * -0.5) * fgascale[2]) * self.exportvf_scale)
)
fw("\n%f,%f,%f," % (
((tempDensity[0] * 0.5) * fgascale[0]) * self.exportvf_scale,
((tempDensity[1] * 0.5) * fgascale[1]) * self.exportvf_scale,
((tempDensity[2] * 0.5) * fgascale[2]) * self.exportvf_scale)
)
# Velocities
if usevelscale and not self.exportvf_allowmanualbounds:
for vec in exportvol.custom_vectorfield:
fw("\n%f,%f,%f," % (
vec.vvelocity[0] * self.exportvf_scale,
vec.vvelocity[1] * self.exportvf_scale,
vec.vvelocity[2] * self.exportvf_scale)
)
else:
if self.exportvf_allowmanualbounds:
for vec in exportvol.custom_vectorfield:
fw("\n%f,%f,%f," % (
vec.vvelocity[0] * self.exportvf_manualvelocityscale,
vec.vvelocity[1] * self.exportvf_manualvelocityscale,
vec.vvelocity[2] * self.exportvf_manualvelocityscale)
)
else:
for vec in exportvol.custom_vectorfield:
fw("\n%f,%f,%f," % (
vec.vvelocity[0],
vec.vvelocity[1],
vec.vvelocity[2])
)
file.close()
================================================
FILE: README.md
================================================
Blender - FGA Vector Field Editor
=======================================
Allows 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.
- Requires Blender 2.8 and up.
- 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.
-------------------------------------------------------------------------------------------------------
**Out-of-date Documentation is available here**: https://github.com/isathar/Blender_UE4_VectorFieldEditor/wiki (or the wiki link on the side)
- Very out of date, will be updated as soon as time permits.
Some example .fga files: http://www.mediafire.com/download/4x174fgf8lmec6g/VF_Examples.zip
-------------------------------------------------------------------------------------------------------
## Features
**Vector Field Editor:**
- Saves current particle system velocities and blends them with saved results using one of the following methods:
- *Replace, Average, Add, Multiply, Cross Product, Vector Reflection*
- Particle velocities used in these calculations can be obtained using the following methods:
- *Velocity, Offset Distance, Angular Velocity, Custom Vector, Point*
- Curve Force tool that uses wind forces to move particles along a line.
**Importer + Exporter** for FGA files for use in Unreal Engine 4
-------------------------------------------------------------------------------------------------------
## Installation
- Extract to your addons directory
- Enable it in the addon manager (named *FGA Vector Field Tools*)
- A new tab named *Vector Fields* should be visible in the tools panel
---------------------------------------------------------------------------------------------------------
## Notes
###### Performance
- This addon's performance is heavily dependent on the speed of your CPU and memory.
- Blender may stop responding during the Create and Calculate operations, but shouldn't crash.
- 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.
- 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).
- 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.
###### 128x128x128 and System Memory
- 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.
- This is due to the amount of particles that need to have their dynamics cached (and the number of vertices in the volume mesh)
- To avoid running out of memory while editing very high resolution vector fields, you may want to lower your undo history steps.
- Using a disk cache for your particles may help, too.
-------------------------------------------------------------------------------------------------------
## Changelog:
***1.2.1***:
- probably fixed noneType error messages *(Thank you to kkaja123 for reporting and looking into fixing it!)*
- moved the menu to the panels at the right side of the workspace
***1.2.0***:
- initial update to support Blender 2.8.
***1.1.5***:
- changed sorting of the calculation/velocity dropdown lists
- new calculation method: Vector Reflection
- added constraint to vector field volume, only moving the bounding box now moves the volume
***1.1.4***:
- fix for display lines not updating when new velocities are calculated
- new calculation method: Cross Product
***1.1.3***:
- removed the need for the update data/offsets buttons
- slight performance optimizations for display lines
***1.1.2***:
- new presets can now be added/removed
- export should now work with any part of the vector field selected (or both)
***1.1.0***:
- added support for selecting a physics preset to edit particle physics settings easily for selected vector field
***1.0.1***:
- renamed curve path tool to Wind Curve Force
- added editor for created curve wind force strength, distance + falloff
- the Ratio property should now work (apparently forgot to use the variable)
***1.0.0***:
- *General:*
- tools panel category renamed to *Vector Fields*
- saved data only includes velocities now (removed position, index)
- files made with old versions are still compatible
- cleaned up ui panel to reduce clutter, added section toggles
- some code cleanup
- *Import/Export:*
- moved import/export to standard menu
- made import/export properties local to their functions
- *Editor:*
- performance tweaks for creating new vector fields + calculating velocities
- removed slice selection tool (redundant, easily done in edit mode)
- matched default scaling to (grid units x field density)
- distances were at half scale before
- density variable used for creation is now distance between particles
- added undo functionality to *Generate* function
- new velocity mode: Point
- *Curve Force Tool:*
- changed curve force tool to create an object group to remove scene outliner clutter
- fixed curve force fields' parenting issue
- all transformations to the curve force object should now work
- curve forces now display an arrow pointing in the force's direction
***0.9.5***:
- *Editor:*
- added new calculation method: Multiply
- added different methods for obtaining velocities
- reorganized the main editor
- added calculate for selection
- added invert all button
- seperated normalize function from calculation - it's a button again
- *Import/Export:*
- moved density variable to object space for export script
- allows multiple vector fields in the scene during export (still exports one at a time)
- added ability to use object locations as offsets + import/export them
- scaling tweaks
- manual bounds option
- *General:*
- added ability to undo slice selection, calculation, curve tool, and normalize
- select x,y,z slice
- created index by axis for velocities list for slice selection and upcoming features
- switched bpy.context to passed context where possible
- description text for all variables + operators (some may be vague)
- added bug reporting url to addon manager (Github)
- readme formatting
***0.9.1***:
- added different generation modes: *Replace, Additive, Average*
- added trail option for curve path (fade influence with curve position)
- changed the way invert and normalize work
- slight calculation performance tweak
***0.9***:
- another performance tweak
- added invert, normalize, disable gravity options
***0.8***:
- added import functionality
- massive speed improvement
***0.5***:
- initial upload
gitextract_6jzh4irb/ ├── .github/ │ └── FUNDING.yml ├── FGA_VectorFields/ │ ├── __init__.py │ ├── vf_editor.py │ └── vf_io.py └── README.md
SYMBOL INDEX (52 symbols across 3 files)
FILE: FGA_VectorFields/__init__.py
class VFTOOLS_PT_menupanel (line 22) | class VFTOOLS_PT_menupanel(bpy.types.Panel):
method __init__ (line 29) | def __init__(self):
method poll (line 33) | def poll(self, context):
method draw (line 36) | def draw(self, context):
class vector_field (line 124) | class vector_field(bpy.types.PropertyGroup):
class export_vectorfieldfile (line 130) | class export_vectorfieldfile(bpy.types.Operator, ExportHelper):
method check_extension (line 170) | def check_extension(self):
method check (line 173) | def check(self, context):
method draw (line 177) | def draw(self,context):
method execute (line 190) | def execute(self, context):
class import_vectorfieldfile (line 216) | class import_vectorfieldfile(bpy.types.Operator, ImportHelper):
method draw (line 237) | def draw(self,context):
method execute (line 250) | def execute(self, context):
class Preset_VFCreate (line 257) | class Preset_VFCreate(AddPresetBase, bpy.types.Operator):
class Preset_VFCreate_Fluid (line 281) | class Preset_VFCreate_Fluid(AddPresetBase, bpy.types.Operator):
class Presets_VFCreate (line 312) | class Presets_VFCreate(bpy.types.Menu):
class Presets_VFCreate_Fluid (line 320) | class Presets_VFCreate_Fluid(bpy.types.Menu):
function exportmenu_func (line 328) | def exportmenu_func(self, context):
function importmenu_func (line 332) | def importmenu_func(self, context):
function initdefaults (line 337) | def initdefaults():
function clearvars (line 435) | def clearvars():
function register (line 476) | def register():
function unregister (line 486) | def unregister():
FILE: FGA_VectorFields/vf_editor.py
function build_vectorfield (line 14) | def build_vectorfield(context):
class VFTOOLS_OT_create_vectorfield (line 124) | class VFTOOLS_OT_create_vectorfield(bpy.types.Operator):
method execute (line 130) | def execute(self, context):
class calc_vectorfieldvelocities (line 138) | class calc_vectorfieldvelocities(bpy.types.Operator):
method poll (line 145) | def poll(cls, context):
method execute (line 148) | def execute(self, context):
class vf_normalizevelocities (line 271) | class vf_normalizevelocities(bpy.types.Operator):
method poll (line 278) | def poll(cls, context):
method execute (line 281) | def execute(self, context):
class vf_invertvelocities (line 290) | class vf_invertvelocities(bpy.types.Operator):
method poll (line 297) | def poll(cls, context):
method execute (line 300) | def execute(self, context):
class calc_curvewindforce (line 315) | class calc_curvewindforce(bpy.types.Operator):
method poll (line 322) | def poll(cls, context):
method execute (line 325) | def execute(self, context):
class edit_curvewindforce (line 403) | class edit_curvewindforce(bpy.types.Operator):
method poll (line 410) | def poll(cls, context):
method execute (line 414) | def execute(self, context):
class toggle_vectorfieldinfo (line 436) | class toggle_vectorfieldinfo(bpy.types.Operator):
method poll (line 442) | def poll(cls, context):
method execute (line 445) | def execute(self, context):
class toggle_vectorfieldvelocities (line 450) | class toggle_vectorfieldvelocities(bpy.types.Operator):
method poll (line 458) | def poll(cls, context):
method modal (line 461) | def modal(self, context, event):
method invoke (line 471) | def invoke(self, context, event):
function draw_vectorfield (line 512) | def draw_vectorfield(shader, batch, color):
FILE: FGA_VectorFields/vf_io.py
function build_importedVectorField (line 10) | def build_importedVectorField(tempvelList, tempOffset):
function parse_fgafile (line 25) | def parse_fgafile(self, context):
function write_fgafile (line 96) | def write_fgafile(self, exportvol):
Condensed preview — 5 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (53K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 17,
"preview": "patreon: isathar\n"
},
{
"path": "FGA_VectorFields/__init__.py",
"chars": 18480,
"preview": "bl_info = {\n\t\"name\": \"Vector Field Tools\",\n\t\"author\": \"Andreas Wiehn (isathar)\",\n\t\"version\": (1, 2, 1),\n\t\"blender\": (2, "
},
{
"path": "FGA_VectorFields/vf_editor.py",
"chars": 17076,
"preview": "### Editor Functions\n\nimport bpy\nimport gpu\n\nfrom mathutils import Vector, Matrix\n\nfrom gpu_extras.batch import batch_fo"
},
{
"path": "FGA_VectorFields/vf_io.py",
"chars": 5771,
"preview": "### Import/Export Functions\n\nimport bpy\nimport os.path\nfrom mathutils import Vector\n\n### Import\n\n# create new vector fie"
},
{
"path": "README.md",
"chars": 7003,
"preview": "Blender - FGA Vector Field Editor\n=======================================\n\nAllows creation and manipulation of vector fi"
}
]
About this extraction
This page contains the full source code of the isathar/Blender_UE4_VectorFieldEditor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 5 files (47.2 KB), approximately 13.0k tokens, and a symbol index with 52 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.