Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'object_grease_scatter.py')
-rw-r--r--object_grease_scatter.py431
1 files changed, 431 insertions, 0 deletions
diff --git a/object_grease_scatter.py b/object_grease_scatter.py
new file mode 100644
index 00000000..4d6aeddb
--- /dev/null
+++ b/object_grease_scatter.py
@@ -0,0 +1,431 @@
+# ##### 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, 5, 8),
+ "api": 36079,
+ "location": "3D View, Toolbar",
+ "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.5/Py/"
+ "Scripts/Object/Grease_Scatter"),
+ "tracker_url": ("https://projects.blender.org/tracker/index.php?"
+ "func=detail&aid=TODO"),
+ "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 faces_from_hits(hit_list):
+ def from_pydata(self, verts, edges, faces):
+ """
+ Make a mesh from a list of verts/edges/faces
+ Until we have a nicer way to make geometry, use this.
+ """
+ self.add_geometry(len(verts), len(edges), len(faces))
+
+ verts_flat = [f for v in verts for f in v]
+ self.verts.foreach_set("co", verts_flat)
+ del verts_flat
+
+ edges_flat = [i for e in edges for i in e]
+ self.edges.foreach_set("verts", edges_flat)
+ del edges_flat
+
+ def treat_face(f):
+ if len(f) == 3:
+ return f[0], f[1], f[2], 0
+ elif f[3] == 0:
+ return f[3], f[0], f[1], f[2]
+ return f
+
+ faces_flat = [v for f in faces for v in treat_face(f)]
+ self.faces.foreach_set("verts_raw", faces_flat)
+ del faces_flat
+
+ 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):
+ hit, no, ind = closest_point_on_mesh(obj_mat_inv * p)
+ if ind != -1:
+ 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):
+ hit, nor, ind = ray(pofs, pofs + (m * n_seek))
+ if ind != -1:
+ dist = (pofs - hit).length
+ if dist < best_dist:
+ best_dist = dist
+ 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 incase.
+ 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
+
+ hit, hit_no, ind = ray(vantage, vantage + ntmp)
+ # print(hit, hit_no)
+ if ind != -1:
+ 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 testiing
+ '''
+ 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 alignmet 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()