-
meta-androcto authoredmeta-androcto authored
add_mesh_aggregate.py 9.90 KiB
# ##### 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 #####
# Simple aggregate of particles / meshes
# Copy the selected objects on the active object
# Based on the position of the cursor and a defined volume
# Allows to control growth by using a Build modifier
bl_info = {
"name": "Aggregate Mesh",
"author": "liero",
"version": (0, 0, 5),
"blender": (2, 7, 0),
"location": "View3D > Tool Shelf",
"description": "Adds geometry to a mesh like in DLA aggregators",
"category": "Object"}
import bpy
import bmesh
from random import (
choice,
gauss,
seed,
)
from mathutils import Matrix
from bpy.props import (
BoolProperty,
FloatProperty,
IntProperty,
)
from bpy.types import Operator
def use_random_seed(self):
seed(self.rSeed)
return
def rg(n):
return (round(gauss(0, n), 2))
def remover(sel=False):
bpy.ops.object.editmode_toggle()
if sel:
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold=0.0001)
bpy.ops.object.mode_set()
class OBJECT_OT_agregate_mesh(Operator):
bl_idname = "object.agregate_mesh"
bl_label = "Aggregate"
bl_description = ("Adds geometry to a mesh like in DLA aggregators\n"
"Needs at least two selected Mesh objects")
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
volX = FloatProperty(
name="Volume X",
min=0.1, max=25,
default=3,
description="The cloud around cursor"
)
volY = FloatProperty(
name="Volume Y",
min=0.1, max=25,
default=3,
description="The cloud around cursor"
)
volZ = FloatProperty(
name="Volume Z",
min=0.1, max=25,
default=3,
description="The cloud around cursor"
)
baseSca = FloatProperty(
name="Scale",
min=0.01, max=5,
default=.25,
description="Particle Scale"
)
varSca = FloatProperty(
name="Var",
min=0, max=1,
default=0,
description="Particle Scale Variation"
)
rotX = FloatProperty(
name="Rot Var X",
min=0, max=2,
default=0,
description="X Rotation Variation"
)
rotY = FloatProperty(
name="Rot Var Y",
min=0, max=2,
default=0,
description="Y Rotation Variation"
)
rotZ = FloatProperty(
name="Rot Var Z",
min=0, max=2,
default=1,
description="Z Rotation Variation"
)
rSeed = IntProperty(
name="Random seed",
min=0, max=999999,
default=1,
description="Seed to feed random values"
)
numP = IntProperty(
name="Number",
min=1,
max=9999, soft_max=500,
default=50,
description="Number of particles"
)
nor = BoolProperty(
name="Normal Oriented",
default=False,
description="Align Z axis with Faces normals"
)
cent = BoolProperty(
name="Use Face Center",
default=False,
description="Center on Faces"
)
track = BoolProperty(
name="Cursor Follows",
default=False,
description="Cursor moves as structure grows / more compact results"
)
anim = BoolProperty(
name="Animatable",
default=False,
description="Sort faces so you can regrow with Build Modifier, materials are lost"
)
refresh = BoolProperty(
name="Update",
default=False
)
auto_refresh = BoolProperty(
name="Auto",
description="Auto update spline",
default=False
)
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
row = col.row(align=True)
if self.auto_refresh is False:
self.refresh = False
elif self.auto_refresh is True:
self.refresh = True
row.prop(self, "auto_refresh", toggle=True, icon="AUTO")
row.prop(self, "refresh", toggle=True, icon="FILE_REFRESH")
col = layout.column(align=True)
col.separator()
col = layout.column(align=True)
col.prop(self, "volX", slider=True)
col.prop(self, "volY", slider=True)
col.prop(self, "volZ", slider=True)
layout.label(text="Particles:")
col = layout.column(align=True)
col.prop(self, "baseSca", slider=True)
col.prop(self, "varSca", slider=True)
col = layout.column(align=True)
col.prop(self, "rotX", slider=True)
col.prop(self, "rotY", slider=True)
col.prop(self, "rotZ", slider=True)
col = layout.column(align=True)
col.prop(self, "rSeed", slider=False)
col.prop(self, "numP")
row = layout.row(align=True)
row.prop(self, "nor")
row.prop(self, "cent")
row = layout.row(align=True)
row.prop(self, "track")
row.prop(self, "anim")
@classmethod
def poll(cls, context):
return (len(bpy.context.selected_objects) > 1 and
bpy.context.object.type == 'MESH')
def invoke(self, context, event):
self.refresh = True
return self.execute(context)
def execute(self, context):
if not self.refresh:
return {'PASS_THROUGH'}
scn = bpy.context.scene
obj = bpy.context.active_object
use_random_seed(self)
mat = Matrix((
(1, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 1, 0),
(0, 0, 0, 1))
)
if obj.matrix_world != mat:
self.report({'WARNING'},
"Please, Apply transformations to Active Object first")
return{'FINISHED'}
par = [o for o in bpy.context.selected_objects if o.type == 'MESH' and o != obj]
if not par:
return{'FINISHED'}
bpy.ops.object.mode_set()
bpy.ops.object.select_all(action='DESELECT')
obj.select = True
msv = []
for i in range(len(obj.modifiers)):
msv.append(obj.modifiers[i].show_viewport)
obj.modifiers[i].show_viewport = False
cur = scn.cursor_location
for i in range(self.numP):
mes = choice(par).data
newobj = bpy.data.objects.new('nuevo', mes)
scn.objects.link(newobj)
origen = (rg(self.volX) + cur[0], rg(self.volY) + cur[1], rg(self.volZ) + cur[2])
cpom = obj.closest_point_on_mesh(origen)
if self.cent:
bm = bmesh.new()
bm.from_mesh(obj.data)
if hasattr(bm.verts, "ensure_lookup_table"):
bm.verts.ensure_lookup_table()
bm.faces.ensure_lookup_table()
newobj.location = bm.faces[cpom[3]].calc_center_median()
bm.free()
else:
newobj.location = cpom[1]
if self.nor:
newobj.rotation_mode = 'QUATERNION'
newobj.rotation_quaternion = cpom[1].to_track_quat('Z', 'Y')
newobj.rotation_mode = 'XYZ'
newobj.rotation_euler[0] += rg(self.rotX)
newobj.rotation_euler[1] += rg(self.rotY)
newobj.rotation_euler[2] += rg(self.rotZ)
else:
newobj.rotation_euler = (rg(self.rotX), rg(self.rotY), rg(self.rotZ))
newobj.scale = [self.baseSca + self.baseSca * rg(self.varSca)] * 3
if self.anim:
newobj.select = True
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', obdata=True)
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
bme = bmesh.new()
bme.from_mesh(obj.data)
tmp = bmesh.new()
tmp.from_mesh(newobj.data)
for f in tmp.faces:
# z = len(bme.verts)
for v in f.verts:
bme.verts.new(list(v.co))
bme.faces.new(bme.verts[-len(f.verts):])
bme.to_mesh(obj.data)
remover(True)
# Note: foo.user_clear() is deprecated use do_unlink=True instead
bpy.data.meshes.remove(newobj.data, do_unlink=True)
else:
scn.objects.active = obj
newobj.select = True
bpy.ops.object.join()
if self.track:
cur = scn.cursor_location = cpom[1]
for i in range(len(msv)):
obj.modifiers[i].show_viewport = msv[i]
for o in par:
o.select = True
obj.select = True
if self.auto_refresh is False:
self.refresh = False
return{'FINISHED'}
def register():
bpy.utils.register_class(OBJECT_OT_agregate_mesh)
def unregister():
bpy.utils.unregister_class(OBJECT_OT_agregate_mesh)
if __name__ == '__main__':
register()