Skip to content
Snippets Groups Projects
object_grease_scatter.py 12.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### 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",
    
        "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
    
                    "Scripts/Object/Grease_Scatter",
    
        "tracker_url": "https://developer.blender.org",
    
        "support": 'OFFICIAL',
        "category": "Object"}
    
    
    from mathutils import Vector, Matrix, Quaternion
    
    from random import uniform, shuffle
    import bpy
    
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
    def _main(self,
              obj,
              group,
              DENSITY=1.0,
              SCALE=0.6,
              RAND_LOC=0.8,
              RAND_ALIGN=0.75,
              ):
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
        from math import radians, pi
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
    Campbell Barton's avatar
    Campbell Barton committed
        SEEK = 2.0  # distance for ray to seek
        BAD_NORMAL = Vector((0.0, 0.0, -1.0))
    
        mats = (Matrix.Rotation(radians(-45), 3, 'X'),
    
    Campbell Barton's avatar
    Campbell Barton committed
                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'),
    
    Campbell Barton's avatar
    Campbell Barton committed
    
        Z_UP = Vector((0.0, 0.0, 1.0))
    
        Y_UP = Vector((0.0, 1.0, 0.0))
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
            self.report({'WARNING'}, "Group '%s' not found" % obj.name)
    
    Campbell Barton's avatar
    Campbell Barton committed
        def debug_edge(v1, v2):
    
            mesh = bpy.data.meshes.new("Retopo")
    
    Campbell Barton's avatar
    Campbell Barton committed
            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()
    
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
            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]
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
            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
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
            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
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                    # # dont self intersect
                    best_nor = None
    
    Campbell Barton's avatar
    Campbell Barton committed
                    #best_hit = None
    
                    best_dist = 10000000.0
                    pofs = p + n * 0.01
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                    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
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                    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
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
            # 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
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                        vantage = p + n
                        if DEBUG:
                            p[:] = vantage
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                        # 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)):
    
    Campbell Barton's avatar
    Campbell Barton committed
                        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
                            '''
    
    Campbell Barton's avatar
    Campbell Barton committed
    
                            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
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                            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)
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                mesh = bpy.data.meshes.new("ScatterDupliFace")
    
                mesh.from_pydata(hits, [], [])
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                scene = bpy.context.scene
                mesh.update()
    
                obj_new = bpy.data.objects.new("ScatterPar", mesh)
    
                obj_new.layers[:] = obj.layers
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                # 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:
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                def apply_faces(triples):
                    # first randomize the faces
                    shuffle(triples)
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                    obs = group.objects[:]
                    tot = len(obs)
                    tot_div = int(len(triples) / tot)
    
                    for inst_ob in obs:
    
                        triple_sub = triples[0:tot_div]
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                        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)
    
    
                        obj_new.layers[:] = obj.layers
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                        # Now setup dupli-faces
                        obj_new.dupli_type = 'FACES'
                        obj_new.use_dupli_faces_scale = True
                        obj_new.dupli_faces_scale = 100.0
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                        inst_ob.location = 0.0, 0.0, 0.0
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                        # 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
                        '''
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                # build faces from vert/normals
    
    Campbell Barton's avatar
    Campbell Barton committed
                tri = (Vector((0.0, 0.0, 0.01)),
                       Vector((0.0, 0.0, 0.0)),
                       Vector((0.0, 0.01, 0.01)))
    
    
    Campbell Barton's avatar
    Campbell Barton committed
                # face_ind = []
    
                for i in range(len(hits)):
                    co = hits[i]
                    no = nors[i]
                    ori = oris[i]
                    quat = no.to_track_quat('X', 'Z')
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                    angle = uniform(-pi, pi)
                    angle_aligned = -(ori.angle(quat * Y_UP, pi))
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                    quat = Quaternion(no,
                                      (angle * (1.0 - RAND_ALIGN)) +
                                      (angle_aligned * RAND_ALIGN)
                                      ).cross(quat)
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
                    coords.append([co + (quat * (tri[0] * f)),
                                   co + (quat * (tri[1] * f)),
                                   co + (quat * (tri[2] * f)),
                                   ])
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
    from bpy.props import FloatProperty,  StringProperty
    
    class Scatter(bpy.types.Operator):
    
        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(
    
    Campbell Barton's avatar
    Campbell Barton committed
                name="Group",
    
                description=("Group name to use for object placement, "
                             "defaults to object name when that matches a group"))
    
            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'}
    
                  obj,
                  group,
                  DENSITY=self.density,
                  SCALE=self.scale,
                  RAND_LOC=self.rand_loc,
                  RAND_ALIGN=self.rand_align,
    
        def check(self, context):
            if self.group not in bpy.data.groups:
                self.group = ""
                return True
            return False
    
        def invoke(self, context, event):
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
            # 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')
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
        bpy.utils.register_class(Scatter)
    
        bpy.types.INFO_MT_mesh_add.append(menu_func)
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
        bpy.utils.unregister_class(Scatter)
    
        bpy.types.INFO_MT_mesh_add.remove(menu_func)