Module sverchok.utils.pulga_physics_modular_core
Expand source code
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE
import numpy as np
from mathutils.bvhtree import BVHTree
from sverchok.utils.modules.vector_math_utils import angle_between, unit_vector
from sverchok.dependencies import FreeCAD
if FreeCAD is not None:
import Part
from FreeCAD import Base
from sverchok.utils.surface.freecad import is_solid_face_surface
from sverchok.dependencies import scipy
from sverchok.utils.sv_mesh_utils import polygons_to_edges_np
from sverchok.utils.modules.edge_utils import adjacent_faces_number
def np_dot(u, v, axis=1):
return np.sum(u * v, axis=axis)
def reflect(v1, mirror):
dot2 = 2 * np.sum(mirror * v1, axis=1)
return v1 - (dot2[:, np.newaxis] * mirror)
def cross_indices3(n):
'''create crossed indices'''
nu = np.sum(np.arange(n, dtype=np.int64))
ind = np.zeros((nu, 2), dtype=np.int16)
c = 0
for i in range(n-1):
l = n-i-1
np_i = np.full(n-i-1, i, dtype=np.int32)
np_j = np.arange(i+1, n, dtype=np.int32)
np_a = np.stack((np_i, np_j), axis=-1)
ind[c:c+l, :] = np_a
c += l
return ind
def numpy_match_long_repeat(p):
'''match list length by repeating last one'''
q = []
maxl = 0
for g in p:
maxl = max(maxl, g.shape[0])
for g in p:
difl = maxl - g.shape[0]
if difl > 0:
rr = np.repeat(g[np.newaxis, -1], difl, axis=0)
g = np.concatenate((g, rr))
q.append(g)
return q
def numpy_fit_long_repeat(p, maxl):
'''match list length by repeating last one or removing end'''
q = []
for g in p:
difl = maxl - g.shape[0]
if difl > 0:
rr = np.repeat(g[np.newaxis, -1], difl, axis=0)
g = np.concatenate((g, rr))
if difl < 0:
g = g[:maxl]
q.append(g)
return q
def match_cylce(p, n):
'''append cylcing till have n items'''
difference = n - len(p)
for i in range(difference):
p.append(p[i % len(p)])
def expand_pols_numpy(p, len_max):
'''to fit variable sided polygons in one array (cycling)'''
np_match_cycle = np.vectorize(match_cylce)
np_match_cycle(p, len_max)
new_pols = np.zeros((len(p), len_max), dtype=np.int32)
for i in range(len(p)):
new_pols[i, :] = p[i]
return new_pols
def calc_rest_length(np_verts, np_springs):
'''calculate edges length'''
pairs_springs = np_verts[np_springs, :]
vect_rest = (pairs_springs[:, 0, :] - pairs_springs[:, 1, :])
dist_rest = np.linalg.norm(vect_rest, axis=1)
return dist_rest
def pols_normals(pol_v, mag):
'''get actual polygons normal with controlled magnitude'''
v1 = pol_v[:, 1, :] - pol_v[:, 0, :]
v2 = pol_v[:, 2, :] - pol_v[:, 0, :]
pols_normal = np.cross(v1, v2)
pols_normal_d = np.linalg.norm(pols_normal, axis=1)
return pols_normal / pols_normal_d[:, np.newaxis] * mag
def calc_area(pol_side_max, pol_v, pols_normal):
'''calculate polygons area (equal sided polygons)'''
prod = np.zeros((pol_side_max, len(pols_normal), 3), dtype=np.float32)
for i in range(pol_side_max):
prod[i, :, :] = np.cross(pol_v[:, i, :], pol_v[:, (i + 1) % pol_side_max, :])
tot = np.sum(prod, axis=0)
return abs(np.sum(tot * pols_normal, axis=1) / 2)
def calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal):
'''calculate polygons area (variable sided polygons)'''
prod = np.zeros((pol_side_max, len(pols_sides), 3), dtype=np.float32)
for i in range(pol_side_max):
mask = pols_sides > i
prod[i, mask, :] = np.cross(pol_v[mask, i, :], pol_v[mask, (i + 1) % pol_side_max, :])
tot = np.sum(prod, axis=0)
return abs(np.sum(tot * pols_normal, axis=1) / 2)
def bvh_safe_check(verts, pols):
len_v = len(verts)
for p in pols:
for c in p:
if c > len_v:
raise Exception(f"Index {c} should be less than vertices length ({len_v})")
class SvAttractorsForce():
def __init__(self, location, strength, clamp, decay_power):
np_location = np.array(location)
np_strength = np.array(strength)
np_clamp = np.array(clamp)
np_decay_pow = np.array(decay_power)
params = numpy_match_long_repeat([np_location, np_strength, np_clamp, np_decay_pow])
self.points = params[0]
self.strength = params[1]
self.use_attractors = np.any(params[1] != 0)
self.clamp = params[2]
self.decay = params[3]
def setup(self, ps):
pass
def add(self, ps):
if not self.use_attractors:
return
v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :]
dist_attract = np.linalg.norm(v_attract, axis=2)
mask = dist_attract > self.clamp[:, np.newaxis]
mask_true = np.invert(mask)
dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis])
dist_attract_cor = np.clip(dist_attract2[mask_true], 1e-2, 1e4)
v_attract *= self.strength[:, np.newaxis, np.newaxis]
v_attract_normal = v_attract[mask_true] / dist_attract_cor[:, np.newaxis]
v_attract[mask_true] = -v_attract_normal
v_attract[mask, :] = 0
r_attract = np.sum(v_attract, axis=0)
ps.force_resultant += r_attract
class SvAttractorsLineForce():
def __init__(self, location, direction, strength, clamp, decay_power):
np_location = np.array(location)
np_direction = np.array(direction)
np_strength = np.array(strength)
np_clamp = np.array(clamp)
np_decay_pow = np.array(decay_power)
params = numpy_match_long_repeat([np_location, np_direction, np_strength, np_clamp, np_decay_pow])
self.points = params[0]
self.direction = params[1]/np.linalg.norm(params[1], axis=1)[:, np.newaxis]
self.strength = params[2]
self.use_attractors = np.any(params[2] != 0)
self.clamp = params[3]
self.decay = params[4]
def setup(self, ps):
pass
def add(self, ps):
if not self.use_attractors:
return
v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :]
vect_proy = np_dot(v_attract, self.direction[:, np.newaxis, :], axis=2)
closest_point = self.points[:, np.newaxis, :] + vect_proy[:, :, np.newaxis] * self.direction[:, np.newaxis, :]
dif_v = closest_point - ps.verts[np.newaxis, :, :]
dist_attract = np.linalg.norm(dif_v, axis=2)
mask = dist_attract > self.clamp[:, np.newaxis]
mask_true = np.invert(mask)
dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis])
dist_attract_cor = np.clip(dist_attract2[mask_true], 1e-2, 1e4)
dif_v *= self.strength[:, np.newaxis, np.newaxis]
v_attract_normal = dif_v[mask_true] / dist_attract_cor[:, np.newaxis]
v_attract[mask_true] = v_attract_normal
v_attract[mask, :] = 0
r_attract = np.sum(v_attract, axis=0)
ps.force_resultant += r_attract
class SvAttractorsPlaneForce():
def __init__(self, location, normal, strength, clamp, decay_power):
np_location = np.array(location)
np_normal = np.array(normal)
np_strength = np.array(strength)
np_clamp = np.array(clamp)
np_decay_pow = np.array(decay_power)
params = numpy_match_long_repeat([np_location, np_normal, np_strength, np_clamp, np_decay_pow])
self.points = params[0]
self.normal = params[1]/np.linalg.norm(params[1], axis=1)[:, np.newaxis]
self.strength = params[2]
self.use_attractors = np.any(params[2] != 0)
self.clamp = params[3]
self.decay = params[4]
def setup(self, ps):
pass
def add(self, ps):
if not self.use_attractors:
return
v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :]
dist_attract = np_dot(v_attract, self.normal[:, np.newaxis, :], axis=2)
mask = dist_attract > self.clamp[:, np.newaxis]
mask_true = np.invert(mask)
plane_side = dist_attract < 0
dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis])
dist_attract_cor = np.clip(dist_attract2, 1e-2, 1e4)
dist_attract_cor[plane_side] *= -1
v_attract_normal = self.normal[:, np.newaxis, :] / dist_attract_cor[:, :, np.newaxis] * self.strength[:, np.newaxis, np.newaxis]
v_attract[mask_true] = -v_attract_normal[mask_true]
v_attract[mask, :] = 0
r_attract = np.sum(v_attract, axis=0)
ps.force_resultant += r_attract
class SvVortexForce():
def __init__(self, location, direction, rot_strength, inflow_strength, forward_strength, clamp, decay_power):
np_location = np.array(location)
np_direction = np.array(direction)
np_rot_strength = np.array(rot_strength)
np_inflow_strength = np.array(inflow_strength)
np_forward_strength = np.array(forward_strength)
np_clamp = np.array(clamp)
np_decay_pow = np.array(decay_power)
params = numpy_match_long_repeat([np_location, np_direction, np_rot_strength, np_inflow_strength, np_forward_strength, np_clamp, np_decay_pow])
self.points = params[0]
self.direction = params[1]/np.linalg.norm(params[1], axis=1)[:, np.newaxis]
self.rot_strength = params[2]
self.use_rotation = np.any(params[2] != 0)
self.inflow_strength = params[3]
self.use_inflow = np.any(params[3] != 0)
self.forward_strength = params[4]
self.use_forward = np.any(params[4] != 0)
self.use_vortex = np.any([self.use_rotation, self.use_inflow, self.use_forward])
self.clamp = params[5]
self.decay = params[6]
def setup(self, ps):
pass
def add(self, ps):
if not self.use_vortex:
return
v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :]
vect_proy = np_dot(v_attract, self.direction[:, np.newaxis, :],axis=2)
closest_point = self.points[:, np.newaxis, :] +vect_proy[:, :, np.newaxis] * self.direction[:, np.newaxis, :]
dif_v = closest_point - ps.verts[np.newaxis, :, :]
dist_attract = np.linalg.norm(dif_v, axis=2)
mask_true = dist_attract < self.clamp[:, np.newaxis]
dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis])
dist_attract_cor = np.clip(dist_attract2, 1e-2, 1e4)
v_attract_normal = dif_v/ dist_attract_cor[:, :,np.newaxis]
v_attract[:] = 0
if self.use_rotation:
v_rotation = np.cross(v_attract_normal, self.direction[:, np.newaxis, :]) * self.rot_strength[:, np.newaxis, np.newaxis]
v_attract[mask_true] += v_rotation[mask_true]
if self.use_forward:
v_forward = self.direction[:, np.newaxis, :] * self.forward_strength[:, np.newaxis, np.newaxis] / dist_attract_cor[:, :,np.newaxis]
v_attract[mask_true] += v_forward[mask_true]
if self.use_inflow:
v_attract_normal *= self.inflow_strength[:, np.newaxis, np.newaxis]
v_attract[mask_true] += v_attract_normal[mask_true]
r_attract = np.sum(v_attract, axis=0)
ps.force_resultant += r_attract
class SvBoundingBoxForce():
def __init__(self, b_box):
np_bbox = np.array(b_box)
bbox_max = np.amax(np_bbox, axis=0)
bbox_min = np.amin(np_bbox, axis=0)
self.bbox_min = bbox_min
self.bbox_max = bbox_max
def setup(self, ps):
pass
def add(self, ps):
min_mask = ps.verts <= self.bbox_min + ps.rads[:, np.newaxis]
max_mask = ps.verts >= self.bbox_max - ps.rads[:, np.newaxis]
out_vals = np.any([min_mask, max_mask], axis=0)
ps.vel[out_vals] = 0
ps.force_resultant[out_vals] = 0
ps.verts = np.clip(ps.verts, self.bbox_min + ps.rads[:, np.newaxis], self.bbox_max - ps.rads[:, np.newaxis])
def project_on_plane(vects, normal):
distance = np.sum(vects * normal, axis=1)
return vects - normal * distance[:,np.newaxis]
class SvBoundingSphereForce():
def __init__(self, center, radius):
self.center = center[0]
self.radius = radius[0]
def setup(self, ps):
pass
def add(self, ps):
vs = ps.verts - self.center
dist = np.linalg.norm(vs, axis=1)
mask = dist + ps.rads > self.radius
vs_normal = vs[mask]/dist[mask, np.newaxis]
ps.verts[mask] = vs_normal*(self.radius - ps.rads[mask])[:,np.newaxis]
ps.vel[mask] = project_on_plane(ps.vel[mask], vs_normal)
ps.force_resultant[mask] = project_on_plane(ps.force_resultant[mask], vs_normal)
class SvBoundingSphereSurfaceForce():
def __init__(self, center, radius):
self.center = center[0]
self.radius = radius[0]
def setup(self, ps):
pass
def add(self, ps):
vs = ps.verts - self.center
dist = np.linalg.norm(vs, axis=1)
mask = dist == 0
vs[mask]=ps.verts[mask]+[[1,0,0]]
dist[mask] = 1
vs_normal = vs/dist[:, np.newaxis]
ps.verts = vs_normal * self.radius
ps.vel = project_on_plane(ps.vel, vs_normal)
ps.force_resultant = project_on_plane(ps.force_resultant, vs_normal)
class SvBoundingPlaneSurfaceForce():
def __init__(self, center, normal):
self.center = np.array(center[0])
self.normal = np.array(normal[0])
self.normal = self.normal/np.linalg.norm(self.normal)
def setup(self, ps):
pass
def add(self, ps):
vs = ps.verts - self.center
distance = np.sum(vs * self.normal, axis=1)
ps.verts = ps.verts - self.normal[np.newaxis, :] * distance[:, np.newaxis]
ps.vel = project_on_plane(ps.vel, self.normal)
ps.force_resultant = project_on_plane(ps.force_resultant, self.normal)
class SvBoundingMeshForce():
def __init__(self, vertices, polygons, volume=False):
bvh_safe_check(vertices, polygons)
self.bvh = BVHTree.FromPolygons(vertices, polygons, all_triangles=False, epsilon=0.0)
if volume:
self.add = self.add_volume
else:
self.add = self.add_surface
def setup(self, ps):
self.nearest = np.zeros(ps.verts.shape, dtype=np.float64)
self.normals = np.zeros(ps.verts.shape, dtype=np.float64)
def find_nearest(self, verts):
v_nearest = self.nearest
v_normals = self.normals
for v, near, norm in zip(verts, v_nearest, v_normals):
nearest, normal, _, _ = self.bvh.find_nearest(v)
near[0] = nearest[0]
near[1] = nearest[1]
near[2] = nearest[2]
norm[0] = normal[0]
norm[1] = normal[1]
norm[2] = normal[2]
def add_surface(self, ps):
self.find_nearest(ps.verts)
ps.verts = self.nearest
ps.vel = project_on_plane(ps.vel, self.normals)
ps.force_resultant = project_on_plane(ps.force_resultant, self.normals)
def add_volume(self, ps):
self.find_nearest(ps.verts)
outer_mask = np_dot(self.nearest - ps.verts, self.normals) <= 0
ps.verts[outer_mask] = self.nearest[outer_mask]
ps.vel[outer_mask] = project_on_plane(ps.vel[outer_mask], self.normals[outer_mask])
ps.force_resultant[outer_mask] = project_on_plane(ps.force_resultant[outer_mask], self.normals[outer_mask])
class SvBoundingSolidForce():
def __init__(self, solid, volume=False):
if isinstance(solid, Part.Solid):
self.shape = solid.OuterShell
elif is_solid_face_surface(solid):
self.shape = solid.face
else:
self.shape = solid
if volume:
self.add = self.add_volume
else:
self.add = self.add_surface
def find_closest(self,v):
vertex = Part.Vertex(Base.Vector(v))
dist = self.shape.distToShape(vertex)
if str(dist[2][0][0]) == "b'Face'":
normal = self.shape.Faces[dist[2][0][1]].normalAt(*dist[2][0][2])
elif str(dist[2][0][0]) == "b'Edge'":
edge = self.shape.Edges[dist[2][0][1]]
vector = self.shape.Edges[dist[2][0][1]].valueAt(dist[2][0][2])
face_list = self.shape.ancestorsOfType(edge, Part.Face)
normal = [0,0,0]
count = 0
for face in face_list:
for edge1 in face.Edges:
if edge1.isSame(edge):
param = face.Surface.parameter(vector)
normal_ed = face.normalAt(*param)
normal[0] += normal_ed[0]
normal[1] += normal_ed[1]
normal[2] += normal_ed[2]
count+=1
break
if count > 0:
normal[0] /= count
normal[1] /= count
normal[2] /= count
else:
normal = [0, 0, 1]
else:
vertex = self.shape.Vertexes[dist[2][0][1]]
face_list = self.shape.ancestorsOfType(vertex, Part.Face)
normal = [0, 0, 0]
count = 0
for face in face_list:
for vertex1 in face.Vertexes:
if vertex1.isSame(vertex):
param = face.Surface.parameter(vertex.Point)
normal_ed = face.normalAt(*param)
normal[0] += normal_ed[0]
normal[1] += normal_ed[1]
normal[2] += normal_ed[2]
count+=1
break
if count > 0:
normal[0] /= count
normal[1] /= count
normal[2] /= count
else:
normal = [0, 0, 1]
return dist[1][0][0], normal
def find(self, verts):
v_nearest = self.nearest
v_normals = self.normals
for v, near, norm in zip(verts, v_nearest, v_normals):
nearest, normal = self.find_closest(v)
near[0] = nearest[0]
near[1] = nearest[1]
near[2] = nearest[2]
norm[0] = normal[0]
norm[1] = normal[1]
norm[2] = normal[2]
def find_masked(self, verts, mask):
v_nearest = self.nearest
v_normals = self.normals
for v, near, norm in zip(verts[mask], v_nearest[mask], v_normals[mask]):
nearest, normal = self.find_closest(v)
near[0] = nearest[0]
near[1] = nearest[1]
near[2] = nearest[2]
norm[0] = normal[0]
norm[1] = normal[1]
norm[2] = normal[2]
def is_outside(self, verts):
return np.array([not self.shape.isInside(Base.Vector(v), 1e-6, False) for v in verts], dtype=np.bool_)
def setup(self, ps):
self.nearest = np.zeros(ps.verts.shape, dtype=np.float64)
self.normals = np.zeros(ps.verts.shape, dtype=np.float64)
def add_surface(self, ps):
self.find(ps.verts)
ps.verts = self.nearest
ps.vel = project_on_plane(ps.vel, self.normals)
ps.force_resultant = project_on_plane(ps.force_resultant, self.normals)
def add_volume(self, ps):
outside = self.is_outside(ps.verts)
self.find_masked(ps.verts, outside)
ps.verts[outside] = self.nearest[outside]
ps.vel[outside] = project_on_plane(ps.vel[outside], self.normals[outside])
ps.force_resultant[outside] = project_on_plane(ps.force_resultant[outside], self.normals[outside])
class SvRandomForce():
def __init__(self, random_force, random_variation, random_seed):
# random_seed, random_force, random_variation = local_params
np_random_force = np.array(random_force)
np_random_variation = np.array(random_variation)
random_force = np_random_force[:, np.newaxis]
random_variation = np_random_variation[:, np.newaxis]
random_variate = any(random_variation > 1e-6)
self.seed = random_seed[0]
self.random_force = random_force
self.random_variate = random_variate
self.random_variation = random_variation
def setup(self, ps):
np.random.seed(int(self.seed))
self.random_v = self.random_force * np.random.random((ps.v_len, 3)) - self.random_force / 2
def add(self, ps):
if self.random_variate:
random_var = 2 * self.random_force * np.random.random((ps.v_len, 3)) - self.random_force
self.random_v = self.random_v * (1 - self.random_variation) + random_var * self.random_variation
ps.force_resultant += self.random_v
class SvCollisionForce():
def __init__(self, magnitude, use_kdtree=False):
self.magnitude = np.array(magnitude)
self.uniform_magnitude = len(magnitude) < 2
self.needs = ['dif_v', 'dist', 'dist_cor', 'collide', 'normal_v']
self.use_kdtree = use_kdtree
if self.use_kdtree:
self.needs = ['kd_tree', 'max_radius', 'kd_collisions']
self.add = self.add_kdt
else:
self.needs = ['indexes', 'sum_rad', 'dif_v', 'dist', 'dist_cor', 'collide', 'normal_v']
self.add = self.add_brute_force
def setup(self, ps):
ps.aware = True
for need in self.needs:
ps.relations.needed[need] = True
if self.uniform_magnitude:
self.f_magnitude = self.magnitude
else:
self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0]
def add_brute_force(self, ps):
id0 = ps.relations.index_inter[:, 0]
id1 = ps.relations.index_inter[:, 1]
le = ps.relations.dist[ps.relations.mask, np.newaxis] - ps.relations.sum_rad[ps.relations.mask, np.newaxis]
no = ps.relations.normal_v[ps.relations.mask]
sf = self.f_magnitude[:, np.newaxis]
len0, len1 = [sf, sf] if self.uniform_magnitude else [sf[id1], sf[id0]]
np.add.at(ps.force_resultant, id0, -no * le * len0)
np.add.at(ps.force_resultant, id1, no * le * len1)
def add_kdt(self, ps):
relations = ps.relations
if len(relations.kd_indexes) > 0:
id0 = relations.kd_indexes[:, 0]
id1 = relations.kd_indexes[:, 1]
sum_rad = relations.kd_sum_rad
dif_v = relations.kd_dif_v
dist = relations.kd_dist
mask = relations.kd_mask
dist_cor = np.clip(dist[mask], 1e-6, 1e4)
normal_v = dif_v[mask] / dist_cor[:, np.newaxis]
le = (dist[mask] - sum_rad[mask])[:, np.newaxis]
variable_coll = len(self.f_magnitude) > 1
sf = self.f_magnitude[:, np.newaxis]
len0, len1 = [sf[id1[mask]], sf[id0[mask]]] if variable_coll else [sf, sf]
np.add.at(ps.force_resultant, id0[mask], -normal_v * le * len0)
np.add.at(ps.force_resultant, id1[mask], normal_v * le * len1)
class SvAttractionForce():
def __init__(self, magnitude, decay, max_distance, stop_on_collide=False, use_kdtree=False):
self.magnitude = np.array(magnitude)
self.uniform_magnitude = len(magnitude) < 2
self.decay = np.array(decay[0])
self.use_kdtree = use_kdtree
self.max_distance = max_distance[0]
self.stop_on_collide = stop_on_collide
if self.use_kdtree:
self.needs = ['kd_tree']
self.add = self.add_kdt
else:
self.needs = ['indexes', 'sum_rad', 'mass_product', 'dif_v', 'dist', 'dist_cor', 'normal_v']
if self.stop_on_collide:
self.needs.append('attract_mask')
self.add = self.add_brute_force
def setup(self, ps):
ps.aware = True
for need in self.needs:
ps.relations.needed[need] = True
if self.uniform_magnitude:
self.f_magnitude = self.magnitude
else:
self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0]
def add_brute_force(self, ps):
relations = ps.relations
if self.stop_on_collide:
mask = np.all((relations.dist < self.max_distance, relations.attract_mask), axis=0)
else:
mask = relations.dist < self.max_distance
index_non_inter = ps.relations.indexes[mask]
id0 = index_non_inter[:, 0]
id1 = index_non_inter[:, 1]
dist2 = np.power(relations.dist[mask], self.decay)[:, np.newaxis]
normal = relations.normal_v[mask]
direction = normal / dist2 * relations.mass_product[mask, np.newaxis]
att = self.f_magnitude
len0, len1 = [att, att] if self.uniform_magnitude else [att[id1], att[id0]]
np.add.at(ps.force_resultant, id0, -direction * len0)
np.add.at(ps.force_resultant, id1, direction * len1)
def add_kdt(self, ps):
relations = ps.relations
indexes = relations.kd_tree.query_pairs(r=self.max_distance, output_type='ndarray')
if len(indexes) > 0:
id0 = indexes[:, 0]
id1 = indexes[:, 1]
dif_v = ps.verts[id0, :] - ps.verts[id1, :]
dist = np.linalg.norm(dif_v, axis=1)
if self.stop_on_collide:
collide_mask = dist > ps.mass[id0] * ps.mass[id1]
dist_cor = np.clip(dist[collide_mask], 1e-6, 1e4)
dist2 = np.power(dist[collide_mask], self.decay)[:, np.newaxis]
normal_v = dif_v[collide_mask] / dist_cor[:, np.newaxis]
id0 = indexes[collide_mask, 0]
id1 = indexes[collide_mask, 1]
mass_product = (ps.mass[id0] * ps.mass[id1])
else:
mass_product = ps.mass[id0] * ps.mass[id1]
dist_cor = np.clip(dist, 1e-6, 1e4)
dist2 = np.power(dist, self.decay)[:, np.newaxis]
normal_v = dif_v / dist_cor[:, np.newaxis]
direction = normal_v / dist2 * mass_product[:, np.newaxis]
att = self.f_magnitude
len0, len1 = [att, att] if self.uniform_magnitude else [att[id1], att[id0]]
np.add.at(ps.force_resultant, id0, -direction * len0)
np.add.at(ps.force_resultant, id1, direction * len1)
class SvAlignForce():
def __init__(self, strength, decay, max_distance, use_kdtree=False):
self.strength = np.array(strength)
self.uniform_strength = len(strength) < 2
self.decay = decay[0]
self.f_strength = self.strength
self.max_distance = np.array(max_distance[0])
self.use_kdtree = use_kdtree
if self.use_kdtree:
self.needs = ['kd_tree']
self.add = self.add_kdt
else:
self.needs = ['indexes', 'dif_v', 'dist', 'dist_cor']
self.add = self.add_brute_force
def setup(self, ps):
ps.aware = True
for need in self.needs:
ps.relations.needed[need] = True
if self.uniform_strength:
self.f_strength = self.strength
else:
self.f_strength = numpy_fit_long_repeat([self.strength], ps.v_len)[0]
def add_brute_force(self, ps):
relations = ps.relations
mask = relations.dist_cor < self.max_distance
id0 = relations.indexes[mask, 0]
id1 = relations.indexes[mask, 1]
dist2 = np.power(relations.dist_cor[mask], self.decay)
if self.uniform_strength:
constant = (self.f_strength / (dist2 * ps.v_len))[:, np.newaxis]
np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant)
np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant)
else:
constant0 = (self.f_strength[id0] / (dist2 * ps.v_len))[:, np.newaxis]
constant1 = (self.f_strength[id1] / (dist2 * ps.v_len))[:, np.newaxis]
np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant0)
np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant1)
def add_kdt(self, ps):
relations = ps.relations
indexes = relations.kd_tree.query_pairs(r=self.max_distance, output_type='ndarray')
if len(indexes) > 0:
dif_v = ps.verts[indexes[:, 0], :] - ps.verts[indexes[:, 1], :]
dist = np.linalg.norm(dif_v, axis=1)
dist_cor = np.clip(dist, 1e-6, 1e4)
id0 = indexes[:, 0]
id1 = indexes[:, 1]
dist2 = np.power(dist_cor, self.decay)
if self.uniform_strength:
constant = (self.f_strength / (dist2 * ps.v_len))[:, np.newaxis]
np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant)
np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant)
else:
constant0 = (self.f_strength[id0] / (dist2 * ps.v_len))[:, np.newaxis]
constant1 = (self.f_strength[id1] / (dist2 * ps.v_len))[:, np.newaxis]
np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant0)
np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant1)
class SvFitForce():
def __init__(self, magnitude, min_radius, max_radius, mode, use_kdtree=False):
self.magnitude = np.array(magnitude)
self.uniform_magnitude = len(magnitude) < 2
self.min_radius = np.array(min_radius)
self.max_radius = np.array(max_radius)
if mode == 'Absolute':
self.absolute = True
else:
self.absolute = False
if mode == 'Percent':
self.magnitude /= 100
self.size_changer = True
self.use_kdtree = use_kdtree
if self.use_kdtree:
self.needs = ['kd_tree', 'max_radius', 'kd_collisions']
self.add = self.add_kdt
else:
self.needs = ['indexes', 'sum_rad', 'dif_v', 'dist', 'collide']
self.add = self.add_brute_force
def setup(self, ps):
ps.aware = True
self.all_range = np.arange(ps.v_len, dtype=np.int16)
for need in self.needs:
ps.relations.needed[need] = True
if self.uniform_magnitude:
self.f_magnitude = self.magnitude
else:
self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0]
def add_brute_force(self, ps):
rel = ps.relations
touch = np.unique(rel.index_inter)
free = np.setdiff1d(self.all_range, touch)
u_grow = self.uniform_magnitude
grow_un, grow_tou = [self.f_magnitude, self.f_magnitude] if u_grow else [self.f_magnitude[free], self.f_magnitude[touch]]
if self.absolute:
ps.rads[free] += grow_un
ps.rads[touch] -= grow_tou
else:
ps.rads[free] += grow_un * ps.rads[free]
ps.rads[touch] -= grow_tou * ps.rads[touch]
ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius)
def add_kdt(self, ps):
relations = ps.relations
if len(relations.kd_indexes) > 0:
touch = np.unique(relations.kd_indexes[ps.relations.kd_mask])
free = np.setdiff1d(self.all_range, touch)
u_grow = self.uniform_magnitude
grow_un, grow_tou = [self.f_magnitude, self.f_magnitude] if u_grow else [self.f_magnitude[free], self.f_magnitude[touch]]
if self.absolute:
ps.rads[free] += grow_un
ps.rads[touch] -= grow_tou
else:
ps.rads[free] += grow_un * ps.rads[free]
ps.rads[touch] -= grow_tou * ps.rads[touch]
ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius)
else:
if self.absolute:
ps.rads += self.f_magnitude
else:
ps.rads += self.f_magnitude * ps.rads
ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius)
class SvDragForce():
def __init__(self, drag_force, exponent):
self.magnitude = np.array(drag_force)
self.surf = 0
self.exponent = exponent
self.add = self.add_size_change
def setup(self, ps):
size_change = ps.size_change
if len(self.magnitude) < 2:
self.ap_magnitude = self.magnitude
else:
self.ap_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0]
if len(self.exponent) < 2:
self.ap_exponent = self.exponent
else:
self.ap_exponent = numpy_fit_long_repeat([self.exponent], ps.v_len)[0]
self.surf = np.power(ps.rads, 2)
if not size_change:
self.constant = (self.ap_magnitude * self.surf)[:, np.newaxis]
self.add = self.add_constant_size
else:
self.add = self.add_size_change
self.ap_magnitude = self.magnitude[:, np.newaxis]
def add_size_change(self, ps):
vel_mag = np.linalg.norm(ps.vel, axis=1)
vel_mag_zero = vel_mag == 0
vel_mag[vel_mag_zero] = 1
vel_mag2 = np.power(vel_mag, self.ap_exponent)
vel_norm = ps.vel/vel_mag[:, np.newaxis]
self.surf = np.power(ps.rads, 2)
drag = -vel_norm * self.ap_magnitude * vel_mag2[:, np.newaxis] * self.surf[:, np.newaxis]
ps.force_resultant += drag
def add_constant_size(self, ps):
vel_mag = np.linalg.norm(ps.vel, axis=1)
vel_mag_zero = vel_mag == 0
vel_mag[vel_mag_zero] = 1
vel_mag2 = np.power(vel_mag, self.ap_exponent)
vel_norm = ps.vel/vel_mag[:, np.newaxis]
drag = -vel_norm * vel_mag2[:, np.newaxis] * self.constant
ps.force_resultant += drag
class SvWorldForce():
def __init__(self, force, strength, mass_proportional):
self.strength, self.force = numpy_match_long_repeat([np.array(strength), np.array(force)])
self.force *= self.strength[:, np.newaxis]
self.func = self.apply_mass_proportional
self.mass_proportional = mass_proportional
def setup(self, ps):
size_change = ps.size_change
if len(self.force) > 1:
self.force2 = numpy_fit_long_repeat([self.force], ps.v_len)[0]
if size_change and self.mass_proportional:
self.func = self.apply_mass_proportional
elif self.mass_proportional:
self.force_final = self.force * ps.mass[:, np.newaxis]
self.add = self.apply_dependant
else:
self.force_final = self.force
self.add = self.apply_dependant
def apply_mass_proportional(self, ps):
'''apply constant forces'''
ps.force_resultant += self.force_final * ps.mass[:, np.newaxis]
def apply_dependant(self, ps):
'''apply constant forces'''
ps.force_resultant += self.force_final
class SvFieldForce():
def __init__(self, field, strength, mass_proportional):
self.field = field
self.func = self.apply_mass_proportional
self.mass_proportional = mass_proportional
self.strength = np.array(strength)
if self.mass_proportional:
self.add_func = self.apply_dependant
else:
self.add_func = self.apply_mass_proportional
def setup(self, ps):
pass
def apply_mass_proportional(self, ps):
'''apply constant forces'''
if not np.any(self.strength != 0):
return
xs = ps.verts[:, 0]
ys = ps.verts[:, 1]
zs = ps.verts[:, 2]
rx, ry, rz = self.field.evaluate_grid(xs, ys, zs)
ps.force_resultant[:, 0] += rx * ps.mass * self.strength
ps.force_resultant[:, 1] += ry * ps.mass * self.strength
ps.force_resultant[:, 2] += rz * ps.mass * self.strength
def apply_dependant(self, ps):
'''apply constant forces'''
if not np.any(self.strength != 0):
return
xs = ps.verts[:, 0]
ys = ps.verts[:, 1]
zs = ps.verts[:, 2]
rx, ry, rz = self.field.evaluate_grid(xs, ys, zs)
ps.force_resultant[:, 0] += rx * self.strength
ps.force_resultant[:, 1] += ry * self.strength
ps.force_resultant[:, 2] += rz * self.strength
def add(self, ps):
self.add_func(ps)
def pin_type_get(pin_type):
if isinstance(pin_type[0], str):
axis = []
for ch in pin_type[0]:
if ch == "X":
axis.append(0)
elif ch == "Y":
axis.append(1)
else:
axis.append(2)
return np.array(axis)
pin_types = [[0, 1, 2], [0, 1], [0, 2], [1, 2], [0], [1], [2]]
return np.array(pin_types[pin_type[0]])
class SvPinForce():
def __init__(self, indices, pin_type, pins_goal_pos, use_pins_goal):
self.pins = np.array(indices)
self.unpinned = []
self.use_pins_goal = use_pins_goal
self.pins_goal_pos = np.array(pins_goal_pos)
self.pin_force = True
self.pin_type = pin_type_get(pin_type)
def setup(self, ps):
if self.pins.dtype == np.int32:
if len(self.pins) == len(ps.verts):
self.pins = self.pins == 1
self.unpinned = np.invert(self.pins)
else:
self.unpinned = np.ones(len(ps.verts), dtype=np.bool_)
self.unpinned[self.pins] = False
for axis in self.pin_type:
ps.vel[self.pins, axis] = 0
def add(self, ps):
if self.use_pins_goal:
ps.verts[self.pins, :] = self.pins_goal_pos
ps.params["Pins Reactions"][self.pins] = -ps.force_resultant[self.pins]
for i in range(3):
if not i in self.pin_type:
ps.params["Pins Reactions"][:, i] = 0
for axis in self.pin_type:
ps.vel[self.pins, axis] = 0
ps.force_resultant[self.pins, axis] = 0
ps.params['unpinned'][self.pins] = False
class SvSpringsForce():
def __init__(self, springs, spring_k, fixed_len, clamp, use_fix_len):
self.springs = np.array(springs)
self.spring_k = np.array(spring_k)
self.use_fix_len = use_fix_len
self.fixed_len = fixed_len
self.dist_rest = fixed_len
self.clamp = np.array(clamp)
self.use_clamp = np.any(self.clamp > 0)
def setup(self, ps):
if self.use_fix_len or self.fixed_len[0] > 0:
self.dist_rest = self.fixed_len
self.clamp_distance = self.dist_rest * self.clamp
else:
self.dist_rest = calc_rest_length(ps.verts, self.springs)
self.clamp_distance = self.dist_rest * self.clamp
def add(self, ps):
id0 = self.springs[:, 0]
id1 = self.springs[:, 1]
dif_v = ps.verts[id0, :] - ps.verts[id1, :]
dist = np.linalg.norm(dif_v, axis=1)
if self.use_clamp:
dif_l = np.clip(dist - self.dist_rest, -self.clamp_distance, self.clamp_distance)
else:
dif_l = dist - self.dist_rest
dist[dist == 0] = 1
dif_v /= dist[:, np.newaxis]
force = dif_v * (dif_l * self.spring_k)[:, np.newaxis]
np.subtract.at(ps.force_resultant, id0, force)
np.add.at(ps.force_resultant, id1, force)
class SvPolygonsAngleForce():
def __init__(self, polygons, polygons_k, fixed_angle, use_fix_angle):
self.polygons = np.array(polygons)
self.spring_k = np.array(polygons_k)
self.use_fix_angle = use_fix_angle
self.fixed_angle = fixed_angle
self.rest_angles = np.array(fixed_angle)
self.edges = polygons_to_edges_np([polygons], True, False)[0]
ad_faces = adjacent_faces_number(self.edges, polygons)
e_sorted = [sorted(e) for e in self.edges]
ad_faces = [[] for e in self.edges]
for idp, pol in enumerate(polygons):
for edge in zip(pol, pol[1:] + [pol[0]]):
e_s = sorted(edge)
if e_s in e_sorted:
idx = e_sorted.index(e_s)
ad_faces[idx].append(idp)
self.adjacent_faces = ad_faces
valid_adjecent_faces = []
valid_edges = []
for idx, edg in enumerate(self.adjacent_faces):
if len(edg) > 1:
faces_idx = [edg[0], edg[1]]
valid_adjecent_faces.append(faces_idx)
valid_edges.append(e_sorted[idx])
self.np_adjecent_faces = np.array(valid_adjecent_faces)
self.valid_edges = np.array(valid_edges)
def setup(self, ps):
if not self.use_fix_angle:
pol_v = ps.verts[self.polygons, :]
pols_normal = pols_normals(pol_v, 1)
dot_p = np_dot(pols_normal[self.np_adjecent_faces[:, 0]], pols_normal[self.np_adjecent_faces[:, 1]])
rest_angles = np.arccos(np.clip(dot_p, -1.0, 1.0))
self.rest_angles = rest_angles
def add(self, ps):
pol_v = ps.verts[self.polygons, :]
pols_normal = pols_normals(pol_v, 1)
v1 = pols_normal[self.np_adjecent_faces[:, 0]]
v2 = pols_normal[self.np_adjecent_faces[:, 1]]
act_angles = np.arccos(np.clip(np_dot(v1, v2), -1.0, 1.0))
average_vector = (v1 + v2)/2
force = average_vector * ((self.rest_angles - act_angles) * self.spring_k)[:, np.newaxis]
np.add.at(ps.force_resultant, self.valid_edges[:, 0], force)
np.add.at(ps.force_resultant, self.valid_edges[:, 1], force)
class SvEdgesAngleForce():
def __init__(self, springs, spring_k, fixed_angle, use_fix_angle):
self.springs = np.array(springs)
self.spring_k = np.array(spring_k)
self.use_fix_angle = use_fix_angle
self.fixed_angle = fixed_angle
self.rest_ang = fixed_angle
def setup(self, ps):
rel=[[] for i in range(ps.v_len)]
for (e0, e1) in self.springs:
rel[e0].append(e1)
rel[e1].append(e0)
target_v = []
rest_ang = []
rel_idx = []
if self.use_fix_angle or self.fixed_angle[0] > 0:
self.rest_ang = self.fixed_angle
for v,r, idx in zip(ps.verts, rel, range(ps.v_len)):
if len(r) == 2:
target_v.append(idx)
rel_idx.append(r)
elif len(r)>2:
for idx0, idx1 in zip(r,r[1:]+[r[0]]):
target_v.append(idx)
rel_idx.append([idx0, idx1])
self.target_v = np.array(target_v)
self.rest_ang = self.fixed_angle
self.rel_idx = np.array(rel_idx)
else:
for v,r, idx in zip(ps.verts, rel, range(ps.v_len)):
if len(r) == 2:
vec = ps.verts[r[0]] - v
vec2 = ps.verts[r[1]] - v
rest_ang.append(angle_between([vec], [vec2])[0])
target_v.append(idx)
rel_idx.append(r)
elif len(r)>2:
for idx0, idx1 in zip(r,r[1:]+[r[0]]):
vec = ps.verts[idx0] - v
vec2 = ps.verts[idx1] - v
rest_ang.append(angle_between([vec], [vec2])[0])
target_v.append(idx)
rel_idx.append([idx0, idx1])
self.target_v = np.array(target_v)
self.rest_ang = np.array(rest_ang)
self.rel_idx = np.array(rel_idx)
def add(self, ps):
vs = ps.verts[self.target_v, :]
v1_u = unit_vector(ps.verts[self.rel_idx[:,0]] - vs)
v2_u = unit_vector(ps.verts[self.rel_idx[:,1]] - vs)
act_ang = np.arccos(np.clip(np_dot(v1_u, v2_u), -1.0, 1.0))
average_vector = (v1_u + v2_u)/2
f = average_vector * ((self.rest_ang - act_ang)*self.spring_k)[:, np.newaxis]
np.add.at(ps.force_resultant, self.target_v, f)
class SvTimedForce():
def __init__(self, force, start, end):
self.force = force
self.start = start
self.end = end
if hasattr(force,'size_changer'):
self.size_changer = True
if hasattr(force,'pin_force'):
self.pin_force = True
self.use_pins_goal = force.use_pins_goal
def setup(self, ps):
self.force.setup(ps)
def add(self, ps):
iteration = ps.iteration
if self.end > iteration >= self.start:
self.force.add(ps)
class SvObstaclesBVHForce():
def __init__(self, verts, pols, absorption):
bvh_safe_check(verts, pols)
self.bvh = BVHTree.FromPolygons(verts, pols, all_triangles=False, epsilon=0.0)
self.absorption = 1 - max(min(absorption[0],1),0)
def setup(self, ps):
self.nearest = np.zeros(ps.verts.shape, dtype=np.float64)
self.normals = np.zeros(ps.verts.shape, dtype=np.float64)
self.distance = np.zeros(len(ps.verts), dtype=np.float64)
def find_nearest(self, verts):
v_nearest = self.nearest
v_normals = self.normals
v_dist = self.distance
for v, near, norm, idx in zip(verts, v_nearest, v_normals, range(len(verts))):
nearest, normal, _, distance = self.bvh.find_nearest(v)
near[0] = nearest[0]
near[1] = nearest[1]
near[2] = nearest[2]
norm[0] = normal[0]
norm[1] = normal[1]
norm[2] = normal[2]
v_dist[idx] = distance
def add(self, ps):
self.find_nearest(ps.verts)
mask = self.distance < ps.rads
outer_mask = np_dot(self.nearest[mask] - ps.verts[mask], self.normals[mask]) >= 0
sign = np.ones(outer_mask.shape, dtype=np.int32)
sign[outer_mask] = -1
ps.verts[mask] = self.nearest[mask] + self.normals[mask] * ps.rads[mask, np.newaxis] * sign[:, np.newaxis]
ps.vel[mask] = reflect(ps.vel[mask], self.normals[mask]) * self.absorption
ps.force_resultant[mask] = reflect(ps.force_resultant[mask], self.normals[mask]) * self.absorption
class SvInflateForce():
def __init__(self, pols, inflate):
np_pols = np.array(pols)
p_len = len(pols)
if np_pols.dtype == np.object:
np_len = np.vectorize(len)
pols_sides = np_len(np_pols)
pol_side_max = np.amax(pols_sides)
np_pols = expand_pols_numpy(np_pols, pol_side_max)
p_regular = False
else:
p_regular = True
pols_sides = np.array(p_len)
pol_side_max = len(pols[0])
self.pols = np_pols
self.pols_side_max = pol_side_max
self.pols_sides = pols_sides
self.inflate = inflate
self.p_regular = p_regular
def setup(self, ps):
pass
def add(self, ps):
np_pols = self.pols
pol_side_max = self.pols_side_max
pols_sides = self.pols_sides
inflate = self.inflate
p_regular = self.p_regular
pol_v = ps.verts[np_pols, :]
pols_normal = pols_normals(pol_v, inflate)
if p_regular:
p_area = calc_area(pol_side_max, pol_v, pols_normal)[:, np.newaxis]
for i in range(pol_side_max):
np.add.at(ps.force_resultant, np_pols[:, i], pols_normal * p_area)
else:
p_area = calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal)[:, np.newaxis]
for i in range(pol_side_max):
mask = pols_sides > i
np.add.at(ps.force_resultant, np_pols[mask, i], pols_normal[mask] * p_area[mask])
def limit_speed(np_vel, max_vel):
''''constrain speed magniture'''
vel_mag = np.linalg.norm(np_vel, axis=1)
vel_exceded = vel_mag > max_vel
np_vel[vel_exceded] = np_vel[vel_exceded] / vel_mag[vel_exceded, np.newaxis] * max_vel
class PulgaSystem():
'''Store states'''
verts, rads, vel, density = [[], [], [], []]
v_len = []
params = {}
def __init__(self, init_params):
self.main_setup(init_params)
self.mass = self.density * np.power(self.rads, 3)
self.random_v = []
self.force_resultant = np.zeros((self.v_len, 3), dtype=np.float64)
self.index = np.arange(self.v_len)
self.size_change = False
self.aware = False
self.iteration = 0
self.aware = False
self.pinned = False
self.goal_pins = True
self.relations = lambda: None
self.relations.needed = {}
for force in self.forces:
if hasattr(force, 'pin_force'):
self.pinned = True
self.params['unpinned'] = np.ones(self.v_len, dtype=np.bool_)
if force.use_pins_goal:
self.goal_pins = True
break
if self.pinned:
self.params["Pins Reactions"] = np.zeros(self.verts.shape, dtype=np.float64)
else:
self.params["Pins Reactions"] = np.array([[]])
self.params['unpinned'] = True
for force in self.forces:
if hasattr(force, 'size_changer'):
self.size_change = True
def relations_setup(self):
if 'indexes' in self.relations.needed:
self.relations.indexes = cross_indices3(self.v_len)
if 'cross_matrix' in self.relations.needed:
self.relations.result = np.zeros((self.v_len, self.v_len, 3), dtype=np.float64)
if not self.size_change:
if 'sum_rad' in self.relations.needed:
self.relations.sum_rad = self.rads[self.relations.indexes[:, 0]] + self.rads[self.relations.indexes[:, 1]]
if 'mass_product' in self.relations.needed:
self.relations.mass_product = self.mass[self.relations.indexes[:, 0]] * self.mass[self.relations.indexes[:, 1]]
def relations_update(self):
if 'max_radius' in self.relations.needed:
self.relations.max_radius = np.amax(self.rads)
if 'kd_tree' in self.relations.needed:
self.relations.kd_tree = scipy.spatial.cKDTree(self.verts)
if 'kd_collisions' in self.relations.needed:
indexes = self.relations.kd_tree.query_pairs(r=self.relations.max_radius*2, output_type='ndarray')
self.relations.kd_indexes = indexes
if len(indexes) > 0:
self.relations.kd_dif_v = self.verts[indexes[:, 0], :] - self.verts[indexes[:, 1], :]
self.relations.kd_sum_rad = self.rads[indexes[:, 0]] + self.rads[indexes[:, 1]]
self.relations.kd_dist = np.linalg.norm(self.relations.kd_dif_v, axis=1)
self.relations.kd_mask = self.relations.kd_dist < self.relations.kd_sum_rad
if self.size_change:
if 'sum_rad' in self.relations.needed:
self.relations.sum_rad = self.rads[self.relations.indexes[:, 0]] + self.rads[self.relations.indexes[:, 1]]
if 'mass_product' in self.relations.needed:
self.relations.mass_product = self.mass[self.relations.indexes[:, 0]] * self.mass[self.relations.indexes[:, 1]]
if 'dif_v' in self.relations.needed:
self.relations.dif_v = self.verts[self.relations.indexes[:, 0], :] - self.verts[self.relations.indexes[:, 1], :]
if 'dist' in self.relations.needed:
self.relations.dist = np.linalg.norm(self.relations.dif_v, axis=1)
if 'collide' in self.relations.needed or 'attract_mask' in self.relations.needed:
self.relations.mask = self.relations.sum_rad > self.relations.dist
self.relations.index_inter = self.relations.indexes[self.relations.mask]
if 'collide' in self.relations.needed:
self.relations.some_collisions = len(self.relations.index_inter) > 0
if 'attract_mask' in self.relations.needed:
self.relations.attract_mask = np.invert(self.relations.mask)
if 'dist_cor' in self.relations.needed or 'normal_v' in self.relations.needed:
self.relations.dist_cor = np.clip(self.relations.dist, 1e-6, 1e4)
if 'normal_v' in self.relations.needed:
self.relations.normal_v = self.relations.dif_v / self.relations.dist_cor[:, np.newaxis]
if 'cross_matrix' in self.relations.needed:
self.relations.result[:] = 0
def main_setup(self, local_params):
'''prepare main data'''
params = self.params
initial_pos, _, rads_in, initial_vel, max_vel, density, forces = local_params
self.forces = forces
self.verts = np.array(initial_pos)
self.rads = np.array(rads_in, dtype=np.float64)
self.vel = np.array(initial_vel, dtype=np.float64)
self.v_len = len(self.verts)
params['max_vel'] = np.array(max_vel)
self.rads, self.vel = numpy_fit_long_repeat([self.rads, self.vel], self.v_len)
if len(params['max_vel']) > 1:
params['max_vel'] = numpy_fit_long_repeat([params['max_vel']], self.v_len)[0]
self.density = np.array(density)
if len(density) > 1:
self.density = numpy_fit_long_repeat([self.density], self.v_len)[0]
def hard_update(self, cache):
'''replace verts, rads and vel (in NumPy)'''
size_change = self.size_change
verts, rads, vel, react = cache
if len(verts) == self.v_len:
if self.pinned and self.goal_pins:
unpinned = self.params['unpinned']
self.verts[unpinned] = verts[unpinned]
else:
self.verts = verts
self.vel = vel
if size_change:
self.rads = rads
self.mass = self.density * np.power(self.rads, 3)
def hard_update_list(self, cache):
'''replace verts, rads and velocity'''
size_change = self.size_change
verts, rads, vel, react = cache
if type(verts) == list:
if len(verts) == self.v_len:
if self.pinned and self.goal_pins:
unpinned = self.params['unpinned']
self.verts[unpinned] = np.array(verts)[unpinned]
else:
self.verts = np.array(verts)
if size_change:
self.rads = np.array(rads)
self.mass = self.density * np.power(self.rads, 3)
self.vel = np.array(vel)
else:
self.hard_update(cache)
def apply_forces(self):
'''resultant --> acceleration --> speed --> position'''
acc = self.force_resultant / self.mass[:, np.newaxis]
self.vel += acc
if np.any(self.params['max_vel']) > 0:
limit_speed(self.vel, self.params['max_vel'])
self.verts += self.vel
self.force_resultant[:] = 0
def setup_forces(self):
for force in self.forces:
if hasattr(force, 'size_changer'):
self.size_change = True
break
for force in self.forces:
force.setup(self)
if self.aware:
self.relations_setup()
def iterate(self):
if self.aware:
self.relations_update()
if self.pinned:
self.params['unpinned'][:] = True
for force in self.forces:
force.add(self)
self.iteration += 1
self.apply_forces()
def local_dict(dictionaries, name):
'''get all related to the name'''
return [dictionaries[0][name], dictionaries[1][name], dictionaries[2][name]]
def pulga_system_init(parameters, gates, out_lists, cache):
'''the main function of the engine'''
ps = PulgaSystem(parameters)
iterations = parameters[1]
iterations_max = max(iterations)
iterations_rec = [i-1 for i in iterations]
out_params = [iterations_rec, ps, gates["output"], out_lists]
ps.setup_forces()
if gates["accumulate"] and len(cache) > 0:
ps.hard_update_list(cache)
iterate(iterations_max, out_params)
return ps.verts, ps.rads, ps.vel, ps.params["Pins Reactions"][np.invert(ps.params['unpinned'])]
def iterate(iterations_max, out_params):
''' execute repeatedly the defined force map'''
ps = out_params[1]
for it in range(iterations_max):
ps.iterate()
output_data(it, out_params)
def output_data(it, params):
'''if is pertinent output the data'''
iterations_rec, ps, gate, out_lists = params
record_iteration = it in iterations_rec
if record_iteration:
data_out = prepare_output_data(ps, gate)
record_data(data_out, out_lists)
def prepare_output_data(ps, use_numpy_out):
'''prepare data to output'''
if use_numpy_out:
return [ps.verts, ps.rads, ps.vel, ps.params["Pins Reactions"][np.invert(ps.params['unpinned'])]]
return [ps.verts.tolist(), ps.rads.tolist(), ps.vel.tolist(), ps.params["Pins Reactions"][np.invert(ps.params['unpinned'])].tolist()]
def record_data(data_out, out_lists):
'''save data to main list'''
verts_out, rads_out, velocity_out, reactions_out = out_lists
new_verts, new_rad, new_vel, new_react = data_out
verts_out.append(new_verts)
rads_out.append(new_rad)
velocity_out.append(new_vel)
reactions_out.append(new_react)
Functions
def bvh_safe_check(verts, pols)
-
Expand source code
def bvh_safe_check(verts, pols): len_v = len(verts) for p in pols: for c in p: if c > len_v: raise Exception(f"Index {c} should be less than vertices length ({len_v})")
def calc_area(pol_side_max, pol_v, pols_normal)
-
calculate polygons area (equal sided polygons)
Expand source code
def calc_area(pol_side_max, pol_v, pols_normal): '''calculate polygons area (equal sided polygons)''' prod = np.zeros((pol_side_max, len(pols_normal), 3), dtype=np.float32) for i in range(pol_side_max): prod[i, :, :] = np.cross(pol_v[:, i, :], pol_v[:, (i + 1) % pol_side_max, :]) tot = np.sum(prod, axis=0) return abs(np.sum(tot * pols_normal, axis=1) / 2)
def calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal)
-
calculate polygons area (variable sided polygons)
Expand source code
def calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal): '''calculate polygons area (variable sided polygons)''' prod = np.zeros((pol_side_max, len(pols_sides), 3), dtype=np.float32) for i in range(pol_side_max): mask = pols_sides > i prod[i, mask, :] = np.cross(pol_v[mask, i, :], pol_v[mask, (i + 1) % pol_side_max, :]) tot = np.sum(prod, axis=0) return abs(np.sum(tot * pols_normal, axis=1) / 2)
def calc_rest_length(np_verts, np_springs)
-
calculate edges length
Expand source code
def calc_rest_length(np_verts, np_springs): '''calculate edges length''' pairs_springs = np_verts[np_springs, :] vect_rest = (pairs_springs[:, 0, :] - pairs_springs[:, 1, :]) dist_rest = np.linalg.norm(vect_rest, axis=1) return dist_rest
def cross_indices3(n)
-
create crossed indices
Expand source code
def cross_indices3(n): '''create crossed indices''' nu = np.sum(np.arange(n, dtype=np.int64)) ind = np.zeros((nu, 2), dtype=np.int16) c = 0 for i in range(n-1): l = n-i-1 np_i = np.full(n-i-1, i, dtype=np.int32) np_j = np.arange(i+1, n, dtype=np.int32) np_a = np.stack((np_i, np_j), axis=-1) ind[c:c+l, :] = np_a c += l return ind
def expand_pols_numpy(p, len_max)
-
to fit variable sided polygons in one array (cycling)
Expand source code
def expand_pols_numpy(p, len_max): '''to fit variable sided polygons in one array (cycling)''' np_match_cycle = np.vectorize(match_cylce) np_match_cycle(p, len_max) new_pols = np.zeros((len(p), len_max), dtype=np.int32) for i in range(len(p)): new_pols[i, :] = p[i] return new_pols
def iterate(iterations_max, out_params)
-
execute repeatedly the defined force map
Expand source code
def iterate(iterations_max, out_params): ''' execute repeatedly the defined force map''' ps = out_params[1] for it in range(iterations_max): ps.iterate() output_data(it, out_params)
def limit_speed(np_vel, max_vel)
-
'constrain speed magniture
Expand source code
def limit_speed(np_vel, max_vel): ''''constrain speed magniture''' vel_mag = np.linalg.norm(np_vel, axis=1) vel_exceded = vel_mag > max_vel np_vel[vel_exceded] = np_vel[vel_exceded] / vel_mag[vel_exceded, np.newaxis] * max_vel
def local_dict(dictionaries, name)
-
get all related to the name
Expand source code
def local_dict(dictionaries, name): '''get all related to the name''' return [dictionaries[0][name], dictionaries[1][name], dictionaries[2][name]]
def match_cylce(p, n)
-
append cylcing till have n items
Expand source code
def match_cylce(p, n): '''append cylcing till have n items''' difference = n - len(p) for i in range(difference): p.append(p[i % len(p)])
def np_dot(u, v, axis=1)
-
Expand source code
def np_dot(u, v, axis=1): return np.sum(u * v, axis=axis)
def numpy_fit_long_repeat(p, maxl)
-
match list length by repeating last one or removing end
Expand source code
def numpy_fit_long_repeat(p, maxl): '''match list length by repeating last one or removing end''' q = [] for g in p: difl = maxl - g.shape[0] if difl > 0: rr = np.repeat(g[np.newaxis, -1], difl, axis=0) g = np.concatenate((g, rr)) if difl < 0: g = g[:maxl] q.append(g) return q
def numpy_match_long_repeat(p)
-
match list length by repeating last one
Expand source code
def numpy_match_long_repeat(p): '''match list length by repeating last one''' q = [] maxl = 0 for g in p: maxl = max(maxl, g.shape[0]) for g in p: difl = maxl - g.shape[0] if difl > 0: rr = np.repeat(g[np.newaxis, -1], difl, axis=0) g = np.concatenate((g, rr)) q.append(g) return q
def output_data(it, params)
-
if is pertinent output the data
Expand source code
def output_data(it, params): '''if is pertinent output the data''' iterations_rec, ps, gate, out_lists = params record_iteration = it in iterations_rec if record_iteration: data_out = prepare_output_data(ps, gate) record_data(data_out, out_lists)
def pin_type_get(pin_type)
-
Expand source code
def pin_type_get(pin_type): if isinstance(pin_type[0], str): axis = [] for ch in pin_type[0]: if ch == "X": axis.append(0) elif ch == "Y": axis.append(1) else: axis.append(2) return np.array(axis) pin_types = [[0, 1, 2], [0, 1], [0, 2], [1, 2], [0], [1], [2]] return np.array(pin_types[pin_type[0]])
def pols_normals(pol_v, mag)
-
get actual polygons normal with controlled magnitude
Expand source code
def pols_normals(pol_v, mag): '''get actual polygons normal with controlled magnitude''' v1 = pol_v[:, 1, :] - pol_v[:, 0, :] v2 = pol_v[:, 2, :] - pol_v[:, 0, :] pols_normal = np.cross(v1, v2) pols_normal_d = np.linalg.norm(pols_normal, axis=1) return pols_normal / pols_normal_d[:, np.newaxis] * mag
def prepare_output_data(ps, use_numpy_out)
-
prepare data to output
Expand source code
def prepare_output_data(ps, use_numpy_out): '''prepare data to output''' if use_numpy_out: return [ps.verts, ps.rads, ps.vel, ps.params["Pins Reactions"][np.invert(ps.params['unpinned'])]] return [ps.verts.tolist(), ps.rads.tolist(), ps.vel.tolist(), ps.params["Pins Reactions"][np.invert(ps.params['unpinned'])].tolist()]
def project_on_plane(vects, normal)
-
Expand source code
def project_on_plane(vects, normal): distance = np.sum(vects * normal, axis=1) return vects - normal * distance[:,np.newaxis]
def pulga_system_init(parameters, gates, out_lists, cache)
-
the main function of the engine
Expand source code
def pulga_system_init(parameters, gates, out_lists, cache): '''the main function of the engine''' ps = PulgaSystem(parameters) iterations = parameters[1] iterations_max = max(iterations) iterations_rec = [i-1 for i in iterations] out_params = [iterations_rec, ps, gates["output"], out_lists] ps.setup_forces() if gates["accumulate"] and len(cache) > 0: ps.hard_update_list(cache) iterate(iterations_max, out_params) return ps.verts, ps.rads, ps.vel, ps.params["Pins Reactions"][np.invert(ps.params['unpinned'])]
def record_data(data_out, out_lists)
-
save data to main list
Expand source code
def record_data(data_out, out_lists): '''save data to main list''' verts_out, rads_out, velocity_out, reactions_out = out_lists new_verts, new_rad, new_vel, new_react = data_out verts_out.append(new_verts) rads_out.append(new_rad) velocity_out.append(new_vel) reactions_out.append(new_react)
def reflect(v1, mirror)
-
Expand source code
def reflect(v1, mirror): dot2 = 2 * np.sum(mirror * v1, axis=1) return v1 - (dot2[:, np.newaxis] * mirror)
Classes
class PulgaSystem (init_params)
-
Store states
Expand source code
class PulgaSystem(): '''Store states''' verts, rads, vel, density = [[], [], [], []] v_len = [] params = {} def __init__(self, init_params): self.main_setup(init_params) self.mass = self.density * np.power(self.rads, 3) self.random_v = [] self.force_resultant = np.zeros((self.v_len, 3), dtype=np.float64) self.index = np.arange(self.v_len) self.size_change = False self.aware = False self.iteration = 0 self.aware = False self.pinned = False self.goal_pins = True self.relations = lambda: None self.relations.needed = {} for force in self.forces: if hasattr(force, 'pin_force'): self.pinned = True self.params['unpinned'] = np.ones(self.v_len, dtype=np.bool_) if force.use_pins_goal: self.goal_pins = True break if self.pinned: self.params["Pins Reactions"] = np.zeros(self.verts.shape, dtype=np.float64) else: self.params["Pins Reactions"] = np.array([[]]) self.params['unpinned'] = True for force in self.forces: if hasattr(force, 'size_changer'): self.size_change = True def relations_setup(self): if 'indexes' in self.relations.needed: self.relations.indexes = cross_indices3(self.v_len) if 'cross_matrix' in self.relations.needed: self.relations.result = np.zeros((self.v_len, self.v_len, 3), dtype=np.float64) if not self.size_change: if 'sum_rad' in self.relations.needed: self.relations.sum_rad = self.rads[self.relations.indexes[:, 0]] + self.rads[self.relations.indexes[:, 1]] if 'mass_product' in self.relations.needed: self.relations.mass_product = self.mass[self.relations.indexes[:, 0]] * self.mass[self.relations.indexes[:, 1]] def relations_update(self): if 'max_radius' in self.relations.needed: self.relations.max_radius = np.amax(self.rads) if 'kd_tree' in self.relations.needed: self.relations.kd_tree = scipy.spatial.cKDTree(self.verts) if 'kd_collisions' in self.relations.needed: indexes = self.relations.kd_tree.query_pairs(r=self.relations.max_radius*2, output_type='ndarray') self.relations.kd_indexes = indexes if len(indexes) > 0: self.relations.kd_dif_v = self.verts[indexes[:, 0], :] - self.verts[indexes[:, 1], :] self.relations.kd_sum_rad = self.rads[indexes[:, 0]] + self.rads[indexes[:, 1]] self.relations.kd_dist = np.linalg.norm(self.relations.kd_dif_v, axis=1) self.relations.kd_mask = self.relations.kd_dist < self.relations.kd_sum_rad if self.size_change: if 'sum_rad' in self.relations.needed: self.relations.sum_rad = self.rads[self.relations.indexes[:, 0]] + self.rads[self.relations.indexes[:, 1]] if 'mass_product' in self.relations.needed: self.relations.mass_product = self.mass[self.relations.indexes[:, 0]] * self.mass[self.relations.indexes[:, 1]] if 'dif_v' in self.relations.needed: self.relations.dif_v = self.verts[self.relations.indexes[:, 0], :] - self.verts[self.relations.indexes[:, 1], :] if 'dist' in self.relations.needed: self.relations.dist = np.linalg.norm(self.relations.dif_v, axis=1) if 'collide' in self.relations.needed or 'attract_mask' in self.relations.needed: self.relations.mask = self.relations.sum_rad > self.relations.dist self.relations.index_inter = self.relations.indexes[self.relations.mask] if 'collide' in self.relations.needed: self.relations.some_collisions = len(self.relations.index_inter) > 0 if 'attract_mask' in self.relations.needed: self.relations.attract_mask = np.invert(self.relations.mask) if 'dist_cor' in self.relations.needed or 'normal_v' in self.relations.needed: self.relations.dist_cor = np.clip(self.relations.dist, 1e-6, 1e4) if 'normal_v' in self.relations.needed: self.relations.normal_v = self.relations.dif_v / self.relations.dist_cor[:, np.newaxis] if 'cross_matrix' in self.relations.needed: self.relations.result[:] = 0 def main_setup(self, local_params): '''prepare main data''' params = self.params initial_pos, _, rads_in, initial_vel, max_vel, density, forces = local_params self.forces = forces self.verts = np.array(initial_pos) self.rads = np.array(rads_in, dtype=np.float64) self.vel = np.array(initial_vel, dtype=np.float64) self.v_len = len(self.verts) params['max_vel'] = np.array(max_vel) self.rads, self.vel = numpy_fit_long_repeat([self.rads, self.vel], self.v_len) if len(params['max_vel']) > 1: params['max_vel'] = numpy_fit_long_repeat([params['max_vel']], self.v_len)[0] self.density = np.array(density) if len(density) > 1: self.density = numpy_fit_long_repeat([self.density], self.v_len)[0] def hard_update(self, cache): '''replace verts, rads and vel (in NumPy)''' size_change = self.size_change verts, rads, vel, react = cache if len(verts) == self.v_len: if self.pinned and self.goal_pins: unpinned = self.params['unpinned'] self.verts[unpinned] = verts[unpinned] else: self.verts = verts self.vel = vel if size_change: self.rads = rads self.mass = self.density * np.power(self.rads, 3) def hard_update_list(self, cache): '''replace verts, rads and velocity''' size_change = self.size_change verts, rads, vel, react = cache if type(verts) == list: if len(verts) == self.v_len: if self.pinned and self.goal_pins: unpinned = self.params['unpinned'] self.verts[unpinned] = np.array(verts)[unpinned] else: self.verts = np.array(verts) if size_change: self.rads = np.array(rads) self.mass = self.density * np.power(self.rads, 3) self.vel = np.array(vel) else: self.hard_update(cache) def apply_forces(self): '''resultant --> acceleration --> speed --> position''' acc = self.force_resultant / self.mass[:, np.newaxis] self.vel += acc if np.any(self.params['max_vel']) > 0: limit_speed(self.vel, self.params['max_vel']) self.verts += self.vel self.force_resultant[:] = 0 def setup_forces(self): for force in self.forces: if hasattr(force, 'size_changer'): self.size_change = True break for force in self.forces: force.setup(self) if self.aware: self.relations_setup() def iterate(self): if self.aware: self.relations_update() if self.pinned: self.params['unpinned'][:] = True for force in self.forces: force.add(self) self.iteration += 1 self.apply_forces()
Class variables
var density
var params
var rads
var v_len
var vel
var verts
Methods
def apply_forces(self)
-
resultant –> acceleration –> speed –> position
Expand source code
def apply_forces(self): '''resultant --> acceleration --> speed --> position''' acc = self.force_resultant / self.mass[:, np.newaxis] self.vel += acc if np.any(self.params['max_vel']) > 0: limit_speed(self.vel, self.params['max_vel']) self.verts += self.vel self.force_resultant[:] = 0
def hard_update(self, cache)
-
replace verts, rads and vel (in NumPy)
Expand source code
def hard_update(self, cache): '''replace verts, rads and vel (in NumPy)''' size_change = self.size_change verts, rads, vel, react = cache if len(verts) == self.v_len: if self.pinned and self.goal_pins: unpinned = self.params['unpinned'] self.verts[unpinned] = verts[unpinned] else: self.verts = verts self.vel = vel if size_change: self.rads = rads self.mass = self.density * np.power(self.rads, 3)
def hard_update_list(self, cache)
-
replace verts, rads and velocity
Expand source code
def hard_update_list(self, cache): '''replace verts, rads and velocity''' size_change = self.size_change verts, rads, vel, react = cache if type(verts) == list: if len(verts) == self.v_len: if self.pinned and self.goal_pins: unpinned = self.params['unpinned'] self.verts[unpinned] = np.array(verts)[unpinned] else: self.verts = np.array(verts) if size_change: self.rads = np.array(rads) self.mass = self.density * np.power(self.rads, 3) self.vel = np.array(vel) else: self.hard_update(cache)
def iterate(self)
-
Expand source code
def iterate(self): if self.aware: self.relations_update() if self.pinned: self.params['unpinned'][:] = True for force in self.forces: force.add(self) self.iteration += 1 self.apply_forces()
def main_setup(self, local_params)
-
prepare main data
Expand source code
def main_setup(self, local_params): '''prepare main data''' params = self.params initial_pos, _, rads_in, initial_vel, max_vel, density, forces = local_params self.forces = forces self.verts = np.array(initial_pos) self.rads = np.array(rads_in, dtype=np.float64) self.vel = np.array(initial_vel, dtype=np.float64) self.v_len = len(self.verts) params['max_vel'] = np.array(max_vel) self.rads, self.vel = numpy_fit_long_repeat([self.rads, self.vel], self.v_len) if len(params['max_vel']) > 1: params['max_vel'] = numpy_fit_long_repeat([params['max_vel']], self.v_len)[0] self.density = np.array(density) if len(density) > 1: self.density = numpy_fit_long_repeat([self.density], self.v_len)[0]
def relations_setup(self)
-
Expand source code
def relations_setup(self): if 'indexes' in self.relations.needed: self.relations.indexes = cross_indices3(self.v_len) if 'cross_matrix' in self.relations.needed: self.relations.result = np.zeros((self.v_len, self.v_len, 3), dtype=np.float64) if not self.size_change: if 'sum_rad' in self.relations.needed: self.relations.sum_rad = self.rads[self.relations.indexes[:, 0]] + self.rads[self.relations.indexes[:, 1]] if 'mass_product' in self.relations.needed: self.relations.mass_product = self.mass[self.relations.indexes[:, 0]] * self.mass[self.relations.indexes[:, 1]]
def relations_update(self)
-
Expand source code
def relations_update(self): if 'max_radius' in self.relations.needed: self.relations.max_radius = np.amax(self.rads) if 'kd_tree' in self.relations.needed: self.relations.kd_tree = scipy.spatial.cKDTree(self.verts) if 'kd_collisions' in self.relations.needed: indexes = self.relations.kd_tree.query_pairs(r=self.relations.max_radius*2, output_type='ndarray') self.relations.kd_indexes = indexes if len(indexes) > 0: self.relations.kd_dif_v = self.verts[indexes[:, 0], :] - self.verts[indexes[:, 1], :] self.relations.kd_sum_rad = self.rads[indexes[:, 0]] + self.rads[indexes[:, 1]] self.relations.kd_dist = np.linalg.norm(self.relations.kd_dif_v, axis=1) self.relations.kd_mask = self.relations.kd_dist < self.relations.kd_sum_rad if self.size_change: if 'sum_rad' in self.relations.needed: self.relations.sum_rad = self.rads[self.relations.indexes[:, 0]] + self.rads[self.relations.indexes[:, 1]] if 'mass_product' in self.relations.needed: self.relations.mass_product = self.mass[self.relations.indexes[:, 0]] * self.mass[self.relations.indexes[:, 1]] if 'dif_v' in self.relations.needed: self.relations.dif_v = self.verts[self.relations.indexes[:, 0], :] - self.verts[self.relations.indexes[:, 1], :] if 'dist' in self.relations.needed: self.relations.dist = np.linalg.norm(self.relations.dif_v, axis=1) if 'collide' in self.relations.needed or 'attract_mask' in self.relations.needed: self.relations.mask = self.relations.sum_rad > self.relations.dist self.relations.index_inter = self.relations.indexes[self.relations.mask] if 'collide' in self.relations.needed: self.relations.some_collisions = len(self.relations.index_inter) > 0 if 'attract_mask' in self.relations.needed: self.relations.attract_mask = np.invert(self.relations.mask) if 'dist_cor' in self.relations.needed or 'normal_v' in self.relations.needed: self.relations.dist_cor = np.clip(self.relations.dist, 1e-6, 1e4) if 'normal_v' in self.relations.needed: self.relations.normal_v = self.relations.dif_v / self.relations.dist_cor[:, np.newaxis] if 'cross_matrix' in self.relations.needed: self.relations.result[:] = 0
def setup_forces(self)
-
Expand source code
def setup_forces(self): for force in self.forces: if hasattr(force, 'size_changer'): self.size_change = True break for force in self.forces: force.setup(self) if self.aware: self.relations_setup()
class SvAlignForce (strength, decay, max_distance, use_kdtree=False)
-
Expand source code
class SvAlignForce(): def __init__(self, strength, decay, max_distance, use_kdtree=False): self.strength = np.array(strength) self.uniform_strength = len(strength) < 2 self.decay = decay[0] self.f_strength = self.strength self.max_distance = np.array(max_distance[0]) self.use_kdtree = use_kdtree if self.use_kdtree: self.needs = ['kd_tree'] self.add = self.add_kdt else: self.needs = ['indexes', 'dif_v', 'dist', 'dist_cor'] self.add = self.add_brute_force def setup(self, ps): ps.aware = True for need in self.needs: ps.relations.needed[need] = True if self.uniform_strength: self.f_strength = self.strength else: self.f_strength = numpy_fit_long_repeat([self.strength], ps.v_len)[0] def add_brute_force(self, ps): relations = ps.relations mask = relations.dist_cor < self.max_distance id0 = relations.indexes[mask, 0] id1 = relations.indexes[mask, 1] dist2 = np.power(relations.dist_cor[mask], self.decay) if self.uniform_strength: constant = (self.f_strength / (dist2 * ps.v_len))[:, np.newaxis] np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant) np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant) else: constant0 = (self.f_strength[id0] / (dist2 * ps.v_len))[:, np.newaxis] constant1 = (self.f_strength[id1] / (dist2 * ps.v_len))[:, np.newaxis] np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant0) np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant1) def add_kdt(self, ps): relations = ps.relations indexes = relations.kd_tree.query_pairs(r=self.max_distance, output_type='ndarray') if len(indexes) > 0: dif_v = ps.verts[indexes[:, 0], :] - ps.verts[indexes[:, 1], :] dist = np.linalg.norm(dif_v, axis=1) dist_cor = np.clip(dist, 1e-6, 1e4) id0 = indexes[:, 0] id1 = indexes[:, 1] dist2 = np.power(dist_cor, self.decay) if self.uniform_strength: constant = (self.f_strength / (dist2 * ps.v_len))[:, np.newaxis] np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant) np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant) else: constant0 = (self.f_strength[id0] / (dist2 * ps.v_len))[:, np.newaxis] constant1 = (self.f_strength[id1] / (dist2 * ps.v_len))[:, np.newaxis] np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant0) np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant1)
Methods
def add_brute_force(self, ps)
-
Expand source code
def add_brute_force(self, ps): relations = ps.relations mask = relations.dist_cor < self.max_distance id0 = relations.indexes[mask, 0] id1 = relations.indexes[mask, 1] dist2 = np.power(relations.dist_cor[mask], self.decay) if self.uniform_strength: constant = (self.f_strength / (dist2 * ps.v_len))[:, np.newaxis] np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant) np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant) else: constant0 = (self.f_strength[id0] / (dist2 * ps.v_len))[:, np.newaxis] constant1 = (self.f_strength[id1] / (dist2 * ps.v_len))[:, np.newaxis] np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant0) np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant1)
def add_kdt(self, ps)
-
Expand source code
def add_kdt(self, ps): relations = ps.relations indexes = relations.kd_tree.query_pairs(r=self.max_distance, output_type='ndarray') if len(indexes) > 0: dif_v = ps.verts[indexes[:, 0], :] - ps.verts[indexes[:, 1], :] dist = np.linalg.norm(dif_v, axis=1) dist_cor = np.clip(dist, 1e-6, 1e4) id0 = indexes[:, 0] id1 = indexes[:, 1] dist2 = np.power(dist_cor, self.decay) if self.uniform_strength: constant = (self.f_strength / (dist2 * ps.v_len))[:, np.newaxis] np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant) np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant) else: constant0 = (self.f_strength[id0] / (dist2 * ps.v_len))[:, np.newaxis] constant1 = (self.f_strength[id1] / (dist2 * ps.v_len))[:, np.newaxis] np.add.at(ps.force_resultant, id0, ps.vel[id1, :] * constant0) np.add.at(ps.force_resultant, id1, ps.vel[id0, :] * constant1)
def setup(self, ps)
-
Expand source code
def setup(self, ps): ps.aware = True for need in self.needs: ps.relations.needed[need] = True if self.uniform_strength: self.f_strength = self.strength else: self.f_strength = numpy_fit_long_repeat([self.strength], ps.v_len)[0]
class SvAttractionForce (magnitude, decay, max_distance, stop_on_collide=False, use_kdtree=False)
-
Expand source code
class SvAttractionForce(): def __init__(self, magnitude, decay, max_distance, stop_on_collide=False, use_kdtree=False): self.magnitude = np.array(magnitude) self.uniform_magnitude = len(magnitude) < 2 self.decay = np.array(decay[0]) self.use_kdtree = use_kdtree self.max_distance = max_distance[0] self.stop_on_collide = stop_on_collide if self.use_kdtree: self.needs = ['kd_tree'] self.add = self.add_kdt else: self.needs = ['indexes', 'sum_rad', 'mass_product', 'dif_v', 'dist', 'dist_cor', 'normal_v'] if self.stop_on_collide: self.needs.append('attract_mask') self.add = self.add_brute_force def setup(self, ps): ps.aware = True for need in self.needs: ps.relations.needed[need] = True if self.uniform_magnitude: self.f_magnitude = self.magnitude else: self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0] def add_brute_force(self, ps): relations = ps.relations if self.stop_on_collide: mask = np.all((relations.dist < self.max_distance, relations.attract_mask), axis=0) else: mask = relations.dist < self.max_distance index_non_inter = ps.relations.indexes[mask] id0 = index_non_inter[:, 0] id1 = index_non_inter[:, 1] dist2 = np.power(relations.dist[mask], self.decay)[:, np.newaxis] normal = relations.normal_v[mask] direction = normal / dist2 * relations.mass_product[mask, np.newaxis] att = self.f_magnitude len0, len1 = [att, att] if self.uniform_magnitude else [att[id1], att[id0]] np.add.at(ps.force_resultant, id0, -direction * len0) np.add.at(ps.force_resultant, id1, direction * len1) def add_kdt(self, ps): relations = ps.relations indexes = relations.kd_tree.query_pairs(r=self.max_distance, output_type='ndarray') if len(indexes) > 0: id0 = indexes[:, 0] id1 = indexes[:, 1] dif_v = ps.verts[id0, :] - ps.verts[id1, :] dist = np.linalg.norm(dif_v, axis=1) if self.stop_on_collide: collide_mask = dist > ps.mass[id0] * ps.mass[id1] dist_cor = np.clip(dist[collide_mask], 1e-6, 1e4) dist2 = np.power(dist[collide_mask], self.decay)[:, np.newaxis] normal_v = dif_v[collide_mask] / dist_cor[:, np.newaxis] id0 = indexes[collide_mask, 0] id1 = indexes[collide_mask, 1] mass_product = (ps.mass[id0] * ps.mass[id1]) else: mass_product = ps.mass[id0] * ps.mass[id1] dist_cor = np.clip(dist, 1e-6, 1e4) dist2 = np.power(dist, self.decay)[:, np.newaxis] normal_v = dif_v / dist_cor[:, np.newaxis] direction = normal_v / dist2 * mass_product[:, np.newaxis] att = self.f_magnitude len0, len1 = [att, att] if self.uniform_magnitude else [att[id1], att[id0]] np.add.at(ps.force_resultant, id0, -direction * len0) np.add.at(ps.force_resultant, id1, direction * len1)
Methods
def add_brute_force(self, ps)
-
Expand source code
def add_brute_force(self, ps): relations = ps.relations if self.stop_on_collide: mask = np.all((relations.dist < self.max_distance, relations.attract_mask), axis=0) else: mask = relations.dist < self.max_distance index_non_inter = ps.relations.indexes[mask] id0 = index_non_inter[:, 0] id1 = index_non_inter[:, 1] dist2 = np.power(relations.dist[mask], self.decay)[:, np.newaxis] normal = relations.normal_v[mask] direction = normal / dist2 * relations.mass_product[mask, np.newaxis] att = self.f_magnitude len0, len1 = [att, att] if self.uniform_magnitude else [att[id1], att[id0]] np.add.at(ps.force_resultant, id0, -direction * len0) np.add.at(ps.force_resultant, id1, direction * len1)
def add_kdt(self, ps)
-
Expand source code
def add_kdt(self, ps): relations = ps.relations indexes = relations.kd_tree.query_pairs(r=self.max_distance, output_type='ndarray') if len(indexes) > 0: id0 = indexes[:, 0] id1 = indexes[:, 1] dif_v = ps.verts[id0, :] - ps.verts[id1, :] dist = np.linalg.norm(dif_v, axis=1) if self.stop_on_collide: collide_mask = dist > ps.mass[id0] * ps.mass[id1] dist_cor = np.clip(dist[collide_mask], 1e-6, 1e4) dist2 = np.power(dist[collide_mask], self.decay)[:, np.newaxis] normal_v = dif_v[collide_mask] / dist_cor[:, np.newaxis] id0 = indexes[collide_mask, 0] id1 = indexes[collide_mask, 1] mass_product = (ps.mass[id0] * ps.mass[id1]) else: mass_product = ps.mass[id0] * ps.mass[id1] dist_cor = np.clip(dist, 1e-6, 1e4) dist2 = np.power(dist, self.decay)[:, np.newaxis] normal_v = dif_v / dist_cor[:, np.newaxis] direction = normal_v / dist2 * mass_product[:, np.newaxis] att = self.f_magnitude len0, len1 = [att, att] if self.uniform_magnitude else [att[id1], att[id0]] np.add.at(ps.force_resultant, id0, -direction * len0) np.add.at(ps.force_resultant, id1, direction * len1)
def setup(self, ps)
-
Expand source code
def setup(self, ps): ps.aware = True for need in self.needs: ps.relations.needed[need] = True if self.uniform_magnitude: self.f_magnitude = self.magnitude else: self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0]
class SvAttractorsForce (location, strength, clamp, decay_power)
-
Expand source code
class SvAttractorsForce(): def __init__(self, location, strength, clamp, decay_power): np_location = np.array(location) np_strength = np.array(strength) np_clamp = np.array(clamp) np_decay_pow = np.array(decay_power) params = numpy_match_long_repeat([np_location, np_strength, np_clamp, np_decay_pow]) self.points = params[0] self.strength = params[1] self.use_attractors = np.any(params[1] != 0) self.clamp = params[2] self.decay = params[3] def setup(self, ps): pass def add(self, ps): if not self.use_attractors: return v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :] dist_attract = np.linalg.norm(v_attract, axis=2) mask = dist_attract > self.clamp[:, np.newaxis] mask_true = np.invert(mask) dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2[mask_true], 1e-2, 1e4) v_attract *= self.strength[:, np.newaxis, np.newaxis] v_attract_normal = v_attract[mask_true] / dist_attract_cor[:, np.newaxis] v_attract[mask_true] = -v_attract_normal v_attract[mask, :] = 0 r_attract = np.sum(v_attract, axis=0) ps.force_resultant += r_attract
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): if not self.use_attractors: return v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :] dist_attract = np.linalg.norm(v_attract, axis=2) mask = dist_attract > self.clamp[:, np.newaxis] mask_true = np.invert(mask) dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2[mask_true], 1e-2, 1e4) v_attract *= self.strength[:, np.newaxis, np.newaxis] v_attract_normal = v_attract[mask_true] / dist_attract_cor[:, np.newaxis] v_attract[mask_true] = -v_attract_normal v_attract[mask, :] = 0 r_attract = np.sum(v_attract, axis=0) ps.force_resultant += r_attract
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvAttractorsLineForce (location, direction, strength, clamp, decay_power)
-
Expand source code
class SvAttractorsLineForce(): def __init__(self, location, direction, strength, clamp, decay_power): np_location = np.array(location) np_direction = np.array(direction) np_strength = np.array(strength) np_clamp = np.array(clamp) np_decay_pow = np.array(decay_power) params = numpy_match_long_repeat([np_location, np_direction, np_strength, np_clamp, np_decay_pow]) self.points = params[0] self.direction = params[1]/np.linalg.norm(params[1], axis=1)[:, np.newaxis] self.strength = params[2] self.use_attractors = np.any(params[2] != 0) self.clamp = params[3] self.decay = params[4] def setup(self, ps): pass def add(self, ps): if not self.use_attractors: return v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :] vect_proy = np_dot(v_attract, self.direction[:, np.newaxis, :], axis=2) closest_point = self.points[:, np.newaxis, :] + vect_proy[:, :, np.newaxis] * self.direction[:, np.newaxis, :] dif_v = closest_point - ps.verts[np.newaxis, :, :] dist_attract = np.linalg.norm(dif_v, axis=2) mask = dist_attract > self.clamp[:, np.newaxis] mask_true = np.invert(mask) dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2[mask_true], 1e-2, 1e4) dif_v *= self.strength[:, np.newaxis, np.newaxis] v_attract_normal = dif_v[mask_true] / dist_attract_cor[:, np.newaxis] v_attract[mask_true] = v_attract_normal v_attract[mask, :] = 0 r_attract = np.sum(v_attract, axis=0) ps.force_resultant += r_attract
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): if not self.use_attractors: return v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :] vect_proy = np_dot(v_attract, self.direction[:, np.newaxis, :], axis=2) closest_point = self.points[:, np.newaxis, :] + vect_proy[:, :, np.newaxis] * self.direction[:, np.newaxis, :] dif_v = closest_point - ps.verts[np.newaxis, :, :] dist_attract = np.linalg.norm(dif_v, axis=2) mask = dist_attract > self.clamp[:, np.newaxis] mask_true = np.invert(mask) dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2[mask_true], 1e-2, 1e4) dif_v *= self.strength[:, np.newaxis, np.newaxis] v_attract_normal = dif_v[mask_true] / dist_attract_cor[:, np.newaxis] v_attract[mask_true] = v_attract_normal v_attract[mask, :] = 0 r_attract = np.sum(v_attract, axis=0) ps.force_resultant += r_attract
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvAttractorsPlaneForce (location, normal, strength, clamp, decay_power)
-
Expand source code
class SvAttractorsPlaneForce(): def __init__(self, location, normal, strength, clamp, decay_power): np_location = np.array(location) np_normal = np.array(normal) np_strength = np.array(strength) np_clamp = np.array(clamp) np_decay_pow = np.array(decay_power) params = numpy_match_long_repeat([np_location, np_normal, np_strength, np_clamp, np_decay_pow]) self.points = params[0] self.normal = params[1]/np.linalg.norm(params[1], axis=1)[:, np.newaxis] self.strength = params[2] self.use_attractors = np.any(params[2] != 0) self.clamp = params[3] self.decay = params[4] def setup(self, ps): pass def add(self, ps): if not self.use_attractors: return v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :] dist_attract = np_dot(v_attract, self.normal[:, np.newaxis, :], axis=2) mask = dist_attract > self.clamp[:, np.newaxis] mask_true = np.invert(mask) plane_side = dist_attract < 0 dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2, 1e-2, 1e4) dist_attract_cor[plane_side] *= -1 v_attract_normal = self.normal[:, np.newaxis, :] / dist_attract_cor[:, :, np.newaxis] * self.strength[:, np.newaxis, np.newaxis] v_attract[mask_true] = -v_attract_normal[mask_true] v_attract[mask, :] = 0 r_attract = np.sum(v_attract, axis=0) ps.force_resultant += r_attract
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): if not self.use_attractors: return v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :] dist_attract = np_dot(v_attract, self.normal[:, np.newaxis, :], axis=2) mask = dist_attract > self.clamp[:, np.newaxis] mask_true = np.invert(mask) plane_side = dist_attract < 0 dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2, 1e-2, 1e4) dist_attract_cor[plane_side] *= -1 v_attract_normal = self.normal[:, np.newaxis, :] / dist_attract_cor[:, :, np.newaxis] * self.strength[:, np.newaxis, np.newaxis] v_attract[mask_true] = -v_attract_normal[mask_true] v_attract[mask, :] = 0 r_attract = np.sum(v_attract, axis=0) ps.force_resultant += r_attract
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvBoundingBoxForce (b_box)
-
Expand source code
class SvBoundingBoxForce(): def __init__(self, b_box): np_bbox = np.array(b_box) bbox_max = np.amax(np_bbox, axis=0) bbox_min = np.amin(np_bbox, axis=0) self.bbox_min = bbox_min self.bbox_max = bbox_max def setup(self, ps): pass def add(self, ps): min_mask = ps.verts <= self.bbox_min + ps.rads[:, np.newaxis] max_mask = ps.verts >= self.bbox_max - ps.rads[:, np.newaxis] out_vals = np.any([min_mask, max_mask], axis=0) ps.vel[out_vals] = 0 ps.force_resultant[out_vals] = 0 ps.verts = np.clip(ps.verts, self.bbox_min + ps.rads[:, np.newaxis], self.bbox_max - ps.rads[:, np.newaxis])
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): min_mask = ps.verts <= self.bbox_min + ps.rads[:, np.newaxis] max_mask = ps.verts >= self.bbox_max - ps.rads[:, np.newaxis] out_vals = np.any([min_mask, max_mask], axis=0) ps.vel[out_vals] = 0 ps.force_resultant[out_vals] = 0 ps.verts = np.clip(ps.verts, self.bbox_min + ps.rads[:, np.newaxis], self.bbox_max - ps.rads[:, np.newaxis])
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvBoundingMeshForce (vertices, polygons, volume=False)
-
Expand source code
class SvBoundingMeshForce(): def __init__(self, vertices, polygons, volume=False): bvh_safe_check(vertices, polygons) self.bvh = BVHTree.FromPolygons(vertices, polygons, all_triangles=False, epsilon=0.0) if volume: self.add = self.add_volume else: self.add = self.add_surface def setup(self, ps): self.nearest = np.zeros(ps.verts.shape, dtype=np.float64) self.normals = np.zeros(ps.verts.shape, dtype=np.float64) def find_nearest(self, verts): v_nearest = self.nearest v_normals = self.normals for v, near, norm in zip(verts, v_nearest, v_normals): nearest, normal, _, _ = self.bvh.find_nearest(v) near[0] = nearest[0] near[1] = nearest[1] near[2] = nearest[2] norm[0] = normal[0] norm[1] = normal[1] norm[2] = normal[2] def add_surface(self, ps): self.find_nearest(ps.verts) ps.verts = self.nearest ps.vel = project_on_plane(ps.vel, self.normals) ps.force_resultant = project_on_plane(ps.force_resultant, self.normals) def add_volume(self, ps): self.find_nearest(ps.verts) outer_mask = np_dot(self.nearest - ps.verts, self.normals) <= 0 ps.verts[outer_mask] = self.nearest[outer_mask] ps.vel[outer_mask] = project_on_plane(ps.vel[outer_mask], self.normals[outer_mask]) ps.force_resultant[outer_mask] = project_on_plane(ps.force_resultant[outer_mask], self.normals[outer_mask])
Methods
def add_surface(self, ps)
-
Expand source code
def add_surface(self, ps): self.find_nearest(ps.verts) ps.verts = self.nearest ps.vel = project_on_plane(ps.vel, self.normals) ps.force_resultant = project_on_plane(ps.force_resultant, self.normals)
def add_volume(self, ps)
-
Expand source code
def add_volume(self, ps): self.find_nearest(ps.verts) outer_mask = np_dot(self.nearest - ps.verts, self.normals) <= 0 ps.verts[outer_mask] = self.nearest[outer_mask] ps.vel[outer_mask] = project_on_plane(ps.vel[outer_mask], self.normals[outer_mask]) ps.force_resultant[outer_mask] = project_on_plane(ps.force_resultant[outer_mask], self.normals[outer_mask])
def find_nearest(self, verts)
-
Expand source code
def find_nearest(self, verts): v_nearest = self.nearest v_normals = self.normals for v, near, norm in zip(verts, v_nearest, v_normals): nearest, normal, _, _ = self.bvh.find_nearest(v) near[0] = nearest[0] near[1] = nearest[1] near[2] = nearest[2] norm[0] = normal[0] norm[1] = normal[1] norm[2] = normal[2]
def setup(self, ps)
-
Expand source code
def setup(self, ps): self.nearest = np.zeros(ps.verts.shape, dtype=np.float64) self.normals = np.zeros(ps.verts.shape, dtype=np.float64)
class SvBoundingPlaneSurfaceForce (center, normal)
-
Expand source code
class SvBoundingPlaneSurfaceForce(): def __init__(self, center, normal): self.center = np.array(center[0]) self.normal = np.array(normal[0]) self.normal = self.normal/np.linalg.norm(self.normal) def setup(self, ps): pass def add(self, ps): vs = ps.verts - self.center distance = np.sum(vs * self.normal, axis=1) ps.verts = ps.verts - self.normal[np.newaxis, :] * distance[:, np.newaxis] ps.vel = project_on_plane(ps.vel, self.normal) ps.force_resultant = project_on_plane(ps.force_resultant, self.normal)
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): vs = ps.verts - self.center distance = np.sum(vs * self.normal, axis=1) ps.verts = ps.verts - self.normal[np.newaxis, :] * distance[:, np.newaxis] ps.vel = project_on_plane(ps.vel, self.normal) ps.force_resultant = project_on_plane(ps.force_resultant, self.normal)
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvBoundingSolidForce (solid, volume=False)
-
Expand source code
class SvBoundingSolidForce(): def __init__(self, solid, volume=False): if isinstance(solid, Part.Solid): self.shape = solid.OuterShell elif is_solid_face_surface(solid): self.shape = solid.face else: self.shape = solid if volume: self.add = self.add_volume else: self.add = self.add_surface def find_closest(self,v): vertex = Part.Vertex(Base.Vector(v)) dist = self.shape.distToShape(vertex) if str(dist[2][0][0]) == "b'Face'": normal = self.shape.Faces[dist[2][0][1]].normalAt(*dist[2][0][2]) elif str(dist[2][0][0]) == "b'Edge'": edge = self.shape.Edges[dist[2][0][1]] vector = self.shape.Edges[dist[2][0][1]].valueAt(dist[2][0][2]) face_list = self.shape.ancestorsOfType(edge, Part.Face) normal = [0,0,0] count = 0 for face in face_list: for edge1 in face.Edges: if edge1.isSame(edge): param = face.Surface.parameter(vector) normal_ed = face.normalAt(*param) normal[0] += normal_ed[0] normal[1] += normal_ed[1] normal[2] += normal_ed[2] count+=1 break if count > 0: normal[0] /= count normal[1] /= count normal[2] /= count else: normal = [0, 0, 1] else: vertex = self.shape.Vertexes[dist[2][0][1]] face_list = self.shape.ancestorsOfType(vertex, Part.Face) normal = [0, 0, 0] count = 0 for face in face_list: for vertex1 in face.Vertexes: if vertex1.isSame(vertex): param = face.Surface.parameter(vertex.Point) normal_ed = face.normalAt(*param) normal[0] += normal_ed[0] normal[1] += normal_ed[1] normal[2] += normal_ed[2] count+=1 break if count > 0: normal[0] /= count normal[1] /= count normal[2] /= count else: normal = [0, 0, 1] return dist[1][0][0], normal def find(self, verts): v_nearest = self.nearest v_normals = self.normals for v, near, norm in zip(verts, v_nearest, v_normals): nearest, normal = self.find_closest(v) near[0] = nearest[0] near[1] = nearest[1] near[2] = nearest[2] norm[0] = normal[0] norm[1] = normal[1] norm[2] = normal[2] def find_masked(self, verts, mask): v_nearest = self.nearest v_normals = self.normals for v, near, norm in zip(verts[mask], v_nearest[mask], v_normals[mask]): nearest, normal = self.find_closest(v) near[0] = nearest[0] near[1] = nearest[1] near[2] = nearest[2] norm[0] = normal[0] norm[1] = normal[1] norm[2] = normal[2] def is_outside(self, verts): return np.array([not self.shape.isInside(Base.Vector(v), 1e-6, False) for v in verts], dtype=np.bool_) def setup(self, ps): self.nearest = np.zeros(ps.verts.shape, dtype=np.float64) self.normals = np.zeros(ps.verts.shape, dtype=np.float64) def add_surface(self, ps): self.find(ps.verts) ps.verts = self.nearest ps.vel = project_on_plane(ps.vel, self.normals) ps.force_resultant = project_on_plane(ps.force_resultant, self.normals) def add_volume(self, ps): outside = self.is_outside(ps.verts) self.find_masked(ps.verts, outside) ps.verts[outside] = self.nearest[outside] ps.vel[outside] = project_on_plane(ps.vel[outside], self.normals[outside]) ps.force_resultant[outside] = project_on_plane(ps.force_resultant[outside], self.normals[outside])
Methods
def add_surface(self, ps)
-
Expand source code
def add_surface(self, ps): self.find(ps.verts) ps.verts = self.nearest ps.vel = project_on_plane(ps.vel, self.normals) ps.force_resultant = project_on_plane(ps.force_resultant, self.normals)
def add_volume(self, ps)
-
Expand source code
def add_volume(self, ps): outside = self.is_outside(ps.verts) self.find_masked(ps.verts, outside) ps.verts[outside] = self.nearest[outside] ps.vel[outside] = project_on_plane(ps.vel[outside], self.normals[outside]) ps.force_resultant[outside] = project_on_plane(ps.force_resultant[outside], self.normals[outside])
def find(self, verts)
-
Expand source code
def find(self, verts): v_nearest = self.nearest v_normals = self.normals for v, near, norm in zip(verts, v_nearest, v_normals): nearest, normal = self.find_closest(v) near[0] = nearest[0] near[1] = nearest[1] near[2] = nearest[2] norm[0] = normal[0] norm[1] = normal[1] norm[2] = normal[2]
def find_closest(self, v)
-
Expand source code
def find_closest(self,v): vertex = Part.Vertex(Base.Vector(v)) dist = self.shape.distToShape(vertex) if str(dist[2][0][0]) == "b'Face'": normal = self.shape.Faces[dist[2][0][1]].normalAt(*dist[2][0][2]) elif str(dist[2][0][0]) == "b'Edge'": edge = self.shape.Edges[dist[2][0][1]] vector = self.shape.Edges[dist[2][0][1]].valueAt(dist[2][0][2]) face_list = self.shape.ancestorsOfType(edge, Part.Face) normal = [0,0,0] count = 0 for face in face_list: for edge1 in face.Edges: if edge1.isSame(edge): param = face.Surface.parameter(vector) normal_ed = face.normalAt(*param) normal[0] += normal_ed[0] normal[1] += normal_ed[1] normal[2] += normal_ed[2] count+=1 break if count > 0: normal[0] /= count normal[1] /= count normal[2] /= count else: normal = [0, 0, 1] else: vertex = self.shape.Vertexes[dist[2][0][1]] face_list = self.shape.ancestorsOfType(vertex, Part.Face) normal = [0, 0, 0] count = 0 for face in face_list: for vertex1 in face.Vertexes: if vertex1.isSame(vertex): param = face.Surface.parameter(vertex.Point) normal_ed = face.normalAt(*param) normal[0] += normal_ed[0] normal[1] += normal_ed[1] normal[2] += normal_ed[2] count+=1 break if count > 0: normal[0] /= count normal[1] /= count normal[2] /= count else: normal = [0, 0, 1] return dist[1][0][0], normal
def find_masked(self, verts, mask)
-
Expand source code
def find_masked(self, verts, mask): v_nearest = self.nearest v_normals = self.normals for v, near, norm in zip(verts[mask], v_nearest[mask], v_normals[mask]): nearest, normal = self.find_closest(v) near[0] = nearest[0] near[1] = nearest[1] near[2] = nearest[2] norm[0] = normal[0] norm[1] = normal[1] norm[2] = normal[2]
def is_outside(self, verts)
-
Expand source code
def is_outside(self, verts): return np.array([not self.shape.isInside(Base.Vector(v), 1e-6, False) for v in verts], dtype=np.bool_)
def setup(self, ps)
-
Expand source code
def setup(self, ps): self.nearest = np.zeros(ps.verts.shape, dtype=np.float64) self.normals = np.zeros(ps.verts.shape, dtype=np.float64)
class SvBoundingSphereForce (center, radius)
-
Expand source code
class SvBoundingSphereForce(): def __init__(self, center, radius): self.center = center[0] self.radius = radius[0] def setup(self, ps): pass def add(self, ps): vs = ps.verts - self.center dist = np.linalg.norm(vs, axis=1) mask = dist + ps.rads > self.radius vs_normal = vs[mask]/dist[mask, np.newaxis] ps.verts[mask] = vs_normal*(self.radius - ps.rads[mask])[:,np.newaxis] ps.vel[mask] = project_on_plane(ps.vel[mask], vs_normal) ps.force_resultant[mask] = project_on_plane(ps.force_resultant[mask], vs_normal)
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): vs = ps.verts - self.center dist = np.linalg.norm(vs, axis=1) mask = dist + ps.rads > self.radius vs_normal = vs[mask]/dist[mask, np.newaxis] ps.verts[mask] = vs_normal*(self.radius - ps.rads[mask])[:,np.newaxis] ps.vel[mask] = project_on_plane(ps.vel[mask], vs_normal) ps.force_resultant[mask] = project_on_plane(ps.force_resultant[mask], vs_normal)
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvBoundingSphereSurfaceForce (center, radius)
-
Expand source code
class SvBoundingSphereSurfaceForce(): def __init__(self, center, radius): self.center = center[0] self.radius = radius[0] def setup(self, ps): pass def add(self, ps): vs = ps.verts - self.center dist = np.linalg.norm(vs, axis=1) mask = dist == 0 vs[mask]=ps.verts[mask]+[[1,0,0]] dist[mask] = 1 vs_normal = vs/dist[:, np.newaxis] ps.verts = vs_normal * self.radius ps.vel = project_on_plane(ps.vel, vs_normal) ps.force_resultant = project_on_plane(ps.force_resultant, vs_normal)
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): vs = ps.verts - self.center dist = np.linalg.norm(vs, axis=1) mask = dist == 0 vs[mask]=ps.verts[mask]+[[1,0,0]] dist[mask] = 1 vs_normal = vs/dist[:, np.newaxis] ps.verts = vs_normal * self.radius ps.vel = project_on_plane(ps.vel, vs_normal) ps.force_resultant = project_on_plane(ps.force_resultant, vs_normal)
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvCollisionForce (magnitude, use_kdtree=False)
-
Expand source code
class SvCollisionForce(): def __init__(self, magnitude, use_kdtree=False): self.magnitude = np.array(magnitude) self.uniform_magnitude = len(magnitude) < 2 self.needs = ['dif_v', 'dist', 'dist_cor', 'collide', 'normal_v'] self.use_kdtree = use_kdtree if self.use_kdtree: self.needs = ['kd_tree', 'max_radius', 'kd_collisions'] self.add = self.add_kdt else: self.needs = ['indexes', 'sum_rad', 'dif_v', 'dist', 'dist_cor', 'collide', 'normal_v'] self.add = self.add_brute_force def setup(self, ps): ps.aware = True for need in self.needs: ps.relations.needed[need] = True if self.uniform_magnitude: self.f_magnitude = self.magnitude else: self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0] def add_brute_force(self, ps): id0 = ps.relations.index_inter[:, 0] id1 = ps.relations.index_inter[:, 1] le = ps.relations.dist[ps.relations.mask, np.newaxis] - ps.relations.sum_rad[ps.relations.mask, np.newaxis] no = ps.relations.normal_v[ps.relations.mask] sf = self.f_magnitude[:, np.newaxis] len0, len1 = [sf, sf] if self.uniform_magnitude else [sf[id1], sf[id0]] np.add.at(ps.force_resultant, id0, -no * le * len0) np.add.at(ps.force_resultant, id1, no * le * len1) def add_kdt(self, ps): relations = ps.relations if len(relations.kd_indexes) > 0: id0 = relations.kd_indexes[:, 0] id1 = relations.kd_indexes[:, 1] sum_rad = relations.kd_sum_rad dif_v = relations.kd_dif_v dist = relations.kd_dist mask = relations.kd_mask dist_cor = np.clip(dist[mask], 1e-6, 1e4) normal_v = dif_v[mask] / dist_cor[:, np.newaxis] le = (dist[mask] - sum_rad[mask])[:, np.newaxis] variable_coll = len(self.f_magnitude) > 1 sf = self.f_magnitude[:, np.newaxis] len0, len1 = [sf[id1[mask]], sf[id0[mask]]] if variable_coll else [sf, sf] np.add.at(ps.force_resultant, id0[mask], -normal_v * le * len0) np.add.at(ps.force_resultant, id1[mask], normal_v * le * len1)
Methods
def add_brute_force(self, ps)
-
Expand source code
def add_brute_force(self, ps): id0 = ps.relations.index_inter[:, 0] id1 = ps.relations.index_inter[:, 1] le = ps.relations.dist[ps.relations.mask, np.newaxis] - ps.relations.sum_rad[ps.relations.mask, np.newaxis] no = ps.relations.normal_v[ps.relations.mask] sf = self.f_magnitude[:, np.newaxis] len0, len1 = [sf, sf] if self.uniform_magnitude else [sf[id1], sf[id0]] np.add.at(ps.force_resultant, id0, -no * le * len0) np.add.at(ps.force_resultant, id1, no * le * len1)
def add_kdt(self, ps)
-
Expand source code
def add_kdt(self, ps): relations = ps.relations if len(relations.kd_indexes) > 0: id0 = relations.kd_indexes[:, 0] id1 = relations.kd_indexes[:, 1] sum_rad = relations.kd_sum_rad dif_v = relations.kd_dif_v dist = relations.kd_dist mask = relations.kd_mask dist_cor = np.clip(dist[mask], 1e-6, 1e4) normal_v = dif_v[mask] / dist_cor[:, np.newaxis] le = (dist[mask] - sum_rad[mask])[:, np.newaxis] variable_coll = len(self.f_magnitude) > 1 sf = self.f_magnitude[:, np.newaxis] len0, len1 = [sf[id1[mask]], sf[id0[mask]]] if variable_coll else [sf, sf] np.add.at(ps.force_resultant, id0[mask], -normal_v * le * len0) np.add.at(ps.force_resultant, id1[mask], normal_v * le * len1)
def setup(self, ps)
-
Expand source code
def setup(self, ps): ps.aware = True for need in self.needs: ps.relations.needed[need] = True if self.uniform_magnitude: self.f_magnitude = self.magnitude else: self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0]
class SvDragForce (drag_force, exponent)
-
Expand source code
class SvDragForce(): def __init__(self, drag_force, exponent): self.magnitude = np.array(drag_force) self.surf = 0 self.exponent = exponent self.add = self.add_size_change def setup(self, ps): size_change = ps.size_change if len(self.magnitude) < 2: self.ap_magnitude = self.magnitude else: self.ap_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0] if len(self.exponent) < 2: self.ap_exponent = self.exponent else: self.ap_exponent = numpy_fit_long_repeat([self.exponent], ps.v_len)[0] self.surf = np.power(ps.rads, 2) if not size_change: self.constant = (self.ap_magnitude * self.surf)[:, np.newaxis] self.add = self.add_constant_size else: self.add = self.add_size_change self.ap_magnitude = self.magnitude[:, np.newaxis] def add_size_change(self, ps): vel_mag = np.linalg.norm(ps.vel, axis=1) vel_mag_zero = vel_mag == 0 vel_mag[vel_mag_zero] = 1 vel_mag2 = np.power(vel_mag, self.ap_exponent) vel_norm = ps.vel/vel_mag[:, np.newaxis] self.surf = np.power(ps.rads, 2) drag = -vel_norm * self.ap_magnitude * vel_mag2[:, np.newaxis] * self.surf[:, np.newaxis] ps.force_resultant += drag def add_constant_size(self, ps): vel_mag = np.linalg.norm(ps.vel, axis=1) vel_mag_zero = vel_mag == 0 vel_mag[vel_mag_zero] = 1 vel_mag2 = np.power(vel_mag, self.ap_exponent) vel_norm = ps.vel/vel_mag[:, np.newaxis] drag = -vel_norm * vel_mag2[:, np.newaxis] * self.constant ps.force_resultant += drag
Methods
def add_constant_size(self, ps)
-
Expand source code
def add_constant_size(self, ps): vel_mag = np.linalg.norm(ps.vel, axis=1) vel_mag_zero = vel_mag == 0 vel_mag[vel_mag_zero] = 1 vel_mag2 = np.power(vel_mag, self.ap_exponent) vel_norm = ps.vel/vel_mag[:, np.newaxis] drag = -vel_norm * vel_mag2[:, np.newaxis] * self.constant ps.force_resultant += drag
def add_size_change(self, ps)
-
Expand source code
def add_size_change(self, ps): vel_mag = np.linalg.norm(ps.vel, axis=1) vel_mag_zero = vel_mag == 0 vel_mag[vel_mag_zero] = 1 vel_mag2 = np.power(vel_mag, self.ap_exponent) vel_norm = ps.vel/vel_mag[:, np.newaxis] self.surf = np.power(ps.rads, 2) drag = -vel_norm * self.ap_magnitude * vel_mag2[:, np.newaxis] * self.surf[:, np.newaxis] ps.force_resultant += drag
def setup(self, ps)
-
Expand source code
def setup(self, ps): size_change = ps.size_change if len(self.magnitude) < 2: self.ap_magnitude = self.magnitude else: self.ap_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0] if len(self.exponent) < 2: self.ap_exponent = self.exponent else: self.ap_exponent = numpy_fit_long_repeat([self.exponent], ps.v_len)[0] self.surf = np.power(ps.rads, 2) if not size_change: self.constant = (self.ap_magnitude * self.surf)[:, np.newaxis] self.add = self.add_constant_size else: self.add = self.add_size_change self.ap_magnitude = self.magnitude[:, np.newaxis]
class SvEdgesAngleForce (springs, spring_k, fixed_angle, use_fix_angle)
-
Expand source code
class SvEdgesAngleForce(): def __init__(self, springs, spring_k, fixed_angle, use_fix_angle): self.springs = np.array(springs) self.spring_k = np.array(spring_k) self.use_fix_angle = use_fix_angle self.fixed_angle = fixed_angle self.rest_ang = fixed_angle def setup(self, ps): rel=[[] for i in range(ps.v_len)] for (e0, e1) in self.springs: rel[e0].append(e1) rel[e1].append(e0) target_v = [] rest_ang = [] rel_idx = [] if self.use_fix_angle or self.fixed_angle[0] > 0: self.rest_ang = self.fixed_angle for v,r, idx in zip(ps.verts, rel, range(ps.v_len)): if len(r) == 2: target_v.append(idx) rel_idx.append(r) elif len(r)>2: for idx0, idx1 in zip(r,r[1:]+[r[0]]): target_v.append(idx) rel_idx.append([idx0, idx1]) self.target_v = np.array(target_v) self.rest_ang = self.fixed_angle self.rel_idx = np.array(rel_idx) else: for v,r, idx in zip(ps.verts, rel, range(ps.v_len)): if len(r) == 2: vec = ps.verts[r[0]] - v vec2 = ps.verts[r[1]] - v rest_ang.append(angle_between([vec], [vec2])[0]) target_v.append(idx) rel_idx.append(r) elif len(r)>2: for idx0, idx1 in zip(r,r[1:]+[r[0]]): vec = ps.verts[idx0] - v vec2 = ps.verts[idx1] - v rest_ang.append(angle_between([vec], [vec2])[0]) target_v.append(idx) rel_idx.append([idx0, idx1]) self.target_v = np.array(target_v) self.rest_ang = np.array(rest_ang) self.rel_idx = np.array(rel_idx) def add(self, ps): vs = ps.verts[self.target_v, :] v1_u = unit_vector(ps.verts[self.rel_idx[:,0]] - vs) v2_u = unit_vector(ps.verts[self.rel_idx[:,1]] - vs) act_ang = np.arccos(np.clip(np_dot(v1_u, v2_u), -1.0, 1.0)) average_vector = (v1_u + v2_u)/2 f = average_vector * ((self.rest_ang - act_ang)*self.spring_k)[:, np.newaxis] np.add.at(ps.force_resultant, self.target_v, f)
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): vs = ps.verts[self.target_v, :] v1_u = unit_vector(ps.verts[self.rel_idx[:,0]] - vs) v2_u = unit_vector(ps.verts[self.rel_idx[:,1]] - vs) act_ang = np.arccos(np.clip(np_dot(v1_u, v2_u), -1.0, 1.0)) average_vector = (v1_u + v2_u)/2 f = average_vector * ((self.rest_ang - act_ang)*self.spring_k)[:, np.newaxis] np.add.at(ps.force_resultant, self.target_v, f)
def setup(self, ps)
-
Expand source code
def setup(self, ps): rel=[[] for i in range(ps.v_len)] for (e0, e1) in self.springs: rel[e0].append(e1) rel[e1].append(e0) target_v = [] rest_ang = [] rel_idx = [] if self.use_fix_angle or self.fixed_angle[0] > 0: self.rest_ang = self.fixed_angle for v,r, idx in zip(ps.verts, rel, range(ps.v_len)): if len(r) == 2: target_v.append(idx) rel_idx.append(r) elif len(r)>2: for idx0, idx1 in zip(r,r[1:]+[r[0]]): target_v.append(idx) rel_idx.append([idx0, idx1]) self.target_v = np.array(target_v) self.rest_ang = self.fixed_angle self.rel_idx = np.array(rel_idx) else: for v,r, idx in zip(ps.verts, rel, range(ps.v_len)): if len(r) == 2: vec = ps.verts[r[0]] - v vec2 = ps.verts[r[1]] - v rest_ang.append(angle_between([vec], [vec2])[0]) target_v.append(idx) rel_idx.append(r) elif len(r)>2: for idx0, idx1 in zip(r,r[1:]+[r[0]]): vec = ps.verts[idx0] - v vec2 = ps.verts[idx1] - v rest_ang.append(angle_between([vec], [vec2])[0]) target_v.append(idx) rel_idx.append([idx0, idx1]) self.target_v = np.array(target_v) self.rest_ang = np.array(rest_ang) self.rel_idx = np.array(rel_idx)
class SvFieldForce (field, strength, mass_proportional)
-
Expand source code
class SvFieldForce(): def __init__(self, field, strength, mass_proportional): self.field = field self.func = self.apply_mass_proportional self.mass_proportional = mass_proportional self.strength = np.array(strength) if self.mass_proportional: self.add_func = self.apply_dependant else: self.add_func = self.apply_mass_proportional def setup(self, ps): pass def apply_mass_proportional(self, ps): '''apply constant forces''' if not np.any(self.strength != 0): return xs = ps.verts[:, 0] ys = ps.verts[:, 1] zs = ps.verts[:, 2] rx, ry, rz = self.field.evaluate_grid(xs, ys, zs) ps.force_resultant[:, 0] += rx * ps.mass * self.strength ps.force_resultant[:, 1] += ry * ps.mass * self.strength ps.force_resultant[:, 2] += rz * ps.mass * self.strength def apply_dependant(self, ps): '''apply constant forces''' if not np.any(self.strength != 0): return xs = ps.verts[:, 0] ys = ps.verts[:, 1] zs = ps.verts[:, 2] rx, ry, rz = self.field.evaluate_grid(xs, ys, zs) ps.force_resultant[:, 0] += rx * self.strength ps.force_resultant[:, 1] += ry * self.strength ps.force_resultant[:, 2] += rz * self.strength def add(self, ps): self.add_func(ps)
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): self.add_func(ps)
def apply_dependant(self, ps)
-
apply constant forces
Expand source code
def apply_dependant(self, ps): '''apply constant forces''' if not np.any(self.strength != 0): return xs = ps.verts[:, 0] ys = ps.verts[:, 1] zs = ps.verts[:, 2] rx, ry, rz = self.field.evaluate_grid(xs, ys, zs) ps.force_resultant[:, 0] += rx * self.strength ps.force_resultant[:, 1] += ry * self.strength ps.force_resultant[:, 2] += rz * self.strength
def apply_mass_proportional(self, ps)
-
apply constant forces
Expand source code
def apply_mass_proportional(self, ps): '''apply constant forces''' if not np.any(self.strength != 0): return xs = ps.verts[:, 0] ys = ps.verts[:, 1] zs = ps.verts[:, 2] rx, ry, rz = self.field.evaluate_grid(xs, ys, zs) ps.force_resultant[:, 0] += rx * ps.mass * self.strength ps.force_resultant[:, 1] += ry * ps.mass * self.strength ps.force_resultant[:, 2] += rz * ps.mass * self.strength
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvFitForce (magnitude, min_radius, max_radius, mode, use_kdtree=False)
-
Expand source code
class SvFitForce(): def __init__(self, magnitude, min_radius, max_radius, mode, use_kdtree=False): self.magnitude = np.array(magnitude) self.uniform_magnitude = len(magnitude) < 2 self.min_radius = np.array(min_radius) self.max_radius = np.array(max_radius) if mode == 'Absolute': self.absolute = True else: self.absolute = False if mode == 'Percent': self.magnitude /= 100 self.size_changer = True self.use_kdtree = use_kdtree if self.use_kdtree: self.needs = ['kd_tree', 'max_radius', 'kd_collisions'] self.add = self.add_kdt else: self.needs = ['indexes', 'sum_rad', 'dif_v', 'dist', 'collide'] self.add = self.add_brute_force def setup(self, ps): ps.aware = True self.all_range = np.arange(ps.v_len, dtype=np.int16) for need in self.needs: ps.relations.needed[need] = True if self.uniform_magnitude: self.f_magnitude = self.magnitude else: self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0] def add_brute_force(self, ps): rel = ps.relations touch = np.unique(rel.index_inter) free = np.setdiff1d(self.all_range, touch) u_grow = self.uniform_magnitude grow_un, grow_tou = [self.f_magnitude, self.f_magnitude] if u_grow else [self.f_magnitude[free], self.f_magnitude[touch]] if self.absolute: ps.rads[free] += grow_un ps.rads[touch] -= grow_tou else: ps.rads[free] += grow_un * ps.rads[free] ps.rads[touch] -= grow_tou * ps.rads[touch] ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius) def add_kdt(self, ps): relations = ps.relations if len(relations.kd_indexes) > 0: touch = np.unique(relations.kd_indexes[ps.relations.kd_mask]) free = np.setdiff1d(self.all_range, touch) u_grow = self.uniform_magnitude grow_un, grow_tou = [self.f_magnitude, self.f_magnitude] if u_grow else [self.f_magnitude[free], self.f_magnitude[touch]] if self.absolute: ps.rads[free] += grow_un ps.rads[touch] -= grow_tou else: ps.rads[free] += grow_un * ps.rads[free] ps.rads[touch] -= grow_tou * ps.rads[touch] ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius) else: if self.absolute: ps.rads += self.f_magnitude else: ps.rads += self.f_magnitude * ps.rads ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius)
Methods
def add_brute_force(self, ps)
-
Expand source code
def add_brute_force(self, ps): rel = ps.relations touch = np.unique(rel.index_inter) free = np.setdiff1d(self.all_range, touch) u_grow = self.uniform_magnitude grow_un, grow_tou = [self.f_magnitude, self.f_magnitude] if u_grow else [self.f_magnitude[free], self.f_magnitude[touch]] if self.absolute: ps.rads[free] += grow_un ps.rads[touch] -= grow_tou else: ps.rads[free] += grow_un * ps.rads[free] ps.rads[touch] -= grow_tou * ps.rads[touch] ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius)
def add_kdt(self, ps)
-
Expand source code
def add_kdt(self, ps): relations = ps.relations if len(relations.kd_indexes) > 0: touch = np.unique(relations.kd_indexes[ps.relations.kd_mask]) free = np.setdiff1d(self.all_range, touch) u_grow = self.uniform_magnitude grow_un, grow_tou = [self.f_magnitude, self.f_magnitude] if u_grow else [self.f_magnitude[free], self.f_magnitude[touch]] if self.absolute: ps.rads[free] += grow_un ps.rads[touch] -= grow_tou else: ps.rads[free] += grow_un * ps.rads[free] ps.rads[touch] -= grow_tou * ps.rads[touch] ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius) else: if self.absolute: ps.rads += self.f_magnitude else: ps.rads += self.f_magnitude * ps.rads ps.rads = np.clip(ps.rads, self.min_radius, self.max_radius)
def setup(self, ps)
-
Expand source code
def setup(self, ps): ps.aware = True self.all_range = np.arange(ps.v_len, dtype=np.int16) for need in self.needs: ps.relations.needed[need] = True if self.uniform_magnitude: self.f_magnitude = self.magnitude else: self.f_magnitude = numpy_fit_long_repeat([self.magnitude], ps.v_len)[0]
class SvInflateForce (pols, inflate)
-
Expand source code
class SvInflateForce(): def __init__(self, pols, inflate): np_pols = np.array(pols) p_len = len(pols) if np_pols.dtype == np.object: np_len = np.vectorize(len) pols_sides = np_len(np_pols) pol_side_max = np.amax(pols_sides) np_pols = expand_pols_numpy(np_pols, pol_side_max) p_regular = False else: p_regular = True pols_sides = np.array(p_len) pol_side_max = len(pols[0]) self.pols = np_pols self.pols_side_max = pol_side_max self.pols_sides = pols_sides self.inflate = inflate self.p_regular = p_regular def setup(self, ps): pass def add(self, ps): np_pols = self.pols pol_side_max = self.pols_side_max pols_sides = self.pols_sides inflate = self.inflate p_regular = self.p_regular pol_v = ps.verts[np_pols, :] pols_normal = pols_normals(pol_v, inflate) if p_regular: p_area = calc_area(pol_side_max, pol_v, pols_normal)[:, np.newaxis] for i in range(pol_side_max): np.add.at(ps.force_resultant, np_pols[:, i], pols_normal * p_area) else: p_area = calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal)[:, np.newaxis] for i in range(pol_side_max): mask = pols_sides > i np.add.at(ps.force_resultant, np_pols[mask, i], pols_normal[mask] * p_area[mask])
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): np_pols = self.pols pol_side_max = self.pols_side_max pols_sides = self.pols_sides inflate = self.inflate p_regular = self.p_regular pol_v = ps.verts[np_pols, :] pols_normal = pols_normals(pol_v, inflate) if p_regular: p_area = calc_area(pol_side_max, pol_v, pols_normal)[:, np.newaxis] for i in range(pol_side_max): np.add.at(ps.force_resultant, np_pols[:, i], pols_normal * p_area) else: p_area = calc_area_var_sides(pol_side_max, pols_sides, pol_v, pols_normal)[:, np.newaxis] for i in range(pol_side_max): mask = pols_sides > i np.add.at(ps.force_resultant, np_pols[mask, i], pols_normal[mask] * p_area[mask])
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvObstaclesBVHForce (verts, pols, absorption)
-
Expand source code
class SvObstaclesBVHForce(): def __init__(self, verts, pols, absorption): bvh_safe_check(verts, pols) self.bvh = BVHTree.FromPolygons(verts, pols, all_triangles=False, epsilon=0.0) self.absorption = 1 - max(min(absorption[0],1),0) def setup(self, ps): self.nearest = np.zeros(ps.verts.shape, dtype=np.float64) self.normals = np.zeros(ps.verts.shape, dtype=np.float64) self.distance = np.zeros(len(ps.verts), dtype=np.float64) def find_nearest(self, verts): v_nearest = self.nearest v_normals = self.normals v_dist = self.distance for v, near, norm, idx in zip(verts, v_nearest, v_normals, range(len(verts))): nearest, normal, _, distance = self.bvh.find_nearest(v) near[0] = nearest[0] near[1] = nearest[1] near[2] = nearest[2] norm[0] = normal[0] norm[1] = normal[1] norm[2] = normal[2] v_dist[idx] = distance def add(self, ps): self.find_nearest(ps.verts) mask = self.distance < ps.rads outer_mask = np_dot(self.nearest[mask] - ps.verts[mask], self.normals[mask]) >= 0 sign = np.ones(outer_mask.shape, dtype=np.int32) sign[outer_mask] = -1 ps.verts[mask] = self.nearest[mask] + self.normals[mask] * ps.rads[mask, np.newaxis] * sign[:, np.newaxis] ps.vel[mask] = reflect(ps.vel[mask], self.normals[mask]) * self.absorption ps.force_resultant[mask] = reflect(ps.force_resultant[mask], self.normals[mask]) * self.absorption
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): self.find_nearest(ps.verts) mask = self.distance < ps.rads outer_mask = np_dot(self.nearest[mask] - ps.verts[mask], self.normals[mask]) >= 0 sign = np.ones(outer_mask.shape, dtype=np.int32) sign[outer_mask] = -1 ps.verts[mask] = self.nearest[mask] + self.normals[mask] * ps.rads[mask, np.newaxis] * sign[:, np.newaxis] ps.vel[mask] = reflect(ps.vel[mask], self.normals[mask]) * self.absorption ps.force_resultant[mask] = reflect(ps.force_resultant[mask], self.normals[mask]) * self.absorption
def find_nearest(self, verts)
-
Expand source code
def find_nearest(self, verts): v_nearest = self.nearest v_normals = self.normals v_dist = self.distance for v, near, norm, idx in zip(verts, v_nearest, v_normals, range(len(verts))): nearest, normal, _, distance = self.bvh.find_nearest(v) near[0] = nearest[0] near[1] = nearest[1] near[2] = nearest[2] norm[0] = normal[0] norm[1] = normal[1] norm[2] = normal[2] v_dist[idx] = distance
def setup(self, ps)
-
Expand source code
def setup(self, ps): self.nearest = np.zeros(ps.verts.shape, dtype=np.float64) self.normals = np.zeros(ps.verts.shape, dtype=np.float64) self.distance = np.zeros(len(ps.verts), dtype=np.float64)
class SvPinForce (indices, pin_type, pins_goal_pos, use_pins_goal)
-
Expand source code
class SvPinForce(): def __init__(self, indices, pin_type, pins_goal_pos, use_pins_goal): self.pins = np.array(indices) self.unpinned = [] self.use_pins_goal = use_pins_goal self.pins_goal_pos = np.array(pins_goal_pos) self.pin_force = True self.pin_type = pin_type_get(pin_type) def setup(self, ps): if self.pins.dtype == np.int32: if len(self.pins) == len(ps.verts): self.pins = self.pins == 1 self.unpinned = np.invert(self.pins) else: self.unpinned = np.ones(len(ps.verts), dtype=np.bool_) self.unpinned[self.pins] = False for axis in self.pin_type: ps.vel[self.pins, axis] = 0 def add(self, ps): if self.use_pins_goal: ps.verts[self.pins, :] = self.pins_goal_pos ps.params["Pins Reactions"][self.pins] = -ps.force_resultant[self.pins] for i in range(3): if not i in self.pin_type: ps.params["Pins Reactions"][:, i] = 0 for axis in self.pin_type: ps.vel[self.pins, axis] = 0 ps.force_resultant[self.pins, axis] = 0 ps.params['unpinned'][self.pins] = False
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): if self.use_pins_goal: ps.verts[self.pins, :] = self.pins_goal_pos ps.params["Pins Reactions"][self.pins] = -ps.force_resultant[self.pins] for i in range(3): if not i in self.pin_type: ps.params["Pins Reactions"][:, i] = 0 for axis in self.pin_type: ps.vel[self.pins, axis] = 0 ps.force_resultant[self.pins, axis] = 0 ps.params['unpinned'][self.pins] = False
def setup(self, ps)
-
Expand source code
def setup(self, ps): if self.pins.dtype == np.int32: if len(self.pins) == len(ps.verts): self.pins = self.pins == 1 self.unpinned = np.invert(self.pins) else: self.unpinned = np.ones(len(ps.verts), dtype=np.bool_) self.unpinned[self.pins] = False for axis in self.pin_type: ps.vel[self.pins, axis] = 0
class SvPolygonsAngleForce (polygons, polygons_k, fixed_angle, use_fix_angle)
-
Expand source code
class SvPolygonsAngleForce(): def __init__(self, polygons, polygons_k, fixed_angle, use_fix_angle): self.polygons = np.array(polygons) self.spring_k = np.array(polygons_k) self.use_fix_angle = use_fix_angle self.fixed_angle = fixed_angle self.rest_angles = np.array(fixed_angle) self.edges = polygons_to_edges_np([polygons], True, False)[0] ad_faces = adjacent_faces_number(self.edges, polygons) e_sorted = [sorted(e) for e in self.edges] ad_faces = [[] for e in self.edges] for idp, pol in enumerate(polygons): for edge in zip(pol, pol[1:] + [pol[0]]): e_s = sorted(edge) if e_s in e_sorted: idx = e_sorted.index(e_s) ad_faces[idx].append(idp) self.adjacent_faces = ad_faces valid_adjecent_faces = [] valid_edges = [] for idx, edg in enumerate(self.adjacent_faces): if len(edg) > 1: faces_idx = [edg[0], edg[1]] valid_adjecent_faces.append(faces_idx) valid_edges.append(e_sorted[idx]) self.np_adjecent_faces = np.array(valid_adjecent_faces) self.valid_edges = np.array(valid_edges) def setup(self, ps): if not self.use_fix_angle: pol_v = ps.verts[self.polygons, :] pols_normal = pols_normals(pol_v, 1) dot_p = np_dot(pols_normal[self.np_adjecent_faces[:, 0]], pols_normal[self.np_adjecent_faces[:, 1]]) rest_angles = np.arccos(np.clip(dot_p, -1.0, 1.0)) self.rest_angles = rest_angles def add(self, ps): pol_v = ps.verts[self.polygons, :] pols_normal = pols_normals(pol_v, 1) v1 = pols_normal[self.np_adjecent_faces[:, 0]] v2 = pols_normal[self.np_adjecent_faces[:, 1]] act_angles = np.arccos(np.clip(np_dot(v1, v2), -1.0, 1.0)) average_vector = (v1 + v2)/2 force = average_vector * ((self.rest_angles - act_angles) * self.spring_k)[:, np.newaxis] np.add.at(ps.force_resultant, self.valid_edges[:, 0], force) np.add.at(ps.force_resultant, self.valid_edges[:, 1], force)
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): pol_v = ps.verts[self.polygons, :] pols_normal = pols_normals(pol_v, 1) v1 = pols_normal[self.np_adjecent_faces[:, 0]] v2 = pols_normal[self.np_adjecent_faces[:, 1]] act_angles = np.arccos(np.clip(np_dot(v1, v2), -1.0, 1.0)) average_vector = (v1 + v2)/2 force = average_vector * ((self.rest_angles - act_angles) * self.spring_k)[:, np.newaxis] np.add.at(ps.force_resultant, self.valid_edges[:, 0], force) np.add.at(ps.force_resultant, self.valid_edges[:, 1], force)
def setup(self, ps)
-
Expand source code
def setup(self, ps): if not self.use_fix_angle: pol_v = ps.verts[self.polygons, :] pols_normal = pols_normals(pol_v, 1) dot_p = np_dot(pols_normal[self.np_adjecent_faces[:, 0]], pols_normal[self.np_adjecent_faces[:, 1]]) rest_angles = np.arccos(np.clip(dot_p, -1.0, 1.0)) self.rest_angles = rest_angles
class SvRandomForce (random_force, random_variation, random_seed)
-
Expand source code
class SvRandomForce(): def __init__(self, random_force, random_variation, random_seed): # random_seed, random_force, random_variation = local_params np_random_force = np.array(random_force) np_random_variation = np.array(random_variation) random_force = np_random_force[:, np.newaxis] random_variation = np_random_variation[:, np.newaxis] random_variate = any(random_variation > 1e-6) self.seed = random_seed[0] self.random_force = random_force self.random_variate = random_variate self.random_variation = random_variation def setup(self, ps): np.random.seed(int(self.seed)) self.random_v = self.random_force * np.random.random((ps.v_len, 3)) - self.random_force / 2 def add(self, ps): if self.random_variate: random_var = 2 * self.random_force * np.random.random((ps.v_len, 3)) - self.random_force self.random_v = self.random_v * (1 - self.random_variation) + random_var * self.random_variation ps.force_resultant += self.random_v
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): if self.random_variate: random_var = 2 * self.random_force * np.random.random((ps.v_len, 3)) - self.random_force self.random_v = self.random_v * (1 - self.random_variation) + random_var * self.random_variation ps.force_resultant += self.random_v
def setup(self, ps)
-
Expand source code
def setup(self, ps): np.random.seed(int(self.seed)) self.random_v = self.random_force * np.random.random((ps.v_len, 3)) - self.random_force / 2
class SvSpringsForce (springs, spring_k, fixed_len, clamp, use_fix_len)
-
Expand source code
class SvSpringsForce(): def __init__(self, springs, spring_k, fixed_len, clamp, use_fix_len): self.springs = np.array(springs) self.spring_k = np.array(spring_k) self.use_fix_len = use_fix_len self.fixed_len = fixed_len self.dist_rest = fixed_len self.clamp = np.array(clamp) self.use_clamp = np.any(self.clamp > 0) def setup(self, ps): if self.use_fix_len or self.fixed_len[0] > 0: self.dist_rest = self.fixed_len self.clamp_distance = self.dist_rest * self.clamp else: self.dist_rest = calc_rest_length(ps.verts, self.springs) self.clamp_distance = self.dist_rest * self.clamp def add(self, ps): id0 = self.springs[:, 0] id1 = self.springs[:, 1] dif_v = ps.verts[id0, :] - ps.verts[id1, :] dist = np.linalg.norm(dif_v, axis=1) if self.use_clamp: dif_l = np.clip(dist - self.dist_rest, -self.clamp_distance, self.clamp_distance) else: dif_l = dist - self.dist_rest dist[dist == 0] = 1 dif_v /= dist[:, np.newaxis] force = dif_v * (dif_l * self.spring_k)[:, np.newaxis] np.subtract.at(ps.force_resultant, id0, force) np.add.at(ps.force_resultant, id1, force)
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): id0 = self.springs[:, 0] id1 = self.springs[:, 1] dif_v = ps.verts[id0, :] - ps.verts[id1, :] dist = np.linalg.norm(dif_v, axis=1) if self.use_clamp: dif_l = np.clip(dist - self.dist_rest, -self.clamp_distance, self.clamp_distance) else: dif_l = dist - self.dist_rest dist[dist == 0] = 1 dif_v /= dist[:, np.newaxis] force = dif_v * (dif_l * self.spring_k)[:, np.newaxis] np.subtract.at(ps.force_resultant, id0, force) np.add.at(ps.force_resultant, id1, force)
def setup(self, ps)
-
Expand source code
def setup(self, ps): if self.use_fix_len or self.fixed_len[0] > 0: self.dist_rest = self.fixed_len self.clamp_distance = self.dist_rest * self.clamp else: self.dist_rest = calc_rest_length(ps.verts, self.springs) self.clamp_distance = self.dist_rest * self.clamp
class SvTimedForce (force, start, end)
-
Expand source code
class SvTimedForce(): def __init__(self, force, start, end): self.force = force self.start = start self.end = end if hasattr(force,'size_changer'): self.size_changer = True if hasattr(force,'pin_force'): self.pin_force = True self.use_pins_goal = force.use_pins_goal def setup(self, ps): self.force.setup(ps) def add(self, ps): iteration = ps.iteration if self.end > iteration >= self.start: self.force.add(ps)
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): iteration = ps.iteration if self.end > iteration >= self.start: self.force.add(ps)
def setup(self, ps)
-
Expand source code
def setup(self, ps): self.force.setup(ps)
class SvVortexForce (location, direction, rot_strength, inflow_strength, forward_strength, clamp, decay_power)
-
Expand source code
class SvVortexForce(): def __init__(self, location, direction, rot_strength, inflow_strength, forward_strength, clamp, decay_power): np_location = np.array(location) np_direction = np.array(direction) np_rot_strength = np.array(rot_strength) np_inflow_strength = np.array(inflow_strength) np_forward_strength = np.array(forward_strength) np_clamp = np.array(clamp) np_decay_pow = np.array(decay_power) params = numpy_match_long_repeat([np_location, np_direction, np_rot_strength, np_inflow_strength, np_forward_strength, np_clamp, np_decay_pow]) self.points = params[0] self.direction = params[1]/np.linalg.norm(params[1], axis=1)[:, np.newaxis] self.rot_strength = params[2] self.use_rotation = np.any(params[2] != 0) self.inflow_strength = params[3] self.use_inflow = np.any(params[3] != 0) self.forward_strength = params[4] self.use_forward = np.any(params[4] != 0) self.use_vortex = np.any([self.use_rotation, self.use_inflow, self.use_forward]) self.clamp = params[5] self.decay = params[6] def setup(self, ps): pass def add(self, ps): if not self.use_vortex: return v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :] vect_proy = np_dot(v_attract, self.direction[:, np.newaxis, :],axis=2) closest_point = self.points[:, np.newaxis, :] +vect_proy[:, :, np.newaxis] * self.direction[:, np.newaxis, :] dif_v = closest_point - ps.verts[np.newaxis, :, :] dist_attract = np.linalg.norm(dif_v, axis=2) mask_true = dist_attract < self.clamp[:, np.newaxis] dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2, 1e-2, 1e4) v_attract_normal = dif_v/ dist_attract_cor[:, :,np.newaxis] v_attract[:] = 0 if self.use_rotation: v_rotation = np.cross(v_attract_normal, self.direction[:, np.newaxis, :]) * self.rot_strength[:, np.newaxis, np.newaxis] v_attract[mask_true] += v_rotation[mask_true] if self.use_forward: v_forward = self.direction[:, np.newaxis, :] * self.forward_strength[:, np.newaxis, np.newaxis] / dist_attract_cor[:, :,np.newaxis] v_attract[mask_true] += v_forward[mask_true] if self.use_inflow: v_attract_normal *= self.inflow_strength[:, np.newaxis, np.newaxis] v_attract[mask_true] += v_attract_normal[mask_true] r_attract = np.sum(v_attract, axis=0) ps.force_resultant += r_attract
Methods
def add(self, ps)
-
Expand source code
def add(self, ps): if not self.use_vortex: return v_attract = ps.verts[np.newaxis, :, :] - self.points[:, np.newaxis, :] vect_proy = np_dot(v_attract, self.direction[:, np.newaxis, :],axis=2) closest_point = self.points[:, np.newaxis, :] +vect_proy[:, :, np.newaxis] * self.direction[:, np.newaxis, :] dif_v = closest_point - ps.verts[np.newaxis, :, :] dist_attract = np.linalg.norm(dif_v, axis=2) mask_true = dist_attract < self.clamp[:, np.newaxis] dist_attract2 = np.power(dist_attract, self.decay[:, np.newaxis]) dist_attract_cor = np.clip(dist_attract2, 1e-2, 1e4) v_attract_normal = dif_v/ dist_attract_cor[:, :,np.newaxis] v_attract[:] = 0 if self.use_rotation: v_rotation = np.cross(v_attract_normal, self.direction[:, np.newaxis, :]) * self.rot_strength[:, np.newaxis, np.newaxis] v_attract[mask_true] += v_rotation[mask_true] if self.use_forward: v_forward = self.direction[:, np.newaxis, :] * self.forward_strength[:, np.newaxis, np.newaxis] / dist_attract_cor[:, :,np.newaxis] v_attract[mask_true] += v_forward[mask_true] if self.use_inflow: v_attract_normal *= self.inflow_strength[:, np.newaxis, np.newaxis] v_attract[mask_true] += v_attract_normal[mask_true] r_attract = np.sum(v_attract, axis=0) ps.force_resultant += r_attract
def setup(self, ps)
-
Expand source code
def setup(self, ps): pass
class SvWorldForce (force, strength, mass_proportional)
-
Expand source code
class SvWorldForce(): def __init__(self, force, strength, mass_proportional): self.strength, self.force = numpy_match_long_repeat([np.array(strength), np.array(force)]) self.force *= self.strength[:, np.newaxis] self.func = self.apply_mass_proportional self.mass_proportional = mass_proportional def setup(self, ps): size_change = ps.size_change if len(self.force) > 1: self.force2 = numpy_fit_long_repeat([self.force], ps.v_len)[0] if size_change and self.mass_proportional: self.func = self.apply_mass_proportional elif self.mass_proportional: self.force_final = self.force * ps.mass[:, np.newaxis] self.add = self.apply_dependant else: self.force_final = self.force self.add = self.apply_dependant def apply_mass_proportional(self, ps): '''apply constant forces''' ps.force_resultant += self.force_final * ps.mass[:, np.newaxis] def apply_dependant(self, ps): '''apply constant forces''' ps.force_resultant += self.force_final
Methods
def apply_dependant(self, ps)
-
apply constant forces
Expand source code
def apply_dependant(self, ps): '''apply constant forces''' ps.force_resultant += self.force_final
def apply_mass_proportional(self, ps)
-
apply constant forces
Expand source code
def apply_mass_proportional(self, ps): '''apply constant forces''' ps.force_resultant += self.force_final * ps.mass[:, np.newaxis]
def setup(self, ps)
-
Expand source code
def setup(self, ps): size_change = ps.size_change if len(self.force) > 1: self.force2 = numpy_fit_long_repeat([self.force], ps.v_len)[0] if size_change and self.mass_proportional: self.func = self.apply_mass_proportional elif self.mass_proportional: self.force_final = self.force * ps.mass[:, np.newaxis] self.add = self.apply_dependant else: self.force_final = self.force self.add = self.apply_dependant