Geo-Scatter - 5.4 Scatter Groups

its very far from being finished tho :slightly_smiling_face: in the end i’ll release an example scene with the final product, as same quality as the render above. And done automatically by the addon.

1 Like

I don’t know if it’ll be any useful but it might be worth to mention that @Jacques_Lucke was working on "Scatter Objects" addon that had some cool options.

2 Likes

okay okay

added a random range option in the custom slot editor, so that the same custom scattering operator could give different result because of size and offset randomisation.

i’ll try to do more range randomisation tomorrow ? what kind of parameters would you like to see ? more would be killer no ? Shall i work on other procedural texture ? right now the only possibility is the cloud texture.

custom

# "Scatter" Add-on
# Copyright (C) 2019 Dorian Borremans aka BD3D
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
# <pep8 compliant>

bl_info = {
    "name" : "Scatter [BD3D]", 
    "author" : "BD3D",   
    "description" : "The scattering tool of 2.8",
    "blender" : (2, 80, 0),
    "location" : "Operator",
    "warning" : "",
    "category" : "Generic"
}

import bpy
import random
from bpy.types import Menu
import webbrowser
from bpy.types import Operator, AddonPreferences, PropertyGroup
from bpy.props import StringProperty, IntProperty, BoolProperty, FloatProperty
import rna_keymap_ui
context = bpy.context

def find_collection(context, item):
    collections = item.users_collection
    if len(collections) > 0:
        return collections[0]
    return context.scene.collection

def make_collection(collection_name, parent_collection):
    if collection_name in bpy.data.collections:
        return bpy.data.collections[collection_name]
    else:
        new_collection = bpy.data.collections.new(collection_name)
        parent_collection.children.link(new_collection)
        return new_collection

######################################################################################
######################################################################################
# # # # # # # # # # # #         SCATTER ADDON PREF             # # # # # # # # # # # # 
######################################################################################
######################################################################################

class ScatterPref(AddonPreferences):
    bl_idname = __name__

    scatter_01_count = IntProperty( 
            name="Emission Number",
            subtype='NONE',
            default=1000,
            min=0,
            )#percentage couvert? 
    scatter_01_seed = IntProperty( 
            name="Emission Seed Value",
            subtype='NONE',
            default=5,
            min=0,
            )
    scatter_01_seed_is_random = BoolProperty( 
            name="Emission Random Seed Value",
            subtype='NONE',
            default=True,
            )
    scatter_01_particle_size = FloatProperty(
            name="Render Scale",
            subtype='NONE',
            default=0.25,
            min=0.01,
            max=3
            )
    scatter_01_size_random = FloatProperty(
            name="Render Scale Randomness",
            subtype='NONE',
            default=0.35,
            min=0,
            max=1
            )
    scatter_01_phase_factor_random = FloatProperty(
            name="Rotation Randomize Phase",
            subtype='NONE',
            default=2,
            min=0,
            max=2
            )
    scatter_01_display_percentage = IntProperty(
            name="Viewport Display Percentage",
            subtype='NONE',
            default=100,
            min=0,
            max=100
            )

    scatter_01_noise_scale = FloatProperty(
            name="Texture Noise Size",
            subtype='NONE',
            default=0.45,
            min=0.01,
            max=100
            )
    scatter_01_noise_depth = IntProperty(
            name="Texture Noise Depth",
            subtype='NONE',
            default=0,
            min=0,
            max=20
            )
    scatter_01_contrast = FloatProperty(
            name="Texture Color Contrast",
            subtype='NONE',
            default=3,
            min=0,
            max=5
            )
    scatter_01_intensity = FloatProperty(
            name="Texture Color Brightness",
            subtype='NONE',
            default=1,
            min=0,
            max=2
            )
    scatter_01_density_factor = FloatProperty(
            name="Texture Influence Density",
            subtype='NONE',
            default=1,
            min=0,
            max=1
            )
    scatter_01_length_factor = FloatProperty(
            name="Texture Influence Length",
            subtype='NONE',
            default=0.3,
            min=0,
            max=1
            )

    scatter_01_scalex = FloatProperty(
            name="Texture Mapping size X",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )
    scatter_01_scaley = FloatProperty(
            name="Texture Mapping size Y",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )
    scatter_01_scalez = FloatProperty(
            name="Texture Mapping size Z",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )

    scatter_01_offsetx = FloatProperty(
            name="Texture Mapping offset X in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offsety = FloatProperty(
            name="Texture Mapping offset Y in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offsetz = FloatProperty(
            name="Texture Mapping offset Z in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )


    scatter_01_size_is_random = BoolProperty( 
            name="Texture Mapping Random Size Value XYZ",
            subtype='NONE',
            default=False,
            )
    scatter_01_size_A = FloatProperty(
            name="Size Possibilities Range From A",
            subtype='NONE',
            default=0.7,
            min=-100,
            max=100
            )
    scatter_01_size_B = FloatProperty(
            name="To B",
            subtype='NONE',
            default=1.4,
            min=-100,
            max=100
            )


    scatter_01_offset_is_random = BoolProperty( 
            name="Texture Mapping Random Offset Value XYZ in meters",
            subtype='NONE',
            default=True,
            )
    scatter_01_offset_A = FloatProperty(
            name="Offset Possibilities Range From A",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offset_B = FloatProperty(
            name="To B",
            subtype='NONE',
            default=10,
            min=-10,
            max=10
            )


    scatter_01_open= bpy.props.BoolProperty(
        name="Show Debug Tools",
        description="Expand me senpai",
        default=False
    )
######################################################################################

    def draw(self, context): #DRAWING HERE - DRAWING HERE - DRAWING HERE- DRAWING HERE
        layout = self.layout
        full= "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
        addon_prefs = context.preferences.addons[__name__].preferences

        box = layout.box()
        col = box.column()
        row = col.row(align=True)
        row.alignment = 'LEFT'
        row.prop(self,
                 'scatter_01_open',
                 text="Custom Scattering Slot 01",
                 emboss=False,
                 icon="PLAY" if self.scatter_01_open else "PLAY")
        if self.scatter_01_open:

            col.label(text=full)            

            col.prop(self,"scatter_01_count")
            if addon_prefs.scatter_01_seed_is_random == True:
                col.prop(self,"scatter_01_seed_is_random")
            else:
                col.prop(self,"scatter_01_seed_is_random")
                col.prop(self,"scatter_01_seed")
            col.prop(self,"scatter_01_particle_size")
            col.prop(self,"scatter_01_size_random")
            col.prop(self,"scatter_01_phase_factor_random")

            col.label(text=' ')
            col.prop(self,"scatter_01_density_factor")
            col.prop(self,"scatter_01_length_factor")

            col.label(text=' ')
            col.prop(self,"scatter_01_offset_is_random")
            if addon_prefs.scatter_01_offset_is_random == True:
                col.prop(self,"scatter_01_offset_A")
                col.prop(self,"scatter_01_offset_B")
            else:
                col.prop(self,"scatter_01_offsetx")
                col.prop(self,"scatter_01_offsety")
                col.prop(self,"scatter_01_offsetz")
            
            col.label(text=' ')
            col.prop(self,"scatter_01_size_is_random")
            if addon_prefs.scatter_01_size_is_random == True:
                col.prop(self,"scatter_01_size_A")
                col.prop(self,"scatter_01_size_B")
            else:
                col.prop(self,"scatter_01_scalex")
                col.prop(self,"scatter_01_scaley")
                col.prop(self,"scatter_01_scalez")

            col.label(text=' ')
            col.prop(self,"scatter_01_noise_scale")
            col.prop(self,"scatter_01_noise_depth")
            col.prop(self,"scatter_01_contrast")
            col.prop(self,"scatter_01_intensity")

            col.label(text=' ')
            col.prop(self,"scatter_01_display_percentage")

 


######################################################################################
######################################################################################
# # # # # # # # # # # #        SCATTER OPERATOR                # # # # # # # # # # # # 
######################################################################################
######################################################################################

class Scatter_OT_Custom01(bpy.types.Operator):
    bl_idname = "scatter.custom01"
    bl_label = "custom scattering operator 01"
    bl_description = ""

    def execute(self, context):
        context = bpy.context
        addon_prefs = context.preferences.addons[__name__].preferences
        scene = context.scene
        A = context.object
        ### ### Naming
        slotname = "Custom1"
        if len(bpy.context.selected_objects) > 2:
            particlename = bpy.context.selected_objects[1].name + " ..."
        else:
            particlename = bpy.context.selected_objects[1].name #BUG why the f does the name change when multiple execution ???
        pref = "SCATTER: ["+slotname+"] ["+particlename+"] v."
        i = 1
        name = "%s%d" % (pref, i)
        while A.modifiers.get(name):    
            i += 1
            name = "%s%d" % (pref, i)
        ### ### add particle system + rename and add selection in new coll
        m = A.modifiers.new(name, type='PARTICLE_SYSTEM')
        A.select_set(state=False)
        for o in bpy.context.selected_objects:
            o_collection = find_collection(bpy.context, o)
            new_collection = make_collection(name , o_collection)
            new_collection.objects.link(o)
            o_collection.objects.unlink(o)
        A.select_set(state=True)
        ### ### particle parameters
        ps = m.particle_system
        ps.name = name
        ps.settings.name = name
        ps.settings.type = 'HAIR'
        ps.settings.render_type = 'COLLECTION'
        ps.settings.instance_collection = bpy.data.collections[name]
        ps.settings.particle_size = addon_prefs.scatter_01_particle_size
        ps.settings.hair_length = 4
        ps.settings.size_random = addon_prefs.scatter_01_size_random
        ps.settings.count = addon_prefs.scatter_01_count
        if addon_prefs.scatter_01_seed_is_random == True:
            bpy.context.object.particle_systems[name].seed = random.randint(0,10000)
        else:
            bpy.context.object.particle_systems[name].seed = addon_prefs.scatter_01_seed
        ps.settings.display_percentage = addon_prefs.scatter_01_display_percentage
        ps.settings.use_advanced_hair = True
        ps.settings.use_rotations = True
        ps.settings.rotation_factor_random = 1
        ps.settings.phase_factor = 1
        ps.settings.phase_factor_random = addon_prefs.scatter_01_phase_factor_random
        ### ### texture parameters
        bpy.ops.texture.new()
        texturename = name
        bpy.data.textures[-1].name = texturename
        bpy.data.textures[texturename].type = 'CLOUDS' #LATER SUPPORT OTHER NOISE TYPE AND OPTIONS
        bpy.data.textures[texturename].noise_scale = addon_prefs.scatter_01_noise_scale
        bpy.data.textures[texturename].noise_depth = addon_prefs.scatter_01_noise_depth
        bpy.data.textures[texturename].contrast = addon_prefs.scatter_01_contrast
        bpy.data.textures[texturename].intensity = addon_prefs.scatter_01_intensity
        ps.settings.texture_slots.add().texture = bpy.data.textures[texturename]
        ps.settings.texture_slots[0].blend_type = 'MULTIPLY'
        ps.settings.texture_slots[0].use_map_time = False
        ps.settings.texture_slots[0].use_map_density = True
        ps.settings.texture_slots[0].density_factor = addon_prefs.scatter_01_density_factor
        ps.settings.texture_slots[0].use_map_length = True 
        ps.settings.texture_slots[0].length_factor = addon_prefs.scatter_01_length_factor

        if addon_prefs.scatter_01_offset_is_random == False:
            ps.settings.texture_slots[0].scale[1] = addon_prefs.scatter_01_scalex
            ps.settings.texture_slots[0].scale[2] = addon_prefs.scatter_01_scaley
            ps.settings.texture_slots[0].scale[0] = addon_prefs.scatter_01_scalez
        else:
            randomn =round(random.uniform(addon_prefs.scatter_01_size_A,addon_prefs.scatter_01_size_B), 2)
            ps.settings.texture_slots[0].scale[1] = randomn
            ps.settings.texture_slots[0].scale[2] = randomn
            ps.settings.texture_slots[0].scale[0] = randomn

        if addon_prefs.scatter_01_offset_is_random == False:
            ps.settings.texture_slots[0].offset[1] = addon_prefs.scatter_01_offsetx
            ps.settings.texture_slots[0].offset[2] = addon_prefs.scatter_01_offsety
            ps.settings.texture_slots[0].offset[0] = addon_prefs.scatter_01_offsetz
        else:
            randomno =round(random.uniform(addon_prefs.scatter_01_offset_A,addon_prefs.scatter_01_offset_B), 2)
            ps.settings.texture_slots[0].offset[1] = randomno
            ps.settings.texture_slots[0].offset[2] = randomno
            ps.settings.texture_slots[0].offset[0] = randomno

        ps.settings.texture_slots[0].texture_coords = 'GLOBAL'
        return {'FINISHED'}

######################################################################################
######################################################################################
# # # # # # # # # # # #            N MENU                      # # # # # # # # # # # # 
######################################################################################
######################################################################################

class Scatter_PT_ScatteringPanel(bpy.types.Panel):
    bl_idname = "Scatter_PT_ScatteringPanel"
    bl_label = "Scatter"
    bl_category = "Scatter"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout
        bigbutton = layout.column()
        bigbutton.scale_y=2
        bigbutton.operator('scatter.custom01', text="SCATTER",  icon='PLAY')

######################################################################################
######################################################################################
# # # # # # # # # # # #                 REG                    # # # # # # # # # # # # 
######################################################################################
######################################################################################

def register():
    bpy.utils.register_class(ScatterPref)
    bpy.utils.register_class(Scatter_OT_Custom01)
    bpy.utils.register_class(Scatter_PT_ScatteringPanel)

def unregister():
    bpy.utils.unregister_class(ScatterPref)
    bpy.utils.unregister_class(Scatter_OT_Custom01)
    bpy.utils.unregister_class(Scatter_PT_ScatteringPanel)


if __name__ == "__main__":
    register()
    #write ops to do on script exec

Scatter_v01.py (17.3 KB)

@filibis i don’t think thoses are true particles ? are they ?

4 Likes

Yeah, they are not particles, it’s using instancing. You can try it out, addon comes within Blender.
image

1 Like

@BD3D This looks really good! Need to try it out as soons as possible.
Does it support masking by weight/vertex group?
Would like to define a path with gravel.

2 Likes

all it does it automatically setting all the particle setting in different part of the property editor. So you can do what you want with it.

2 Likes

yes i know, i already tried but it was quite a deception. Didn’t touch the addon afterward. :cry:

Finally someone is making scattering tool blender is lacking. Scattering via hair system is rought and inconvinient.
Thanks a lot!

I wish the devfund developer could built similar to CoronaScatter tool.
https://coronarenderer.freshdesk.com/support/solutions/articles/12000055675-how-to-use-scale-map-in-corona-scatter

2 Likes

except the distance map set up in your link, blender can already do all of this.

All my addon do is automating everything.
I will also put all the sliders that trully matters in the same emplacement.

2 Likes

hoy hoy

now featuring batch scattering the selection to the active object.
Because of the chosen custom randomization range for the texture coordinate offset and scale, executing the same scattering custom slot will do different result. Ideal for batch scattering.

custom

Scatter_v01.py (23.2 KB)

7 Likes

Some weeks ago I started this tool for sculpt https://twitter.com/jfranmatheu/status/1163454345209360392 (that version is already old but just to show the thing) What I used at first was object instances and it’s pretty nice but has much limitations for what I need to the point I’m thinking to add particle system instances because look more handy, random and quicker and also I’ll can see the differences between both systems… If you want, when I’ll compare both I’ll can show you :slight_smile:

When all it’s about joining 10k instanced objects in a HP model from a row and then remesh, Blender doesn’t like it xD So bad idea for sculpt if they are different objects, but the thing is instances of meshes being part of the same object, not sure if it’s possible atm.

Cool project btw, looking forward your progress @BD3D !!! ^u^

2 Likes

Okay. Added some more randomisation option for noise scale, random noise range (brightness/Contrast/depth randomisation)

Also added Emission number per square meters. So that the density of the particles will always stay the same. No matter the terrain size.

as you can see the clustering is adapting itself.

both particle system were made in one click. Your future slots of customizations will be smart.

But carreful with viewport lag. this method may cause too much particles for your computer to handle if you misdjuge your terrain surface. I’ll try to automatically adapt the density of the particles from the distance of the active camera to the terrain later.

hopefully i can have some help from other devs.



# "Scatter" Add-on
# Copyright (C) 2019 Dorian Borremans aka BD3D
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
# <pep8 compliant>

bl_info = {
    "name" : "Scatter [BD3D]", 
    "author" : "BD3D",   
    "description" : "The scattering tool of 2.8",
    "blender" : (2, 80, 0),
    "location" : "Operator",
    "warning" : "",
    "category" : "Generic"
}

import bpy
import bmesh
import random
from bpy.types import Menu
import webbrowser
from bpy.types import Operator, AddonPreferences, PropertyGroup
from bpy.props import StringProperty, IntProperty, BoolProperty, FloatProperty
import rna_keymap_ui
context = bpy.context

def find_collection(context, item):
    collections = item.users_collection
    if len(collections) > 0:
        return collections[0]
    return context.scene.collection

def make_collection(collection_name, parent_collection):
    if collection_name in bpy.data.collections:
        return bpy.data.collections[collection_name]
    else:
        new_collection = bpy.data.collections.new(collection_name)
        parent_collection.children.link(new_collection)
        return new_collection

def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'): #Message function
    def draw(self, context):
        self.layout.label(text=message)
    bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)

######################################################################################
######################################################################################
# # # # # # # # # # # #         SCATTER ADDON PREF             # # # # # # # # # # # # 
######################################################################################
######################################################################################

class ScatterPref(AddonPreferences):
    bl_idname = __name__

    scatter_01_type= bpy.props.EnumProperty(
        name = 'Particle Type',
        description = 'particle rendering type',
        items = [
            ('Collection', 'Collection', '', 'COLLECTION_NEW',1),
            ('Object', 'Object', '', 'OBJECT_DATA',2),
            ],
        default = 'Object')
    scatter_01_count = IntProperty( 
            name="Emission Number",
            subtype='NONE',
            default=1000,
            min=0,
            )#percentage couvert? 
    scatter_01_countispersquare = BoolProperty( 
            name="Emission Number is per square meters (be sure to apply the scale of the terrain)",
            subtype='NONE',
            default=True,
            )
    scatter_01_countpersquare = IntProperty( 
            name="Emission Number per square meters",
            subtype='NONE',
            default=50,
            min=0,
            )
    scatter_01_seed = IntProperty( 
            name="Emission Seed Value",
            subtype='NONE',
            default=5,
            min=0,
            )
    scatter_01_seed_is_random = BoolProperty( 
            name="Emission Random Seed Value",
            subtype='NONE',
            default=True,
            )
    scatter_01_particle_size = FloatProperty(
            name="Render Scale (Default = True size) ",
            subtype='NONE',
            default=0.25,
            min=0.01,
            max=3
            )
    scatter_01_size_random = FloatProperty(
            name="Render Scale Randomness",
            subtype='NONE',
            default=0.35,
            min=0,
            max=1
            )
    scatter_01_phase_factor_random = FloatProperty(
            name="Rotation Randomize Phase",
            subtype='NONE',
            default=2,
            min=0,
            max=2
            )
    scatter_01_display_percentage = IntProperty(
            name="Viewport Display Percentage",
            subtype='NONE',
            default=100,
            min=0,
            max=100
            )
    scatter_01_noise_scale = FloatProperty(
            name="Texture Noise Size",
            subtype='NONE',
            default=1.5,
            min=0.01,
            max=100
            )
    scatter_01_noise_scaleisrandom = BoolProperty( 
            name="Texture Noise Size Random Values",
            subtype='NONE',
            default=False,
            )
    scatter_01_noise_scaleA = FloatProperty(
            name="Texture Noise Size Possibilities Range From A",
            subtype='NONE',
            default=0.75,
            min=0.01,
            max=100
            )
    scatter_01_noise_scaleB = FloatProperty(
            name="To B",
            subtype='NONE',
            default=2.3,
            min=0.01,
            max=100
            )

    scatter_01_noise_randomtext = BoolProperty( 
            name="Texture Depth/Contrast/Brightness Randomisation",
            subtype='NONE',
            default=False,
            )
    scatter_01_noise_depth = IntProperty(
            name="Texture Noise Depth",
            subtype='NONE',
            default=0,
            min=0,
            max=20
            )
    scatter_01_contrast = FloatProperty(
            name="Texture Color Contrast",
            subtype='NONE',
            default=3,
            min=0,
            max=5
            )
    scatter_01_intensity = FloatProperty(
            name="Texture Color Brightness",
            subtype='NONE',
            default=1,
            min=0,
            max=2
            )
    scatter_01_density_factor = FloatProperty(
            name="Texture Influence Density",
            subtype='NONE',
            default=1,
            min=0,
            max=1
            )
    scatter_01_length_factor = FloatProperty(
            name="Texture Influence Length",
            subtype='NONE',
            default=0.3,
            min=0,
            max=1
            )

    scatter_01_scalex = FloatProperty(
            name="Texture Mapping size X",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )
    scatter_01_scaley = FloatProperty(
            name="Texture Mapping size Y",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )
    scatter_01_scalez = FloatProperty(
            name="Texture Mapping size Z",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )

    scatter_01_offsetx = FloatProperty(
            name="Texture Mapping offset X in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offsety = FloatProperty(
            name="Texture Mapping offset Y in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offsetz = FloatProperty(
            name="Texture Mapping offset Z in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_size_is_random = BoolProperty( 
            name="Texture Mapping Random Size Value XYZ",
            subtype='NONE',
            default=False,
            )
    scatter_01_size_A = FloatProperty(
            name="Size Possibilities Range From A",
            subtype='NONE',
            default=0.7,
            min=-100,
            max=100
            )
    scatter_01_size_B = FloatProperty(
            name="To B",
            subtype='NONE',
            default=1.4,
            min=-100,
            max=100
            )
    scatter_01_offset_is_random = BoolProperty( 
            name="Texture Mapping Random Offset Value XYZ in meters",
            subtype='NONE',
            default=True,
            )
    scatter_01_offset_A = FloatProperty(
            name="Offset Possibilities Range From A",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offset_B = FloatProperty(
            name="To B",
            subtype='NONE',
            default=10,
            min=-10,
            max=10
            )
    scatter_01_open= bpy.props.BoolProperty(
        name="Show Debug Tools",
        description="Expand me senpai",
        default=False
    )
######################################################################################

    def draw(self, context): #DRAWING HERE - DRAWING HERE - DRAWING HERE- DRAWING HERE
        layout = self.layout
        full= "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
        addon_prefs = context.preferences.addons[__name__].preferences

        box = layout.box()
        col = box.column()
        row = col.row(align=True)
        row.alignment = 'LEFT'
        row.prop(self,
                 'scatter_01_open',
                 text="Custom Scattering Slot 01",
                 emboss=False,
                 icon="PLAY" if self.scatter_01_open else "PLAY")
        if self.scatter_01_open:

            col.label(text=full)

            col.prop(self,"scatter_01_type")
            if addon_prefs.scatter_01_type == 'Object':
                col.label(text='(Will Batch-Scatter if more than one particle asset is selected)')
            else: 
                col.label(text=' ')

            col.prop(self,"scatter_01_countispersquare")
            if addon_prefs.scatter_01_countispersquare == True:
                col.prop(self,"scatter_01_countpersquare")
            else:
                col.prop(self,"scatter_01_count")
            if addon_prefs.scatter_01_seed_is_random == True:
                col.prop(self,"scatter_01_seed_is_random")
            else:
                col.prop(self,"scatter_01_seed_is_random")
                col.prop(self,"scatter_01_seed")
            #col.prop(self,"scatter_01_particle_size")
            col.prop(self,"scatter_01_size_random")
            col.prop(self,"scatter_01_phase_factor_random")
            col.prop(self,"scatter_01_display_percentage")

            col.label(text=' ')
            col.label(text='(Set Both texture influence to 0 to turn OFF the texture clustering the particles)')
            col.prop(self,"scatter_01_density_factor")
            col.prop(self,"scatter_01_length_factor")

            if addon_prefs.scatter_01_length_factor + addon_prefs.scatter_01_density_factor !=0:

                col.prop(self,"scatter_01_offset_is_random")
                if addon_prefs.scatter_01_offset_is_random == True:
                    col.prop(self,"scatter_01_offset_A")
                    col.prop(self,"scatter_01_offset_B")
                else:
                    col.prop(self,"scatter_01_offsetx")
                    col.prop(self,"scatter_01_offsety")
                    col.prop(self,"scatter_01_offsetz")
                
                col.prop(self,"scatter_01_size_is_random")
                if addon_prefs.scatter_01_size_is_random == True:
                    col.prop(self,"scatter_01_size_A")
                    col.prop(self,"scatter_01_size_B")
                else:
                    col.prop(self,"scatter_01_scalex")
                    col.prop(self,"scatter_01_scaley")
                    col.prop(self,"scatter_01_scalez")

                col.label(text=' ')
                col.prop(self,"scatter_01_noise_scaleisrandom")
                if addon_prefs.scatter_01_noise_scaleisrandom == True:
                    col.prop(self,"scatter_01_noise_scaleA")
                    col.prop(self,"scatter_01_noise_scaleB")
                else:
                    col.prop(self,"scatter_01_noise_scale")

                col.prop(self,"scatter_01_noise_randomtext")
                if addon_prefs.scatter_01_noise_randomtext == False:
                    col.prop(self,"scatter_01_noise_depth")
                    col.prop(self,"scatter_01_contrast")
                    col.prop(self,"scatter_01_intensity")
        

 


######################################################################################
######################################################################################
# # # # # # # # # # # #        SCATTER OPERATOR                # # # # # # # # # # # # 
######################################################################################
######################################################################################

class Scatter_OT_Custom01(bpy.types.Operator):
    bl_idname = "scatter.custom01"
    bl_label = "custom scattering operator 01"
    bl_description = ""

    def execute(self, context):

        context = bpy.context
        addon_prefs = context.preferences.addons[__name__].preferences
        scene = context.scene
        A = context.object

        bm = bmesh.new()
        bm.from_mesh(A.data)
        squarearea = sum(f.calc_area() for f in bm.faces)
        bm.free()

        if len(bpy.context.selected_objects) < 2:
            ShowMessageBox("Must have at least two object selected", "Be Careful" ,"ERROR")
            return {'FINISHED'}

        ### ### Naming
        slotname = "Custom1"

        if addon_prefs.scatter_01_type == 'Collection':

            if len(bpy.context.selected_objects) > 2:
                particlename = bpy.context.selected_objects[1].name + " ..."
            else:
                particlename = bpy.context.selected_objects[1].name #BUG why the f does the name change when multiple execution ???
            pref = "SCATTER: ["+slotname+"] ["+particlename+"] v."
            i = 1
            name = "%s%d" % (pref, i)
            while A.modifiers.get(name):    
                i += 1
                name = "%s%d" % (pref, i)
            ### ### add particle system + rename and add selection in new coll
            m = A.modifiers.new(name, type='PARTICLE_SYSTEM')

            A.select_set(state=False)
            for o in bpy.context.selected_objects:
                o_collection = find_collection(bpy.context, o)
                new_collection = make_collection(name , o_collection)
                new_collection.objects.link(o)
                o_collection.objects.unlink(o)
            A.select_set(state=True)

            ### ### particle parameters
            ps = m.particle_system
            ps.name = name
            ps.settings.name = name
            ps.settings.type = 'HAIR'

            ps.settings.render_type = 'COLLECTION'
            ps.settings.instance_collection = bpy.data.collections[name]

            ps.settings.particle_size = addon_prefs.scatter_01_particle_size
            ps.settings.hair_length = 4
            ps.settings.size_random = addon_prefs.scatter_01_size_random

            if addon_prefs.scatter_01_countispersquare == True:
                ps.settings.count = addon_prefs.scatter_01_countpersquare * squarearea
            else:
                ps.settings.count = addon_prefs.scatter_01_count

            if addon_prefs.scatter_01_seed_is_random == True:
                bpy.context.object.particle_systems[name].seed = random.randint(0,10000)
            else:
                bpy.context.object.particle_systems[name].seed = addon_prefs.scatter_01_seed
            ps.settings.display_percentage = addon_prefs.scatter_01_display_percentage
            ps.settings.use_advanced_hair = True
            ps.settings.use_rotations = True
            ps.settings.rotation_factor_random = 1
            ps.settings.phase_factor = 1
            ps.settings.phase_factor_random = addon_prefs.scatter_01_phase_factor_random
            ### ### texture parameters
            bpy.ops.texture.new()
            texturename = name
            bpy.data.textures[-1].name = texturename
            bpy.data.textures[texturename].type = 'CLOUDS' #LATER SUPPORT OTHER NOISE TYPE AND OPTIONS
            if addon_prefs.scatter_01_noise_scaleisrandom == True:
                bpy.data.textures[texturename].noise_scale = round(random.uniform(addon_prefs.scatter_01_noise_scaleA,addon_prefs.scatter_01_noise_scaleB), 2)
            else:
                bpy.data.textures[texturename].noise_scale = addon_prefs.scatter_01_noise_scale

            if addon_prefs.scatter_01_noise_randomtext == True:
                bpy.data.textures[texturename].noise_depth = random.randint(0,5)
                bpy.data.textures[texturename].contrast = round(random.uniform(1.5,5),2)
                bpy.data.textures[texturename].intensity = round(random.uniform(0.3,0.9),2)
            else:
                bpy.data.textures[texturename].noise_depth = addon_prefs.scatter_01_noise_depth 
                bpy.data.textures[texturename].contrast = addon_prefs.scatter_01_contrast 
                bpy.data.textures[texturename].intensity = addon_prefs.scatter_01_intensity 

            ps.settings.texture_slots.add().texture = bpy.data.textures[texturename]
            ps.settings.texture_slots[0].blend_type = 'MULTIPLY'
            ps.settings.texture_slots[0].use_map_time = False
            ps.settings.texture_slots[0].use_map_density = True
            ps.settings.texture_slots[0].density_factor = addon_prefs.scatter_01_density_factor
            ps.settings.texture_slots[0].use_map_length = True 
            ps.settings.texture_slots[0].length_factor = addon_prefs.scatter_01_length_factor
            ps.settings.texture_slots[0].texture_coords = 'GLOBAL'
            
            if addon_prefs.scatter_01_size_is_random == False:
                ps.settings.texture_slots[0].scale[1] = addon_prefs.scatter_01_scalex
                ps.settings.texture_slots[0].scale[2] = addon_prefs.scatter_01_scaley
                ps.settings.texture_slots[0].scale[0] = addon_prefs.scatter_01_scalez
            else:
                randomn =round(random.uniform(addon_prefs.scatter_01_size_A,addon_prefs.scatter_01_size_B), 2)
                ps.settings.texture_slots[0].scale[1] = randomn
                ps.settings.texture_slots[0].scale[2] = randomn
                ps.settings.texture_slots[0].scale[0] = randomn

            if addon_prefs.scatter_01_offset_is_random == False:
                ps.settings.texture_slots[0].offset[1] = addon_prefs.scatter_01_offsetx
                ps.settings.texture_slots[0].offset[2] = addon_prefs.scatter_01_offsety
                ps.settings.texture_slots[0].offset[0] = addon_prefs.scatter_01_offsetz
            else:
                randomno =round(random.uniform(addon_prefs.scatter_01_offset_A,addon_prefs.scatter_01_offset_B), 2)
                ps.settings.texture_slots[0].offset[1] = randomno
                ps.settings.texture_slots[0].offset[2] = randomno
                ps.settings.texture_slots[0].offset[0] = randomno
            return {'FINISHED'}

        else:
            A.select_set(state=False)
            for ob in bpy.context.selected_objects:
                particlename = ob.name

                pref = "SCATTER: ["+slotname+"] ["+particlename+"] v."
                i = 1
                name = "%s%d" % (pref, i)
                while A.modifiers.get(name):    
                    i += 1
                    name = "%s%d" % (pref, i)
                ### ### add particle system + rename and add selection in new coll
                m = A.modifiers.new(name, type='PARTICLE_SYSTEM')

                ps = m.particle_system
                ps.name = name
                ps.settings.name = name
                ps.settings.type = 'HAIR'

                ps.settings.render_type = 'OBJECT'
                ps.settings.instance_object = bpy.data.objects[ob.name]
                
                ps.settings.particle_size = addon_prefs.scatter_01_particle_size
                ps.settings.hair_length = 4
                ps.settings.size_random = addon_prefs.scatter_01_size_random

                if addon_prefs.scatter_01_countispersquare == True:
                    ps.settings.count = addon_prefs.scatter_01_countpersquare * squarearea
                else:
                    ps.settings.count = addon_prefs.scatter_01_count

                if addon_prefs.scatter_01_seed_is_random == True:
                    bpy.context.object.particle_systems[name].seed = random.randint(0,10000)
                else:
                    bpy.context.object.particle_systems[name].seed = addon_prefs.scatter_01_seed
                ps.settings.display_percentage = addon_prefs.scatter_01_display_percentage
                ps.settings.use_advanced_hair = True
                ps.settings.use_rotations = True
                ps.settings.rotation_factor_random = 1
                ps.settings.phase_factor = 1
                ps.settings.phase_factor_random = addon_prefs.scatter_01_phase_factor_random
                ### ### texture parameters
                bpy.ops.texture.new()
                texturename = name
                bpy.data.textures[-1].name = texturename
                bpy.data.textures[texturename].type = 'CLOUDS' #LATER SUPPORT OTHER NOISE TYPE AND OPTIONS
                if addon_prefs.scatter_01_noise_scaleisrandom == True:
                    bpy.data.textures[texturename].noise_scale = round(random.uniform(addon_prefs.scatter_01_noise_scaleA,addon_prefs.scatter_01_noise_scaleB), 2)
                else:
                    bpy.data.textures[texturename].noise_scale = addon_prefs.scatter_01_noise_scale

                if addon_prefs.scatter_01_noise_randomtext == True:
                    bpy.data.textures[texturename].noise_depth = random.randint(0,5)
                    bpy.data.textures[texturename].contrast = round(random.uniform(1.5,5),2)
                    bpy.data.textures[texturename].intensity = round(random.uniform(0.3,0.9),2)
                else:
                    bpy.data.textures[texturename].noise_depth = addon_prefs.scatter_01_noise_depth 
                    bpy.data.textures[texturename].contrast = addon_prefs.scatter_01_contrast 
                    bpy.data.textures[texturename].intensity = addon_prefs.scatter_01_intensity 

                ps.settings.texture_slots.add().texture = bpy.data.textures[texturename]
                ps.settings.texture_slots[0].blend_type = 'MULTIPLY'
                ps.settings.texture_slots[0].use_map_time = False
                ps.settings.texture_slots[0].use_map_density = True
                ps.settings.texture_slots[0].density_factor = addon_prefs.scatter_01_density_factor
                ps.settings.texture_slots[0].use_map_length = True 
                ps.settings.texture_slots[0].length_factor = addon_prefs.scatter_01_length_factor
                ps.settings.texture_slots[0].texture_coords = 'GLOBAL'

                if addon_prefs.scatter_01_size_is_random == False:
                    ps.settings.texture_slots[0].scale[1] = addon_prefs.scatter_01_scalex
                    ps.settings.texture_slots[0].scale[2] = addon_prefs.scatter_01_scaley
                    ps.settings.texture_slots[0].scale[0] = addon_prefs.scatter_01_scalez
                else:
                    randomn =round(random.uniform(addon_prefs.scatter_01_size_A,addon_prefs.scatter_01_size_B), 2)
                    ps.settings.texture_slots[0].scale[1] = randomn
                    ps.settings.texture_slots[0].scale[2] = randomn
                    ps.settings.texture_slots[0].scale[0] = randomn

                if addon_prefs.scatter_01_offset_is_random == False:
                    ps.settings.texture_slots[0].offset[1] = addon_prefs.scatter_01_offsetx
                    ps.settings.texture_slots[0].offset[2] = addon_prefs.scatter_01_offsety
                    ps.settings.texture_slots[0].offset[0] = addon_prefs.scatter_01_offsetz
                else:
                    randomno =round(random.uniform(addon_prefs.scatter_01_offset_A,addon_prefs.scatter_01_offset_B), 2)
                    ps.settings.texture_slots[0].offset[1] = randomno
                    ps.settings.texture_slots[0].offset[2] = randomno
                    ps.settings.texture_slots[0].offset[0] = randomno

            A.select_set(state=True)
            return {'FINISHED'}

        return {'FINISHED'}

######################################################################################
######################################################################################
# # # # # # # # # # # #            N MENU                      # # # # # # # # # # # # 
######################################################################################
######################################################################################

class Scatter_PT_ScatteringPanel(bpy.types.Panel):
    bl_idname = "Scatter_PT_ScatteringPanel"
    bl_label = "Scatter"
    bl_category = "Scatter"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout
        bigbutton = layout.column()
        bigbutton.scale_y=2
        bigbutton.operator('scatter.custom01', text="SCATTER",  icon='PLAY')

######################################################################################
######################################################################################
# # # # # # # # # # # #                 REG                    # # # # # # # # # # # # 
######################################################################################
######################################################################################

def register():
    bpy.utils.register_class(ScatterPref)
    bpy.utils.register_class(Scatter_OT_Custom01)
    bpy.utils.register_class(Scatter_PT_ScatteringPanel)

def unregister():
    bpy.utils.unregister_class(ScatterPref)
    bpy.utils.unregister_class(Scatter_OT_Custom01)
    bpy.utils.unregister_class(Scatter_PT_ScatteringPanel)

if __name__ == "__main__":
    register()
    #write ops to do on script exec

Scatter_v01.py (27.0 KB)

9 Likes

I didn’t have the time to test it again, but is it using particles or is it instances? Instances will cripple the viewport very quickly and at least with particles they can be represented as bounding boxes

1 Like

This is particle systems

2 Likes

Scatter_v01.py (31.0 KB)

custom

added support for band noise texture with the turbulence option. Is there really no way to adjust the rotation of thoses within the texture editor ? really ?

also started to work on the implementation of multiple custom slots.
there’s going to be 5 custom slots with thoses settings

Annotation%202019-09-03%20002603

i also planned an automatic particle recognition mode. Parameters dedicated per particles assets based on their respective names. You’ll set up how you want this asset to be scattered once in the addon pref and it will be done automatically when the scater operator is set on auto. Lots of work ahead. It’s easy to code, the hard part is the custom slot storage system. I need to find a way to keep the code short and avoiding repetition of large chunks of the code. Ouch…

be aware, i encountered a small problem with reg/unreg when enabling/disabling/re-enabling the addon. there’s going to be a weird error message. Just ignore and re-enable it.

please let me know of potential bugs.

8 Likes

This already looks really awesome :slight_smile:

Would it make sense to have a list of particle objects and maybe even emitter objects in the addon’s UI. That way, the scattered objects are easily interchangable and you could scatter across separate surfaces using the same particle system parameters.

Maybe this could be added at a later stage when all the core functionalities are implemented.

1 Like

Excellent idea and script , well done :grinning:

2 Likes

I am going to do an article and a video for most essential addons for architectural design for Blender 2.8 (an update of this video now that i know how to do voice overs properly). I would like to mention this addon and I am wondering whether you have an idea of a release timeframe and type of release (free or paid)?

1 Like

waouw !

a first version will be avaible within 3 weeks maximum with 5 custom scattering slots, an automatic slots, and basic quick adjustment sliders in the N panel. Is that timeframe too much for the videos ? The beta will alwyas be here anyways. I’d really appreciate some publicity. :slight_smile:

But the problem is that i want this addon to be much bigger than that. Adding more operators and features.

For example batch-adjusting multiples particles system, save to addon pref easely from properties, export and import addon prefs, add default prefs as good looking as the render on the first post for famous product like the gras essentials or graswald, automatic weight vertex distribution for cameras, pathways from bezier curves, edges detection, automatic terrain displacement, a fully custom proxy system, and point cloud proxies… ect… But with the help of other devs that i’m sure will be happy to join thep project. so the full finished project will take months. But beta versions and the first version will be as effective.

7 Likes

Dude, that’s awesome!
I’d recommend you to release the addon on Gumroad/BlenderMarket, so there will be a way to support you.

1 Like