OO/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY

4281 lines
166 KiB
Python

# ##### 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 #####
# ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
# ------------------------------- version 0.84 ------------------------------- #
# #
# Creates duplicates of selected mesh to active morphing the shape according #
# to target faces. #
# #
# (c) Alessandro Zomparelli #
# (2017) #
# #
# http://www.co-de-it.com/ #
# #
# ############################################################################ #
import bpy
from bpy.types import (
Operator,
Panel,
PropertyGroup,
)
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
IntProperty,
StringProperty,
PointerProperty
)
from mathutils import Vector
import numpy as np
from math import *
import random, time, copy
import bmesh
from .utils import *
def anim_tessellate_active(self, context):
ob = context.object
props = ob.tissue_tessellate
if not (props.bool_lock or props.bool_hold):
try:
props.generator.name
props.component.name
bpy.ops.object.tissue_update_tessellate()
except: pass
def anim_tessellate_object(ob):
try:
#bpy.context.view_layer.objects.active = ob
bpy.ops.object.tissue_update_tessellate()
except:
return None
#from bpy.app.handlers import persistent
def anim_tessellate(scene):
try:
active_object = bpy.context.object
old_mode = bpy.context.object.mode
selected_objects = bpy.context.selected_objects
except: active_object = old_mode = selected_objects = None
if old_mode in ('OBJECT', 'PAINT_WEIGHT'):
update_objects = []
for ob in scene.objects:
if ob.tissue_tessellate.bool_run and not ob.tissue_tessellate.bool_lock:
if ob not in update_objects: update_objects.append(ob)
update_objects = list(reversed(update_dependencies(ob, update_objects)))
for ob in update_objects:
override = {'object': ob}
'''
win = bpy.data.window_managers[0].windows[0]#bpy.context.window
scr = win.screen
areas3d = [area for area in scr.areas if area.type == 'VIEW_3D']
region = [region for region in areas3d[0].regions if region.type == 'WINDOW']
override = {
'window':win,
'screen':scr,
'area' :areas3d[0],
'region':region[0],
'scene' :scene,
'object': ob
}
'''
print(override)
bpy.ops.object.tissue_update_tessellate(override)
# restore selected objects
if old_mode != None:
for o in scene.objects:
if not o.hide_viewport: o.select_set(o in selected_objects)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.mode_set(mode=old_mode)
return
def set_tessellate_handler(self, context):
old_handlers = []
blender_handlers = bpy.app.handlers.frame_change_post
for h in blender_handlers:
if "anim_tessellate" in str(h):
old_handlers.append(h)
for h in old_handlers: blender_handlers.remove(h)
for o in context.scene.objects:
if o.tissue_tessellate.bool_run:
blender_handlers.append(anim_tessellate)
break
return
class tissue_tessellate_prop(PropertyGroup):
bool_lock : BoolProperty(
name="Lock",
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.",
default=False
)
bool_hold : BoolProperty(
name="Hold",
description="Wait...",
default=False
)
bool_dependencies : BoolProperty(
name="Update Dependencies",
description="Automatically updates base and components as well, if results of other tessellations",
default=False
)
bool_run : BoolProperty(
name="Animatable Tessellation",
description="Automatically recompute the tessellation when the frame is changed. Currently is not working during Render Animation",
default = False,
update = set_tessellate_handler
)
zscale : FloatProperty(
name="Scale", default=1, soft_min=0, soft_max=10,
description="Scale factor for the component thickness",
update = anim_tessellate_active
)
scale_mode : EnumProperty(
items=(
('CONSTANT', "Constant", "Uniform thinkness"),
('ADAPTIVE', "Relative", "Preserve component's proportions")
),
default='ADAPTIVE',
name="Z-Scale according to faces size",
update = anim_tessellate_active
)
offset : FloatProperty(
name="Surface Offset",
default=1,
min=-1,
max=1,
soft_min=-1,
soft_max=1,
description="Surface offset",
update = anim_tessellate_active
)
mode : EnumProperty(
items=(
('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
default='BOUNDS',
name="Component Mode",
update = anim_tessellate_active
)
rotation_mode : EnumProperty(
items=(('RANDOM', "Random", "Random faces rotation"),
('UV', "Active UV", "Rotate according to UV coordinates"),
('WEIGHT', "Active Weight", "Rotate according to Vertex Group gradient"),
('DEFAULT', "Default", "Default rotation")),
default='DEFAULT',
name="Component Rotation",
update = anim_tessellate_active
)
rotation_direction : EnumProperty(
items=(('ORTHO', "Orthogonal", "Component main directions in XY"),
('DIAG', "Diagonal", "Component main direction aligned with diagonal")),
default='ORTHO',
name="Direction",
update = anim_tessellate_active
)
rotation_shift : IntProperty(
name="Shift",
default=0,
soft_min=0,
soft_max=3,
description="Shift components rotation",
update = anim_tessellate_active
)
fill_mode : EnumProperty(
items=(
('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
('PATCH', 'Patch', 'Curved tessellation according to the last ' +
'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
'patches.\nAfter the last Subsurf (or Multires) only ' +
'deformation\nmodifiers can be used'),
('FRAME', 'Frame', 'Essellation along the edges of each face')),
default='QUAD',
name="Fill Mode",
update = anim_tessellate_active
)
combine_mode : EnumProperty(
items=(
('LAST', 'Last', 'Show only the last iteration'),
('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
('ALL', 'All', 'Combine the result of all iterations')),
default='LAST',
name="Combine Mode",
update = anim_tessellate_active
)
gen_modifiers : BoolProperty(
name="Generator Modifiers",
default=False,
description="Apply Modifiers and Shape Keys to the base object",
update = anim_tessellate_active
)
com_modifiers : BoolProperty(
name="Component Modifiers",
default=False,
description="Apply Modifiers and Shape Keys to the component object",
update = anim_tessellate_active
)
merge : BoolProperty(
name="Merge",
default=False,
description="Merge vertices in adjacent duplicates",
update = anim_tessellate_active
)
merge_thres : FloatProperty(
name="Distance",
default=0.001,
soft_min=0,
soft_max=10,
description="Limit below which to merge vertices",
update = anim_tessellate_active
)
generator : PointerProperty(
type=bpy.types.Object,
name="",
description="Base object for the tessellation",
update = anim_tessellate_active
)
component : PointerProperty(
type=bpy.types.Object,
name="",
description="Component object for the tessellation",
#default="",
update = anim_tessellate_active
)
bool_random : BoolProperty(
name="Randomize",
default=False,
description="Randomize component rotation",
update = anim_tessellate_active
)
random_seed : IntProperty(
name="Seed",
default=0,
soft_min=0,
soft_max=10,
description="Random seed",
update = anim_tessellate_active
)
bool_vertex_group : BoolProperty(
name="Map Vertex Group",
default=False,
description="Transfer all Vertex Groups from Base object",
update = anim_tessellate_active
)
bool_selection : BoolProperty(
name="On selected Faces",
default=False,
description="Create Tessellation only on selected faces",
update = anim_tessellate_active
)
bool_shapekeys : BoolProperty(
name="Use Shape Keys",
default=False,
description="Transfer Component's Shape Keys. If the name of Vertex "
"Groups and Shape Keys are the same, they will be "
"automatically combined",
update = anim_tessellate_active
)
bool_smooth : BoolProperty(
name="Smooth Shading",
default=False,
description="Output faces with smooth shading rather than flat shaded",
update = anim_tessellate_active
)
bool_materials : BoolProperty(
name="Transfer Materials",
default=False,
description="Preserve component's materials",
update = anim_tessellate_active
)
bool_material_id : BoolProperty(
name="Tessellation on Material ID",
default=False,
description="Apply the component only on the selected Material",
update = anim_tessellate_active
)
material_id : IntProperty(
name="Material ID",
default=0,
min=0,
description="Material ID",
update = anim_tessellate_active
)
bool_dissolve_seams : BoolProperty(
name="Dissolve Seams",
default=False,
description="Dissolve all seam edges",
update = anim_tessellate_active
)
iterations : IntProperty(
name="Iterations",
default=1,
min=1,
soft_max=5,
description="Automatically repeat the Tessellation using the "
+ "generated geometry as new base object.\nUsefull for "
+ "for branching systems. Dangerous!",
update = anim_tessellate_active
)
bool_combine : BoolProperty(
name="Combine unused",
default=False,
description="Combine the generated geometry with unused faces",
update = anim_tessellate_active
)
bool_advanced : BoolProperty(
name="Advanced Settings",
default=False,
description="Show more settings"
)
normals_mode : EnumProperty(
items=(
('VERTS', 'Normals', 'Consistent direction based on vertices normal'),
('FACES', 'Individual Faces', 'Based on individual faces normal'),
('CUSTOM', 'Custom', "According to Base object's shape keys")),
default='VERTS',
name="Direction",
update = anim_tessellate_active
)
bool_multi_components : BoolProperty(
name="Multi Components",
default=False,
description="Combine different components according to materials name",
update = anim_tessellate_active
)
error_message : StringProperty(
name="Error Message",
default=""
)
warning_message : StringProperty(
name="Warning Message",
default=""
)
bounds_x : EnumProperty(
items=(
('EXTEND', 'Extend', 'Default X coordinates'),
('CLIP', 'Clip', 'Trim out of bounds in X direction'),
('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
default='EXTEND',
name="Bounds X",
update = anim_tessellate_active
)
bounds_y : EnumProperty(
items=(
('EXTEND', 'Extend', 'Default Y coordinates'),
('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
default='EXTEND',
name="Bounds Y",
update = anim_tessellate_active
)
close_mesh : EnumProperty(
items=(
('NONE', 'None', 'Keep the mesh open'),
('CAP', 'Cap Holes', 'Automatically cap open loops'),
('BRIDGE', 'Bridge Loops', 'Automatically bridge loop pairs')),
default='NONE',
name="Close Mesh",
update = anim_tessellate_active
)
cap_faces : BoolProperty(
name="Cap Holes",
default=False,
description="Cap open edges loops",
update = anim_tessellate_active
)
frame_boundary : BoolProperty(
name="Frame Boundary",
default=False,
description="Support face boundaries",
update = anim_tessellate_active
)
fill_frame : BoolProperty(
name="Fill Frame",
default=False,
description="Fill inner faces with Fan tessellation",
update = anim_tessellate_active
)
frame_boundary_mat : IntProperty(
name="Material Offset",
default=0,
description="Material Offset for boundaries",
update = anim_tessellate_active
)
fill_frame_mat : IntProperty(
name="Material Offset",
default=0,
description="Material Offset for inner faces",
update = anim_tessellate_active
)
open_edges_crease : FloatProperty(
name="Open Edges Crease",
default=0,
min=0,
max=1,
description="Automatically set crease for open edges",
update = anim_tessellate_active
)
bridge_smoothness : FloatProperty(
name="Smoothness",
default=1,
min=0,
max=1,
description="Bridge Smoothness",
update = anim_tessellate_active
)
frame_thickness : FloatProperty(
name="Frame Thickness",
default=0.2,
min=0,
soft_max=2,
description="Frame Thickness",
update = anim_tessellate_active
)
frame_mode : EnumProperty(
items=(
('CONSTANT', 'Constant', 'Even thickness'),
('RELATIVE', 'Relative', 'Frame offset depends on face areas')),
default='CONSTANT',
name="Offset",
update = anim_tessellate_active
)
bridge_cuts : IntProperty(
name="Cuts",
default=0,
min=0,
max=20,
description="Bridge Cuts",
update = anim_tessellate_active
)
cap_material_index : IntProperty(
name="Material",
default=0,
min=0,
description="Material index for the cap/bridge faces",
update = anim_tessellate_active
)
patch_subs : IntProperty(
name="Patch Subdivisions",
default=1,
min=0,
description="Subdivisions levels for Patch tessellation after the first iteration",
update = anim_tessellate_active
)
def store_parameters(operator, ob):
ob.tissue_tessellate.bool_hold = True
ob.tissue_tessellate.bool_lock = operator.bool_lock
ob.tissue_tessellate.bool_dependencies = operator.bool_dependencies
ob.tissue_tessellate.generator = bpy.data.objects[operator.generator]
ob.tissue_tessellate.component = bpy.data.objects[operator.component]
ob.tissue_tessellate.zscale = operator.zscale
ob.tissue_tessellate.offset = operator.offset
ob.tissue_tessellate.gen_modifiers = operator.gen_modifiers
ob.tissue_tessellate.com_modifiers = operator.com_modifiers
ob.tissue_tessellate.mode = operator.mode
ob.tissue_tessellate.rotation_mode = operator.rotation_mode
ob.tissue_tessellate.rotation_shift = operator.rotation_shift
ob.tissue_tessellate.rotation_direction = operator.rotation_direction
ob.tissue_tessellate.merge = operator.merge
ob.tissue_tessellate.merge_thres = operator.merge_thres
ob.tissue_tessellate.scale_mode = operator.scale_mode
ob.tissue_tessellate.bool_random = operator.bool_random
ob.tissue_tessellate.random_seed = operator.random_seed
ob.tissue_tessellate.fill_mode = operator.fill_mode
ob.tissue_tessellate.bool_vertex_group = operator.bool_vertex_group
ob.tissue_tessellate.bool_selection = operator.bool_selection
ob.tissue_tessellate.bool_shapekeys = operator.bool_shapekeys
ob.tissue_tessellate.bool_smooth = operator.bool_smooth
ob.tissue_tessellate.bool_materials = operator.bool_materials
ob.tissue_tessellate.bool_material_id = operator.bool_material_id
ob.tissue_tessellate.material_id = operator.material_id
ob.tissue_tessellate.bool_dissolve_seams = operator.bool_dissolve_seams
ob.tissue_tessellate.iterations = operator.iterations
ob.tissue_tessellate.bool_advanced = operator.bool_advanced
ob.tissue_tessellate.normals_mode = operator.normals_mode
ob.tissue_tessellate.bool_combine = operator.bool_combine
ob.tissue_tessellate.bool_multi_components = operator.bool_multi_components
ob.tissue_tessellate.combine_mode = operator.combine_mode
ob.tissue_tessellate.bounds_x = operator.bounds_x
ob.tissue_tessellate.bounds_y = operator.bounds_y
ob.tissue_tessellate.cap_faces = operator.cap_faces
ob.tissue_tessellate.close_mesh = operator.close_mesh
ob.tissue_tessellate.bridge_cuts = operator.bridge_cuts
ob.tissue_tessellate.bridge_smoothness = operator.bridge_smoothness
ob.tissue_tessellate.frame_thickness = operator.frame_thickness
ob.tissue_tessellate.frame_mode = operator.frame_mode
ob.tissue_tessellate.frame_boundary = operator.frame_boundary
ob.tissue_tessellate.fill_frame = operator.fill_frame
ob.tissue_tessellate.frame_boundary_mat = operator.frame_boundary_mat
ob.tissue_tessellate.fill_frame_mat = operator.fill_frame_mat
ob.tissue_tessellate.cap_material_index = operator.cap_material_index
ob.tissue_tessellate.patch_subs = operator.patch_subs
ob.tissue_tessellate.bool_hold = False
return ob
def load_parameters(operator, ob):
operator.bool_lock = ob.tissue_tessellate.bool_lock
operator.bool_dependencies = ob.tissue_tessellate.bool_dependencies
operator.generator = ob.tissue_tessellate.generator.name
operator.component = ob.tissue_tessellate.component.name
operator.zscale = ob.tissue_tessellate.zscale
operator.offset = ob.tissue_tessellate.offset
operator.gen_modifiers = ob.tissue_tessellate.gen_modifiers
operator.com_modifiers = ob.tissue_tessellate.com_modifiers
operator.mode = ob.tissue_tessellate.mode
operator.rotation_mode = ob.tissue_tessellate.rotation_mode
operator.rotation_shift = ob.tissue_tessellate.rotation_shift
operator.rotation_direction = ob.tissue_tessellate.rotation_direction
operator.merge = ob.tissue_tessellate.merge
operator.merge_thres = ob.tissue_tessellate.merge_thres
operator.scale_mode = ob.tissue_tessellate.scale_mode
operator.bool_random = ob.tissue_tessellate.bool_random
operator.random_seed = ob.tissue_tessellate.random_seed
operator.fill_mode = ob.tissue_tessellate.fill_mode
operator.bool_vertex_group = ob.tissue_tessellate.bool_vertex_group
operator.bool_selection = ob.tissue_tessellate.bool_selection
operator.bool_shapekeys = ob.tissue_tessellate.bool_shapekeys
operator.bool_smooth = ob.tissue_tessellate.bool_smooth
operator.bool_materials = ob.tissue_tessellate.bool_materials
operator.bool_material_id = ob.tissue_tessellate.bool_material_id
operator.material_id = ob.tissue_tessellate.material_id
operator.bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams
operator.iterations = ob.tissue_tessellate.iterations
operator.bool_advanced = ob.tissue_tessellate.bool_advanced
operator.normals_mode = ob.tissue_tessellate.normals_mode
operator.bool_combine = ob.tissue_tessellate.bool_combine
operator.bool_multi_components = ob.tissue_tessellate.bool_multi_components
operator.combine_mode = ob.tissue_tessellate.combine_mode
operator.bounds_x = ob.tissue_tessellate.bounds_x
operator.bounds_y = ob.tissue_tessellate.bounds_y
operator.cap_faces = ob.tissue_tessellate.cap_faces
operator.close_mesh = ob.tissue_tessellate.close_mesh
operator.bridge_cuts = ob.tissue_tessellate.bridge_cuts
operator.bridge_smoothness = ob.tissue_tessellate.bridge_smoothness
operator.cap_material_index = ob.tissue_tessellate.cap_material_index
operator.patch_subs = ob.tissue_tessellate.patch_subs
operator.frame_boundary = ob.tissue_tessellate.frame_boundary
operator.fill_frame = ob.tissue_tessellate.fill_frame
operator.frame_boundary_mat = ob.tissue_tessellate.frame_boundary_mat
operator.fill_frame_mat = ob.tissue_tessellate.fill_frame_mat
operator.frame_thickness = ob.tissue_tessellate.frame_thickness
operator.frame_mode = ob.tissue_tessellate.frame_mode
return ob
def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode,
scale_mode, rotation_mode, rotation_shift, rand_seed, bool_vertex_group,
bool_selection, bool_shapekeys, bool_material_id, material_id,
normals_mode, bounds_x, bounds_y):
random.seed(rand_seed)
if normals_mode == 'CUSTOM':
if _ob0.data.shape_keys != None:
ob0_sk = convert_object_to_mesh(_ob0)
me0_sk = ob0_sk.data
key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks]
for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0
else: normals_mode = 'VERTS'
ob0 = convert_object_to_mesh(_ob0)
me0 = ob0.data
# base normals
normals0 = []
if normals_mode == 'CUSTOM':
for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val
for v0, v1 in zip(ob0.data.vertices, me0_sk.vertices):
normals0.append(v1.co - v0.co)
bpy.data.objects.remove(ob0_sk)
else:
ob0.data.update()
normals0 = [v.normal for v in ob0.data.vertices]
# ob0 = convert_object_to_mesh(_ob0)
ob0.name = _ob0.name + "_apply_mod"
me0 = _ob0.data
# Check if zero faces are selected
if _ob0.type == 'MESH':
bool_cancel = True
for p in me0.polygons:
check_sel = check_mat = False
if not bool_selection or p.select: check_sel = True
if not bool_material_id or p.material_index == material_id: check_mat = True
if check_sel and check_mat:
bool_cancel = False
break
if bool_cancel:
bpy.data.meshes.remove(ob0.data)
#bpy.data.objects.remove(ob0)
return 0
levels = 0
sculpt_levels = 0
render_levels = 0
bool_multires = False
multires_name = ""
not_allowed = ['FLUID_SIMULATION', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD',
'DECIMATE', 'EDGE_SPLIT', 'MASK', 'MIRROR', 'REMESH',
'SCREW', 'SOLIDIFY', 'TRIANGULATE', 'WIREFRAME', 'SKIN',
'EXPLODE', 'PARTICLE_INSTANCE', 'PARTICLE_SYSTEM', 'SMOKE']
modifiers0 = list(_ob0.modifiers)#[m for m in ob0.modifiers]
show_modifiers = [m.show_viewport for m in _ob0.modifiers]
show_modifiers.reverse()
modifiers0.reverse()
for m in modifiers0:
visible = m.show_viewport
if not visible: continue
#m.show_viewport = False
if m.type in ('SUBSURF', 'MULTIRES') and visible:
levels = m.levels
multires_name = m.name
if m.type == 'MULTIRES':
bool_multires = True
multires_name = m.name
sculpt_levels = m.sculpt_levels
render_levels = m.render_levels
else: bool_multires = False
break
elif m.type in not_allowed:
bpy.data.meshes.remove(ob0.data)
#bpy.data.meshes.remove(me0)
return "modifiers_error"
before = _ob0.copy()
before.name = _ob0.name + "_before_subs"
bpy.context.collection.objects.link(before)
#if ob0.type == 'MESH': before.data = me0
before_mod = list(before.modifiers)
before_mod.reverse()
for m in before_mod:
if m.type in ('SUBSURF', 'MULTIRES') and m.show_viewport:
before.modifiers.remove(m)
break
else: before.modifiers.remove(m)
before_subsurf = simple_to_mesh(before)
before_bm = bmesh.new()
before_bm.from_mesh(before_subsurf)
before_bm.faces.ensure_lookup_table()
before_bm.edges.ensure_lookup_table()
before_bm.verts.ensure_lookup_table()
error = ""
for f in before_bm.faces:
if len(f.loops) != 4:
error = "topology_error"
break
for e in before_bm.edges:
if len(e.link_faces) == 0:
error = "wires_error"
break
for v in before_bm.verts:
if len(v.link_faces) == 0:
error = "verts_error"
break
if error != "":
bpy.data.meshes.remove(ob0.data)
#bpy.data.meshes.remove(me0)
bpy.data.meshes.remove(before_subsurf)
bpy.data.objects.remove(before)
return error
me0 = ob0.data
verts0 = me0.vertices # Collect generator vertices
if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
# set Shape Keys to zero
if bool_shapekeys or not com_modifiers:
try:
original_key_values = []
for sk in _ob1.data.shape_keys.key_blocks:
original_key_values.append(sk.value)
sk.value = 0
except:
bool_shapekeys = False
if not com_modifiers and not bool_shapekeys:
mod_visibility = []
for m in _ob1.modifiers:
mod_visibility.append(m.show_viewport)
m.show_viewport = False
com_modifiers = True
ob1 = convert_object_to_mesh(_ob1, com_modifiers, False)
me1 = ob1.data
if mode != 'BOUNDS':
ob1.active_shape_key_index = 0
# Bound X
if bounds_x != 'EXTEND':
if mode == 'GLOBAL':
planes_co = ((0,0,0),(1,1,1))
plane_no = (1,0,0)
if mode == 'LOCAL':
planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0)))
plane_no = planes_co[0]-planes_co[1]
bpy.ops.object.mode_set(mode='EDIT')
for co in planes_co:
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
bpy.ops.mesh.mark_seam()
bpy.ops.object.mode_set(mode='OBJECT')
_faces = ob1.data.polygons
if mode == 'GLOBAL':
for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]:
f.select = True
for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]:
f.select = True
else:
for f in [f for f in _faces if f.center.x > 1]:
f.select = True
for f in [f for f in _faces if f.center.x < 0]:
f.select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
if bounds_x == 'CLIP':
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
if bounds_x == 'CYCLIC':
bpy.ops.mesh.split()
bpy.ops.object.mode_set(mode='OBJECT')
# Bound Y
if bounds_y != 'EXTEND':
if mode == 'GLOBAL':
planes_co = ((0,0,0),(1,1,1))
plane_no = (0,1,0)
if mode == 'LOCAL':
planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0)))
plane_no = planes_co[0]-planes_co[1]
bpy.ops.object.mode_set(mode='EDIT')
for co in planes_co:
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
bpy.ops.mesh.mark_seam()
bpy.ops.object.mode_set(mode='OBJECT')
_faces = ob1.data.polygons
if mode == 'GLOBAL':
for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]:
f.select = True
for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]:
f.select = True
else:
for f in [f for f in _faces if f.center.y > 1]:
f.select = True
for f in [f for f in _faces if f.center.y < 0]:
f.select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
if bounds_y == 'CLIP':
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
if bounds_y == 'CYCLIC':
bpy.ops.mesh.split()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='OBJECT')
# Component statistics
n_verts = len(me1.vertices)
# Create empty lists
new_verts = []
new_edges = []
new_faces = []
new_verts_np = np.array(())
# Component bounding box
min_c = Vector((0, 0, 0))
max_c = Vector((0, 0, 0))
first = True
for v in me1.vertices:
vert = v.co
if vert[0] < min_c[0] or first:
min_c[0] = vert[0]
if vert[1] < min_c[1] or first:
min_c[1] = vert[1]
if vert[2] < min_c[2] or first:
min_c[2] = vert[2]
if vert[0] > max_c[0] or first:
max_c[0] = vert[0]
if vert[1] > max_c[1] or first:
max_c[1] = vert[1]
if vert[2] > max_c[2] or first:
max_c[2] = vert[2]
first = False
bb = max_c - min_c
# adaptive XY
verts1 = []
for v in me1.vertices:
if mode == 'BOUNDS':
vert = v.co - min_c # (ob1.matrix_world * v.co) - min_c
vert[0] = vert[0] / bb[0] if bb[0] != 0 else 0.5
vert[1] = vert[1] / bb[1] if bb[1] != 0 else 0.5
vert[2] = vert[2] / bb[2] if bb[2] != 0 else 0
vert[2] = (vert[2] - 0.5 + offset * 0.5) * zscale
elif mode == 'LOCAL':
vert = v.co.xyz
vert[2] *= zscale
#vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
elif mode == 'GLOBAL':
vert = ob1.matrix_world @ v.co
vert[2] *= zscale
try:
for sk in me1.shape_keys.key_blocks:
sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
except: pass
#verts1.append(vert)
v.co = vert
# Bounds X, Y
if mode != 'BOUNDS':
if bounds_x == 'CYCLIC':
move_verts = []
for f in [f for f in me1.polygons if (f.center).x > 1]:
for v in f.vertices:
if v not in move_verts: move_verts.append(v)
for v in move_verts:
me1.vertices[v].co.x -= 1
try:
_ob1.active_shape_key_index = 0
for sk in me1.shape_keys.key_blocks:
sk.data[v].co.x -= 1
except: pass
move_verts = []
for f in [f for f in me1.polygons if (f.center).x < 0]:
for v in f.vertices:
if v not in move_verts: move_verts.append(v)
for v in move_verts:
me1.vertices[v].co.x += 1
try:
_ob1.active_shape_key_index = 0
for sk in me1.shape_keys.key_blocks:
sk.data[v].co.x += 1
except: pass
if bounds_y == 'CYCLIC':
move_verts = []
for f in [f for f in me1.polygons if (f.center).y > 1]:
for v in f.vertices:
if v not in move_verts: move_verts.append(v)
for v in move_verts:
me1.vertices[v].co.y -= 1
try:
_ob1.active_shape_key_index = 0
for sk in me1.shape_keys.key_blocks:
sk.data[v].co.y -= 1
except: pass
move_verts = []
for f in [f for f in me1.polygons if (f.center).y < 0]:
for v in f.vertices:
if v not in move_verts: move_verts.append(v)
for v in move_verts:
me1.vertices[v].co.y += 1
try:
_ob1.active_shape_key_index = 0
for sk in me1.shape_keys.key_blocks:
sk.data[v].co.y += 1
except: pass
verts1 = [v.co for v in me1.vertices]
n_verts1 = len(verts1)
patch_faces = 4**levels
sides = int(sqrt(patch_faces))
step = 1/sides
sides0 = sides-2
patch_faces0 = int((sides-2)**2)
n_patches = int(len(me0.polygons)/patch_faces)
if len(me0.polygons)%patch_faces != 0:
#ob0.data = old_me0
return "topology_error"
new_verts = []
new_edges = []
new_faces = []
for o in bpy.context.view_layer.objects: o.select_set(False)
new_patch = None
# All vertex group
if bool_vertex_group:
try:
weight = []
for vg in ob0.vertex_groups:
_weight = []
for v in me0.vertices:
try:
_weight.append(vg.weight(v.index))
except:
_weight.append(0)
weight.append(_weight)
except:
bool_vertex_group = False
# Adaptive Z
if scale_mode == 'ADAPTIVE':
com_area = bb[0]*bb[1]
if mode != 'BOUNDS' or com_area == 0: com_area = 1
#mult = 1/com_area
verts_area = []
bm = bmesh.new()
bm.from_mesh(me0)
bm.verts.ensure_lookup_table()
for v in bm.verts:
area = 0
faces = v.link_faces
for f in faces:
area += f.calc_area()
area = area/len(faces)*patch_faces/com_area
#area*=mult*
verts_area.append(sqrt(area)*bb[2])
random.seed(rand_seed)
bool_correct = False
_faces = [[[0] for ii in range(sides)] for jj in range(sides)]
_verts = [[[0] for ii in range(sides+1)] for jj in range(sides+1)]
# find relative UV component's vertices
verts1_uv_quads = [0]*len(verts1)
verts1_uv = [0]*len(verts1)
for i, vert in enumerate(verts1):
# grid coordinates
u = int(vert[0]//step)
v = int(vert[1]//step)
u1 = min(u+1, sides)
v1 = min(v+1, sides)
if mode != 'BOUNDS':
if u > sides-1:
u = sides-1
u1 = sides
if u < 0:
u = 0
u1 = 1
if v > sides-1:
v = sides-1
v1 = sides
if v < 0:
v = 0
v1 = 1
verts1_uv_quads[i] = (u,v,u1,v1)
# factor coordinates
fu = (vert[0]-u*step)/step
fv = (vert[1]-v*step)/step
fw = vert.z
# interpolate Z scaling factor
verts1_uv[i] = Vector((fu,fv,fw))
sk_uv_quads = []
sk_uv = []
if bool_shapekeys:
for sk in ob1.data.shape_keys.key_blocks:
source = sk.data
_sk_uv_quads = [0]*len(verts1)
_sk_uv = [0]*len(verts1)
for i, sk_v in enumerate(source):
if mode == 'BOUNDS':
sk_vert = sk_v.co - min_c
sk_vert[0] = (sk_vert[0] / bb[0] if bb[0] != 0 else 0.5)
sk_vert[1] = (sk_vert[1] / bb[1] if bb[1] != 0 else 0.5)
sk_vert[2] = (sk_vert[2] / bb[2] if bb[2] != 0 else sk_vert[2])
sk_vert[2] = (sk_vert[2] - 0.5 + offset * 0.5) * zscale
elif mode == 'LOCAL':
sk_vert = sk_v.co
sk_vert[2] *= zscale
elif mode == 'GLOBAL':
sk_vert = sk_v.co
sk_vert[2] *= zscale
# grid coordinates
u = int(sk_vert[0]//step)
v = int(sk_vert[1]//step)
u1 = min(u+1, sides)
v1 = min(v+1, sides)
if mode != 'BOUNDS':
if u > sides-1:
u = sides-1
u1 = sides
if u < 0:
u = 0
u1 = 1
if v > sides-1:
v = sides-1
v1 = sides
if v < 0:
v = 0
v1 = 1
_sk_uv_quads[i] = (u,v,u1,v1)
# factor coordinates
fu = (sk_vert[0]-u*step)/step
fv = (sk_vert[1]-v*step)/step
fw = sk_vert.z
_sk_uv[i] = Vector((fu,fv,fw))
sk_uv_quads.append(_sk_uv_quads)
sk_uv.append(_sk_uv)
for i in range(n_patches):
poly = me0.polygons[i*patch_faces]
if bool_selection and not poly.select: continue
if bool_material_id and not poly.material_index == material_id: continue
bool_correct = True
new_patch = bpy.data.objects.new("patch", me1.copy())
bpy.context.collection.objects.link(new_patch)
new_patch.select_set(True)
bpy.context.view_layer.objects.active = new_patch
for area in bpy.context.screen.areas:
for space in area.spaces:
try: new_patch.local_view_set(space, True)
except: pass
# Vertex Group
if bool_vertex_group:
for vg in ob0.vertex_groups:
new_patch.vertex_groups.new(name=vg.name)
# find patch faces
faces = _faces.copy()
verts = _verts.copy()
shift1 = sides
shift2 = sides*2-1
shift3 = sides*3-2
for j in range(patch_faces):
if j < patch_faces0:
if levels == 0:
u = j%sides0
v = j//sides0
else:
u = j%sides0+1
v = j//sides0+1
elif j < patch_faces0 + shift1:
u = j-patch_faces0
v = 0
elif j < patch_faces0 + shift2:
u = sides-1
v = j-(patch_faces0 + sides)+1
elif j < patch_faces0 + shift3:
jj = j-(patch_faces0 + shift2)
u = sides-jj-2
v = sides-1
else:
jj = j-(patch_faces0 + shift3)
u = 0
v = sides-jj-2
face = me0.polygons[j+i*patch_faces]
faces[u][v] = face
verts[u][v] = verts0[face.vertices[0]]
if u == sides-1:
verts[sides][v] = verts0[face.vertices[1]]
if v == sides-1:
verts[u][sides] = verts0[face.vertices[3]]
if u == v == sides-1:
verts[sides][sides] = verts0[face.vertices[2]]
# Random rotation
if rotation_mode == 'RANDOM' or rotation_shift != 0:
if rotation_mode == 'RANDOM': rot = random.randint(0, 3)
else: rot = rotation_shift%4
if rot == 1:
verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
elif rot == 2:
verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
elif rot == 3:
verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
# UV rotation
if rotation_mode == 'UV' and ob0.type == 'MESH':
if len(ob0.data.uv_layers) > 0:
uv0 = me0.uv_layers.active.data[faces[0][0].index*4].uv
uv1 = me0.uv_layers.active.data[faces[0][-1].index*4 + 3].uv
uv2 = me0.uv_layers.active.data[faces[-1][-1].index*4 + 2].uv
uv3 = me0.uv_layers.active.data[faces[-1][0].index*4 + 1].uv
v01 = (uv0 + uv1)
v32 = (uv3 + uv2)
v0132 = v32 - v01
v0132.normalize()
v12 = (uv1 + uv2)
v03 = (uv0 + uv3)
v1203 = v03 - v12
v1203.normalize()
vertUV = []
dot1203 = v1203.x
dot0132 = v0132.x
if(abs(dot1203) < abs(dot0132)):
if (dot0132 > 0):
pass
else:
verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
else:
if(dot1203 < 0):
verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
else:
verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
if True:
verts_xyz = np.array([[v.co for v in _verts] for _verts in verts])
#verts_norm = np.array([[v.normal for v in _verts] for _verts in verts])
verts_norm = np.array([[normals0[v.index] for v in _verts] for _verts in verts])
if normals_mode == 'FACES':
verts_norm = np.mean(verts_norm, axis=(0,1))
verts_norm = np.expand_dims(verts_norm, axis=0)
verts_norm = np.repeat(verts_norm,len(verts),axis=0)
verts_norm = np.expand_dims(verts_norm, axis=0)
verts_norm = np.repeat(verts_norm,len(verts),axis=0)
np_verts1_uv = np.array(verts1_uv)
verts1_uv_quads = np.array(verts1_uv_quads)
u = verts1_uv_quads[:,0]
v = verts1_uv_quads[:,1]
u1 = verts1_uv_quads[:,2]
v1 = verts1_uv_quads[:,3]
v00 = verts_xyz[u,v]
v10 = verts_xyz[u1,v]
v01 = verts_xyz[u,v1]
v11 = verts_xyz[u1,v1]
n00 = verts_norm[u,v]
n10 = verts_norm[u1,v]
n01 = verts_norm[u,v1]
n11 = verts_norm[u1,v1]
vx = np_verts1_uv[:,0].reshape((n_verts1,1))
vy = np_verts1_uv[:,1].reshape((n_verts1,1))
vz = np_verts1_uv[:,2].reshape((n_verts1,1))
co2 = np_lerp2(v00,v10,v01,v11,vx,vy)
n2 = np_lerp2(n00,n10,n01,n11,vx,vy)
if scale_mode == 'ADAPTIVE':
areas = np.array([[verts_area[v.index] for v in verts_v] for verts_v in verts])
a00 = areas[u,v].reshape((n_verts1,1))
a10 = areas[u1,v].reshape((n_verts1,1))
a01 = areas[u,v1].reshape((n_verts1,1))
a11 = areas[u1,v1].reshape((n_verts1,1))
# remapped z scale
a2 = np_lerp2(a00,a10,a01,a11,vx,vy)
co3 = co2 + n2 * vz * a2
else:
co3 = co2 + n2 * vz
coordinates = co3.flatten().tolist()
new_patch.data.vertices.foreach_set('co',coordinates)
# vertex groups
if bool_vertex_group:
for _weight, vg in zip(weight, new_patch.vertex_groups):
np_weight = np.array([[_weight[v.index] for v in verts_v] for verts_v in verts])
w00 = np_weight[u,v].reshape((n_verts1,1))
w10 = np_weight[u1,v].reshape((n_verts1,1))
w01 = np_weight[u,v1].reshape((n_verts1,1))
w11 = np_weight[u1,v1].reshape((n_verts1,1))
# remapped z scale
w2 = np_lerp2(w00,w10,w01,w11,vx,vy)
for vert_id in range(n_verts1):
vg.add([vert_id], w2[vert_id], "ADD")
if bool_shapekeys:
for i_sk, sk in enumerate(ob1.data.shape_keys.key_blocks):
np_verts1_uv = np.array(sk_uv[i_sk])
np_sk_uv_quads = np.array(sk_uv_quads[i_sk])
u = np_sk_uv_quads[:,0]
v = np_sk_uv_quads[:,1]
u1 = np_sk_uv_quads[:,2]
v1 = np_sk_uv_quads[:,3]
v00 = verts_xyz[u,v]
v10 = verts_xyz[u1,v]
v01 = verts_xyz[u,v1]
v11 = verts_xyz[u1,v1]
vx = np_verts1_uv[:,0].reshape((n_verts1,1))
vy = np_verts1_uv[:,1].reshape((n_verts1,1))
vz = np_verts1_uv[:,2].reshape((n_verts1,1))
co2 = np_lerp2(v00,v10,v01,v11,vx,vy)
n2 = np_lerp2(n00,n10,n01,n11,vx,vy)
if scale_mode == 'ADAPTIVE':
areas = np.array([[verts_area[v.index] for v in verts_v] for verts_v in verts])
a00 = areas[u,v].reshape((n_verts1,1))
a10 = areas[u1,v].reshape((n_verts1,1))
a01 = areas[u,v1].reshape((n_verts1,1))
a11 = areas[u1,v1].reshape((n_verts1,1))
# remapped z scale
a2 = np_lerp2(a00,a10,a01,a11,vx,vy)
co3 = co2 + n2 * vz * a2
else:
co3 = co2 + n2 * vz
coordinates = co3.flatten().tolist()
new_patch.data.shape_keys.key_blocks[sk.name].data.foreach_set('co', coordinates)
#new_patch.data.shape_keys.key_blocks[sk.name].data[i_vert].co = sk_co
else:
for _fvec, uv_quad, patch_vert in zip(verts1_uv, verts1_uv_quads, new_patch.data.vertices):
u = uv_quad[0]
v = uv_quad[1]
u1 = uv_quad[2]
v1 = uv_quad[3]
v00 = verts[u][v]
v10 = verts[u1][v]
v01 = verts[u][v1]
v11 = verts[u1][v1]
# interpolate Z scaling factor
fvec = _fvec.copy()
if scale_mode == 'ADAPTIVE':
a00 = verts_area[v00.index]
a10 = verts_area[v10.index]
a01 = verts_area[v01.index]
a11 = verts_area[v11.index]
fvec[2]*=lerp2(a00,a10,a01,a11,fvec)
# interpolate vertex on patch
patch_vert.co = lerp3(v00, v10, v01, v11, fvec)
# Vertex Group
if bool_vertex_group:
for _weight, vg in zip(weight, new_patch.vertex_groups):
w00 = _weight[v00.index]
w10 = _weight[v10.index]
w01 = _weight[v01.index]
w11 = _weight[v11.index]
wuv = lerp2(w00,w10,w01,w11, fvec)
vg.add([patch_vert.index], wuv, "ADD")
if bool_shapekeys:
for i_sk, sk in enumerate(ob1.data.shape_keys.key_blocks):
for i_vert, _fvec, _sk_uv_quad in zip(range(len(new_patch.data.vertices)), sk_uv[i_sk], sk_uv_quads[i_sk]):
u = _sk_uv_quad[0]
v = _sk_uv_quad[1]
u1 = _sk_uv_quad[2]
v1 = _sk_uv_quad[3]
v00 = verts[u][v]
v10 = verts[u1][v]
v01 = verts[u][v1]
v11 = verts[u1][v1]
fvec = _fvec.copy()
if scale_mode == 'ADAPTIVE':
a00 = verts_area[v00.index]
a10 = verts_area[v10.index]
a01 = verts_area[v01.index]
a11 = verts_area[v11.index]
fvec[2]*=lerp2(a00, a10, a01, a11, fvec)
sk_co = lerp3(v00, v10, v01, v11, fvec)
new_patch.data.shape_keys.key_blocks[sk.name].data[i_vert].co = sk_co
#if ob0.type == 'MESH': ob0.data = old_me0
if not bool_correct: return 0
bpy.ops.object.join()
if bool_shapekeys:
# set original values and combine Shape Keys and Vertex Groups
for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
sk.value = val
new_patch.data.shape_keys.key_blocks[sk.name].value = val
if bool_vertex_group:
for sk in new_patch.data.shape_keys.key_blocks:
for vg in new_patch.vertex_groups:
if sk.name == vg.name:
sk.vertex_group = vg.name
else:
try:
for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
sk.value = val
except: pass
new_name = ob0.name + "_" + ob1.name
new_patch.name = "tessellate_temp"
if bool_multires:
for m in ob0.modifiers:
if m.type == 'MULTIRES' and m.name == multires_name:
m.levels = levels
m.sculpt_levels = sculpt_levels
m.render_levels = render_levels
# restore original modifiers visibility for component object
try:
for m, vis in zip(_ob1.modifiers, mod_visibility):
m.show_viewport = vis
except: pass
bpy.data.objects.remove(before)
bpy.data.objects.remove(ob0)
bpy.data.objects.remove(ob1)
return new_patch
def tessellate_original(_ob0, _ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
scale_mode, rotation_mode, rotation_shift, rotation_direction, rand_seed, fill_mode,
bool_vertex_group, bool_selection, bool_shapekeys,
bool_material_id, material_id, normals_mode, bounds_x, bounds_y):
if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
random.seed(rand_seed)
if bool_shapekeys:
try:
original_key_values = []
for sk in _ob1.data.shape_keys.key_blocks:
original_key_values.append(sk.value)
sk.value = 0
except:
bool_shapekeys = False
if normals_mode == 'CUSTOM':
if _ob0.data.shape_keys != None:
ob0_sk = convert_object_to_mesh(_ob0, True, True)
me0_sk = ob0_sk.data
key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks]
for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0
else: normals_mode == 'VERTS'
ob0 = convert_object_to_mesh(_ob0, gen_modifiers, True)
me0 = ob0.data
ob1 = convert_object_to_mesh(_ob1, com_modifiers, True)
me1 = ob1.data
# base normals
normals0 = []
if normals_mode == 'CUSTOM' and _ob0.data.shape_keys != None:
for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val
for v0, v1 in zip(me0.vertices, me0_sk.vertices):
normals0.append(v1.co - v0.co)
bpy.data.objects.remove(ob0_sk)
else:
me0.update()
normals0 = [v.normal for v in me0.vertices]
base_polygons = []
base_face_normals = []
n_faces0 = len(me0.polygons)
# Check if zero faces are selected
if (bool_selection and ob0.type == 'MESH') or bool_material_id:
for p in me0.polygons:
if (bool_selection and ob0.type == 'MESH'):
is_sel = p.select
else: is_sel = True
if bool_material_id:
is_mat = p.material_index == material_id
else: is_mat = True
if is_sel and is_mat:
base_polygons.append(p)
base_face_normals.append(p.normal)
else:
base_polygons = me0.polygons
base_face_normals = [p.normal for p in me0.polygons]
# numpy test: slower
#base_face_normals = np.zeros(n_faces0*3)
#me0.polygons.foreach_get("normal", base_face_normals)
#base_face_normals = base_face_normals.reshape((n_faces0,3))
if len(base_polygons) == 0:
bpy.data.objects.remove(ob0)
bpy.data.objects.remove(ob1)
bpy.data.meshes.remove(me1)
bpy.data.meshes.remove(me0)
return 0
if mode != 'BOUNDS':
bpy.ops.object.select_all(action='DESELECT')
for o in bpy.context.view_layer.objects: o.select_set(False)
bpy.context.view_layer.objects.active = ob1
ob1.select_set(True)
ob1.active_shape_key_index = 0
# Bound X
if bounds_x != 'EXTEND':
if mode == 'GLOBAL':
planes_co = ((0,0,0),(1,1,1))
plane_no = (1,0,0)
if mode == 'LOCAL':
planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0)))
plane_no = planes_co[0]-planes_co[1]
bpy.ops.object.mode_set(mode='EDIT')
for co in planes_co:
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
bpy.ops.mesh.mark_seam()
bpy.ops.object.mode_set(mode='OBJECT')
_faces = ob1.data.polygons
if mode == 'GLOBAL':
for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]:
f.select = True
for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]:
f.select = True
else:
for f in [f for f in _faces if f.center.x > 1]:
f.select = True
for f in [f for f in _faces if f.center.x < 0]:
f.select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
if bounds_x == 'CLIP':
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
if bounds_x == 'CYCLIC':
bpy.ops.mesh.split()
bpy.ops.object.mode_set(mode='OBJECT')
# Bound Y
if bounds_y != 'EXTEND':
if mode == 'GLOBAL':
planes_co = ((0,0,0),(1,1,1))
plane_no = (0,1,0)
if mode == 'LOCAL':
planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0)))
plane_no = planes_co[0]-planes_co[1]
bpy.ops.object.mode_set(mode='EDIT')
for co in planes_co:
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
bpy.ops.mesh.mark_seam()
bpy.ops.object.mode_set(mode='OBJECT')
_faces = ob1.data.polygons
if mode == 'GLOBAL':
for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]:
f.select = True
for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]:
f.select = True
else:
for f in [f for f in _faces if f.center.y > 1]:
f.select = True
for f in [f for f in _faces if f.center.y < 0]:
f.select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
if bounds_y == 'CLIP':
bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
if bounds_y == 'CYCLIC':
bpy.ops.mesh.split()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='OBJECT')
#ob1 = new_ob1
me1 = ob1.data
verts0 = me0.vertices # Collect generator vertices
# Component statistics
n_verts1 = len(me1.vertices)
n_edges1 = len(me1.edges)
n_faces1 = len(me1.polygons)
# Create empty lists
new_verts = []
new_edges = []
new_faces = []
new_verts_np = np.array(())
# Component Coordinates
co1 = [0]*n_verts1*3
if mode == 'GLOBAL':
for v in me1.vertices:
v.co = ob1.matrix_world @ v.co
try:
for sk in me1.shape_keys.key_blocks:
sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
except: pass
if mode != 'BOUNDS':
if bounds_x == 'CYCLIC':
move_verts = []
for f in [f for f in me1.polygons if (f.center).x > 1]:
for v in f.vertices:
if v not in move_verts: move_verts.append(v)
for v in move_verts:
me1.vertices[v].co.x -= 1
try:
_ob1.active_shape_key_index = 0
for sk in me1.shape_keys.key_blocks:
sk.data[v].co.x -= 1
except: pass
move_verts = []
for f in [f for f in me1.polygons if (f.center).x < 0]:
for v in f.vertices:
if v not in move_verts: move_verts.append(v)
for v in move_verts:
me1.vertices[v].co.x += 1
try:
_ob1.active_shape_key_index = 0
for sk in me1.shape_keys.key_blocks:
sk.data[v].co.x += 1
except: pass
if bounds_y == 'CYCLIC':
move_verts = []
for f in [f for f in me1.polygons if (f.center).y > 1]:
for v in f.vertices:
if v not in move_verts: move_verts.append(v)
for v in move_verts:
me1.vertices[v].co.y -= 1
try:
#new_ob1.active_shape_key_index = 0
for sk in me1.shape_keys.key_blocks:
sk.data[v].co.y -= 1
except: pass
move_verts = []
for f in [f for f in me1.polygons if (f.center).y < 0]:
for v in f.vertices:
if v not in move_verts: move_verts.append(v)
for v in move_verts:
me1.vertices[v].co.y += 1
try:
#new_ob1.active_shape_key_index = 0
for sk in me1.shape_keys.key_blocks:
sk.data[v].co.y += 1
except: pass
if len(me1.vertices) == 0:
bpy.data.objects.remove(ob0)
bpy.data.objects.remove(ob1)
return 0
me1.vertices.foreach_get("co", co1)
co1 = np.array(co1)
vx = co1[0::3].reshape((n_verts1,1))
vy = co1[1::3].reshape((n_verts1,1))
vz = co1[2::3].reshape((n_verts1,1))
min_c = Vector((vx.min(), vy.min(), vz.min())) # Min BB Corner
max_c = Vector((vx.max(), vy.max(), vz.max())) # Max BB Corner
bb = max_c - min_c # Bounding Box
# Component Coordinates
if mode == 'BOUNDS':
vx = (vx - min_c[0]) / bb[0] if bb[0] != 0 else 0.5
vy = (vy - min_c[1]) / bb[1] if bb[1] != 0 else 0.5
vz = (vz - min_c[2]) / bb[2] if bb[2] != 0 else 0
vz = (vz - 0.5 + offset * 0.5) * zscale
#vz = ((vz - min_c[2]) + (-0.5 + offset * 0.5) * bb[2]) * zscale
else:
vz *= zscale
# Component polygons
fs1 = [[i for i in p.vertices] for p in me1.polygons]
new_faces = fs1[:]
# Component edges
es1 = np.array([[i for i in e.vertices] for e in me1.edges])
#es1 = [[i for i in e.vertices] for e in me1.edges if e.is_loose]
new_edges = es1[:]
# SHAPE KEYS
if bool_shapekeys:
basis = True #com_modifiers
vx_key = []
vy_key = []
vz_key = []
sk_np = []
for sk in ob1.data.shape_keys.key_blocks:
do_shapekeys = True
# set all keys to 0
for _sk in ob1.data.shape_keys.key_blocks: _sk.value = 0
sk.value = 1
if basis:
basis = False
continue
# Apply component modifiers
if com_modifiers:
sk_ob = convert_object_to_mesh(_ob1)
sk_data = sk_ob.data
source = sk_data.vertices
else:
source = sk.data
shapekeys = []
for v in source:
if mode == 'BOUNDS':
vert = v.co - min_c
vert[0] = (vert[0] / bb[0] if bb[0] != 0 else 0.5)
vert[1] = (vert[1] / bb[1] if bb[1] != 0 else 0.5)
vert[2] = (vert[2] / bb[2] if bb[2] != 0 else vert[2])
vert[2] = (vert[2] - 0.5 + offset * 0.5) * zscale
elif mode == 'LOCAL':
vert = v.co.xyz
vert[2] *= zscale
#vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \
# zscale
elif mode == 'GLOBAL':
vert = v.co.xyz
#vert = ob1.matrix_world @ v.co
vert[2] *= zscale
shapekeys.append(vert)
# Component vertices
key1 = np.array([v for v in shapekeys]).reshape(len(shapekeys), 3, 1)
vx_key.append(key1[:, 0])
vy_key.append(key1[:, 1])
vz_key.append(key1[:, 2])
#sk_np.append([])
# All vertex group
if bool_vertex_group or rotation_mode == 'WEIGHT':
try:
weight = []
for vg in ob0.vertex_groups:
_weight = []
for i,v in enumerate(me0.vertices):
try:
_weight.append(vg.weight(i))
except:
_weight.append(0)
weight.append(_weight)
except:
bool_vertex_group = False
# Adaptive Z
if scale_mode == 'ADAPTIVE':
com_area = bb[0]*bb[1]
if mode != 'BOUNDS' or com_area == 0: com_area = 1
verts_area = []
bm = bmesh.new()
bm.from_mesh(me0)
bm.verts.ensure_lookup_table()
for v in bm.verts:
area = 0
faces = v.link_faces
for f in faces:
area += f.calc_area()
try:
area/=len(faces) # average area
area/=com_area
verts_area.append(sqrt(area)*bb[2])
#verts_area.append(area)
except:
verts_area.append(1)
count = 0 # necessary for UV calculation
# TESSELLATION
j = 0
jj = -1
bool_correct = False
# optimization test
n_faces = len(base_polygons)
_vs0 = [0]*n_faces
_nvs0 = [0]*n_faces
_sz = [0]*n_faces
n_vg = len(ob0.vertex_groups)
_w0 = [[0]*n_faces for i in range(n_vg)]
np_faces = [np.array(p) for p in fs1]
new_faces = [0]*n_faces*n_faces1
face1_count = 0
for j, p in enumerate(base_polygons):
bool_correct = True
if rotation_mode in ['UV', 'WEIGHT'] and ob0.type != 'MESH':
rotation_mode = 'DEFAULT'
ordered = p.vertices
# Random rotation
if rotation_mode == 'RANDOM':
shifted_vertices = []
n_poly_verts = len(p.vertices)
rand = random.randint(0, n_poly_verts)
for i in range(n_poly_verts):
shifted_vertices.append(p.vertices[(i + rand) % n_poly_verts])
if scale_mode == 'ADAPTIVE':
verts_area0 = np.array([verts_area[i] for i in shifted_vertices])
ordered = shifted_vertices
# UV rotation
elif rotation_mode == 'UV':
if len(ob0.data.uv_layers) > 0:
i = p.index
if bool_material_id:
count = sum([len(p.vertices) for p in me0.polygons[:i]])
#if i == 0: count = 0
v01 = (me0.uv_layers.active.data[count].uv +
me0.uv_layers.active.data[count + 1].uv)
if len(p.vertices) > 3:
v32 = (me0.uv_layers.active.data[count + 3].uv +
me0.uv_layers.active.data[count + 2].uv)
else:
v32 = (me0.uv_layers.active.data[count].uv +
me0.uv_layers.active.data[count + 2].uv)
v0132 = v32 - v01
v0132.normalize()
v12 = (me0.uv_layers.active.data[count + 1].uv +
me0.uv_layers.active.data[count + 2].uv)
if len(p.vertices) > 3:
v03 = (me0.uv_layers.active.data[count].uv +
me0.uv_layers.active.data[count + 3].uv)
else:
v03 = (me0.uv_layers.active.data[count].uv +
me0.uv_layers.active.data[count].uv)
v1203 = v03 - v12
v1203.normalize()
vertUV = []
dot1203 = v1203.x
dot0132 = v0132.x
if(abs(dot1203) < abs(dot0132)):
if (dot0132 > 0):
vertUV = p.vertices[1:] + p.vertices[:1]
else:
vertUV = p.vertices[3:] + p.vertices[:3]
else:
if(dot1203 < 0):
vertUV = p.vertices[:]
else:
vertUV = p.vertices[2:] + p.vertices[:2]
ordered = vertUV
count += len(p.vertices)
# Weight Rotation
elif rotation_mode == 'WEIGHT':
if len(weight) > 0:
active_weight = weight[ob0.vertex_groups.active_index]
i = p.index
face_weights = [active_weight[v] for v in p.vertices]
face_weights*=2
if rotation_direction == 'DIAG':
differential = [face_weights[ii]-face_weights[ii+2] for ii in range(4)]
else:
differential = [face_weights[ii]+face_weights[ii+1]-face_weights[ii+2]- face_weights[ii+3] for ii in range(4)]
starting = differential.index(max(differential))
ordered = p.vertices[starting:] + p.vertices[:starting]
if rotation_mode != 'RANDOM':
ordered = np.roll(np.array(ordered),rotation_shift)
ordered = np.array((ordered[0], ordered[1], ordered[2], ordered[-1]))
# assign vertices and values
vs0 = np.array([verts0[i].co for i in ordered])
#nvs0 = np.array([verts0[i].normal for i in ordered])
nvs0 = np.array([normals0[i] for i in ordered])
if scale_mode == 'ADAPTIVE':
np_verts_area = np.array([verts_area[i] for i in ordered])
_sz[j] = np_verts_area
# Vertex weight
if bool_vertex_group:
ws0 = []
for w in weight:
_ws0 = []
for i in ordered:
try:
_ws0.append(w[i])
except:
_ws0.append(0)
ws0.append(np.array(_ws0))
# optimization test
_vs0[j] = (vs0[0], vs0[1], vs0[2], vs0[-1])
if normals_mode != 'FACES':
_nvs0[j] = (nvs0[0], nvs0[1], nvs0[2], nvs0[-1])
if bool_vertex_group:
for i_vg, ws0_face in enumerate(ws0):
_w0[i_vg][j] = (ws0_face[0], ws0_face[1], ws0_face[2], ws0_face[-1])
for p in fs1:
new_faces[face1_count] = [i + n_verts1 * j for i in p]
face1_count += 1
# build edges list
n_edges1 = new_edges.shape[0]
new_edges = new_edges.reshape((1, n_edges1, 2))
new_edges = new_edges.repeat(n_faces,axis=0)
new_edges = new_edges.reshape((n_edges1*n_faces, 2))
increment = np.arange(n_faces)*n_verts1
increment = increment.repeat(n_edges1, axis=0)
increment = increment.reshape((n_faces*n_edges1,1))
new_edges = new_edges + increment
# optimization test
_vs0 = np.array(_vs0)
_sz = np.array(_sz)
_vs0_0 = _vs0[:,0].reshape((n_faces,1,3))
_vs0_1 = _vs0[:,1].reshape((n_faces,1,3))
_vs0_2 = _vs0[:,2].reshape((n_faces,1,3))
_vs0_3 = _vs0[:,3].reshape((n_faces,1,3))
# remapped vertex coordinates
v2 = np_lerp2(_vs0_0, _vs0_1, _vs0_3, _vs0_2, vx, vy)
# remapped vertex normal
if normals_mode != 'FACES':
_nvs0 = np.array(_nvs0)
_nvs0_0 = _nvs0[:,0].reshape((n_faces,1,3))
_nvs0_1 = _nvs0[:,1].reshape((n_faces,1,3))
_nvs0_2 = _nvs0[:,2].reshape((n_faces,1,3))
_nvs0_3 = _nvs0[:,3].reshape((n_faces,1,3))
nv2 = np_lerp2(_nvs0_0, _nvs0_1, _nvs0_3, _nvs0_2, vx, vy)
else:
nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
# interpolate vertex groups
if bool_vertex_group:
w = np.array(_w0)
w_0 = w[:,:,0].reshape((n_vg, n_faces,1,1))
w_1 = w[:,:,1].reshape((n_vg, n_faces,1,1))
w_2 = w[:,:,2].reshape((n_vg, n_faces,1,1))
w_3 = w[:,:,3].reshape((n_vg, n_faces,1,1))
# remapped weight
w = np_lerp2(w_0, w_1, w_3, w_2, vx, vy)
w = w.reshape((n_vg, n_faces*n_verts1))
if scale_mode == 'ADAPTIVE':
_sz_0 = _sz[:,0].reshape((n_faces,1,1))
_sz_1 = _sz[:,1].reshape((n_faces,1,1))
_sz_2 = _sz[:,2].reshape((n_faces,1,1))
_sz_3 = _sz[:,3].reshape((n_faces,1,1))
# remapped z scale
sz2 = np_lerp2(_sz_0, _sz_1, _sz_3, _sz_2, vx, vy)
v3 = v2 + nv2 * vz * sz2
else:
v3 = v2 + nv2 * vz
new_verts_np = v3.reshape((n_faces*n_verts1,3))
if bool_shapekeys:
n_sk = len(vx_key)
sk_np = [0]*n_sk
for i in range(n_sk):
vx = np.array(vx_key[i])
vy = np.array(vy_key[i])
vz = np.array(vz_key[i])
# remapped vertex coordinates
v2 = np_lerp2(_vs0_0, _vs0_1, _vs0_3, _vs0_2, vx, vy)
# remapped vertex normal
if normals_mode != 'FACES':
nv2 = np_lerp2(_nvs0_0, _nvs0_1, _nvs0_3, _nvs0_2, vx, vy)
else:
nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
if scale_mode == 'ADAPTIVE':
# remapped z scale
sz2 = np_lerp2(_sz_0, _sz_1, _sz_3, _sz_2, vx, vy)
v3 = v2 + nv2 * vz * sz2
else:
v3 = v2 + nv2 * vz
sk_np[i] = v3.reshape((n_faces*n_verts1,3))
#if ob0.type == 'MESH': ob0.data = old_me0
if not bool_correct:
#bpy.data.objects.remove(ob1)
return 0
new_verts = new_verts_np.tolist()
new_name = ob0.name + "_" + ob1.name
new_me = bpy.data.meshes.new(new_name)
new_me.from_pydata(new_verts, new_edges.tolist(), new_faces)
new_me.update(calc_edges=True)
new_ob = bpy.data.objects.new("tessellate_temp", new_me)
# vertex group
if bool_vertex_group and False:
for vg in ob0.vertex_groups:
new_ob.vertex_groups.new(name=vg.name)
for i in range(len(vg_np[vg.index])):
new_ob.vertex_groups[vg.name].add([i], vg_np[vg.index][i],"ADD")
# vertex group
if bool_vertex_group:
for vg in ob0.vertex_groups:
new_ob.vertex_groups.new(name=vg.name)
for i, vertex_weight in enumerate(w[vg.index]):
new_ob.vertex_groups[vg.name].add([i], vertex_weight,"ADD")
if bool_shapekeys:
basis = com_modifiers
sk_count = 0
for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
sk.value = val
new_ob.shape_key_add(name=sk.name, from_mix=False)
new_ob.data.shape_keys.key_blocks[sk.name].value = val
# set shape keys vertices
sk_data = new_ob.data.shape_keys.key_blocks[sk.name].data
if sk_count == 0:
sk_count += 1
continue
for id in range(len(sk_data)):
sk_data[id].co = sk_np[sk_count-1][id]
sk_count += 1
if bool_vertex_group:
for sk in new_ob.data.shape_keys.key_blocks:
for vg in new_ob.vertex_groups:
if sk.name == vg.name:
sk.vertex_group = vg.name
# EDGES SEAMS
edge_data = [0]*n_edges1
me1.edges.foreach_get("use_seam",edge_data)
if any(edge_data):
edge_data = edge_data*n_faces
new_ob.data.edges.foreach_set("use_seam",edge_data)
# EDGES SHARP
edge_data = [0]*n_edges1
me1.edges.foreach_get("use_edge_sharp",edge_data)
if any(edge_data):
edge_data = edge_data*n_faces
new_ob.data.edges.foreach_set("use_edge_sharp",edge_data)
bpy.ops.object.select_all(action='DESELECT')
bpy.context.collection.objects.link(new_ob)
new_ob.select_set(True)
bpy.context.view_layer.objects.active = new_ob
# EDGES BEVEL
edge_data = [0]*n_edges1
me1.edges.foreach_get("bevel_weight",edge_data)
if any(edge_data):
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.transform.edge_bevelweight(value=1)
bpy.ops.object.mode_set(mode='OBJECT')
edge_data = edge_data*n_faces
new_ob.data.edges.foreach_set("bevel_weight",edge_data)
# EDGE CREASES
edge_data = [0]*n_edges1
me1.edges.foreach_get("crease",edge_data)
if any(edge_data):
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.transform.edge_crease(value=1)
bpy.ops.object.mode_set(mode='OBJECT')
edge_data = edge_data*n_faces
new_ob.data.edges.foreach_set('crease', edge_data)
# MATERIALS
for slot in ob1.material_slots: new_ob.data.materials.append(slot.material)
polygon_materials = [0]*n_faces1
me1.polygons.foreach_get("material_index", polygon_materials)
polygon_materials *= n_faces
new_ob.data.polygons.foreach_set("material_index", polygon_materials)
new_ob.data.update() ###
try:
bpy.data.objects.remove(new_ob1)
except: pass
bpy.data.objects.remove(ob0)
bpy.data.meshes.remove(me0)
bpy.data.objects.remove(ob1)
bpy.data.meshes.remove(me1)
return new_ob
class tissue_tessellate(Operator):
bl_idname = "object.tissue_tessellate"
bl_label = "Tessellate"
bl_description = ("Create a copy of selected object on the active object's "
"faces, adapting the shape to the different faces")
bl_options = {'REGISTER', 'UNDO'}
bool_hold : BoolProperty(
name="Hold",
description="Wait...",
default=False
)
bool_lock : BoolProperty(
name="Lock",
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.",
default=False
)
bool_dependencies : BoolProperty(
name="Update Dependencies",
description="Automatically updates base and components as well, if results of other tessellations",
default=False
)
object_name : StringProperty(
name="",
description="Name of the generated object"
)
zscale : FloatProperty(
name="Scale",
default=1,
soft_min=0,
soft_max=10,
description="Scale factor for the component thickness"
)
scale_mode : EnumProperty(
items=(
('CONSTANT', "Constant", "Uniform thickness"),
('ADAPTIVE', "Relative", "Preserve component's proportions")
),
default='ADAPTIVE',
name="Z-Scale according to faces size"
)
offset : FloatProperty(
name="Surface Offset",
default=1,
min=-1, max=1,
soft_min=-1,
soft_max=1,
description="Surface offset"
)
mode : EnumProperty(
items=(
('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
default='BOUNDS',
name="Component Mode"
)
rotation_mode : EnumProperty(
items=(('RANDOM', "Random", "Random faces rotation"),
('UV', "Active UV", "Face rotation is based on UV coordinates"),
('WEIGHT', "Active Weight", "Rotate according to Vertex Group gradient"),
('DEFAULT', "Default", "Default rotation")),
default='DEFAULT',
name="Component Rotation"
)
rotation_direction : EnumProperty(
items=(('ORTHO', "Orthogonal", "Component main directions in XY"),
('DIAG', "Diagonal", "Component main direction aligned with diagonal")),
default='ORTHO',
name="Direction"
)
rotation_shift : IntProperty(
name="Shift",
default=0,
soft_min=0,
soft_max=3,
description="Shift components rotation"
)
fill_mode : EnumProperty(
items=(
('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
('PATCH', 'Patch', 'Curved tessellation according to the last ' +
'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
'patches.\nAfter the last Subsurf (or Multires) only ' +
'deformation\nmodifiers can be used'),
('FRAME', 'Frame', 'Essellation along the edges of each face')),
default='QUAD',
name="Fill Mode"
)
combine_mode : EnumProperty(
items=(
('LAST', 'Last', 'Show only the last iteration'),
('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
('ALL', 'All', 'Combine the result of all iterations')),
default='LAST',
name="Combine Mode",
)
gen_modifiers : BoolProperty(
name="Generator Modifiers",
default=False,
description="Apply Modifiers and Shape Keys to the base object"
)
com_modifiers : BoolProperty(
name="Component Modifiers",
default=False,
description="Apply Modifiers and Shape Keys to the component object"
)
merge : BoolProperty(
name="Merge",
default=False,
description="Merge vertices in adjacent duplicates"
)
merge_thres : FloatProperty(
name="Distance",
default=0.001,
soft_min=0,
soft_max=10,
description="Limit below which to merge vertices"
)
bool_random : BoolProperty(
name="Randomize",
default=False,
description="Randomize component rotation"
)
random_seed : IntProperty(
name="Seed",
default=0,
soft_min=0,
soft_max=10,
description="Random seed"
)
bool_vertex_group : BoolProperty(
name="Map Vertex Groups",
default=False,
description="Transfer all Vertex Groups from Base object"
)
bool_selection : BoolProperty(
name="On selected Faces",
default=False,
description="Create Tessellation only on selected faces"
)
bool_shapekeys : BoolProperty(
name="Use Shape Keys",
default=False,
description="Transfer Component's Shape Keys. If the name of Vertex "
"Groups and Shape Keys are the same, they will be "
"automatically combined"
)
bool_smooth : BoolProperty(
name="Smooth Shading",
default=False,
description="Output faces with smooth shading rather than flat shaded"
)
bool_materials : BoolProperty(
name="Transfer Materials",
default=True,
description="Preserve component's materials"
)
generator : StringProperty(
name="",
description="Base object for the tessellation",
default = ""
)
component : StringProperty(
name="",
description="Component object for the tessellation",
default = ""
)
bool_material_id : BoolProperty(
name="Tessellation on Material ID",
default=False,
description="Apply the component only on the selected Material"
)
bool_dissolve_seams : BoolProperty(
name="Dissolve Seams",
default=False,
description="Dissolve all seam edges"
)
material_id : IntProperty(
name="Material ID",
default=0,
min=0,
description="Material ID"
)
iterations : IntProperty(
name="Iterations",
default=1,
min=1,
soft_max=5,
description="Automatically repeat the Tessellation using the "
+ "generated geometry as new base object.\nUsefull for "
+ "for branching systems. Dangerous!"
)
bool_combine : BoolProperty(
name="Combine unused",
default=False,
description="Combine the generated geometry with unused faces"
)
bool_advanced : BoolProperty(
name="Advanced Settings",
default=False,
description="Show more settings"
)
normals_mode : EnumProperty(
items=(
('VERTS', 'Normals', 'Consistent direction based on vertices normal'),
('FACES', 'Individual Faces', 'Based on individual faces normal'),
('CUSTOM', 'Custom', "According to Base object's shape keys")),
default='VERTS',
name="Direction"
)
bool_multi_components : BoolProperty(
name="Multi Components",
default=False,
description="Combine different components according to materials name"
)
bounds_x : EnumProperty(
items=(
('EXTEND', 'Extend', 'Default X coordinates'),
('CLIP', 'Clip', 'Trim out of bounds in X direction'),
('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
default='EXTEND',
name="Bounds X",
)
bounds_y : EnumProperty(
items=(
('EXTEND', 'Extend', 'Default Y coordinates'),
('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
default='EXTEND',
name="Bounds Y",
)
close_mesh : EnumProperty(
items=(
('NONE', 'None', 'Keep the mesh open'),
('CAP', 'Cap Holes', 'Automatically cap open loops'),
('BRIDGE', 'Bridge Loops', 'Automatically bridge loop pairs')),
default='NONE',
name="Close Mesh"
)
cap_faces : BoolProperty(
name="Cap Holes",
default=False,
description="Cap open edges loops"
)
frame_boundary : BoolProperty(
name="Frame Boundary",
default=False,
description="Support face boundaries"
)
fill_frame : BoolProperty(
name="Fill Frame",
default=False,
description="Fill inner faces with Fan tessellation"
)
frame_boundary_mat : IntProperty(
name="Material Offset",
default=0,
description="Material Offset for boundaries"
)
fill_frame_mat : IntProperty(
name="Material Offset",
default=0,
description="Material Offset for inner faces"
)
open_edges_crease : FloatProperty(
name="Open Edges Crease",
default=0,
min=0,
max=1,
description="Automatically set crease for open edges"
)
bridge_smoothness : FloatProperty(
name="Smoothness",
default=1,
min=0,
max=1,
description="Bridge Smoothness"
)
frame_thickness : FloatProperty(
name="Frame Thickness",
default=0.2,
min=0,
soft_max=2,
description="Frame Thickness"
)
frame_mode : EnumProperty(
items=(
('CONSTANT', 'Constant', 'Even thickness'),
('RELATIVE', 'Relative', 'Frame offset depends on face areas')),
default='CONSTANT',
name="Offset"
)
bridge_cuts : IntProperty(
name="Cuts",
default=0,
min=0,
max=20,
description="Bridge Cuts"
)
cap_material_index : IntProperty(
name="Material",
default=0,
min=0,
description="Material index for the cap/bridge faces"
)
patch_subs : IntProperty(
name="Patch Subdivisions",
default=1,
min=0,
description="Subdivisions levels for Patch tessellation after the first iteration"
)
working_on = ""
def draw(self, context):
allowed_obj = ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
'''
try:
bool_working = self.working_on == self.object_name and \
self.working_on != ""
except:
bool_working = False
'''
bool_working = False
bool_allowed = False
ob0 = None
ob1 = None
sel = bpy.context.selected_objects
if len(sel) == 1:
try:
ob0 = sel[0].tissue_tessellate.generator
ob1 = sel[0].tissue_tessellate.component
self.generator = ob0.name
self.component = ob1.name
if self.working_on == '':
load_parameters(self,sel[0])
self.working_on = sel[0].name
bool_working = True
bool_allowed = True
except:
pass
if len(sel) == 2:
bool_allowed = True
for o in sel:
if o.type not in allowed_obj:
bool_allowed = False
if len(sel) != 2 and not bool_working:
layout = self.layout
layout.label(icon='INFO')
layout.label(text="Please, select two different objects")
layout.label(text="Select first the Component object, then select")
layout.label(text="the Base object.")
elif not bool_allowed and not bool_working:
layout = self.layout
layout.label(icon='INFO')
layout.label(text="Only Mesh, Curve, Surface or Text objects are allowed")
else:
if ob0 == ob1 == None:
ob0 = bpy.context.active_object
self.generator = ob0.name
for o in sel:
if o != ob0:
ob1 = o
self.component = o.name
self.no_component = False
break
# new object name
if self.object_name == "":
if self.generator == "":
self.object_name = "Tessellation"
else:
#self.object_name = self.generator + "_Tessellation"
self.object_name = "Tessellation"
layout = self.layout
# Base and Component
col = layout.column(align=True)
row = col.row(align=True)
row.label(text="BASE : " + self.generator)
row.label(text="COMPONENT : " + self.component)
# Base Modifiers
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop(self, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
base = bpy.data.objects[self.generator]
try:
if not (base.modifiers or base.data.shape_keys):
col2.enabled = False
self.gen_modifiers = False
except:
col2.enabled = False
self.gen_modifiers = False
# Component Modifiers
row.separator()
col3 = row.column(align=True)
col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
component = bpy.data.objects[self.component]
try:
if not (component.modifiers or component.data.shape_keys):
col3.enabled = False
self.com_modifiers = False
except:
col3.enabled = False
self.com_modifiers = False
col.separator()
# Fill and Rotation
row = col.row(align=True)
row.label(text="Fill Mode:")
row = col.row(align=True)
row.prop(
self, "fill_mode", icon='NONE', expand=True,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
row = col.row(align=True)
row.prop(self, "bool_smooth")
# frame settings
if self.fill_mode == 'FRAME':
col.separator()
col.label(text="Frame Settings:")
row = col.row(align=True)
row.prop(self, "frame_mode", expand=True)
col.prop(self, "frame_thickness", text='Thickness', icon='NONE')
col.separator()
row = col.row(align=True)
row.prop(self, "fill_frame", icon='NONE')
show_frame_mat = self.bool_multi_components or self.bool_material_id
if self.fill_frame and show_frame_mat:
row.prop(self, "fill_frame_mat", icon='NONE')
row = col.row(align=True)
row.prop(self, "frame_boundary", text='Boundary', icon='NONE')
if self.frame_boundary and show_frame_mat:
row.prop(self, "frame_boundary_mat", icon='NONE')
if self.rotation_mode == 'UV':
uv_error = False
if ob0.type != 'MESH':
row = col.row(align=True)
row.label(
text="UV rotation supported only for Mesh objects",
icon='ERROR')
uv_error = True
else:
if len(ob0.data.uv_layers) == 0:
row = col.row(align=True)
check_name = self.generator
row.label(text="'" + check_name +
"' doesn't have UV Maps", icon='ERROR')
uv_error = True
if uv_error:
row = col.row(align=True)
row.label(text="Default rotation will be used instead",
icon='INFO')
# Component Z
col.separator()
col.label(text="Thickness:")
row = col.row(align=True)
row.prop(
self, "scale_mode", text="Scale Mode", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
col.prop(
self, "zscale", text="Scale", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
if self.mode == 'BOUNDS':
col.prop(
self, "offset", text="Offset", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
# Component XY
col.separator()
row = col.row(align=True)
row.label(text="Component Coordinates:")
row = col.row(align=True)
row.prop(
self, "mode", text="Component XY", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
if self.mode != 'BOUNDS':
col.separator()
row = col.row(align=True)
row.label(text="X:")
row.prop(
self, "bounds_x", text="Bounds X", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
row = col.row(align=True)
row.label(text="Y:")
row.prop(
self, "bounds_y", text="Bounds X", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
# merge settings
col = layout.column(align=True)
row = col.row(align=True)
row.prop(self, "merge")
if self.merge:
row.prop(self, "merge_thres")
col.separator()
row = col.row(align=True)
col2 = row.column(align=True)
col2.label(text='Close Mesh:')
col2 = row.column(align=True)
col2.prop(self, "close_mesh",text='')
if self.close_mesh != 'NONE':
row = col.row(align=True)
row.prop(self, "open_edges_crease", text="Crease")
row.prop(self, "cap_material_index")
if self.close_mesh == 'BRIDGE':
row = col.row(align=True)
row.prop(self, "bridge_cuts")
row.prop(self, "bridge_smoothness")
row = col.row(align=True)
row.prop(self, "bool_dissolve_seams")
# Advanced Settings
col = layout.column(align=True)
col.separator()
col.separator()
row = col.row(align=True)
row.prop(self, "bool_advanced", icon='SETTINGS')
if self.bool_advanced:
# rotation
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
col = layout.column(align=True)
col.prop(self, "rotation_mode", text='Rotation', icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
if self.rotation_mode == 'WEIGHT':
col.prop(self, "rotation_direction", expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
if self.rotation_mode == 'RANDOM':
col.prop(self, "random_seed")
else:
col.prop(self, "rotation_shift")
if self.rotation_mode == 'UV':
uv_error = False
if self.generator.type != 'MESH':
row = col.row(align=True)
row.label(
text="UV rotation supported only for Mesh objects",
icon='ERROR')
uv_error = True
else:
if len(self.generator.data.uv_layers) == 0:
row = col.row(align=True)
row.label(text="'" + props.generator.name +
" doesn't have UV Maps", icon='ERROR')
uv_error = True
if uv_error:
row = col.row(align=True)
row.label(text="Default rotation will be used instead",
icon='INFO')
layout.use_property_split = False
# Direction
col = layout.column(align=True)
row = col.row(align=True)
row.label(text="Direction:")
row = col.row(align=True)
row.prop(
self, "normals_mode", text="Direction", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
#row.enabled = self.fill_mode != 'PATCH'
allow_multi = False
allow_shapekeys = not self.com_modifiers
if self.com_modifiers: self.bool_shapekeys = False
for m in ob0.data.materials:
try:
o = bpy.data.objects[m.name]
allow_multi = True
try:
if o.data.shape_keys is None: continue
elif len(o.data.shape_keys.key_blocks) < 2: continue
else: allow_shapekeys = not self.com_modifiers
except: pass
except: pass
# DATA #
col = layout.column(align=True)
col.label(text="Weight and Morphing:")
# vertex group + shape keys
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop(self, "bool_vertex_group", icon='GROUP_VERTEX')
try:
if len(ob0.vertex_groups) == 0:
col2.enabled = False
except:
col2.enabled = False
row.separator()
col2 = row.column(align=True)
row2 = col2.row(align=True)
row2.prop(self, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
row2.enabled = allow_shapekeys
# LIMITED TESSELLATION
col = layout.column(align=True)
col.label(text="Limited Tessellation:")
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop(self, "bool_multi_components", icon='MOD_TINT')
if not allow_multi:
col2.enabled = False
self.bool_multi_components = False
col.separator()
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop(self, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
row.separator()
if ob0.type != 'MESH':
col2.enabled = False
col2 = row.column(align=True)
col2.prop(self, "bool_material_id", icon='MATERIAL_DATA', text="Material ID")
if self.bool_material_id and not self.bool_multi_components:
#col2 = row.column(align=True)
col2.prop(self, "material_id")
col2.enabled = not self.bool_multi_components
col.separator()
row = col.row(align=True)
row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
row.prop(self, 'iterations', text='Repeat', icon='SETTINGS')
if self.iterations > 1 and self.fill_mode == 'PATCH':
col.separator()
row = col.row(align=True)
row.prop(self, 'patch_subs')
col.separator()
row = col.row(align=True)
row.label(text='Combine Iterations:')
row = col.row(align=True)
row.prop(
self, "combine_mode", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
def execute(self, context):
allowed_obj = ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT')
try:
ob0 = bpy.data.objects[self.generator]
ob1 = bpy.data.objects[self.component]
except:
return {'CANCELLED'}
self.object_name = "Tessellation"
# Check if existing object with same name
names = [o.name for o in bpy.data.objects]
if self.object_name in names:
count_name = 1
while True:
test_name = self.object_name + '.{:03d}'.format(count_name)
if not (test_name in names):
self.object_name = test_name
break
count_name += 1
if ob1.type not in allowed_obj:
message = "Component must be Mesh, Curve, Surface, Text or Meta object!"
self.report({'ERROR'}, message)
self.component = None
if ob0.type not in allowed_obj:
message = "Generator must be Mesh, Curve, Surface, Text or Meta object!"
self.report({'ERROR'}, message)
self.generator = ""
if True:#self.component not in ("",None) and self.generator not in ("",None):
if bpy.ops.object.select_all.poll():
bpy.ops.object.select_all(action='TOGGLE')
bpy.ops.object.mode_set(mode='OBJECT')
#data0 = ob0.to_mesh(False)
#data0 = ob0.data.copy()
bool_update = False
if bpy.context.object == ob0:
auto_layer_collection()
#new_ob = bpy.data.objects.new(self.object_name, data0)
new_ob = convert_object_to_mesh(ob0,False,False)
new_ob.data.name = self.object_name
#bpy.context.collection.objects.link(new_ob)
#bpy.context.view_layer.objects.active = new_ob
new_ob.name = self.object_name
#new_ob.select_set(True)
else:
new_ob = bpy.context.object
bool_update = True
new_ob = store_parameters(self, new_ob)
try: bpy.ops.object.tissue_update_tessellate()
except RuntimeError as e:
bpy.data.objects.remove(new_ob)
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
if not bool_update:
self.object_name = new_ob.name
#self.working_on = self.object_name
new_ob.location = ob0.location
new_ob.matrix_world = ob0.matrix_world
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def update_dependencies(ob, objects):
ob0 = ob.tissue_tessellate.generator
ob1 = ob.tissue_tessellate.component
deps = [ob0, ob1]
for o in deps:
if o.tissue_tessellate.bool_lock: continue
o0 = o.tissue_tessellate.generator
o1 = o.tissue_tessellate.component
deps_deps = [o0, o1]
try:
o0.name
o1.name
if o0 not in objects and o1 not in objects:
objects.append(o)
objects = update_dependencies(o, objects)
except:
continue
return objects
class tissue_refresh_tessellate(Operator):
bl_idname = "object.tissue_refresh_tessellate"
bl_label = "Refresh"
bl_description = ("Fast update the tessellated mesh according to base and "
"component changes")
bl_options = {'REGISTER', 'UNDO'}
go = False
@classmethod
def poll(cls, context):
try:
return context.object.tissue_tessellate.generator != None and \
context.object.tissue_tessellate.component != None
except:
return False
@staticmethod
def check_gen_comp(checking):
# note pass the stored name key in here to check it out
return checking in bpy.data.objects.keys()
def execute(self, context):
ob = bpy.context.object
ob0 = ob.tissue_tessellate.generator
ob1 = ob.tissue_tessellate.component
try:
ob0.name
ob1.name
except:
self.report({'ERROR'},
"Active object must be Tessellate before Update")
return {'CANCELLED'}
if ob.tissue_tessellate.bool_dependencies:
update_objects = list(reversed(update_dependencies(ob, [ob])))
else:
update_objects = [ob]
for o in update_objects:
override = {'object': o}
bpy.ops.object.tissue_update_tessellate(override)
return {'FINISHED'}
class tissue_update_tessellate(Operator):
bl_idname = "object.tissue_update_tessellate"
bl_label = "Refresh"
bl_description = ("Fast update the tessellated mesh according to base and "
"component changes")
bl_options = {'REGISTER', 'UNDO'}
go = False
@classmethod
def poll(cls, context):
#try:
try: #context.object == None: return False
return context.object.tissue_tessellate.generator != None and \
context.object.tissue_tessellate.component != None
except:
return False
@staticmethod
def check_gen_comp(checking):
# note pass the stored name key in here to check it out
return checking in bpy.data.objects.keys()
def execute(self, context):
start_time = time.time()
ob = context.object
if not self.go:
generator = ob.tissue_tessellate.generator
component = ob.tissue_tessellate.component
zscale = ob.tissue_tessellate.zscale
scale_mode = ob.tissue_tessellate.scale_mode
rotation_mode = ob.tissue_tessellate.rotation_mode
rotation_shift = ob.tissue_tessellate.rotation_shift
rotation_direction = ob.tissue_tessellate.rotation_direction
offset = ob.tissue_tessellate.offset
merge = ob.tissue_tessellate.merge
merge_thres = ob.tissue_tessellate.merge_thres
gen_modifiers = ob.tissue_tessellate.gen_modifiers
com_modifiers = ob.tissue_tessellate.com_modifiers
bool_random = ob.tissue_tessellate.bool_random
random_seed = ob.tissue_tessellate.random_seed
fill_mode = ob.tissue_tessellate.fill_mode
bool_vertex_group = ob.tissue_tessellate.bool_vertex_group
bool_selection = ob.tissue_tessellate.bool_selection
bool_shapekeys = ob.tissue_tessellate.bool_shapekeys
mode = ob.tissue_tessellate.mode
bool_smooth = ob.tissue_tessellate.bool_smooth
bool_materials = ob.tissue_tessellate.bool_materials
bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams
bool_material_id = ob.tissue_tessellate.bool_material_id
material_id = ob.tissue_tessellate.material_id
iterations = ob.tissue_tessellate.iterations
bool_combine = ob.tissue_tessellate.bool_combine
normals_mode = ob.tissue_tessellate.normals_mode
bool_advanced = ob.tissue_tessellate.bool_advanced
bool_multi_components = ob.tissue_tessellate.bool_multi_components
combine_mode = ob.tissue_tessellate.combine_mode
bounds_x = ob.tissue_tessellate.bounds_x
bounds_y = ob.tissue_tessellate.bounds_y
cap_faces = ob.tissue_tessellate.cap_faces
close_mesh = ob.tissue_tessellate.close_mesh
open_edges_crease = ob.tissue_tessellate.open_edges_crease
bridge_smoothness = ob.tissue_tessellate.bridge_smoothness
frame_thickness = ob.tissue_tessellate.frame_thickness
frame_mode = ob.tissue_tessellate.frame_mode
frame_boundary = ob.tissue_tessellate.frame_boundary
fill_frame = ob.tissue_tessellate.fill_frame
frame_boundary_mat = ob.tissue_tessellate.frame_boundary_mat
fill_frame_mat = ob.tissue_tessellate.fill_frame_mat
bridge_cuts = ob.tissue_tessellate.bridge_cuts
cap_material_index = ob.tissue_tessellate.cap_material_index
patch_subs = ob.tissue_tessellate.patch_subs
try:
generator.name
component.name
except:
self.report({'ERROR'},
"Active object must be Tessellate before Update")
return {'CANCELLED'}
# Solve Local View issues
local_spaces = []
local_ob0 = []
local_ob1 = []
for area in context.screen.areas:
for space in area.spaces:
try:
if ob.local_view_get(space):
local_spaces.append(space)
local_ob0 = ob0.local_view_get(space)
ob0.local_view_set(space, True)
local_ob1 = ob1.local_view_get(space)
ob1.local_view_set(space, True)
except:
pass
starting_mode = context.object.mode
#if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT'
bpy.ops.object.mode_set(mode='OBJECT')
ob0 = generator
ob1 = component
##### auto_layer_collection()
ob0_hide = ob0.hide_get()
ob0_hidev = ob0.hide_viewport
ob0_hider = ob0.hide_render
ob1_hide = ob1.hide_get()
ob1_hidev = ob1.hide_viewport
ob1_hider = ob1.hide_render
ob0.hide_set(False)
ob0.hide_viewport = False
ob0.hide_render = False
ob1.hide_set(False)
ob1.hide_viewport = False
ob1.hide_render = False
if ob0.type == 'META':
base_ob = convert_object_to_mesh(ob0, False, True)
else:
base_ob = ob0.copy()
base_ob.data = ob0.data#
context.collection.objects.link(base_ob)
base_ob.name = '_tissue_tmp_base'
# In Blender 2.80 cache of copied objects is lost, must be re-baked
bool_update_cloth = False
for m in base_ob.modifiers:
if m.type == 'CLOTH':
m.point_cache.frame_end = context.scene.frame_current
bool_update_cloth = True
if bool_update_cloth:
bpy.ops.ptcache.free_bake_all()
bpy.ops.ptcache.bake_all()
base_ob.modifiers.update()
#new_ob.location = ob.location
#new_ob.matrix_world = ob.matrix_world
#bpy.ops.object.select_all(action='DESELECT')
if bool_selection:
faces = base_ob.data.polygons
selections = [False]*len(faces)
faces.foreach_get('select',selections)
selections = np.array(selections)
if not selections.any():
message = "There are no faces selected."
context.view_layer.objects.active = ob
ob.select_set(True)
bpy.ops.object.mode_set(mode=starting_mode)
bpy.data.objects.remove(base_ob)
self.report({'ERROR'}, message)
return {'CANCELLED'}
iter_objects = [base_ob]
ob_location = ob.location
ob_matrix_world = ob.matrix_world
#base_ob = new_ob#.copy()
for iter in range(iterations):
if iter > 0 and len(iter_objects) == 0: break
if iter > 0 and normals_mode == 'CUSTOM': normals_mode = 'VERTS'
same_iteration = []
matched_materials = []
# iterate base object materials (needed for multi-components)
if bool_multi_components: mat_iter = len(base_ob.material_slots)
else: mat_iter = 1
for m_id in range(mat_iter):
if bool_multi_components:
# check if material and components match
try:
mat = base_ob.material_slots[m_id].material
ob1 = bpy.data.objects[mat.name]
if ob1.type not in ('MESH', 'CURVE','SURFACE','FONT', 'META'):
continue
material_id = m_id
matched_materials.append(m_id)
bool_material_id = True
except:
continue
if com_modifiers or ob1.type != 'MESH':
data1 = simple_to_mesh(ob1)
else:
data1 = ob1.data.copy()
n_edges1 = len(data1.edges)
bpy.data.meshes.remove(data1)
if iter != 0: gen_modifiers = True
if fill_mode == 'PATCH':
# patch subdivisions for additional iterations
if iter > 0:
base_ob.modifiers.new('Tissue_Subsurf', type='SUBSURF')
base_ob.modifiers['Tissue_Subsurf'].levels = patch_subs
temp_mod = base_ob.modifiers['Tissue_Subsurf']
# patch tessellation
new_ob = tessellate_patch(
base_ob, ob1, offset, zscale, com_modifiers, mode, scale_mode,
rotation_mode, rotation_shift, random_seed, bool_vertex_group,
bool_selection, bool_shapekeys, bool_material_id, material_id,
normals_mode, bounds_x, bounds_y
)
if iter > 0:
base_ob.modifiers.remove(temp_mod)
else:
### FRAME and FAN ###
if fill_mode in ('FRAME','FAN'):
if fill_mode == 'FRAME': convert_function = convert_to_frame
else: convert_function = convert_to_fan
if normals_mode == 'CUSTOM' and base_ob.data.shape_keys != None:
## base key
sk_values = [sk.value for sk in base_ob.data.shape_keys.key_blocks]
for sk in ob0.data.shape_keys.key_blocks: sk.value = 0
_base_ob = convert_function(base_ob, ob.tissue_tessellate, gen_modifiers)
for i, sk in enumerate(ob0.data.shape_keys.key_blocks):
sk.value = sk_values[i]
## key 1
# hide modifiers
if not gen_modifiers and len(base_ob.modifiers) > 0:
mod_visibility = [m.show_viewport for m in base_ob.modifiers]
for m in base_ob.modifiers: m.show_viewport = False
base_ob.modifiers.update()
base_ob_sk = convert_function(ob0, ob.tissue_tessellate, True)
## combine shapekeys
_base_ob.shape_key_add(name='Basis', from_mix=False)
_base_ob.shape_key_add(name='Key1', from_mix=False)
sk_block = _base_ob.data.shape_keys.key_blocks[1]
sk_block.value = 1
for vert, sk in zip(base_ob_sk.data.vertices, sk_block.data):
sk.co = vert.co
bpy.data.objects.remove(base_ob_sk)
# set original modifiers
if not gen_modifiers and len(base_ob.modifiers) > 0:
for i,m in enumerate(base_ob.modifiers):
m.show_viewport = mod_visibility[i]
base_ob.modifiers.update()
else:
_base_ob = convert_function(base_ob, ob.tissue_tessellate, gen_modifiers)
bpy.data.objects.remove(base_ob)
base_ob = _base_ob
# quad tessellation
new_ob = tessellate_original(
base_ob, ob1, offset, zscale, gen_modifiers,
com_modifiers, mode, scale_mode, rotation_mode,
rotation_shift, rotation_direction,
random_seed, fill_mode, bool_vertex_group,
bool_selection, bool_shapekeys, bool_material_id,
material_id, normals_mode, bounds_x, bounds_y
)
# if empty or error, continue
if type(new_ob) is not bpy.types.Object:
continue
# prepare base object
if iter == 0 and gen_modifiers:
temp_base_ob = convert_object_to_mesh(base_ob, True, True)
bpy.data.objects.remove(base_ob)
base_ob = temp_base_ob
iter_objects = [base_ob]
# rename, make active and change transformations
new_ob.name = '_tissue_tmp_{}_{}'.format(iter,m_id)
new_ob.select_set(True)
context.view_layer.objects.active = new_ob
new_ob.location = ob_location
new_ob.matrix_world = ob_matrix_world
n_components = int(len(new_ob.data.edges) / n_edges1)
# SELECTION
if bool_selection:
try:
# create selection list
polygon_selection = [p.select for p in ob1.data.polygons] * int(
len(new_ob.data.polygons) / len(ob1.data.polygons))
new_ob.data.polygons.foreach_set("select", polygon_selection)
except:
pass
if bool_multi_components: same_iteration.append(new_ob)
base_ob.location = ob_location
base_ob.matrix_world = ob_matrix_world
# join together multiple components iterations
if bool_multi_components:
if len(same_iteration) > 0:
context.view_layer.update()
for o in context.view_layer.objects:
o.select_set(o in same_iteration)
bpy.ops.object.join()
new_ob = context.view_layer.objects.active
new_ob.select_set(True)
#new_ob.data.update()
if type(new_ob) in (int,str):
if iter == 0:
try:
bpy.data.objects.remove(iter_objects[0])
iter_objects = []
except: continue
continue
# Clean last iteration, needed for combine object
if (bool_selection or bool_material_id) and combine_mode == 'UNUSED':
# remove faces from last mesh
bm = bmesh.new()
last_mesh = iter_objects[-1].data.copy()
bm.from_mesh(last_mesh)
bm.faces.ensure_lookup_table()
if bool_multi_components:
remove_materials = matched_materials
elif bool_material_id:
remove_materials = [material_id]
else: remove_materials = []
if bool_selection:
if bool_multi_components or bool_material_id:
remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select]
else:
remove_faces = [f for f in bm.faces if f.select]
else:
remove_faces = [f for f in bm.faces if f.material_index in remove_materials]
bmesh.ops.delete(bm, geom=remove_faces, context='FACES')
bm.to_mesh(last_mesh)
last_mesh.update()
last_mesh.name = '_tissue_tmp_previous_unused'
# delete previous iteration if empty or update it
if len(last_mesh.vertices) > 0:
iter_objects[-1].data = last_mesh.copy()
iter_objects[-1].data.update()
else:
bpy.data.objects.remove(iter_objects[-1])
iter_objects = iter_objects[:-1]
# set new base object for next iteration
base_ob = convert_object_to_mesh(new_ob,True,True)
if iter < iterations-1: new_ob.data = base_ob.data
# store new iteration and set transformations
iter_objects.append(new_ob)
#try:
# bpy.data.objects.remove(bpy.data.objects['_tissue_tmp_base'])
#except:
# pass
base_ob.name = '_tissue_tmp_base'
elif combine_mode == 'ALL':
base_ob = new_ob.copy()
iter_objects.append(new_ob)
else:
if base_ob != new_ob:
bpy.data.objects.remove(base_ob)
base_ob = new_ob
iter_objects = [new_ob]
# Combine
if combine_mode != 'LAST' and len(iter_objects)>0:
if base_ob not in iter_objects and type(base_ob) == bpy.types.Object:
bpy.data.objects.remove(base_ob)
for o in context.view_layer.objects:
o.select_set(o in iter_objects)
bpy.ops.object.join()
new_ob.data.update()
iter_objects = [new_ob]
if merge:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(
use_extend=False, use_expand=False, type='VERT')
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=True, use_boundary=True,
use_multi_face=False, use_non_contiguous=False, use_verts=False)
bpy.ops.mesh.remove_doubles(
threshold=merge_thres, use_unselected=False)
if bool_dissolve_seams:
bpy.ops.mesh.select_mode(type='EDGE')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
for e in new_ob.data.edges:
e.select = e.use_seam
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.dissolve_edges()
bpy.ops.object.mode_set(mode='OBJECT')
if close_mesh != 'NONE':
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(
use_extend=False, use_expand=False, type='EDGE')
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=False, use_boundary=True,
use_multi_face=False, use_non_contiguous=False, use_verts=False)
if open_edges_crease != 0:
bpy.ops.transform.edge_crease(value=open_edges_crease)
if close_mesh == 'CAP':
bpy.ops.mesh.edge_face_add()
if close_mesh == 'BRIDGE':
try:
bpy.ops.mesh.bridge_edge_loops(
type='PAIRS',
number_cuts=bridge_cuts,
interpolation='SURFACE',
smoothness=bridge_smoothness)
except: pass
bpy.ops.object.mode_set(mode='OBJECT')
for f in new_ob.data.polygons:
if f.select: f.material_index = cap_material_index
base_ob = context.view_layer.objects.active
# Combine iterations
if combine_mode != 'LAST' and len(iter_objects)>0:
#if base_ob not in iter_objects and type(base_ob) == bpy.types.Object:
# bpy.data.objects.remove(base_ob)
for o in context.view_layer.objects:
o.select_set(o in iter_objects)
bpy.ops.object.join()
new_ob = context.view_layer.objects.active
elif combine_mode == 'LAST' and type(new_ob) != bpy.types.Object:
# if last iteration gives error, then use the last correct iteration
try:
if type(iter_objects[-1]) == bpy.types.Object:
new_ob = iter_objects[-1]
except: pass
if new_ob == 0:
#bpy.data.objects.remove(base_ob.data)
try: bpy.data.objects.remove(base_ob)
except: pass
message = "The generated object is an empty geometry!"
context.view_layer.objects.active = ob
ob.select_set(True)
bpy.ops.object.mode_set(mode=starting_mode)
self.report({'ERROR'}, message)
return {'CANCELLED'}
errors = {}
errors["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \
"after the last Subsurf (or Multires) are not allowed."
errors["topology_error"] = "Make sure that the topology of the mesh before \n" \
"the last Subsurf (or Multires) is quads only."
errors["wires_error"] = "Please remove all wire edges in the base object."
errors["verts_error"] = "Please remove all floating vertices in the base object"
if new_ob in errors:
for o in iter_objects:
try: bpy.data.objects.remove(o)
except: pass
try: bpy.data.meshes.remove(data1)
except: pass
context.view_layer.objects.active = ob
ob.select_set(True)
message = errors[new_ob]
ob.tissue_tessellate.error_message = message
bpy.ops.object.mode_set(mode=starting_mode)
self.report({'ERROR'}, message)
return {'CANCELLED'}
#new_ob.location = ob_location
#new_ob.matrix_world = ob_matrix_world
# update data and preserve name
if ob.type != 'MESH':
loc, matr = ob.location, ob.matrix_world
ob = convert_object_to_mesh(ob,False,True)
ob.location, ob.matrix_world = loc, matr
data_name = ob.data.name
old_data = ob.data
#ob.data = bpy.data.meshes.new_from_object(new_ob)#
ob.data = new_ob.data.copy()
ob.data.name = data_name
bpy.data.meshes.remove(old_data)
# copy vertex group
if bool_vertex_group:
for vg in new_ob.vertex_groups:
if not vg.name in ob.vertex_groups.keys():
ob.vertex_groups.new(name=vg.name)
new_vg = ob.vertex_groups[vg.name]
for i in range(len(ob.data.vertices)):
try:
weight = vg.weight(i)
except:
weight = 0
new_vg.add([i], weight, 'REPLACE')
selected_objects = [o for o in context.selected_objects]
for o in selected_objects: o.select_set(False)
ob.select_set(True)
context.view_layer.objects.active = ob
if merge:
try:
bpy.ops.object.mode_set(mode='EDIT')
#bpy.ops.mesh.select_mode(
# use_extend=False, use_expand=False, type='VERT')
bpy.ops.mesh.select_mode(type='VERT')
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=True, use_boundary=True,
use_multi_face=False, use_non_contiguous=False, use_verts=False)
bpy.ops.mesh.remove_doubles(
threshold=merge_thres, use_unselected=False)
bpy.ops.object.mode_set(mode='OBJECT')
if bool_dissolve_seams:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='EDGE')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
for e in ob.data.edges:
e.select = e.use_seam
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.dissolve_edges()
except: pass
if close_mesh != 'NONE':
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(
use_extend=False, use_expand=False, type='EDGE')
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=False, use_boundary=True,
use_multi_face=False, use_non_contiguous=False, use_verts=False)
if open_edges_crease != 0:
bpy.ops.transform.edge_crease(value=open_edges_crease)
if close_mesh == 'CAP':
bpy.ops.mesh.edge_face_add()
if close_mesh == 'BRIDGE':
try:
bpy.ops.mesh.bridge_edge_loops(
type='PAIRS',
number_cuts=bridge_cuts,
interpolation='SURFACE',
smoothness=bridge_smoothness)
except:
pass
bpy.ops.object.mode_set(mode='OBJECT')
for f in ob.data.polygons:
if f.select: f.material_index = cap_material_index
#else:
try:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
except: pass
if bool_smooth: bpy.ops.object.shade_smooth()
for mesh in bpy.data.meshes:
if not mesh.users: bpy.data.meshes.remove(mesh)
for o in selected_objects:
try: o.select_set(True)
except: pass
bpy.ops.object.mode_set(mode=starting_mode)
ob.tissue_tessellate.error_message = ""
# Restore Base visibility
ob0.hide_set(ob0_hide)
ob0.hide_viewport = ob0_hidev
ob0.hide_render = ob0_hider
# Restore Component visibility
ob1.hide_set(ob1_hide)
ob1.hide_viewport = ob1_hidev
ob1.hide_render = ob1_hider
# Restore Local visibility
for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1):
ob0.local_view_set(space, local0)
ob1.local_view_set(space, local1)
bpy.data.objects.remove(new_ob)
# clean objects
for o in bpy.data.objects:
#if o.name not in context.view_layer.objects and "_tissue_tmp" in o.name:
if "_tissue_tmp" in o.name:
bpy.data.objects.remove(o)
end_time = time.time()
print('Tissue: object "{}" tessellated in {:.4f} sec'.format(ob.name, end_time-start_time))
return {'FINISHED'}
def check(self, context):
return True
class TISSUE_PT_tessellate(Panel):
bl_label = "Tissue Tools"
bl_category = "Tissue"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
#bl_options = {'DEFAULT_OPEN'}
@classmethod
def poll(cls, context):
return context.mode in {'OBJECT', 'EDIT_MESH'}
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
col.label(text="Tessellate:")
col.operator("object.tissue_tessellate")
col.operator("object.dual_mesh_tessellated")
col.separator()
col.operator("object.tissue_refresh_tessellate", icon='FILE_REFRESH')
col.separator()
col.label(text="Rotate Faces:")
row = col.row(align=True)
row.operator("mesh.tissue_rotate_face_left", text='Left', icon='LOOP_BACK')
row.operator("mesh.tissue_rotate_face_right", text='Right', icon='LOOP_FORWARDS')
col.separator()
col.label(text="Other:")
col.operator("object.dual_mesh")
col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
act = context.object
if act and act.type == 'MESH':
col.operator("object.uv_to_mesh", icon="UV")
if act.mode == 'EDIT':
col.separator()
col.label(text="Weight:")
col.operator("object.tissue_weight_distance", icon="TRACKING")
class TISSUE_PT_tessellate_object(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_label = "Tessellate Settings"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try: return context.object.type == 'MESH'
except: return False
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
if not bool_tessellated:
layout.label(text="The selected object is not a Tessellated object",
icon='INFO')
else:
if props.error_message != "":
layout.label(text=props.error_message,
icon='ERROR')
col = layout.column(align=True)
row = col.row(align=True)
set_tessellate_handler(self,context)
set_animatable_fix_handler(self,context)
row.operator("object.tissue_refresh_tessellate", icon='FILE_REFRESH')
lock_icon = 'LOCKED' if props.bool_lock else 'UNLOCKED'
#lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
deps_icon = 'LINKED' if props.bool_dependencies else 'UNLINKED'
row.prop(props, "bool_dependencies", text="", icon=deps_icon)
row.prop(props, "bool_lock", text="", icon=lock_icon)
col2 = row.column(align=True)
col2.prop(props, "bool_run", text="",icon='TIME')
col2.enabled = not props.bool_lock
'''
col = layout.column(align=True)
row = col.row(align=True)
row.label(text="Base :")
row.label(text="Component :")
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop_search(props, "generator", context.scene, "objects")
row.separator()
col2 = row.column(align=True)
col2.prop_search(props, "component", context.scene, "objects")
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop(props, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
row.separator()
try:
if not (ob0.modifiers or ob0.data.shape_keys) or props.fill_mode == 'PATCH':
col2.enabled = False
except:
col2.enabled = False
col2 = row.column(align=True)
col2.prop(props, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
try:
if not (props.component.modifiers or props.component.data.shape_keys):
col2.enabled = False
except:
col2.enabled = False
'''
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
col = layout.column(align=True)
row = col.row(align=True)
row.label(text='Base:')
row.prop_search(props, "generator", context.scene, "objects")
col2 = row.column(align=True)
col2.prop(props, "gen_modifiers", text='',icon='MODIFIER')
try:
if not (props.generator.modifiers or props.generator.data.shape_keys):
col2.enabled = False
except:
col2.enabled = False
col.separator()
row = col.row(align=True)
row.label(text='Component:')
row.prop_search(props, "component", context.scene, "objects")
col2 = row.column(align=True)
col2.prop(props, "com_modifiers", text='',icon='MODIFIER')
try:
if not (props.component.modifiers or props.component.data.shape_keys):
col2.enabled = False
except:
col2.enabled = False
layout.use_property_split = False
# Fill
col = layout.column(align=True)
col.label(text="Fill Mode:")
# fill
row = col.row(align=True)
row.prop(props, "fill_mode", icon='NONE', expand=True,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
#layout.use_property_split = True
col = layout.column(align=True)
col.prop(props, "bool_smooth")
class TISSUE_PT_tessellate_frame(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_parent_id = "TISSUE_PT_tessellate_object"
bl_label = "Frame Settings"
#bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try:
bool_frame = context.object.tissue_tessellate.fill_mode == 'FRAME'
bool_tessellated = context.object.tissue_tessellate.generator != None
return context.object.type == 'MESH' and bool_frame and bool_tessellated
except:
return False
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
if bool_tessellated:
col = layout.column(align=True)
row = col.row(align=True)
row.prop(props, "frame_mode", expand=True)
row = col.row(align=True)
row.prop(props, "frame_thickness", icon='NONE', expand=True)
row = col.row(align=True)
row.prop(props, "fill_frame", icon='NONE')
show_frame_mat = props.bool_multi_components or props.bool_material_id
if props.fill_frame and show_frame_mat:
row.prop(props, "fill_frame_mat", icon='NONE')
row = col.row(align=True)
row.prop(props, "frame_boundary", text='Boundary', icon='NONE')
if props.frame_boundary and show_frame_mat:
row.prop(props, "frame_boundary_mat", icon='NONE')
class TISSUE_PT_tessellate_coordinates(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_parent_id = "TISSUE_PT_tessellate_object"
bl_label = "Component Coordinates"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try:
bool_tessellated = context.object.tissue_tessellate.generator != None
return context.object.type == 'MESH' and bool_tessellated
except:
return False
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
if bool_tessellated:
col = layout.column(align=True)
# component XY
row = col.row(align=True)
row.prop(props, "mode", expand=True)
if props.mode != 'BOUNDS':
col.separator()
row = col.row(align=True)
row.label(text="X:")
row.prop(
props, "bounds_x", text="Bounds X", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
row = col.row(align=True)
row.label(text="Y:")
row.prop(
props, "bounds_y", text="Bounds X", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
class TISSUE_PT_tessellate_rotation(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_parent_id = "TISSUE_PT_tessellate_object"
bl_label = "Rotation"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try:
bool_tessellated = context.object.tissue_tessellate.generator != None
return context.object.type == 'MESH' and bool_tessellated
except:
return False
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
if bool_tessellated:
# rotation
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
col = layout.column(align=True)
col.prop(props, "rotation_mode", text='Rotation', icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
if props.rotation_mode == 'WEIGHT':
col.prop(props, "rotation_direction", expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
if props.rotation_mode == 'RANDOM':
col.prop(props, "random_seed")
else:
col.prop(props, "rotation_shift")
if props.rotation_mode == 'UV':
uv_error = False
if props.generator.type != 'MESH':
row = col.row(align=True)
row.label(
text="UV rotation supported only for Mesh objects",
icon='ERROR')
uv_error = True
else:
if len(props.generator.data.uv_layers) == 0:
row = col.row(align=True)
row.label(text="'" + props.generator.name +
" doesn't have UV Maps", icon='ERROR')
uv_error = True
if uv_error:
row = col.row(align=True)
row.label(text="Default rotation will be used instead",
icon='INFO')
class TISSUE_PT_tessellate_thickness(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_parent_id = "TISSUE_PT_tessellate_object"
bl_label = "Thickness"
#bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try:
bool_tessellated = context.object.tissue_tessellate.generator != None
return context.object.type == 'MESH' and bool_tessellated
except:
return False
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
#layout.use_property_split = True
if bool_tessellated:
col = layout.column(align=True)
# component Z
row = col.row(align=True)
row.prop(props, "scale_mode", expand=True)
col.prop(props, "zscale", text="Scale", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
if props.mode == 'BOUNDS':
col.prop(props, "offset", text="Offset", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
# Direction
col = layout.column(align=True)
row = col.row(align=True)
row.label(text="Direction:")
row = col.row(align=True)
row.prop(
props, "normals_mode", text="Direction", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
class TISSUE_PT_tessellate_options(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_parent_id = "TISSUE_PT_tessellate_object"
bl_label = " "
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try:
bool_tessellated = context.object.tissue_tessellate.generator != None
return context.object.type == 'MESH' and bool_tessellated
except:
return False
def draw_header(self, context):
ob = context.object
props = ob.tissue_tessellate
self.layout.prop(props, "merge")
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
if bool_tessellated:
col = layout.column(align=True)
if props.merge:
col.prop(props, "merge_thres")
col.prop(props, "bool_dissolve_seams")
col.prop(props, "close_mesh")
if props.close_mesh != 'NONE':
#row = col.row(align=True)
col.separator()
col.prop(props, "open_edges_crease", text="Crease")
col.prop(props, "cap_material_index", text='Material Index')
if props.close_mesh == 'BRIDGE':
col.separator()
col.prop(props, "bridge_cuts")
col.prop(props, "bridge_smoothness")
class TISSUE_PT_tessellate_morphing(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_parent_id = "TISSUE_PT_tessellate_object"
bl_label = "Weight and Morphing"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try:
bool_tessellated = context.object.tissue_tessellate.generator != None
return context.object.type == 'MESH' and bool_tessellated
except:
return False
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
if bool_tessellated:
allow_shapekeys = not props.com_modifiers
for m in ob0.data.materials:
try:
o = bpy.data.objects[m.name]
allow_multi = True
try:
if o.data.shape_keys is None: continue
elif len(o.data.shape_keys.key_blocks) < 2: continue
else: allow_shapekeys = not props.com_modifiers
except: pass
except: pass
col = layout.column(align=True)
#col.label(text="Morphing:")
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop(props, "bool_vertex_group", icon='GROUP_VERTEX')
#col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
try:
if len(props.generator.vertex_groups) == 0:
col2.enabled = False
except:
col2.enabled = False
row.separator()
col2 = row.column(align=True)
row2 = col2.row(align=True)
row2.prop(props, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
row2.enabled = allow_shapekeys
if not allow_shapekeys:
col2 = layout.column(align=True)
row2 = col2.row(align=True)
row2.label(text="Use Shape Keys is not compatible with Use Modifiers", icon='INFO')
class TISSUE_PT_tessellate_selective(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_parent_id = "TISSUE_PT_tessellate_object"
bl_label = "Selective"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try:
bool_tessellated = context.object.tissue_tessellate.generator != None
return context.object.type == 'MESH' and bool_tessellated
except:
return False
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
if bool_tessellated:
allow_multi = False
allow_shapekeys = not props.com_modifiers
for m in ob0.data.materials:
try:
o = bpy.data.objects[m.name]
allow_multi = True
try:
if o.data.shape_keys is None: continue
elif len(o.data.shape_keys.key_blocks) < 2: continue
else: allow_shapekeys = not props.com_modifiers
except: pass
except: pass
# LIMITED TESSELLATION
col = layout.column(align=True)
#col.label(text="Limited Tessellation:")
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
row.separator()
if props.generator.type != 'MESH':
col2.enabled = False
col2 = row.column(align=True)
col2.prop(props, "bool_material_id", icon='MATERIAL_DATA', text="Material ID")
if props.bool_material_id and not props.bool_multi_components:
#col2 = row.column(align=True)
col2.prop(props, "material_id")
if props.bool_multi_components:
col2.enabled = False
col.separator()
row = col.row(align=True)
col2 = row.column(align=True)
col2.prop(props, "bool_multi_components", icon='MOD_TINT')
if not allow_multi:
col2.enabled = False
class TISSUE_PT_tessellate_iterations(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
bl_parent_id = "TISSUE_PT_tessellate_object"
bl_label = "Iterations"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
try:
bool_tessellated = context.object.tissue_tessellate.generator != None
return context.object.type == 'MESH' and bool_tessellated
except:
return False
def draw(self, context):
ob = context.object
props = ob.tissue_tessellate
allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
bool_tessellated = props.generator or props.component != None
ob0 = props.generator
ob1 = props.component
except: bool_tessellated = False
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
if bool_tessellated:
col = layout.column(align=True)
row = col.row(align=True)
#row.label(text='', icon='FILE_REFRESH')
col.prop(props, 'iterations', text='Repeat')#, icon='FILE_REFRESH')
if props.iterations > 1 and props.fill_mode == 'PATCH':
col.separator()
#row = col.row(align=True)
col.prop(props, 'patch_subs')
layout.use_property_split = False
col = layout.column(align=True)
#row = col.row(align=True)
col.label(text='Combine Iterations:')
row = col.row(align=True)
row.prop(
props, "combine_mode", text="Combine:",icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
class tissue_rotate_face_right(Operator):
bl_idname = "mesh.tissue_rotate_face_right"
bl_label = "Rotate Faces Right"
bl_description = "Rotate clockwise selected faces and update tessellated meshes"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
try:
#bool_tessellated = context.object.tissue_tessellate.generator != None
ob = context.object
return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
except:
return False
def execute(self, context):
ob = context.active_object
me = ob.data
bm = bmesh.from_edit_mesh(me)
mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
for face in bm.faces:
if (face.select):
vs = face.verts[:]
vs2 = vs[-1:]+vs[:-1]
material_index = face.material_index
bm.faces.remove(face)
f2 = bm.faces.new(vs2)
f2.select = True
f2.material_index = material_index
bm.normal_update()
# trigger UI update
bmesh.update_edit_mesh(me)
ob.select_set(False)
# update tessellated meshes
bpy.ops.object.mode_set(mode='OBJECT')
for o in [obj for obj in bpy.data.objects if
obj.tissue_tessellate.generator == ob and obj.visible_get()]:
context.view_layer.objects.active = o
bpy.ops.object.tissue_update_tessellate()
o.select_set(False)
ob.select_set(True)
context.view_layer.objects.active = ob
bpy.ops.object.mode_set(mode='EDIT')
context.tool_settings.mesh_select_mode = mesh_select_mode
return {'FINISHED'}
class tissue_rotate_face_left(Operator):
bl_idname = "mesh.tissue_rotate_face_left"
bl_label = "Rotate Faces Left"
bl_description = "Rotate counterclockwise selected faces and update tessellated meshes"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
try:
#bool_tessellated = context.object.tissue_tessellate.generator != None
ob = context.object
return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
except:
return False
def execute(self, context):
ob = context.active_object
me = ob.data
bm = bmesh.from_edit_mesh(me)
mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
for face in bm.faces:
if (face.select):
vs = face.verts[:]
vs2 = vs[1:]+vs[:1]
material_index = face.material_index
bm.faces.remove(face)
f2 = bm.faces.new(vs2)
f2.select = True
f2.material_index = material_index
bm.normal_update()
# trigger UI update
bmesh.update_edit_mesh(me)
ob.select_set(False)
# update tessellated meshes
bpy.ops.object.mode_set(mode='OBJECT')
for o in [obj for obj in bpy.data.objects if
obj.tissue_tessellate.generator == ob and obj.visible_get()]:
context.view_layer.objects.active = o
bpy.ops.object.tissue_update_tessellate()
o.select_set(False)
ob.select_set(True)
context.view_layer.objects.active = ob
bpy.ops.object.mode_set(mode='EDIT')
context.tool_settings.mesh_select_mode = mesh_select_mode
return {'FINISHED'}
def convert_to_frame(ob, props, use_modifiers):
new_ob = convert_object_to_mesh(ob, use_modifiers, True)
# create bmesh
bm = bmesh.new()
bm.from_mesh(new_ob.data)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
if props.bool_selection:
original_faces = [f for f in bm.faces if f.select]
else:
original_faces = list(bm.faces)
# detect edge loops
loops = []
boundaries_mat = []
neigh_face_center = []
face_normals = []
# append boundary loops
if props.frame_boundary:
#selected_edges = [e for e in bm.edges if e.select]
selected_edges = [e for e in bm.edges if e.is_boundary]
if len(selected_edges) > 0:
loop = []
count = 0
e0 = selected_edges[0]
face = e0.link_faces[0]
boundary_mat = [face.material_index]
face_center = [face.calc_center_median()]
loop_normals = [face.normal]
selected_edges = selected_edges[1:]
if props.bool_vertex_group:
n_verts = len(new_ob.data.vertices)
base_vg = [get_weight(vg,n_verts) for vg in new_ob.vertex_groups]
'''
base_vg = []
for vg in new_ob.vertex_groups:
vertex_group = []
for v in bm.verts:
try:
vertex_group.append(vg.weight(v.index))
except:
vertex_group.append(0)
base_vg.append(vertex_group)
'''
while True:
new_vert = None
face = None
for e1 in selected_edges:
if e1.verts[0] in e0.verts: new_vert = e1.verts[1]
elif e1.verts[1] in e0.verts: new_vert = e1.verts[0]
if new_vert != None:
if len(loop)==0:
loop = [v for v in e1.verts if v != new_vert]
loop.append(new_vert)
e0 = e1
face = e0.link_faces[0]
boundary_mat.append(face.material_index)
face_center.append(face.calc_center_median())
loop_normals.append(face.normal)
selected_edges.remove(e0)
break
if new_vert == None:
try:
loops.append(loop)
loop = []
e0 = selected_edges[0]
selected_edges = selected_edges[1:]
boundaries_mat.append(boundary_mat)
neigh_face_center.append(face_center)
face_normals.append(loop_normals)
face = e0.link_faces[0]
boundary_mat = [face.material_index]
face_center = [face.calc_center_median()]
loop_normals = [face.normal]
except: break
boundaries_mat.append(boundary_mat)
neigh_face_center.append(face_center)
face_normals.append(loop_normals)
# compute boundary frames
new_faces = []
vert_ids = []
# append regular faces
for f in original_faces:#bm.faces:
loop = list(f.verts)
loops.append(loop)
boundaries_mat.append([f.material_index for v in loop])
face_normals.append([f.normal for v in loop])
# calc areas for relative frame mode
if props.frame_mode == 'RELATIVE':
verts_area = []
for v in bm.verts:
linked_faces = v.link_faces
if len(linked_faces) > 0:
area = sum([sqrt(f.calc_area())/len(f.verts) for f in v.link_faces])*2
area /= len(linked_faces)
else: area = 0
verts_area.append(area)
for loop_index, loop in enumerate(loops):
is_boundary = loop_index < len(neigh_face_center)
materials = boundaries_mat[loop_index]
new_loop = []
loop_ext = [loop[-1]] + loop + [loop[0]]
# calc tangents
tangents = []
for i in range(len(loop)):
# vertices
vert0 = loop_ext[i]
vert = loop_ext[i+1]
vert1 = loop_ext[i+2]
# edge vectors
vec0 = (vert0.co - vert.co).normalized()
vec1 = (vert.co - vert1.co).normalized()
# tangent
_vec1 = -vec1
_vec0 = -vec0
ang = (pi - vec0.angle(vec1))/2
normal = face_normals[loop_index][i]
tan0 = normal.cross(vec0)
tan1 = normal.cross(vec1)
tangent = (tan0 + tan1).normalized()/sin(ang)*props.frame_thickness
tangents.append(tangent)
# calc correct direction for boundaries
mult = -1
if is_boundary:
dir_val = 0
for i in range(len(loop)):
surf_point = neigh_face_center[loop_index][i]
tangent = tangents[i]
vert = loop_ext[i+1]
dir_val += tangent.dot(vert.co - surf_point)
if dir_val > 0: mult = 1
# add vertices
for i in range(len(loop)):
vert = loop_ext[i+1]
if props.frame_mode == 'RELATIVE': area = verts_area[vert.index]
else: area = 1
new_co = vert.co + tangents[i] * mult * area
# add vertex
new_vert = bm.verts.new(new_co)
new_loop.append(new_vert)
vert_ids.append(vert.index)
new_loop.append(new_loop[0])
# add faces
materials += [materials[0]]
for i in range(len(loop)):
v0 = loop_ext[i+1]
v1 = loop_ext[i+2]
v2 = new_loop[i+1]
v3 = new_loop[i]
face_verts = [v1,v0,v3,v2]
if mult == -1: face_verts = [v0,v1,v2,v3]
new_face = bm.faces.new(face_verts)
new_face.material_index = materials[i+1] + props.frame_boundary_mat
new_face.select = True
new_faces.append(new_face)
# fill frame
if props.fill_frame and not is_boundary:
n_verts = len(new_loop)-1
loop_center = Vector((0,0,0))
for v in new_loop[1:]: loop_center += v.co
loop_center /= n_verts
center = bm.verts.new(loop_center)
for i in range(n_verts):
v0 = new_loop[i+1]
v1 = new_loop[i]
face_verts = [v1,v0,center]
new_face = bm.faces.new(face_verts)
new_face.material_index = materials[i] + props.frame_boundary_mat
new_face.select = True
new_faces.append(new_face)
bpy.ops.object.mode_set(mode='OBJECT')
#for f in bm.faces: f.select_set(f not in new_faces)
for f in original_faces: bm.faces.remove(f)
bm.to_mesh(new_ob.data)
# propagate vertex groups
if props.bool_vertex_group:
base_vg = []
for vg in new_ob.vertex_groups:
vertex_group = []
for v in bm.verts:
try:
vertex_group.append(vg.weight(v.index))
except:
vertex_group.append(0)
base_vg.append(vertex_group)
new_vert_ids = range(len(bm.verts)-len(vert_ids),len(bm.verts))
for vg_id, vg in enumerate(new_ob.vertex_groups):
for ii, jj in zip(vert_ids, new_vert_ids):
vg.add([jj], base_vg[vg_id][ii], 'REPLACE')
new_ob.data.update()
return new_ob
def convert_to_fan(ob, props, use_modifiers):
new_ob = convert_object_to_mesh(ob, use_modifiers, True)
# make base object selected and active
for o in bpy.context.view_layer.objects: o.select_set(False)
new_ob.select_set(True)
bpy.context.view_layer.objects.active = new_ob
sk_index0 = new_ob.active_shape_key_index
new_ob.active_shape_key_index = 0
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(type='FACE')
if not props.bool_selection:
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.poke()
bpy.ops.object.mode_set(mode='OBJECT')
return new_ob