[Bf-extensions-cvs] [54d50aec] blender2.8: New "Scatter Objects" addon

Jacques Lucke noreply at git.blender.org
Tue Oct 30 16:36:07 CET 2018


Commit: 54d50aec6f135236e6a5346b61be56e3f550da55
Author: Jacques Lucke
Date:   Tue Oct 30 16:29:44 2018 +0100
Branches: blender2.8
https://developer.blender.org/rBA54d50aec6f135236e6a5346b61be56e3f550da55

New "Scatter Objects" addon

This addon can distribute objects on another object. The positions of new objects are determined by custom strokes and a few settings.

In the future we can support more features. For now this is mainly a replacement of the old 'Grease Scatter Objects' addon which existed in Blender 2.79.

Reviewers: campbellbarton

Differential Revision: https://developer.blender.org/D3787

===================================================================

A	object_scatter/__init__.py
A	object_scatter/operator.py
A	object_scatter/ui.py

===================================================================

diff --git a/object_scatter/__init__.py b/object_scatter/__init__.py
new file mode 100644
index 00000000..0d929b44
--- /dev/null
+++ b/object_scatter/__init__.py
@@ -0,0 +1,41 @@
+# ##### 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 #####
+
+bl_info = {
+    "name": "Scatter Objects",
+    "author": "Jacques Lucke",
+    "version": (0, 1),
+    "blender": (2, 80, 0),
+    "location": "3D View",
+    "description": "Distribute object instances on another object.",
+    "warning": "",
+    "wiki_url": "",
+    "support": 'OFFICIAL',
+    "category": "Object",
+}
+
+from . import ui
+from . import operator
+
+def register():
+    ui.register()
+    operator.register()
+
+def unregister():
+    ui.unregister()
+    operator.register()
diff --git a/object_scatter/operator.py b/object_scatter/operator.py
new file mode 100644
index 00000000..69c2d6e4
--- /dev/null
+++ b/object_scatter/operator.py
@@ -0,0 +1,508 @@
+# ##### 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 #####
+
+import bpy
+import gpu
+import bgl
+import blf
+import math
+import enum
+import random
+
+from itertools import islice
+from mathutils.bvhtree import BVHTree
+from mathutils import Vector, Matrix, Euler
+from gpu_extras.batch import batch_for_shader
+
+from bpy_extras.view3d_utils import (
+    region_2d_to_vector_3d,
+    region_2d_to_origin_3d
+)
+
+uniform_color_shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
+
+
+# Modal Operator
+################################################################
+
+class ScatterObjects(bpy.types.Operator):
+    bl_idname = "object.scatter"
+    bl_label = "Scatter Objects"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    @classmethod
+    def poll(cls, context):
+        return (
+            currently_in_3d_view(context)
+            and context.active_object is not None
+            and context.active_object.mode == 'OBJECT')
+
+    def invoke(self, context, event):
+        self.target_object = context.active_object
+        self.objects_to_scatter = get_selected_non_active_objects(context)
+
+        if self.target_object is None or len(self.objects_to_scatter) == 0:
+            self.report({'ERROR'}, "Select objects to scatter and a target object.")
+            return {'CANCELLED'}
+
+        self.base_scale = get_max_object_side_length(self.objects_to_scatter)
+
+        self.targets = []
+        self.active_target = None
+        self.target_cache = {}
+
+        self.enable_draw_callback()
+        context.window_manager.modal_handler_add(self)
+        return {'RUNNING_MODAL'}
+
+    def modal(self, context, event):
+        context.area.tag_redraw()
+
+        if not event_is_in_region(event, context.region) and self.active_target is None:
+            return {'PASS_THROUGH'}
+
+        if event.type == 'ESC':
+            return self.finish('CANCELLED')
+
+        if event.type == 'RET' and event.value == 'PRESS':
+            self.create_scatter_object()
+            return self.finish('FINISHED')
+
+        event_used = self.handle_non_exit_event(event)
+        if event_used:
+            return {'RUNNING_MODAL'}
+        else:
+            return {'PASS_THROUGH'}
+
+    def handle_non_exit_event(self, event):
+        if self.active_target is None:
+            if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
+                self.active_target = StrokeTarget()
+                self.active_target.start_build(self.target_object)
+                return True
+        else:
+            build_state = self.active_target.continue_build(event)
+            if build_state == BuildState.FINISHED:
+                self.targets.append(self.active_target)
+                self.active_target = None
+            self.remove_target_from_cache(self.active_target)
+            return True
+
+        return False
+
+    def enable_draw_callback(self):
+        self._draw_callback_view = bpy.types.SpaceView3D.draw_handler_add(self.draw_view, (), 'WINDOW', 'POST_VIEW')
+        self._draw_callback_px = bpy.types.SpaceView3D.draw_handler_add(self.draw_px, (), 'WINDOW', 'POST_PIXEL')
+
+    def disable_draw_callback(self):
+        bpy.types.SpaceView3D.draw_handler_remove(self._draw_callback_view, 'WINDOW')
+        bpy.types.SpaceView3D.draw_handler_remove(self._draw_callback_px, 'WINDOW')
+
+    def draw_view(self):
+        for target in self.iter_targets():
+            target.draw()
+
+        draw_matrices_batches(list(self.iter_matrix_batches()))
+
+    def draw_px(self):
+        draw_text((20, 20, 0), "Instances: " + str(len(self.get_all_matrices())))
+
+    def finish(self, return_value):
+        self.disable_draw_callback()
+        bpy.context.area.tag_redraw()
+        return {return_value}
+
+    def create_scatter_object(self):
+        matrix_chunks = make_random_chunks(
+            self.get_all_matrices(), len(self.objects_to_scatter))
+
+        collection = bpy.data.collections.new("Scatter")
+        bpy.context.collection.children.link(collection)
+
+        for obj, matrices in zip(self.objects_to_scatter, matrix_chunks):
+            make_duplicator(collection, obj, matrices)
+
+    def get_all_matrices(self):
+        settings = self.get_current_settings()
+
+        matrices = []
+        for target in self.iter_targets():
+            self.ensure_target_is_in_cache(target)
+            matrices.extend(self.target_cache[target].get_matrices(settings))
+        return matrices
+
+    def iter_matrix_batches(self):
+        settings = self.get_current_settings()
+        for target in self.iter_targets():
+            self.ensure_target_is_in_cache(target)
+            yield self.target_cache[target].get_batch(settings)
+
+    def iter_targets(self):
+        yield from self.targets
+        if self.active_target is not None:
+            yield self.active_target
+
+    def ensure_target_is_in_cache(self, target):
+        if target not in self.target_cache:
+            entry = TargetCacheEntry(target, self.base_scale)
+            self.target_cache[target] = entry
+
+    def remove_target_from_cache(self, target):
+        self.target_cache.pop(self.active_target, None)
+
+    def get_current_settings(self):
+        return bpy.context.scene.scatter_properties.to_settings()
+
+class TargetCacheEntry:
+    def __init__(self, target, base_scale):
+        self.target = target
+        self.last_used_settings = None
+        self.base_scale = base_scale
+        self.settings_changed()
+
+    def get_matrices(self, settings):
+        self._handle_new_settings(settings)
+        if self.matrices is None:
+            self.matrices = self.target.get_matrices(settings)
+        return self.matrices
+
+    def get_batch(self, settings):
+        self._handle_new_settings(settings)
+        if self.gpu_batch is None:
+            self.gpu_batch = create_batch_for_matrices(self.get_matrices(settings), self.base_scale)
+        return self.gpu_batch
+
+    def _handle_new_settings(self, settings):
+        if settings != self.last_used_settings:
+            self.settings_changed()
+        self.last_used_settings = settings
+
+    def settings_changed(self):
+        self.matrices = None
+        self.gpu_batch = None
+
+
+# Duplicator Creation
+######################################################
+
+def make_duplicator(target_collection, source_object, matrices):
+    triangle_scale = 0.1
+
+    duplicator = triangle_object_from_matrices(source_object.name + " Duplicator", matrices, triangle_scale)
+    duplicator.dupli_type = 'FACES'
+    duplicator.use_dupli_faces_scale = True
+    duplicator.show_duplicator_for_viewport = True
+    duplicator.show_duplicator_for_render = False
+    duplicator.dupli_faces_scale = 1 / triangle_scale
+
+    copy_obj = source_object.copy()
+    copy_obj.name = source_object.name + " - copy"
+    copy_obj.hide_viewport = True
+    copy_obj.hide_render = True
+    copy_obj.location = (0, 0, 0)
+    copy_obj.parent = duplicator
+
+    target_collection.objects.link(duplicator)
+    target_collection.objects.link(copy_obj)
+
+def triangle_object_from_matrices(name, matrices, triangle_scale):
+    mesh = triangle_mesh_from_matrices(name, matrices, triangle_scale)
+    return bpy.data.objects.new(name, mesh)
+
+def triangle_mesh_from_matrices(name, matrices, triangle_scale):
+    mesh = bpy.data.meshes.new(name)
+    vertices, polygons = mesh_data_from_matrices(matrices, triangle_scale)
+    mesh.from_pydata(vertices, [], polygons)
+    mesh.update()
+    mesh.validate()
+    return mesh
+
+unit_triangle_vertices = (
+    Vector((-3**-0.25, -3**-0.75, 0)),
+    Vector((3**-0.25, -3**-0.75, 0)),
+    Vector((0, 2/3**0.75, 0)))
+
+def mesh_data_from_matrices(matrices, triangle_scale):
+    vertices = []
+    polygon

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list