mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-20 13:22:58 +00:00
402 lines
13 KiB
Python
402 lines
13 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8-80 compliant>
|
|
|
|
# Script copyright (C) Campbell Barton
|
|
|
|
bl_info = {
|
|
"name": "Grease Scatter Objects",
|
|
"author": "Campbell Barton",
|
|
"version": (0, 1),
|
|
"blender": (2, 58, 0),
|
|
"location": "3D View, Add Mesh",
|
|
"description": "Scatter a group of objects onto the active mesh using "
|
|
"the grease pencil lines",
|
|
"warning": "",
|
|
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
|
|
"Scripts/Object/Grease_Scatter",
|
|
"support": 'OFFICIAL',
|
|
"category": "Object",
|
|
}
|
|
|
|
from mathutils import Vector, Matrix, Quaternion
|
|
from random import uniform, shuffle
|
|
import bpy
|
|
|
|
|
|
def _main(self,
|
|
obj,
|
|
group,
|
|
DENSITY=1.0,
|
|
SCALE=0.6,
|
|
RAND_LOC=0.8,
|
|
RAND_ALIGN=0.75,
|
|
):
|
|
|
|
from math import radians, pi
|
|
|
|
# OFS = 0.2
|
|
SEEK = 2.0 # distance for ray to seek
|
|
BAD_NORMAL = Vector((0.0, 0.0, -1.0))
|
|
WALL_LIMIT = radians(45.0)
|
|
|
|
mats = (Matrix.Rotation(radians(-45), 3, 'X'),
|
|
Matrix.Rotation(radians(+45), 3, 'X'),
|
|
Matrix.Rotation(radians(-45), 3, 'Y'),
|
|
Matrix.Rotation(radians(+45), 3, 'Y'),
|
|
Matrix.Rotation(radians(-45), 3, 'Z'),
|
|
Matrix.Rotation(radians(+45), 3, 'Z'),
|
|
)
|
|
|
|
Z_UP = Vector((0.0, 0.0, 1.0))
|
|
Y_UP = Vector((0.0, 1.0, 0.0))
|
|
|
|
if not group:
|
|
self.report({'WARNING'}, "Group '%s' not found" % obj.name)
|
|
return
|
|
|
|
def debug_edge(v1, v2):
|
|
mesh = bpy.data.meshes.new("Retopo")
|
|
mesh.from_pydata([v1, v2], [(0.0, 1.0)], [])
|
|
|
|
scene = bpy.context.scene
|
|
mesh.update()
|
|
obj_new = bpy.data.objects.new("Torus", mesh)
|
|
scene.objects.link(obj_new)
|
|
|
|
ray = obj.ray_cast
|
|
closest_point_on_mesh = obj.closest_point_on_mesh
|
|
|
|
obj_mat = obj.matrix_world.copy()
|
|
obj_mat_inv = obj_mat.inverted()
|
|
# obj_quat = obj_mat.to_quaternion()
|
|
# obj_quat_inv = obj_mat_inv.to_quaternion()
|
|
|
|
DEBUG = False
|
|
|
|
def fix_point(p):
|
|
ok, hit, no, ind = closest_point_on_mesh(obj_mat_inv * p)
|
|
if ok:
|
|
if DEBUG:
|
|
return [p, no, None]
|
|
else:
|
|
# print("good", hit, no)
|
|
return [hit, no, None]
|
|
|
|
# worry!
|
|
print("bad!", p, BAD_NORMAL)
|
|
|
|
return [p, BAD_NORMAL, None]
|
|
|
|
def get_points(stroke):
|
|
return [fix_point(point.co) for point in stroke.points]
|
|
|
|
def get_splines(gp):
|
|
if gp.layers.active:
|
|
frame = gp.layers.active.active_frame
|
|
return [get_points(stroke) for stroke in frame.strokes]
|
|
else:
|
|
return []
|
|
|
|
def main():
|
|
scene = bpy.context.scene
|
|
obj = bpy.context.object
|
|
|
|
gp = None
|
|
|
|
if obj:
|
|
gp = obj.grease_pencil
|
|
|
|
if not gp:
|
|
gp = scene.grease_pencil
|
|
|
|
if not gp:
|
|
self.report({'WARNING'}, "No grease pencil layer found")
|
|
return
|
|
|
|
splines = get_splines(gp)
|
|
|
|
for s in splines:
|
|
for pt in s:
|
|
p = pt[0]
|
|
n = pt[1]
|
|
# print(p, n)
|
|
if n is BAD_NORMAL:
|
|
continue
|
|
|
|
# # dont self intersect
|
|
best_nor = None
|
|
#best_hit = None
|
|
best_dist = 10000000.0
|
|
pofs = p + n * 0.01
|
|
|
|
n_seek = n * SEEK
|
|
m_alt_1 = Matrix.Rotation(radians(22.5), 3, n)
|
|
m_alt_2 = Matrix.Rotation(radians(-22.5), 3, n)
|
|
for _m in mats:
|
|
for m in (_m, m_alt_1 * _m, m_alt_2 * _m):
|
|
pdir = m * n_seek
|
|
ok, hit, nor, ind = ray(pofs, pdir, best_dist)
|
|
if ok:
|
|
best_dist = (pofs - hit).length
|
|
best_nor = nor
|
|
# best_hit = hit
|
|
|
|
if best_nor:
|
|
pt[1].length = best_dist
|
|
best_nor.negate()
|
|
pt[2] = best_nor
|
|
|
|
#scene.cursor_location[:] = best_hitnyway
|
|
# bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP',
|
|
# iterations=1)
|
|
# debug_edge(p, best_hit)
|
|
# p[:] = best_hit
|
|
|
|
# Now we need to do scattering.
|
|
# first corners
|
|
hits = []
|
|
nors = []
|
|
oris = []
|
|
for s in splines:
|
|
# point, normal, n_other the closest hit normal
|
|
for p, n, n_other in s:
|
|
if n is BAD_NORMAL:
|
|
continue
|
|
if n_other:
|
|
# cast vectors twice as long as the distance
|
|
# needed just in case.
|
|
n_down = (n * -SEEK)
|
|
l = n_down.length
|
|
n_other.length = l
|
|
|
|
vantage = p + n
|
|
if DEBUG:
|
|
p[:] = vantage
|
|
|
|
# We should cast rays between n_down and n_other
|
|
#for f in (0.0, 0.2, 0.4, 0.6, 0.8, 1.0):
|
|
TOT = int(10 * DENSITY)
|
|
#for i in list(range(TOT)):
|
|
for i in list(range(TOT))[int(TOT / 1.5):]: # second half
|
|
f = i / (TOT - 1)
|
|
|
|
# focus on the center
|
|
'''
|
|
f -= 0.5
|
|
f = f*f
|
|
f += 0.5
|
|
'''
|
|
|
|
ntmp = f * n_down + (1.0 - f) * n_other
|
|
# randomize
|
|
ntmp.x += uniform(-l, l) * RAND_LOC
|
|
ntmp.y += uniform(-l, l) * RAND_LOC
|
|
ntmp.z += uniform(-l, l) * RAND_LOC
|
|
|
|
ok, hit, hit_no, ind = ray(vantage, ntmp, ntmp.length)
|
|
# print(hit, hit_no)
|
|
if ok:
|
|
if hit_no.angle(Z_UP) < WALL_LIMIT:
|
|
hits.append(hit)
|
|
nors.append(hit_no)
|
|
oris.append(n_other.cross(hit_no))
|
|
#oris.append(n_other)
|
|
|
|
if 0:
|
|
mesh = bpy.data.meshes.new("ScatterDupliFace")
|
|
mesh.from_pydata(hits, [], [])
|
|
|
|
scene = bpy.context.scene
|
|
mesh.update()
|
|
obj_new = bpy.data.objects.new("ScatterPar", mesh)
|
|
scene.objects.link(obj_new)
|
|
obj_new.layers[:] = obj.layers
|
|
|
|
# Now setup dupli-faces
|
|
obj_new.dupli_type = 'VERTS'
|
|
ob_child = bpy.data.objects["trash"]
|
|
ob_child.location = obj_new.location
|
|
ob_child.parent = obj_new
|
|
else:
|
|
|
|
def apply_faces(triples):
|
|
# first randomize the faces
|
|
shuffle(triples)
|
|
|
|
obs = group.objects[:]
|
|
tot = len(obs)
|
|
tot_div = int(len(triples) / tot)
|
|
|
|
for inst_ob in obs:
|
|
triple_sub = triples[0:tot_div]
|
|
triples[0:tot_div] = []
|
|
|
|
vv = [tuple(v) for f in triple_sub for v in f]
|
|
|
|
mesh = bpy.data.meshes.new("ScatterDupliFace")
|
|
mesh.from_pydata(vv, [], [(i * 3, i * 3 + 1, i * 3 + 2)
|
|
for i in range(len(triple_sub))])
|
|
|
|
scene = bpy.context.scene
|
|
mesh.update()
|
|
obj_new = bpy.data.objects.new("ScatterPar", mesh)
|
|
|
|
scene.objects.link(obj_new)
|
|
obj_new.layers[:] = obj.layers
|
|
|
|
# Now setup dupli-faces
|
|
obj_new.dupli_type = 'FACES'
|
|
obj_new.use_dupli_faces_scale = True
|
|
obj_new.dupli_faces_scale = 100.0
|
|
|
|
inst_ob.location = 0.0, 0.0, 0.0
|
|
inst_ob.parent = obj_new
|
|
|
|
# align the object with worldspace
|
|
obj_new.matrix_world = obj_mat
|
|
|
|
# BGE settings for testing
|
|
'''
|
|
inst_ob.game.physics_type = 'RIGID_BODY'
|
|
inst_ob.game.use_collision_bounds = True
|
|
inst_ob.game.collision_bounds = 'TRIANGLE_MESH'
|
|
inst_ob.game.collision_margin = 0.1
|
|
obj_new.select = True
|
|
'''
|
|
|
|
# build faces from vert/normals
|
|
tri = (Vector((0.0, 0.0, 0.01)),
|
|
Vector((0.0, 0.0, 0.0)),
|
|
Vector((0.0, 0.01, 0.01)))
|
|
|
|
coords = []
|
|
# face_ind = []
|
|
for i in range(len(hits)):
|
|
co = hits[i]
|
|
no = nors[i]
|
|
ori = oris[i]
|
|
quat = no.to_track_quat('X', 'Z')
|
|
|
|
# make 2 angles and blend
|
|
angle = uniform(-pi, pi)
|
|
angle_aligned = -(ori.angle(quat * Y_UP, pi))
|
|
|
|
quat = Quaternion(no,
|
|
(angle * (1.0 - RAND_ALIGN)) +
|
|
(angle_aligned * RAND_ALIGN)
|
|
).cross(quat)
|
|
|
|
f = uniform(0.1, 1.2) * SCALE
|
|
|
|
coords.append([co + (quat * (tri[0] * f)),
|
|
co + (quat * (tri[1] * f)),
|
|
co + (quat * (tri[2] * f)),
|
|
])
|
|
|
|
apply_faces(coords)
|
|
|
|
main()
|
|
|
|
|
|
from bpy.props import FloatProperty, StringProperty
|
|
|
|
|
|
class Scatter(bpy.types.Operator):
|
|
""""""
|
|
bl_idname = "object.scatter"
|
|
bl_label = "Grease Pencil Scatter"
|
|
|
|
density = FloatProperty(
|
|
name="Density",
|
|
description="Multiplier for the density of items",
|
|
default=1.0, min=0.01, max=10.0,
|
|
)
|
|
scale = FloatProperty(
|
|
name="Scale",
|
|
description="Size multiplier for duplifaces",
|
|
default=1.0, min=0.01, max=10.0,
|
|
)
|
|
rand_align = FloatProperty(
|
|
name="Random Align",
|
|
description="Randomize alignment with the walls",
|
|
default=0.75, min=0.0, max=1.0,
|
|
)
|
|
rand_loc = FloatProperty(
|
|
name="Random Loc",
|
|
description="Randomize placement",
|
|
default=0.75, min=0.0, max=1.0,
|
|
)
|
|
# XXX, should not be a string - TODO, add a way for scritps to select ID's
|
|
group = StringProperty(
|
|
name="Group",
|
|
description=("Group name to use for object placement, "
|
|
"defaults to object name when that matches a group"))
|
|
|
|
def execute(self, context):
|
|
obj = bpy.context.object
|
|
group = bpy.data.groups.get(self.group)
|
|
|
|
if not group:
|
|
self.report({'ERROR'}, "Group %r not found" % self.group)
|
|
return {'CANCELLED'}
|
|
|
|
_main(self,
|
|
obj,
|
|
group,
|
|
DENSITY=self.density,
|
|
SCALE=self.scale,
|
|
RAND_LOC=self.rand_loc,
|
|
RAND_ALIGN=self.rand_align,
|
|
)
|
|
return {'FINISHED'}
|
|
|
|
def check(self, context):
|
|
if self.group not in bpy.data.groups:
|
|
self.group = ""
|
|
return True
|
|
return False
|
|
|
|
def invoke(self, context, event):
|
|
|
|
# useful to initialize, take a guess
|
|
if not self.group and context.object.name in bpy.data.groups:
|
|
self.group = context.object.name
|
|
|
|
wm = context.window_manager
|
|
wm.invoke_props_dialog(self, width=180)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
def menu_func(self, context):
|
|
self.layout.operator(Scatter.bl_idname, icon='AUTO')
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(Scatter)
|
|
bpy.types.INFO_MT_mesh_add.append(menu_func)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(Scatter)
|
|
bpy.types.INFO_MT_mesh_add.remove(menu_func)
|
|
|
|
#if __name__ == "__main__":
|
|
# _main()
|