Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Carlisle <carlisle.b3d@gmail.com>2022-03-01 19:02:00 +0300
committerAaron Carlisle <carlisle.b3d@gmail.com>2022-03-01 19:02:00 +0300
commit3cd4141bdc2698bf22567e8fde9d2e05176e102d (patch)
tree9ae3372d60948af8b991b6b14915f36449b65371 /mesh_tissue/tessellate_numpy.py
parentf727846be862fd0f1891a507f3adefc8e8986902 (diff)
parentda9a50a46ed3e293146428a87a275a947a54b9fe (diff)
Tissue: Update to 3.52
From https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-52 with https://github.com/alessandro-zomparelli/tissue/pull/131 applied. Fixes T80702 Fixes T73136
Diffstat (limited to 'mesh_tissue/tessellate_numpy.py')
-rw-r--r--mesh_tissue/tessellate_numpy.py5394
1 files changed, 3079 insertions, 2315 deletions
diff --git a/mesh_tissue/tessellate_numpy.py b/mesh_tissue/tessellate_numpy.py
index 555bb879..d3c1fc21 100644
--- a/mesh_tissue/tessellate_numpy.py
+++ b/mesh_tissue/tessellate_numpy.py
@@ -28,869 +28,657 @@ from bpy.props import (
StringProperty,
PointerProperty
)
-from mathutils import Vector
+from mathutils import Vector, Quaternion, Matrix
import numpy as np
-from math import sqrt
-import random, time
+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_hold:
- try:
- props.generator.name
- props.component.name
- bpy.ops.object.update_tessellate()
- except: pass
-
-def anim_tessellate_object(ob):
- try:
- #bpy.context.view_layer.objects.active = ob
- bpy.ops.object.update_tessellate()
- except:
- return None
-
-#from bpy.app.handlers import persistent
-
-#@persistent
-def anim_tessellate(scene):
- # store selected objects
- #scene = context.scene
- try: active_object = bpy.context.object
- except: active_object = None
- try: selected_objects = bpy.context.selected_objects
- except: selected_objects = []
- if bpy.context.mode in ('OBJECT', 'PAINT_WEIGHT'):
- old_mode = bpy.context.mode
- if old_mode == 'PAINT_WEIGHT': old_mode = 'WEIGHT_PAINT'
- for ob in scene.objects:
- if ob.tissue_tessellate.bool_run:
- hidden = ob.hide_viewport
- ob.hide_viewport = False
- for o in scene.objects:
- if not o.hide_viewport: ob.select_set(False)
- bpy.context.view_layer.objects.active = ob
- ob.select_set(True)
- try:
- bpy.ops.object.update_tessellate()
- except: pass
- ob.hide_viewport = hidden
- # restore selected objects
- for o in scene.objects:
- if not o.hide_viewport: o.select_set(False)
- for o in selected_objects:
- if not o.hide_viewport: o.select_set(True)
- bpy.context.view_layer.objects.active = active_object
- try: bpy.ops.object.mode_set(mode=old_mode)
- except: pass
+from .weight_tools import *
+from .numba_functions import *
+from .tissue_properties import *
+import os, mathutils
+from pathlib import Path
+
+from . import config
+
+def allowed_objects():
+ return ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
+
+def remove_temp_objects():
+ # clean objects
+ for o in bpy.data.objects:
+ if "_tissue_tmp" in o.name:
+ bpy.data.objects.remove(o)
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
+def tessellated(ob):
+ tess_props = ob.tissue_tessellate
+ if tess_props.generator not in list(bpy.data.objects):
+ return False
+ elif tess_props.component_mode == 'OBJECT':
+ return tess_props.component in list(bpy.data.objects)
+ elif tess_props.component_mode == 'COLLECTION':
+ if tess_props.component_coll in list(bpy.data.collections):
+ for o in list(tess_props.component_coll.objects):
+ if o.type in allowed_objects():
+ return True
+ else:
+ for mat in tess_props.generator.material_slots.keys():
+ if mat in bpy.data.objects.keys():
+ if bpy.data.objects[mat].type in allowed_objects():
+ return True
+ return False
+
+def tessellate_patch(props):
+ tt = time.time()
+
+ ob = props['self']
+ _ob0 = props['generator']
+ components = props['component']
+ offset = props['offset']
+ zscale = props['zscale']
+ gen_modifiers = props['gen_modifiers']
+ com_modifiers = props['com_modifiers']
+ mode = props['mode']
+ fill_mode = props['fill_mode']
+ scale_mode = props['scale_mode']
+ rotation_mode = props['rotation_mode']
+ rotation_shift = props['rotation_shift']
+ rand_seed = props['rand_seed']
+ rand_step = props['rand_step']
+ bool_vertex_group = props['bool_vertex_group']
+ bool_selection = props['bool_selection']
+ bool_shapekeys = props['bool_shapekeys']
+ bool_material_id = props['bool_material_id']
+ material_id = props['material_id']
+ normals_mode = props['normals_mode']
+ bounds_x = props['bounds_x']
+ bounds_y = props['bounds_y']
+ use_origin_offset = props['use_origin_offset']
+ vertex_group_thickness = props['vertex_group_thickness']
+ invert_vertex_group_thickness = props['invert_vertex_group_thickness']
+ vertex_group_thickness_factor = props['vertex_group_thickness_factor']
+ vertex_group_distribution = props['vertex_group_distribution']
+ invert_vertex_group_distribution = props['invert_vertex_group_distribution']
+ vertex_group_distribution_factor = props['vertex_group_distribution_factor']
+ vertex_group_cap_owner = props['vertex_group_cap_owner']
+ vertex_group_cap = props['vertex_group_cap']
+ invert_vertex_group_cap = props['invert_vertex_group_cap']
+ vertex_group_bridge_owner = props['vertex_group_bridge_owner']
+ vertex_group_bridge = props['vertex_group_bridge']
+ invert_vertex_group_bridge = props['invert_vertex_group_bridge']
+ vertex_group_rotation = props['vertex_group_rotation']
+ invert_vertex_group_rotation = props['invert_vertex_group_rotation']
+ rotation_direction = props['rotation_direction']
+ target = props['target']
+ even_thickness = props['even_thickness']
+ even_thickness_iter = props['even_thickness_iter']
+ smooth_normals = props['smooth_normals']
+ smooth_normals_iter = props['smooth_normals_iter']
+ smooth_normals_uv = props['smooth_normals_uv']
+ vertex_group_smooth_normals = props['vertex_group_smooth_normals']
+ invert_vertex_group_smooth_normals = props['invert_vertex_group_smooth_normals']
+ #bool_multi_components = props['bool_multi_components']
+ component_mode = props['component_mode']
+ coll_rand_seed = props['coll_rand_seed']
+ consistent_wedges = props['consistent_wedges']
+ vertex_group_scale_normals = props['vertex_group_scale_normals']
+ invert_vertex_group_scale_normals = props['invert_vertex_group_scale_normals']
+ boundary_mat_offset = props['boundary_mat_offset']
+
+ _props = props.copy()
+
+ # reset messages
+ ob.tissue_tessellate.warning_message_thickness = ''
+
+ if normals_mode == 'SHAPEKEYS':
+ if _ob0.data.shape_keys != None:
+ target = _ob0
+ else:
+ normals_mode = 'VERTS'
+ message = "Base mesh doesn't have Shape Keys"
+ ob.tissue_tessellate.warning_message_thickness = message
+ print("Tissue: " + message)
+ if normals_mode == 'OBJECT' and target == None:
+ normals_mode = 'VERTS'
+ message = "Please select a target object"
+ ob.tissue_tessellate.warning_message_thickness = message
+ print("Tissue: " + message)
-class tissue_tessellate_prop(PropertyGroup):
- bool_hold : BoolProperty(
- name="Hold Update",
- description="Prevent automatic update while other properties are changed",
- 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', "Proportional", "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"),
- ('DEFAULT', "Default", "Default rotation")),
- default='DEFAULT',
- name="Component 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')),
- 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.\nUseful 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', 'Along Normals', 'Consistent direction based on vertices normal'),
- ('FACES', 'Individual Faces', 'Based on individual faces normal')),
- 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
- )
- cap_faces : BoolProperty(
- name="Cap Holes",
- default=False,
- description="Cap open edges loops",
- 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
- )
-
-def store_parameters(operator, ob):
- ob.tissue_tessellate.bool_hold = True
- 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.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.bool_hold = False
- return ob
-
-def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode,
- scale_mode, rotation_mode, rand_seed, bool_vertex_group,
- bool_selection, bool_shapekeys, bool_material_id, material_id,
- bounds_x, bounds_y):
random.seed(rand_seed)
+ if len(_ob0.modifiers) == 0: gen_modifiers = False
- ob0 = convert_object_to_mesh(_ob0)
- 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:
- return 0
+ # Target mesh used for normals
+ if normals_mode in ('SHAPEKEYS', 'OBJECT'):
+ if fill_mode == 'PATCH':
+ ob0_sk = convert_object_to_mesh(target, True, True)
+ else:
+ use_modifiers = gen_modifiers
+ if normals_mode == 'SHAPEKEYS' and not gen_modifiers:
+ target = _ob0
+ for m in target.modifiers:
+ m.show_viewport = False
+ use_modifiers = True
+ _props['use_modifiers'] = use_modifiers
+ if fill_mode == 'FAN': ob0_sk = convert_to_fan(target, _props, add_id_layer=id_layer)
+ elif fill_mode == 'FRAME': ob0_sk = convert_to_frame(target, _props)
+ elif fill_mode == 'TRI': ob0_sk = convert_to_triangles(target, _props)
+ elif fill_mode == 'QUAD': ob0_sk = reduce_to_quads(target, _props)
+ me0_sk = ob0_sk.data
+ normals_target = get_vertices_numpy(me0_sk)
+ bpy.data.objects.remove(ob0_sk)
+ if normals_mode == 'SHAPEKEYS':
+ 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
+ # Base mesh
+ if fill_mode == 'PATCH':
+ ob0 = convert_object_to_mesh(_ob0)
+
+ if boundary_mat_offset != 0:
+ bm=bmesh.new()
+ bm.from_mesh(ob0.data)
+ bm = offset_boundary_materials(
+ bm,
+ boundary_mat_offset = _props['boundary_mat_offset'],
+ boundary_variable_offset = _props['boundary_variable_offset'],
+ auto_rotate_boundary = _props['auto_rotate_boundary'])
+ bm.to_mesh(ob0.data)
+ bm.free()
+ ob0.data.update()
+
+ else:
+ if fill_mode == 'FAN':
+ id_layer = component_mode == 'COLLECTION' and consistent_wedges
+ ob0 = convert_to_fan(_ob0, _props, add_id_layer=id_layer)
+ elif fill_mode == 'FRAME': ob0 = convert_to_frame(_ob0, _props)
+ elif fill_mode == 'TRI': ob0 = convert_to_triangles(_ob0, _props)
+ elif fill_mode == 'QUAD': ob0 = reduce_to_quads(_ob0, _props)
+ ob0.name = "_tissue_tmp_ob0"
+ me0 = ob0.data
+ n_verts0 = len(me0.vertices)
+
+ # read vertices coordinates
+ verts0_co = get_vertices_numpy(me0)
+
+ # base normals
+ if normals_mode in ('SHAPEKEYS','OBJECT'):
+ if len(normals_target) != len(me0.vertices):
+ normals_mode = 'VERTS'
+ message = "Base mesh and Target mesh don't match"
+ ob.tissue_tessellate.warning_message_thickness = message
+ print("Tissue: " + message)
+ else:
+ if normals_mode == 'SHAPEKEYS':
+ for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val
+ verts0_normal = normals_target - verts0_co
+ '''
+ While in Relative thickness method the components are built
+ between the two surfaces, in Constant mode the thickness is uniform.
+ '''
+ if scale_mode == 'CONSTANT':
+ # Normalize vectors
+ verts0_normal /= np.linalg.norm(verts0_normal, axis=1).reshape((-1,1))
+ if not even_thickness:
+ pass
+ #original_normals = get_normals_numpy(me0)
+ #verts0_normal /= np.multiply(verts0_normal, original_normals).sum(1)[:,None]
+ else:
+ # Evaluate maximum components thickness
+ first_component = True
+ for com in components:
+ if com:
+ com = convert_object_to_mesh(com, com_modifiers, False)
+ com, com_area = tessellate_prepare_component(com, props)
+ com_verts = get_vertices_numpy(com.data)
+ bpy.data.objects.remove(com)
+ if first_component:
+ all_com_verts = com_verts
+ first_component = False
+ else:
+ all_com_verts = np.concatenate((all_com_verts, com_verts), axis=0)
+ pos_step_dist = abs(np.max(all_com_verts[:,2]))
+ neg_step_dist = abs(np.min(all_com_verts[:,2]))
+
+ # Rescale normalized vectors according to the angle with the normals
+ original_normals = get_normals_numpy(me0)
+ kd = mathutils.kdtree.KDTree(len(verts0_co))
+ for i, v in enumerate(verts0_co):
+ kd.insert(v, i)
+ kd.balance()
+ step_dist = [neg_step_dist, pos_step_dist]
+ mult = 1
+ sign = [-1,1]
+ for sgn, stp in zip(sign, step_dist):
+ if stp == 0:
+ if sgn == 1: verts0_normal_pos = verts0_normal
+ if sgn == -1: verts0_normal_neg = verts0_normal
+ continue
+ for i in range(even_thickness_iter):
+ test_dist = stp * mult
+ test_pts = verts0_co + verts0_normal * test_dist * sgn
+ # Find the closest point to the sample point
+ closest_dist = []
+ closest_co = []
+ closest_nor = []
+ closest_index = []
+ for find in test_pts:
+ co, index, dist = kd.find(find)
+ closest_co.append(co) # co, index, dist
+ closest_index.append(index) # co, index, dist
+ closest_co = np.array(closest_co)#[:,3,None]
+ closest_index = np.array(closest_index)
+ closest_nor = original_normals[closest_index]
+ closest_vec = test_pts - closest_co
+ projected_vectors = np.multiply(closest_vec, closest_nor).sum(1)[:,None]
+ closest_dist = np.linalg.norm(projected_vectors, axis=1)[:,None]
+ mult = mult*0.2 + test_dist/closest_dist*0.8 # Reduces bouncing effect
+ if sgn == 1: verts0_normal_pos = verts0_normal * mult
+ if sgn == -1: verts0_normal_neg = verts0_normal * mult
+
+ if normals_mode in ('VERTS','FACES'):
+ verts0_normal = get_normals_numpy(me0)
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
- #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:
- #ob0.data = old_me0
- #bpy.data.meshes.remove(me0)
- return "modifiers_error"
-
- before = _ob0.copy()
- #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()
- for f in before_bm.faces:
- if len(f.loops) != 4:
- return "topology_error"
- before_bm.edges.ensure_lookup_table()
- for e in before_bm.edges:
- if len(e.link_faces) == 0:
- return "wires_error"
- before_bm.verts.ensure_lookup_table()
- for v in before_bm.verts:
- if len(v.link_faces) == 0:
- return "verts_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:
- 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':
- bpy.context.object.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] + (-0.5 + offset * 0.5) * bb[2]) * 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]
+ modifiers0 = list(_ob0.modifiers)
+ if len(modifiers0) == 0 or fill_mode != 'PATCH':
+ before_subsurf = me0
+ if fill_mode == 'PATCH':
+ fill_mode = 'QUAD'
+ else:
+ 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
+ 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)
+
+ if boundary_mat_offset != 0:
+ bm=bmesh.new()
+ bm.from_mesh(before_subsurf)
+ bm = offset_boundary_materials(
+ bm,
+ boundary_mat_offset = _props['boundary_mat_offset'],
+ boundary_variable_offset = _props['boundary_variable_offset'],
+ auto_rotate_boundary = _props['auto_rotate_boundary'])
+ bm.to_mesh(before_subsurf)
+ bm.free()
+ before_subsurf.update()
+
+ bpy.data.objects.remove(before)
+
+ tt = tissue_time(tt, "Meshes preparation", levels=2)
+
+ ### PATCHES ###
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
+ if fill_mode == 'PATCH':
+ all_verts, mask, materials = get_patches(before_subsurf, me0, 4, levels, bool_selection)
+ else:
+ all_verts, mask, materials = get_quads(me0, bool_selection)
+ n_patches = len(all_verts)
- # 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
+ tt = tissue_time(tt, "Indexing", levels=2)
- # Adaptive Z
- if scale_mode == 'ADAPTIVE':
- if mode == 'BOUNDS': com_area = (bb[0]*bb[1])
- else: com_area = 1
- mult = 1/com_area*patch_faces
- 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/=len(faces)
- area*=mult
- verts_area.append(sqrt(area))
+ ### WEIGHT ###
- random.seed(rand_seed)
- bool_correct = False
+ # Check if possible to use Weight Rotation
+ if rotation_mode == 'WEIGHT':
+ if not vertex_group_rotation in ob0.vertex_groups.keys():
+ rotation_mode = 'DEFAULT'
- _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)]
+ bool_weight_smooth_normals = vertex_group_smooth_normals in ob0.vertex_groups.keys()
+ bool_weight_thickness = vertex_group_thickness in ob0.vertex_groups.keys()
+ bool_weight_distribution = vertex_group_distribution in ob0.vertex_groups.keys()
+ bool_weight_cap = vertex_group_cap_owner == 'BASE' and vertex_group_cap in ob0.vertex_groups.keys()
+ bool_weight_bridge = vertex_group_bridge_owner == 'BASE' and vertex_group_bridge in ob0.vertex_groups.keys()
+ bool_weight_normals = vertex_group_scale_normals in ob0.vertex_groups.keys()
- 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
+ read_vertex_groups = bool_vertex_group or rotation_mode == 'WEIGHT' or bool_weight_thickness or bool_weight_cap or bool_weight_bridge or bool_weight_smooth_normals or bool_weight_distribution or bool_weight_normals
+ weight = weight_thickness = weight_rotation = None
+ if read_vertex_groups:
+ if bool_vertex_group:
+ weight = [get_weight(vg, n_verts0) for vg in ob0.vertex_groups]
+ weight = np.array(weight)
+ n_vg = len(ob0.vertex_groups)
+ if rotation_mode == 'WEIGHT':
+ vg_id = ob0.vertex_groups[vertex_group_rotation].index
+ weight_rotation = weight[vg_id]
+ if bool_weight_smooth_normals:
+ vg_id = ob0.vertex_groups[bool_weight_smooth_normals].index
+ weight_rotation = weight[vg_id]
+ if bool_weight_distribution:
+ vg_id = ob0.vertex_groups[vertex_group_distribution].index
+ weight_distribution = weight[vg_id]
+ if bool_weight_normals:
+ vg_id = ob0.vertex_groups[vertex_group_scale_normals].index
+ weight_normals = weight[vg_id]
+ else:
+ if rotation_mode == 'WEIGHT':
+ vg = ob0.vertex_groups[vertex_group_rotation]
+ weight_rotation = get_weight_numpy(vg, n_verts0)
+ if bool_weight_smooth_normals:
+ vg = ob0.vertex_groups[vertex_group_smooth_normals]
+ weight_smooth_normals = get_weight_numpy(vg, n_verts0)
+ if bool_weight_distribution:
+ vg = ob0.vertex_groups[vertex_group_distribution]
+ weight_distribution = get_weight_numpy(vg, n_verts0)
+ if bool_weight_normals:
+ vg = ob0.vertex_groups[vertex_group_scale_normals]
+ weight_normals = get_weight_numpy(vg, n_verts0)
+
+ if component_mode == 'COLLECTION':
+ np.random.seed(coll_rand_seed)
+ if fill_mode == 'FAN' and consistent_wedges:
+ bm0 = bmesh.new()
+ bm0.from_mesh(me0)
+ bm0.faces.ensure_lookup_table()
+ lay_id = bm0.faces.layers.int["id"]
+ faces_id = np.array([f[lay_id] for f in bm0.faces])
+ bm0.clear()
+ n_original_faces = faces_id[-1]+1
+ coll_materials = np.random.randint(len(components),size=n_original_faces)
+ coll_materials = coll_materials[faces_id]
+ else:
+ coll_materials = np.random.randint(len(components),size=n_patches)
+ gradient_distribution = []
+ if bool_weight_distribution:
+ if invert_vertex_group_distribution:
+ weight_distribution = 1-weight_distribution
+ v00 = all_verts[:,0,0]
+ v01 = all_verts[:,0,-1]
+ v10 = all_verts[:,-1,0]
+ v11 = all_verts[:,-1,-1]
+ face_weight = (weight_distribution[v00] + weight_distribution[v01] + weight_distribution[v10] + weight_distribution[v11])/4 * len(components)
+ if fill_mode == 'FAN' and consistent_wedges:
+ for i in range(n_original_faces):
+ face_mask = faces_id == i
+ face_weight[face_mask] = np.average(face_weight[face_mask])
+ face_weight = face_weight.clip(max=len(components)-1)
+ coll_materials = coll_materials.astype('float')
+ coll_materials = face_weight + (coll_materials - face_weight)*vertex_group_distribution_factor
+ coll_materials = coll_materials.astype('int')
- bool_correct = True
- new_patch = bpy.data.objects.new("patch", me1.copy())
- bpy.context.collection.objects.link(new_patch)
+ random.seed(rand_seed)
+ bool_correct = False
- new_patch.select_set(True)
- bpy.context.view_layer.objects.active = new_patch
+ tt = tissue_time(tt, "Reading Vertex Groups", levels=2)
- for area in bpy.context.screen.areas:
- for space in area.spaces:
- try: new_patch.local_view_set(space, True)
- except: pass
+ ### SMOOTH NORMALS
+ if smooth_normals:
+ weight_smooth_normals = 0.2
+ weight_smooth_normals0 = 0.2
+ if vertex_group_smooth_normals in ob0.vertex_groups.keys():
+ vg = ob0.vertex_groups[vertex_group_smooth_normals]
+ weight_smooth_normals0 = get_weight_numpy(vg, n_verts0)
+ if invert_vertex_group_smooth_normals:
+ weight_smooth_normals0 = 1-weight_smooth_normals0
+ weight_smooth_normals0 *= 0.2
- # 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
+ verts0_normal = mesh_diffusion_vector(me0, verts0_normal, smooth_normals_iter, weight_smooth_normals0, smooth_normals_uv)
+ '''
+ While in Relative thickness method the components are built
+ between the two surfaces, in Constant mode the thickness is uniform.
+ '''
+ if scale_mode == 'CONSTANT':
+ # Normalize vectors
+ verts0_normal /= np.linalg.norm(verts0_normal, axis=1).reshape((-1,1))
+ # Compare to the original normals direction
+ original_normals = get_normals_numpy(me0)
+ verts0_normal /= np.multiply(verts0_normal, original_normals).sum(1)[:,None]
+
+ tt = tissue_time(tt, "Smooth Normals", levels=2)
+
+ if normals_mode in ('FACES', 'VERTS'):
+ normals_x = props['normals_x']
+ normals_y = props['normals_y']
+ normals_z = props['normals_z']
+ if bool_weight_normals:
+ if invert_vertex_group_scale_normals:
+ weight_normals = 1-weight_normals
+ w_normals_x = 1 - weight_normals * (1 - normals_x)
+ w_normals_y = 1 - weight_normals * (1 - normals_y)
+ w_normals_z = 1 - weight_normals * (1 - normals_z)
+ else:
+ w_normals_x = normals_x
+ w_normals_y = normals_y
+ w_normals_z = normals_z
+ if normals_x < 1: verts0_normal[:,0] *= w_normals_x
+ if normals_y < 1: verts0_normal[:,1] *= w_normals_y
+ if normals_z < 1: verts0_normal[:,2] *= w_normals_z
+ div_value = np.linalg.norm(verts0_normal, axis=1).reshape((-1,1))
+ div_value[div_value == 0] = 0.00001
+ verts0_normal /= div_value
+
+ ### ROTATE PATCHES ###
+
+ if rotation_mode != 'DEFAULT' or rotation_shift != 0:
+
+ # Weight rotation
+ weight_shift = 0
+ if rotation_mode == 'WEIGHT':
+ corners_id = np.array(((0,0,-1,-1),(0,-1,-1,0)))
+ corners = all_verts[:,corners_id[0],corners_id[1]]
+ corners_weight = weight_rotation[corners]
+ if invert_vertex_group_rotation:
+ corners_weight = 1-corners_weight
+ ids4 = np.arange(4)
+ if rotation_direction == 'DIAG':
+ c0 = corners_weight[:,ids4]
+ c3 = corners_weight[:,(ids4+2)%4]
+ differential = c3 - c0
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]]
+ c0 = corners_weight[:,ids4]
+ c1 = corners_weight[:,(ids4+1)%4]
+ c2 = corners_weight[:,(ids4+2)%4]
+ c3 = corners_weight[:,(ids4+3)%4]
+ differential = - c0 + c1 + c2 - c3
+ weight_shift = np.argmax(differential, axis=1)
# Random rotation
+ random_shift = 0
if rotation_mode == 'RANDOM':
- rand = random.randint(0, 3)
- if rand == 1:
- verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
- elif rand == 2:
- verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
- elif rand == 3:
- verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
+ np.random.seed(rand_seed)
+ random_shift = np.random.randint(0,4,size=n_patches)*rand_step
# UV rotation
- elif 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)]
-
- step = 1/sides
- for vert, patch_vert in zip(verts1, new_patch.data.vertices):
- # 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
- v00 = verts[u][v]
- v10 = verts[u1][v]
- v01 = verts[u][v1]
- v11 = verts[u1][v1]
- # factor coordinates
- fu = (vert[0]-u*step)/step
- fv = (vert[1]-v*step)/step
- fw = vert.z
- # interpolate Z scaling factor
- fvec2d = Vector((fu,fv,0))
- if scale_mode == 'ADAPTIVE':
- a00 = verts_area[v00.index]
- a10 = verts_area[v10.index]
- a01 = verts_area[v01.index]
- a11 = verts_area[v11.index]
- fw*=lerp2(a00,a10,a01,a11,fvec2d)
- # build factor vector
- fvec = Vector((fu,fv,fw))
- # 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, fvec2d)
- vg.add([patch_vert.index], wuv, "ADD")
+ UV_shift = 0
+ if rotation_mode == 'UV' and ob0.type == 'MESH':
+ bm = bmesh.new()
+ bm.from_mesh(before_subsurf)
+ uv_lay = bm.loops.layers.uv.active
+ UV_shift = [0]*len(mask)
+ for f in bm.faces:
+ ll = f.loops
+ if len(ll) == 4:
+ uv0 = ll[0][uv_lay].uv
+ uv1 = ll[3][uv_lay].uv
+ uv2 = ll[2][uv_lay].uv
+ uv3 = ll[1][uv_lay].uv
+
+ v01 = (uv0 + uv1) # not necessary to divide by 2
+ v32 = (uv3 + uv2)
+ v0132 = v32 - v01 # axis vector 1
+ v0132.normalize() # based on the rotation not on the size
+ v12 = (uv1 + uv2)
+ v03 = (uv0 + uv3)
+ v1203 = v03 - v12 # axis vector 2
+ v1203.normalize() # based on the rotation not on the size
+
+ dot1203 = v1203.x
+ dot0132 = v0132.x
+ if(abs(dot1203) < abs(dot0132)): # already vertical
+ if (dot0132 > 0): shift = 0
+ else: shift = 2 # rotate 180°
+ else: # horizontal
+ if(dot1203 < 0): shift = 3
+ else: shift = 1
+ #UV_shift.append(shift)
+ UV_shift[f.index] = shift
+
+ UV_shift = np.array(UV_shift)[mask]
+ bm.free()
+
+ # Rotate Patch
+ rotation_shift = np.zeros((n_patches))+rotation_shift
+ rot = weight_shift + random_shift + UV_shift + rotation_shift
+ rot = rot%4
+ flip_u = np.logical_or(rot==2,rot==3)
+ flip_v = np.logical_or(rot==1,rot==2)
+ flip_uv = np.logical_or(rot==1,rot==3)
+ all_verts[flip_u] = all_verts[flip_u,::-1,:]
+ all_verts[flip_v] = all_verts[flip_v,:,::-1]
+ all_verts[flip_uv] = np.transpose(all_verts[flip_uv],(0,2,1))
+
+ tt = tissue_time(tt, "Rotations", levels=2)
+
+ #for o in bpy.context.view_layer.objects: o.select_set(False)
+ new_patch = None
+
+ ### COMPONENT ###
+ new_objects = []
+
+ # Store original values
+ _com_modifiers = com_modifiers
+ _bool_shapekeys = bool_shapekeys
+
+ for mat_id, _ob1 in enumerate(components):
+ if _ob1 == None: continue
+
+ # Set original values (for next commponents)
+ com_modifiers = _com_modifiers
+ bool_shapekeys = _bool_shapekeys
+
+ if component_mode != 'OBJECT':
+ if component_mode == 'COLLECTION':
+ mat_mask = coll_materials == mat_id
+ else:
+ mat_mask = materials == mat_id
+ if bool_material_id:
+ mat_mask = np.logical_and(mat_mask, materials == material_id)
+ masked_verts = all_verts[mat_mask]
+ masked_faces = mat_mask
+ elif bool_material_id:
+ masked_verts = all_verts[materials == material_id]
+ masked_faces = np.logical_and(mask, materials == material_id)
+ else:
+ masked_verts = all_verts
+ masked_faces = mask
+ n_patches = len(masked_verts)
+ if n_patches == 0: continue
+
+ if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
+
+ # set Shape Keys to zero
+ original_key_values = None
+ if (bool_shapekeys or not com_modifiers) and _ob1.type == 'MESH':
+ if _ob1.data.shape_keys:
+ original_key_values = []
+ for sk in _ob1.data.shape_keys.key_blocks:
+ original_key_values.append(sk.value)
+ sk.value = 0
+ else:
+ bool_shapekeys = False
+ else: 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)
+ ob1, com_area = tessellate_prepare_component(ob1, props)
+ ob1.name = "_tissue_tmp_ob1"
+
+ # restore original modifiers visibility for component object
+ try:
+ for m, vis in zip(_ob1.modifiers, mod_visibility):
+ m.show_viewport = vis
+ except: pass
+
+ me1 = ob1.data
+ verts1 = [v.co for v in me1.vertices]
+ n_verts1 = len(verts1)
+ if n_verts1 == 0:
+ bpy.data.objects.remove(ob1)
+ continue
+
+ ### COMPONENT GRID COORDINATES ###
+
+ # find relative UV component's vertices
+ if fill_mode == 'PATCH':
+ verts1_uv_quads = [0]*n_verts1
+ verts1_uv = [0]*n_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))
+ else:
+ verts1_uv = verts1
if bool_shapekeys:
- for sk in ob1.data.shape_keys.key_blocks:
+ sk_uv_quads = []
+ sk_uv = []
+ for sk in ob1.data.shape_keys.key_blocks[1:]:
source = sk.data
- for sk_v, _v in zip(source, me1.vertices):
- if mode == 'BOUNDS':
- sk_vert = sk_v.co - min_c # (ob1.matrix_world * 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] + (-0.5 + offset * 0.5) * bb[2]) * zscale
- elif mode == 'LOCAL':
- sk_vert = sk_v.co#.xyzco
- #sk_vert[2] *= zscale
- #sk_vert[2] = (sk_vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
- elif mode == 'GLOBAL':
- #sk_vert = ob1.matrix_world @ sk_v.co
- sk_vert = sk_v.co
- #sk_vert[2] *= zscale
+ _sk_uv_quads = [0]*n_verts1
+ _sk_uv = [0]*n_verts1
+ for i, sk_v in enumerate(source):
+ sk_vert = sk_v.co
# grid coordinates
u = int(sk_vert[0]//step)
@@ -910,794 +698,340 @@ def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode,
if v < 0:
v = 0
v1 = 1
- v00 = verts[u][v]
- v10 = verts[u1][v]
- v01 = verts[u][v1]
- v11 = verts[u1][v1]
+ _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
-
- if scale_mode == 'ADAPTIVE':
- a00 = verts_area[v00.index]
- a10 = verts_area[v10.index]
- a01 = verts_area[v01.index]
- a11 = verts_area[v11.index]
- fw*=lerp2(a00,a10,a01,a11,Vector((fu,fv,0)))
-
- fvec = Vector((fu,fv,fw))
- sk_co = lerp3(v00, v10, v01, v11, fvec)
-
- new_patch.data.shape_keys.key_blocks[sk.name].data[_v.index].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
+ _sk_uv[i] = Vector((fu,fv,fw))
+ sk_uv_quads.append(_sk_uv_quads)
+ sk_uv.append(_sk_uv)
+ store_sk_coordinates = [[] for t in ob1.data.shape_keys.key_blocks[1:]]
+ sk_uv_quads = np.array(sk_uv_quads)
+ sk_uv = np.array(sk_uv)
+
+ np_verts1_uv = np.array(verts1_uv)
+ if fill_mode == 'PATCH':
+ verts1_uv_quads = np.array(verts1_uv_quads)
+ np_u = verts1_uv_quads[:,0]
+ np_v = verts1_uv_quads[:,1]
+ np_u1 = verts1_uv_quads[:,2]
+ np_v1 = verts1_uv_quads[:,3]
+ else:
+ np_u = 0
+ np_v = 0
+ np_u1 = 1
+ np_v1 = 1
+
+ tt = tissue_time(tt, "Component preparation", levels=2)
+
+ ### DEFORM PATCHES ###
+
+ verts_xyz = verts0_co[masked_verts]
+ v00 = verts_xyz[:, np_u, np_v].reshape((n_patches,-1,3))
+ v10 = verts_xyz[:, np_u1, np_v].reshape((n_patches,-1,3))
+ v01 = verts_xyz[:, np_u, np_v1].reshape((n_patches,-1,3))
+ v11 = verts_xyz[:, np_u1, np_v1].reshape((n_patches,-1,3))
+ vx = np_verts1_uv[:,0].reshape((1,n_verts1,1))
+ vy = np_verts1_uv[:,1].reshape((1,n_verts1,1))
+ vz = np_verts1_uv[:,2].reshape((1,n_verts1,1))
+ co2 = np_lerp2(v00, v10, v01, v11, vx, vy, 'verts')
+
+ ### PATCHES WEIGHT ###
+ weight_thickness = None
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
-
- 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, 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
-
- 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_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:
- 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)
- bpy.context.object.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
+ n_vg = len(weight)
+ patches_weight = weight[:, masked_verts]
+ w00 = patches_weight[:, :, np_u, np_v].reshape((n_vg, n_patches,-1,1))
+ w10 = patches_weight[:, :, np_u1, np_v].reshape((n_vg, n_patches,-1,1))
+ w01 = patches_weight[:, :, np_u, np_v1].reshape((n_vg, n_patches,-1,1))
+ w11 = patches_weight[:, :, np_u1, np_v1].reshape((n_vg, n_patches,-1,1))
+ store_weight = np_lerp2(w00,w10,w01,w11,vx[None,:,:,:],vy[None,:,:,:],'weight')
+
+ if vertex_group_thickness in ob0.vertex_groups.keys():
+ vg_id = ob0.vertex_groups[vertex_group_thickness].index
+ weight_thickness = store_weight[vg_id,:,:]
+ if vertex_group_smooth_normals in ob0.vertex_groups.keys():
+ vg_id = ob0.vertex_groups[vertex_group_smooth_normals].index
+ weight_smooth_normals = store_weight[vg_id,:,:]
+ else:
+ # Read vertex group Thickness
+ if vertex_group_thickness in ob0.vertex_groups.keys():
+ vg = ob0.vertex_groups[vertex_group_thickness]
+ weight_thickness = get_weight_numpy(vg, n_verts0)
+ wt = weight_thickness[masked_verts]
+ wt = wt[:,:,:,np.newaxis]
+ w00 = wt[:, np_u, np_v].reshape((n_patches, -1, 1))
+ w10 = wt[:, np_u1, np_v].reshape((n_patches, -1, 1))
+ w01 = wt[:, np_u, np_v1].reshape((n_patches, -1, 1))
+ w11 = wt[:, np_u1, np_v1].reshape((n_patches, -1, 1))
+ weight_thickness = np_lerp2(w00,w10,w01,w11,vx,vy,'verts')
try:
- #new_ob1.active_shape_key_index = 0
- for sk in me1.shape_keys.key_blocks:
- sk.data[v].co.y -= 1
+ weight_thickness.shape
+ if invert_vertex_group_thickness:
+ weight_thickness = 1-weight_thickness
+ fact = vertex_group_thickness_factor
+ if fact > 0:
+ weight_thickness = weight_thickness*(1-fact) + fact
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
+
+ # Read vertex group smooth normals
+ if vertex_group_smooth_normals in ob0.vertex_groups.keys():
+ vg = ob0.vertex_groups[vertex_group_smooth_normals]
+ weight_smooth_normals = get_weight_numpy(vg, n_verts0)
+ wt = weight_smooth_normals[masked_verts]
+ wt = wt[:,:,:,None]
+ w00 = wt[:, np_u, np_v].reshape((n_patches, -1, 1))
+ w10 = wt[:, np_u1, np_v].reshape((n_patches, -1, 1))
+ w01 = wt[:, np_u, np_v1].reshape((n_patches, -1, 1))
+ w11 = wt[:, np_u1, np_v1].reshape((n_patches, -1, 1))
+ weight_smooth_normals = np_lerp2(w00,w10,w01,w11,vx,vy,'verts')
try:
- #new_ob1.active_shape_key_index = 0
- for sk in me1.shape_keys.key_blocks:
- sk.data[v].co.y += 1
+ weight_smooth_normals.shape
+ if invert_vertex_group_smooth_normals:
+ weight_smooth_normals = 1-weight_smooth_normals
+ #fact = vertex_group_thickness_factor
+ #if fact > 0:
+ # weight_thickness = weight_thickness*(1-fact) + fact
except: pass
+ if normals_mode == 'FACES':
+ n2 = get_attribute_numpy(before_subsurf.polygons,'normal',3)
+ n2 = n2[masked_faces][:,None,:]
+ else:
+ if normals_mode == 'CUSTOM':
+ me0.calc_normals_split()
+ normals_split = [0]*len(me0.loops)*3
+ vertex_indexes = [0]*len(me0.loops)
+ me0.loops.foreach_get('normal', normals_split)
+ me0.loops.foreach_get('vertex_index', vertex_indexes)
+ normals_split = np.array(normals_split).reshape(-1,3)
+ vertex_indexes = np.array(vertex_indexes)
+ verts0_normal = np.zeros((len(me0.vertices),3))
+ np.add.at(verts0_normal, vertex_indexes, normals_split)
+ indexes, counts = np.unique(vertex_indexes,return_counts=True)
+ verts0_normal[indexes] /= counts[:,np.newaxis]
+
+ if 'Eval_Normals' in me1.uv_layers.keys():
+ bm1 = bmesh.new()
+ bm1.from_mesh(me1)
+ uv_co = np.array(uv_from_bmesh(bm1, 'Eval_Normals'))
+ vx_nor = uv_co[:,0]#.reshape((1,n_verts1,1))
+ #vy_nor = uv_co[:,1]#.reshape((1,n_verts1,1))
+
+ # grid coordinates
+ np_u = np.clip(vx_nor//step, 0, sides).astype('int')
+ #np_v = np.maximum(vy_nor//step, 0).astype('int')
+ np_u1 = np.clip(np_u+1, 0, sides).astype('int')
+ #np_v1 = np.minimum(np_v+1, sides).astype('int')
+
+ vx_nor = (vx_nor - np_u * step)/step
+ #vy_nor = (vy_nor - np_v * step)/step
+ vx_nor = vx_nor.reshape((1,n_verts1,1))
+ #vy_nor = vy_nor.reshape((1,n_verts1,1))
+ vy_nor = vy
+ bm1.free()
+ else:
+ vx_nor = vx
+ vy_nor = vy
+
+ if normals_mode in ('SHAPEKEYS','OBJECT') and scale_mode == 'CONSTANT' and even_thickness:
+ verts_norm_pos = verts0_normal_pos[masked_verts]
+ verts_norm_neg = verts0_normal_neg[masked_verts]
+ nor_mask = (vz<0).reshape((-1))
+ n00 = verts_norm_pos[:, np_u, np_v].reshape((n_patches,-1,3))
+ n10 = verts_norm_pos[:, np_u1, np_v].reshape((n_patches,-1,3))
+ n01 = verts_norm_pos[:, np_u, np_v1].reshape((n_patches,-1,3))
+ n11 = verts_norm_pos[:, np_u1, np_v1].reshape((n_patches,-1,3))
+ n00_neg = verts_norm_neg[:, np_u, np_v].reshape((n_patches,-1,3))
+ n10_neg = verts_norm_neg[:, np_u1, np_v].reshape((n_patches,-1,3))
+ n01_neg = verts_norm_neg[:, np_u, np_v1].reshape((n_patches,-1,3))
+ n11_neg = verts_norm_neg[:, np_u1, np_v1].reshape((n_patches,-1,3))
+ n00[:,nor_mask] = n00_neg[:,nor_mask]
+ n10[:,nor_mask] = n10_neg[:,nor_mask]
+ n01[:,nor_mask] = n01_neg[:,nor_mask]
+ n11[:,nor_mask] = n11_neg[:,nor_mask]
+ else:
+ verts_norm = verts0_normal[masked_verts]
+ n00 = verts_norm[:, np_u, np_v].reshape((n_patches,-1,3))
+ n10 = verts_norm[:, np_u1, np_v].reshape((n_patches,-1,3))
+ n01 = verts_norm[:, np_u, np_v1].reshape((n_patches,-1,3))
+ n11 = verts_norm[:, np_u1, np_v1].reshape((n_patches,-1,3))
+ n2 = np_lerp2(n00, n10, n01, n11, vx_nor, vy_nor, 'verts')
+
+ # thickness variation
+ mean_area = []
+ a2 = None
+ if scale_mode == 'ADAPTIVE' and normals_mode not in ('SHAPEKEYS','OBJECT'):
+ #com_area = bb[0]*bb[1]
+ if mode != 'BOUNDS' or com_area == 0: com_area = 1
+ if normals_mode == 'FACES':
+ if levels == 0 and True:
+ areas = [0]*len(mask)
+ before_subsurf.polygons.foreach_get('area',areas)
+ areas = np.sqrt(np.array(areas)/com_area)[masked_faces]
+ a2 = areas[:,None,None]
+ else:
+ areas = calc_verts_area_bmesh(me0)
+ verts_area = np.sqrt(areas*patch_faces/com_area)
+ verts_area = verts_area[masked_verts]
+ verts_area = verts_area.mean(axis=(1,2)).reshape((n_patches,1,1))
+ a2 = verts_area
+ else:
+ areas = calc_verts_area_bmesh(me0)
+ verts_area = np.sqrt(areas*patch_faces/com_area)
+ verts_area = verts_area[masked_verts]
+ a00 = verts_area[:, np_u, np_v].reshape((n_patches,-1,1))
+ a10 = verts_area[:, np_u1, np_v].reshape((n_patches,-1,1))
+ a01 = verts_area[:, np_u, np_v1].reshape((n_patches,-1,1))
+ a11 = verts_area[:, np_u1, np_v1].reshape((n_patches,-1,1))
+ # remapped z scale
+ a2 = np_lerp2(a00,a10,a01,a11,vx,vy,'verts')
- 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]) + (-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
+ store_coordinates = calc_thickness(co2,n2,vz,a2,weight_thickness)
+ co2 = n2 = vz = a2 = None
- # Apply component modifiers
- if com_modifiers:
- sk_ob = convert_object_to_mesh(_ob1)
- sk_data = sk_ob.data
- source = sk_data.vertices
+ if bool_shapekeys:
+ tt_sk = time.time()
+ n_sk = len(sk_uv_quads)
+ # ids of face corners for each vertex (n_sk, n_verts1, 4)
+ np_u = np.clip(sk_uv_quads[:,:,0], 0, sides).astype('int')[:,None,:]
+ np_v = np.clip(sk_uv_quads[:,:,1], 0, sides).astype('int')[:,None,:]
+ np_u1 = np.clip(sk_uv_quads[:,:,2], 0, sides).astype('int')[:,None,:]
+ np_v1 = np.clip(sk_uv_quads[:,:,3], 0, sides).astype('int')[:,None,:]
+ print(np_v1)
+ # face corners for each vertex (n_patches, n_sk, n_verts1, 4)
+ v00 = verts_xyz[:,np_u,np_v].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1)
+ v10 = verts_xyz[:,np_u1,np_v].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1)
+ v01 = verts_xyz[:,np_u,np_v1].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1)
+ v11 = verts_xyz[:,np_u1,np_v1].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1)
+ vx = sk_uv[:,:,0].reshape((1,n_sk,n_verts1,1))
+ vy = sk_uv[:,:,1].reshape((1,n_sk,n_verts1,1))
+ vz = sk_uv[:,:,2].reshape((1,n_sk,n_verts1,1))
+ co2 = np_lerp2(v00,v10,v01,v11,vx,vy,mode='shapekeys')
+
+ if normals_mode == 'FACES':
+ n2 = n2[None,:,:,:]
else:
- source = sk.data
- shapekeys = []
- for v in source:
- if mode == 'BOUNDS':
- vert = v.co - min_c
- vert[0] = vert[0] / bb[0]
- vert[1] = vert[1] / bb[1]
- vert[2] = (vert[2] + (-0.5 + offset * 0.5) * bb[2]) * 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:
- try:
- weight = []
- vertex_groups = ob0.vertex_groups
- for vg in 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':
- if mode == 'BOUNDS': com_area = (bb[0]*bb[1])
- else: com_area = 1
- if com_area == 0: mult = 1
- else: 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()
- try:
- area/=len(faces)
- area*=mult
- verts_area.append(sqrt(area))
- except:
- verts_area.append(1)
-
- # FAN tessellation mode
- if fill_mode == 'FAN':
- fan_verts = [v.co.to_tuple() for v in me0.vertices]
- fan_polygons = []
- fan_select = []
- fan_material = []
- fan_normals = []
- # selected_faces = []
- for p in base_polygons:
- fan_center = Vector((0, 0, 0))
- center_area = 0
- for v in p.vertices:
- fan_center += me0.vertices[v].co
- if scale_mode == 'ADAPTIVE':
- center_area += verts_area[v]
- fan_center /= len(p.vertices)
- center_area /= len(p.vertices)
-
- last_vert = len(fan_verts)
- fan_verts.append(fan_center.to_tuple())
- #fan_verts.append(fan_center)
- if scale_mode == 'ADAPTIVE':
- verts_area.append(center_area)
-
- # Vertex Group
- if bool_vertex_group:
- for w in weight:
- center_weight = sum([w[i] for i in p.vertices]) / len(p.vertices)
- w.append(center_weight)
+ if normals_mode in ('SHAPEKEYS','OBJECT') and scale_mode == 'CONSTANT' and even_thickness:
+ verts_norm_pos = verts0_normal_pos[masked_verts]
+ verts_norm_neg = verts0_normal_neg[masked_verts]
+ nor_mask = (vz<0).reshape((-1))
+ n00 = verts_norm_pos[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3))
+ n10 = verts_norm_pos[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3))
+ n01 = verts_norm_pos[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3))
+ n11 = verts_norm_pos[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3))
+ n00_neg = verts_norm_neg[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3))
+ n10_neg = verts_norm_neg[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3))
+ n01_neg = verts_norm_neg[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3))
+ n11_neg = verts_norm_neg[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3))
+ n00[:,:,nor_mask] = n00_neg[:,:,nor_mask]
+ n10[:,:,nor_mask] = n10_neg[:,:,nor_mask]
+ n01[:,:,nor_mask] = n01_neg[:,:,nor_mask]
+ n11[:,:,nor_mask] = n11_neg[:,:,nor_mask]
+ else:
+ n00 = verts_norm[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3))
+ n10 = verts_norm[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3))
+ n01 = verts_norm[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3))
+ n11 = verts_norm[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3))
+ n2 = np_lerp2(n00,n10,n01,n11,vx,vy,'shapekeys')
- for i in range(len(p.vertices)):
- fan_polygons.append((p.vertices[i],
- p.vertices[(i + 1) % len(p.vertices)],
- last_vert, last_vert))
+ # NOTE: weight thickness is based on the base position of the
+ # vertices, not on the coordinates of the shape keys
- if bool_material_id: fan_material.append(p.material_index)
- if bool_selection: fan_select.append(p.select)
+ if scale_mode == 'ADAPTIVE':# and normals_mode not in ('OBJECT', 'SHAPEKEYS'): ### not sure
if normals_mode == 'FACES':
- fan_normals.append(p.normal)
-
- fan_me = bpy.data.meshes.new('Fan.Mesh')
- fan_me.from_pydata(tuple(fan_verts), [], tuple(fan_polygons))
- me0 = fan_me.copy()
- bpy.data.meshes.remove(fan_me)
- verts0 = me0.vertices
- base_polygons = me0.polygons
- if normals_mode == 'FACES': base_face_normals = fan_normals
-
- 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
- _w0 = [[0]*n_faces]*len(ob0.vertex_groups)
- np_faces = [np.array(p) for p in fs1]
- new_faces = [0]*n_faces*n_faces1
- face1_count = 0
-
- for p in base_polygons:
-
- bool_correct = True
- if rotation_mode == 'UV' and ob0.type != 'MESH':
- rotation_mode = 'DEFAULT'
-
- # 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])
- vs0 = np.array([verts0[i].co for i in shifted_vertices])
- nvs0 = np.array([verts0[i].normal for i in shifted_vertices])
- if normals_mode == 'VERTS':
- nvs0 = np.array([verts0[i].normal for i in shifted_vertices])
- # vertex weight
- if bool_vertex_group:
- ws0 = []
- for w in weight:
- _ws0 = []
- for i in shifted_vertices:
- try:
- _ws0.append(w[i])
- except:
- _ws0.append(0)
- ws0.append(np.array(_ws0))
-
- # UV rotation
- elif rotation_mode == 'UV':
- if len(ob0.data.uv_layers) > 0 and fill_mode != 'FAN':
- 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]
+ a2 = mean_area
else:
- if(dot1203 < 0):
- vertUV = p.vertices[:]
- else:
- vertUV = p.vertices[2:] + p.vertices[:2]
- vs0 = np.array([verts0[i].co for i in vertUV])
- nvs0 = np.array([verts0[i].normal for i in vertUV])
-
- # Vertex weight
- if bool_vertex_group:
- ws0 = []
- for w in weight:
- _ws0 = []
- for i in vertUV:
- try:
- _ws0.append(w[i])
- except:
- _ws0.append(0)
- ws0.append(np.array(_ws0))
-
- count += len(p.vertices)
- else: rotation_mode = 'DEFAULT'
-
- # Default rotation
- if rotation_mode == 'DEFAULT':
- vs0 = np.array([verts0[i].co for i in p.vertices])
- nvs0 = np.array([verts0[i].normal for i in p.vertices])
- # Vertex weight
- if bool_vertex_group:
- ws0 = []
- for w in weight:
- _ws0 = []
- for i in p.vertices:
- 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 == 'VERTS':
- _nvs0[j] = (nvs0[0], nvs0[1], nvs0[2], nvs0[-1])
- #else:
- # _nvs0[j] = base_face_normals[j]
-
-
- # vertex z to normal
- if scale_mode == 'ADAPTIVE':
- poly_faces = (p.vertices[0], p.vertices[1], p.vertices[2], p.vertices[-1])
- if rotation_mode == 'RANDOM': sz = verts_area0
- else: sz = np.array([verts_area[i] for i in poly_faces])
-
- _sz[j] = sz
+ a00 = verts_area[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,1))
+ a10 = verts_area[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,1))
+ a01 = verts_area[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,1))
+ a11 = verts_area[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,1))
+ # remapped z scale
+ a2 = np_lerp2(a00,a10,a01,a11,vx,vy,'shapekeys')
- if bool_vertex_group:
- vg_count = 0
- for _ws0 in ws0:
- _w0[vg_count][j] = (_ws0[0], _ws0[1], _ws0[2], _ws0[-1])
- vg_count += 1
-
- for p in fs1:
- new_faces[face1_count] = [i + n_verts1 * j for i in p]
- face1_count += 1
-
- j += 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
- v0 = _vs0_0 + (_vs0_1 - _vs0_0) * vx
- v1 = _vs0_3 + (_vs0_2 - _vs0_3) * vx
- v2 = v0 + (v1 - v0) * vy
-
- # remapped vertex normal
- if normals_mode == 'VERTS':
- _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))
- nv0 = _nvs0_0 + (_nvs0_1 - _nvs0_0) * vx
- nv1 = _nvs0_3 + (_nvs0_2 - _nvs0_3) * vx
- nv2 = nv0 + (nv1 - nv0) * vy
- else:
- nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
-
- if bool_vertex_group:
- n_vg = len(_w0)
- w = np.array(_w0)
- #for w in _w0:
- #w = np.array(w)
- 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
- w0 = w_0 + (w_1 - w_0) * vx
- w1 = w_3 + (w_2 - w_3) * vx
- w = w0 + (w1 - w0) * vy
- w = w.reshape((n_vg, n_faces*n_verts1))
- #w = w2.tolist()
-
- 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
- sz0 = _sz_0 + (_sz_1 - _sz_0) * vx
- sz1 = _sz_3 + (_sz_2 - _sz_3) * vx
- sz2 = sz0 + (sz1 - sz0) * 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)
- vy = np.array(vy_key)
- vz = np.array(vz_key)
-
- # remapped vertex coordinates
- v0 = _vs0_0 + (_vs0_1 - _vs0_0) * vx
- v1 = _vs0_3 + (_vs0_2 - _vs0_3) * vx
- v2 = v0 + (v1 - v0) * vy
-
- # remapped vertex normal
- if normals_mode == 'VERTS':
- nv0 = _nvs0_0 + (_nvs0_1 - _nvs0_0) * vx
- nv1 = _nvs0_3 + (_nvs0_2 - _nvs0_3) * vx
- nv2 = nv0 + (nv1 - nv0) * vy
- else:
- nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
+ store_sk_coordinates = calc_thickness(co2,n2,vz,a2,weight_thickness)
+ co2 = n2 = vz = a2 = weight_thickness = None
+ tissue_time(tt_sk, "Compute ShapeKeys", levels=3)
- if scale_mode == 'ADAPTIVE':
- # remapped z scale
- sz0 = _sz_0 + (_sz_1 - _sz_0) * vx
- sz1 = _sz_3 + (_sz_2 - _sz_3) * vx
- sz2 = sz0 + (sz1 - sz0) * 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: 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 in range(len(w[vg.index])):
- new_ob.vertex_groups[vg.name].add([i], w[vg.index,i],"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)
- 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)
+ tt = tissue_time(tt, "Compute Coordinates", levels=2)
- # 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)
+ new_me = array_mesh(ob1, len(masked_verts))
+ tt = tissue_time(tt, "Repeat component", levels=2)
- # MATERIALS
- for slot in ob1.material_slots: new_ob.data.materials.append(slot.material)
+ new_patch = bpy.data.objects.new("_tissue_tmp_patch", new_me)
+ bpy.context.collection.objects.link(new_patch)
+ store_coordinates = np.concatenate(store_coordinates, axis=0).reshape((-1)).tolist()
+ new_me.vertices.foreach_set('co',store_coordinates)
- 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() ###
+ for area in bpy.context.screen.areas:
+ for space in area.spaces:
+ try: new_patch.local_view_set(space, True)
+ except: pass
+ tt = tissue_time(tt, "Inject coordinates", levels=2)
- try:
- bpy.data.objects.remove(new_ob1)
- except: pass
+ # Vertex Group
+ for vg in ob1.vertex_groups:
+ vg_name = vg.name
+ if vg_name in ob0.vertex_groups.keys():
+ vg_name = '_{}_'.format(vg_name)
+ new_patch.vertex_groups.new(name=vg_name)
+ if bool_vertex_group:
+ new_groups = []
+ for vg in ob0.vertex_groups:
+ new_groups.append(new_patch.vertex_groups.new(name=vg.name))
+ for vg, w in zip(new_groups, store_weight):
+ set_weight_numpy(vg, w.reshape(-1))
+ tt = tissue_time(tt, "Write Vertex Groups", levels=2)
+ if bool_shapekeys:
+ for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
+ sk.value = val
+ new_patch.shape_key_add(name=sk.name, from_mix=False)
+ new_patch.data.shape_keys.key_blocks[sk.name].value = val
+ for i in range(n_sk):
+ coordinates = np.concatenate(store_sk_coordinates[:,i], axis=0)
+ coordinates = coordinates.flatten().tolist()
+ new_patch.data.shape_keys.key_blocks[i+1].data.foreach_set('co', coordinates)
+
+ # 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:
+ vg_keys = new_patch.vertex_groups.keys()
+ for sk in new_patch.data.shape_keys.key_blocks:
+ if sk.name in vg_keys:
+ sk.vertex_group = sk.name
+ tt = tissue_time(tt, "Shape Keys", levels=2)
+ elif original_key_values:
+ for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
+ sk.value = val
+
+ new_name = ob0.name + "_" + ob1.name
+ new_patch.name = "_tissue_tmp_patch"
+ new_patch.data.update() # needed for updating the normals
+ new_objects.append(new_patch)
+ bpy.data.objects.remove(ob1)
bpy.data.objects.remove(ob0)
- bpy.data.meshes.remove(me0)
- bpy.data.objects.remove(ob1)
- bpy.data.meshes.remove(me1)
- return new_ob
-
+ tt = tissue_time(tt, "Closing Tessellate Iteration", levels=2)
+ return new_objects
-class tessellate(Operator):
- bl_idname = "object.tessellate"
- bl_label = "Tessellate"
+class tissue_tessellate(Operator):
+ bl_idname = "object.tissue_tessellate"
+ bl_label = "Tissue 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
+ )
object_name : StringProperty(
name="",
description="Name of the generated object"
@@ -1712,7 +1046,7 @@ class tessellate(Operator):
scale_mode : EnumProperty(
items=(
('CONSTANT', "Constant", "Uniform thickness"),
- ('ADAPTIVE', "Proportional", "Preserve component's proportions")
+ ('ADAPTIVE', "Relative", "Preserve component's proportions")
),
default='ADAPTIVE',
name="Z-Scale according to faces size"
@@ -1725,6 +1059,15 @@ class tessellate(Operator):
soft_max=1,
description="Surface offset"
)
+ component_mode : EnumProperty(
+ items=(
+ ('OBJECT', "Object", "Use the same component object for all the faces"),
+ ('COLLECTION', "Collection", "Use multiple components from Collection"),
+ ('MATERIALS', "Materials", "Use multiple components by materials name")
+ ),
+ default='OBJECT',
+ name="Component Mode"
+ )
mode : EnumProperty(
items=(
('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
@@ -1736,18 +1079,34 @@ class tessellate(Operator):
rotation_mode : EnumProperty(
items=(('RANDOM', "Random", "Random faces rotation"),
('UV', "Active UV", "Face rotation is based on UV coordinates"),
+ ('WEIGHT', "Weight Gradient", "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=(
+ ('TRI', 'Tri', 'Triangulate the base mesh'),
('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')),
+ 'deformation\nmodifiers can be used'),
+ ('FRAME', 'Frame', 'Tessellation along the edges of each face')),
default='QUAD',
name="Fill Mode"
)
@@ -1761,12 +1120,12 @@ class tessellate(Operator):
)
gen_modifiers : BoolProperty(
name="Generator Modifiers",
- default=False,
+ default=True,
description="Apply Modifiers and Shape Keys to the base object"
)
com_modifiers : BoolProperty(
name="Component Modifiers",
- default=False,
+ default=True,
description="Apply Modifiers and Shape Keys to the component object"
)
merge : BoolProperty(
@@ -1774,9 +1133,14 @@ class tessellate(Operator):
default=False,
description="Merge vertices in adjacent duplicates"
)
+ merge_open_edges_only : BoolProperty(
+ name="Open edges only",
+ default=True,
+ description="Merge only open edges"
+ )
merge_thres : FloatProperty(
name="Distance",
- default=0.001,
+ default=0.0001,
soft_min=0,
soft_max=10,
description="Limit below which to merge vertices"
@@ -1786,13 +1150,27 @@ class tessellate(Operator):
default=False,
description="Randomize component rotation"
)
- random_seed : IntProperty(
+ rand_seed : IntProperty(
name="Seed",
default=0,
soft_min=0,
soft_max=10,
description="Random seed"
)
+ coll_rand_seed : IntProperty(
+ name="Seed",
+ default=0,
+ soft_min=0,
+ soft_max=10,
+ description="Random seed"
+ )
+ rand_step : IntProperty(
+ name="Step",
+ default=1,
+ min=1,
+ soft_max=2,
+ description="Random step"
+ )
bool_vertex_group : BoolProperty(
name="Map Vertex Groups",
default=False,
@@ -1830,6 +1208,28 @@ class tessellate(Operator):
description="Component object for the tessellation",
default = ""
)
+ component_coll : StringProperty(
+ name="",
+ description="Components collection for the tessellation",
+ default = ""
+ )
+ target : StringProperty(
+ name="",
+ description="Target object for custom direction",
+ default = ""
+ )
+ even_thickness : BoolProperty(
+ name="Even Thickness",
+ default=False,
+ description="Iterative sampling method for determine the correct length of the vectors (Experimental)"
+ )
+ even_thickness_iter : IntProperty(
+ name="Even Thickness Iterations",
+ default=3,
+ min = 1,
+ soft_max = 20,
+ description="More iterations produces more accurate results but make the tessellation slower"
+ )
bool_material_id : BoolProperty(
name="Tessellation on Material ID",
default=False,
@@ -1852,7 +1252,7 @@ class tessellate(Operator):
min=1,
soft_max=5,
description="Automatically repeat the Tessellation using the "
- + "generated geometry as new base object.\nUseful for "
+ + "generated geometry as new base object.\nUsefull for "
+ "for branching systems. Dangerous!"
)
bool_combine : BoolProperty(
@@ -1867,16 +1267,13 @@ class tessellate(Operator):
)
normals_mode : EnumProperty(
items=(
- ('VERTS', 'Along Normals', 'Consistent direction based on vertices normal'),
- ('FACES', 'Individual Faces', 'Based on individual faces normal')),
+ ('VERTS', 'Normals', 'Consistent direction based on vertices normal'),
+ ('FACES', 'Faces', 'Based on individual faces normal'),
+ ('SHAPEKEYS', 'Keys', "According to base object's shape keys"),
+ ('OBJECT', 'Object', "According to a target object")),
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'),
@@ -1893,11 +1290,40 @@ class tessellate(Operator):
default='EXTEND',
name="Bounds Y",
)
+ close_mesh : EnumProperty(
+ items=(
+ ('NONE', 'None', 'Keep the mesh open'),
+ ('CAP', 'Cap Holes', 'Automatically cap open loops'),
+ ('BRIDGE', 'Bridge Open Loops', 'Automatically bridge loop pairs'),
+ ('BRIDGE_CAP', 'Custom', 'Bridge loop pairs and cap holes according to vertex groups')),
+ 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"
+ )
+ boundary_mat_offset : IntProperty(
+ name="Material Offset",
+ default=0,
+ description="Material Offset for boundaries (with Multi Components or Material ID)"
+ )
+ fill_frame_mat : IntProperty(
+ name="Material Offset",
+ default=0,
+ description="Material Offset for inner faces (with Multi Components or Material ID)"
+ )
open_edges_crease : FloatProperty(
name="Open Edges Crease",
default=0,
@@ -1905,11 +1331,199 @@ class tessellate(Operator):
max=1,
description="Automatically set crease for open edges"
)
- # NOTE: this was made into an annotation for 2.8x, but should be a class variable.
- # working_on = ""
+ bridge_edges_crease : FloatProperty(
+ name="Bridge Edges Crease",
+ default=0,
+ min=0,
+ max=1,
+ description="Automatically set crease for bridge 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_offset : IntProperty(
+ name="Material Offset",
+ default=0,
+ min=0,
+ description="Material index offset for the cap faces"
+ )
+ bridge_material_offset : IntProperty(
+ name="Material Offset",
+ default=0,
+ min=0,
+ description="Material index offset for the bridge faces"
+ )
+ patch_subs : IntProperty(
+ name="Patch Subdivisions",
+ default=1,
+ min=0,
+ description="Subdivisions levels for Patch tessellation after the first iteration"
+ )
+ use_origin_offset : BoolProperty(
+ name="Align to Origins",
+ default=False,
+ description="Define offset according to components origin and local Z coordinate"
+ )
+
+ vertex_group_thickness : StringProperty(
+ name="Thickness weight", default='',
+ description="Vertex Group used for thickness"
+ )
+ invert_vertex_group_thickness : BoolProperty(
+ name="Invert", default=False,
+ description="Invert the vertex group influence"
+ )
+ vertex_group_thickness_factor : FloatProperty(
+ name="Factor",
+ default=0,
+ min=0,
+ max=1,
+ description="Thickness factor to use for zero vertex group influence"
+ )
+
+ vertex_group_distribution : StringProperty(
+ name="Distribution weight", default='',
+ description="Vertex Group used for gradient distribution"
+ )
+ invert_vertex_group_distribution : BoolProperty(
+ name="Invert", default=False,
+ description="Invert the vertex group influence"
+ )
+ vertex_group_distribution_factor : FloatProperty(
+ name="Factor",
+ default=0,
+ min=0,
+ max=1,
+ description="Randomness factor to use for zero vertex group influence"
+ )
+
+ vertex_group_cap_owner : EnumProperty(
+ items=(
+ ('BASE', 'Base', 'Use base vertex group'),
+ ('COMP', 'Component', 'Use component vertex group')),
+ default='COMP',
+ name="Source"
+ )
+ vertex_group_cap : StringProperty(
+ name="Cap Vertex Group", default='',
+ description="Vertex Group used for cap open edges"
+ )
+ invert_vertex_group_cap : BoolProperty(
+ name="Invert", default=False,
+ description="Invert the vertex group influence"
+ )
+
+ vertex_group_bridge_owner : EnumProperty(
+ items=(
+ ('BASE', 'Base', 'Use base vertex group'),
+ ('COMP', 'Component', 'Use component vertex group')),
+ default='COMP',
+ name="Source"
+ )
+ vertex_group_bridge : StringProperty(
+ name="Thickness weight", default='',
+ description="Vertex Group used for bridge open edges"
+ )
+ invert_vertex_group_bridge : BoolProperty(
+ name="Invert", default=False,
+ description="Invert the vertex group influence"
+ )
+
+ vertex_group_rotation : StringProperty(
+ name="Rotation weight", default='',
+ description="Vertex Group used for rotation"
+ )
+ invert_vertex_group_rotation : BoolProperty(
+ name="Invert", default=False,
+ description="Invert the vertex group influence"
+ )
+ normals_x : FloatProperty(
+ name="X", default=1, min=0, max=1,
+ description="Scale X component of the normals"
+ )
+ normals_y : FloatProperty(
+ name="Y", default=1, min=0, max=1,
+ description="Scale Y component of the normals"
+ )
+ normals_z : FloatProperty(
+ name="Z", default=1, min=0, max=1,
+ description="Scale Z component of the normals"
+ )
+ vertex_group_scale_normals : StringProperty(
+ name="Scale normals weight", default='',
+ description="Vertex Group used for editing the normals directions"
+ )
+ invert_vertex_group_scale_normals : BoolProperty(
+ name="Invert", default=False,
+ description="Invert the vertex group influence"
+ )
+ smooth_normals : BoolProperty(
+ name="Smooth Normals", default=False,
+ description="Smooth normals of the surface in order to reduce intersections"
+ )
+ smooth_normals_iter : IntProperty(
+ name="Iterations",
+ default=5,
+ min=0,
+ description="Smooth iterations"
+ )
+ smooth_normals_uv : FloatProperty(
+ name="UV Anisotropy",
+ default=0,
+ min=-1,
+ max=1,
+ description="0 means no anisotropy, -1 represent the U direction, while 1 represent the V direction"
+ )
+ vertex_group_smooth_normals : StringProperty(
+ name="Smooth Normals weight", default='',
+ description="Vertex Group used for smoothing normals"
+ )
+ invert_vertex_group_smooth_normals : BoolProperty(
+ name="Invert", default=False,
+ description="Invert the vertex group influence"
+ )
+ consistent_wedges : BoolProperty(
+ name="Consistent Wedges", default=True,
+ description="Use same component for the wedges generated by the Fan tessellation"
+ )
+ boundary_variable_offset : BoolProperty(
+ name="Boundary Variable Offset", default=False,
+ description="Additional material offset based on the number of boundary vertices"
+ )
+ auto_rotate_boundary : BoolProperty(
+ name="Automatic Rotation", default=False,
+ description="Automatically rotate the boundary faces"
+ )
+
+ working_on = ""
def draw(self, context):
- allowed_obj = ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
+
'''
try:
bool_working = self.working_on == self.object_name and \
@@ -1923,38 +1537,56 @@ class tessellate(Operator):
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
- bool_working = True
- bool_allowed = True
- except:
- pass
+ ob = context.object
+ sel = context.selected_objects
if len(sel) == 2:
bool_allowed = True
for o in sel:
- if o.type not in allowed_obj:
+ if o.type not in allowed_objects():
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
+ if self.component_mode == 'OBJECT':
+ if len(sel) != 2 and not bool_working:
+ layout = self.layout
+ layout.label(icon='OBJECT_DATA', text='Single Object Component')
+ layout.label(icon='INFO', text="Please, select two different objects. Select first the")
+ layout.label(text="Component object, then select the Base object.")
+ return
+ elif not bool_allowed and not bool_working:
+ layout = self.layout
+ layout.label(icon='OBJECT_DATA', text='Single Object Component')
+ layout.label(icon='ERROR', text="Please, select Mesh, Curve, Surface, Meta or Text")
+ return
+ elif self.component_mode == 'COLLECTION':
+ no_components = True
+ for o in bpy.data.collections[self.component_coll].objects:
+ if o.type in ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT') and o is not ob0:
+ no_components = False
+ break
+ if no_components:
+ layout = self.layout
+ layout.label(icon='OUTLINER_COLLECTION', text='Components from Active Collection')
+ layout.label(icon='INFO', text="The Active Collection does not containt any Mesh,")
+ layout.label(text="Curve, Surface, Meta or Text object.")
+ return
+ elif self.component_mode == 'MATERIALS':
+ no_components = True
+ for mat in ob.material_slots.keys():
+ if mat in bpy.data.objects.keys():
+ if bpy.data.objects[mat].type in allowed_objects():
+ no_components = False
+ break
+ if no_components:
+ layout = self.layout
+ layout.label(icon='INFO', text='Components from Materials')
+ layout.label(text="Can't find any object according to the materials name.")
+ return
+
+ if ob0 == ob1 == None:
+ ob0 = context.object
+ self.generator = ob0.name
+ if self.component_mode == 'OBJECT':
for o in sel:
if o != ob0:
ob1 = o
@@ -1962,250 +1594,121 @@ class tessellate(Operator):
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
+ # 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"
- # Component Modifiers
- row.separator()
- col3 = row.column(align=True)
- col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
+ layout = self.layout
+ # Base and Component
+ col = layout.column(align=True)
+ #col.prop(self, "copy_settings")
+ row = col.row(align=True)
+ row.label(text="Base : " + self.generator, icon='OBJECT_DATA')
+ if self.component_mode == 'OBJECT':
+ row.label(text="Component : " + self.component, icon='OBJECT_DATA')
+ elif self.component_mode == 'COLLECTION':
+ row.label(text="Collection : " + self.component_coll, icon='OUTLINER_COLLECTION')
+ elif self.component_mode == 'MATERIALS':
+ row.label(text="Multiple Components", icon='MATERIAL')
+
+ # 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]
+
+ # Component Modifiers
+ row.separator()
+ col3 = row.column(align=True)
+ col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
+ if self.component_mode == 'OBJECT':
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)
+ # merge settings
+ row.prop(self, "merge")
+ row.prop(self, "bool_smooth")
+
+ # frame settings
+ if self.fill_mode == 'FRAME':
col.separator()
- # Fill and Rotation
+ col.label(text="Frame Settings:")
row = col.row(align=True)
- row.label(text="Fill Mode:")
- row.label(text="Rotation:")
+ row.prop(self, "frame_mode", expand=True)
+ col.prop(self, "frame_thickness", text='Thickness', icon='NONE')
+ col.separator()
row = col.row(align=True)
- #col2 = row.column(align=True)
- row.prop(
- self, "fill_mode", text="", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
- full_event=False, emboss=True, index=-1)
-
- # Rotation
- row.separator()
+ row.prop(self, "fill_frame", icon='NONE')
+ show_frame_mat = self.component_mode == 'MATERIALS' or self.bool_material_id
col2 = row.column(align=True)
- col2.prop(
- self, "rotation_mode", text="", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
- full_event=False, emboss=True, index=-1)
- if self.rotation_mode == 'RANDOM':
- col2.prop(self, "random_seed")
-
- if self.rotation_mode == 'UV':
- uv_error = False
- if self.fill_mode == 'FAN':
- row = col.row(align=True)
- row.label(text="UV rotation doesn't work in FAN mode",
- icon='ERROR')
- uv_error = True
+ col2.prop(self, "fill_frame_mat", icon='NONE')
+ col2.enabled = self.fill_frame and show_frame_mat
+ row = col.row(align=True)
+ row.prop(self, "frame_boundary", text='Boundary', icon='NONE')
+ col2 = row.column(align=True)
+ col2.prop(self, "boundary_mat_offset", icon='NONE')
+ col2.enabled = self.frame_boundary and show_frame_mat
- if ob0.type != 'MESH':
+ 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)
- row.label(
- text="UV rotation supported only for Mesh objects",
- icon='ERROR')
+ check_name = self.generator
+ row.label(text="'" + check_name +
+ "' doesn't have UV Maps", 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 XY
- 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()
+ if uv_error:
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.label(text="Default rotation will be used instead",
+ icon='INFO')
- 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)
-
- # Component Z
- col.label(text="Thickness:")
+ # 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':
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,
+ self, "offset", text="Offset", 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)
-
- # Direction
- 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'
-
- # Merge
- col = layout.column(align=True)
- row = col.row(align=True)
- row.prop(self, "merge")
- if self.merge:
- row.prop(self, "merge_thres")
- row = col.row(align=True)
-
- row = col.row(align=True)
- row.prop(self, "bool_smooth")
- if self.merge:
- col2 = row.column(align=True)
- col2.prop(self, "bool_dissolve_seams")
- #if ob1.type != 'MESH': col2.enabled = False
-
- row = col.row(align=True)
- row.prop(self, "cap_faces")
- if self.cap_faces:
- col2 = row.column(align=True)
- col2.prop(self, "open_edges_crease", text="Crease")
-
- # 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:
- allow_multi = False
- allow_shapekeys = not self.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 self.com_modifiers
- except: pass
- except: pass
- # DATA #
- col = layout.column(align=True)
- col.label(text="Morphing:")
- # vertex group + shape keys
- row = col.row(align=True)
- col2 = row.column(align=True)
- col2.prop(self, "bool_vertex_group", icon='GROUP_VERTEX')
- #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
- 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')
- #if self.bool_material_id or self.bool_selection or self.bool_multi_components:
- #col2 = row.column(align=True)
- # col2.prop(self, "bool_combine")
- 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
+ row.enabled = not self.use_origin_offset
+ col.separator()
+ col.label(text="More settings in the Object Data Properties panel...", icon='PROPERTIES')
- col.separator()
- row = col.row(align=True)
- row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
- row.prop(self, 'iterations', text='Repeat', icon='SETTINGS')
-
- 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]
+ if self.component_mode == 'OBJECT':
+ ob1 = bpy.data.objects[self.component]
except:
return {'CANCELLED'}
@@ -2220,100 +1723,160 @@ class tessellate(Operator):
self.object_name = test_name
break
count_name += 1
+ if self.component_mode == 'OBJECT':
+ if ob1.type not in allowed_objects():
+ message = "Component must be Mesh, Curve, Surface, Text or Meta object!"
+ self.report({'ERROR'}, message)
+ self.component = None
- 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:
+ if ob0.type not in allowed_objects():
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')
+ 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.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
+ bool_update = False
+ if context.object == ob0:
+ auto_layer_collection()
+ new_ob = convert_object_to_mesh(ob0,False,False)
+ new_ob.data.name = self.object_name
+ new_ob.name = self.object_name
+ else:
+ new_ob = context.object
+ bool_update = True
+ new_ob = store_parameters(self, new_ob)
+ new_ob.tissue.tissue_type = 'TESSELLATE'
+ try: bpy.ops.object.tissue_update_tessellate()
+ except RuntimeError as e:
+ bpy.data.objects.remove(new_ob)
+ remove_temp_objects()
+ 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
+
+ # Assign collection of the base object
+ old_coll = new_ob.users_collection
+ if old_coll != ob0.users_collection:
+ for c in old_coll:
+ c.objects.unlink(new_ob)
+ for c in ob0.users_collection:
+ c.objects.link(new_ob)
+ context.view_layer.objects.active = new_ob
- return {'FINISHED'}
+ return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
-class update_tessellate(Operator):
- bl_idname = "object.update_tessellate"
- bl_label = "Refresh"
+class tissue_update_tessellate_deps(Operator):
+ bl_idname = "object.tissue_update_tessellate_deps"
+ bl_label = "Tissue Refresh"
bl_description = ("Fast update the tessellated mesh according to base and "
- "component changes")
+ "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
+ try:
+ return context.object.tissue.tissue_type != 'NONE'
except:
return False
- @staticmethod
- def check_gen_comp(checking):
+ #@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()
+ # return checking in bpy.data.objects.keys()
def execute(self, context):
+
+ active_ob = context.object
+ selected_objects = context.selected_objects
+
+ ### TO-DO: sorting according to dependencies
+ update_objects = [o for o in selected_objects if o.tissue.tissue_type != 'NONE']
+ for ob in selected_objects:
+ update_objects = list(reversed(update_dependencies(ob, update_objects)))
+ #update_objects = list(reversed(update_dependencies(ob, [ob])))
+ for o in update_objects:
+ override = {
+ 'object': o,
+ 'selected_objects' : [o]
+ }
+ if o.type == 'MESH':
+ try:
+ bpy.ops.object.tissue_update_tessellate(override)
+ except:
+ self.report({'ERROR'}, "Can't Tessellate :-(")
+ else:
+ try:
+ bpy.ops.object.tissue_convert_to_curve_update(override)
+ except:
+ self.report({'ERROR'}, "Can't compute Curve :-(")
+
+ context.view_layer.objects.active = active_ob
+ for o in context.view_layer.objects:
+ o.select_set(o in selected_objects)
+
+ return {'FINISHED'}
+
+
+class tissue_update_tessellate(Operator):
+ bl_idname = "object.tissue_update_tessellate"
+ bl_label = "Tissue Refresh Simple"
+ bl_description = ("Fast update the tessellated mesh according to base and "
+ "component changes. Does not update dependencies")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ go = False
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ ob = context.object
+ return ob.tissue.tissue_type == 'TESSELLATE'
+ except:
+ return False
+
+ def execute(self, context):
+
+ tissue_time(None,'Tissue: Tessellating...', levels=0)
start_time = time.time()
- ob = bpy.context.object
+
+ ob = context.object
+ tess_props = props_to_dict(ob)
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_open_edges_only = ob.tissue_tessellate.merge_open_edges_only
merge_thres = ob.tissue_tessellate.merge_thres
+ mode = ob.tissue_tessellate.mode
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
+ rand_seed = ob.tissue_tessellate.rand_seed
+ rand_step = ob.tissue_tessellate.rand_step
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
@@ -2323,26 +1886,67 @@ class update_tessellate(Operator):
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
+ #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_edges_crease = ob.tissue_tessellate.bridge_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
+ boundary_mat_offset = ob.tissue_tessellate.boundary_mat_offset
+ fill_frame_mat = ob.tissue_tessellate.fill_frame_mat
+ bridge_cuts = ob.tissue_tessellate.bridge_cuts
+ cap_material_offset = ob.tissue_tessellate.cap_material_offset
+ bridge_material_offset = ob.tissue_tessellate.bridge_material_offset
+ patch_subs = ob.tissue_tessellate.patch_subs
+ use_origin_offset = ob.tissue_tessellate.use_origin_offset
+ vertex_group_thickness = ob.tissue_tessellate.vertex_group_thickness
+ invert_vertex_group_thickness = ob.tissue_tessellate.invert_vertex_group_thickness
+ vertex_group_thickness_factor = ob.tissue_tessellate.vertex_group_thickness_factor
+ vertex_group_distribution = ob.tissue_tessellate.vertex_group_distribution
+ invert_vertex_group_distribution = ob.tissue_tessellate.invert_vertex_group_distribution
+ vertex_group_distribution_factor = ob.tissue_tessellate.vertex_group_distribution_factor
+ vertex_group_cap_owner = ob.tissue_tessellate.vertex_group_cap_owner
+ vertex_group_cap = ob.tissue_tessellate.vertex_group_cap
+ invert_vertex_group_cap = ob.tissue_tessellate.invert_vertex_group_cap
+ vertex_group_bridge_owner = ob.tissue_tessellate.vertex_group_bridge_owner
+ vertex_group_bridge = ob.tissue_tessellate.vertex_group_bridge
+ invert_vertex_group_bridge = ob.tissue_tessellate.invert_vertex_group_bridge
+ vertex_group_rotation = ob.tissue_tessellate.vertex_group_rotation
+ invert_vertex_group_rotation = ob.tissue_tessellate.invert_vertex_group_rotation
+ vertex_group_smooth_normals = ob.tissue_tessellate.vertex_group_smooth_normals
+ invert_vertex_group_smooth_normals = ob.tissue_tessellate.invert_vertex_group_smooth_normals
+ target = ob.tissue_tessellate.target
+ even_thickness = ob.tissue_tessellate.even_thickness
+ even_thickness_iter = ob.tissue_tessellate.even_thickness_iter
+ component_mode = ob.tissue_tessellate.component_mode
+ component_coll = ob.tissue_tessellate.component_coll
+ coll_rand_seed = ob.tissue_tessellate.coll_rand_seed
try:
generator.name
- component.name
+ if component_mode == 'OBJECT':
+ component.name
except:
self.report({'ERROR'},
- "Active object must be Tessellate before Update")
+ "Active object must be Tessellated before Update")
return {'CANCELLED'}
+ # reset messages
+ ob.tissue_tessellate.warning_message_merge = ''
+
+ tess_props = props_to_dict(ob)
+
# Solve Local View issues
local_spaces = []
local_ob0 = []
local_ob1 = []
- for area in bpy.context.screen.areas:
+ for area in context.screen.areas:
for space in area.spaces:
try:
if ob.local_view_get(space):
@@ -2354,196 +1958,258 @@ class update_tessellate(Operator):
except:
pass
- starting_mode = bpy.context.object.mode
+ starting_mode = context.object.mode
+
#if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT'
- bpy.ops.object.mode_set(mode='OBJECT')
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode='OBJECT')
ob0 = generator
ob1 = component
- auto_layer_collection()
+ ##### 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 component_mode == 'OBJECT':
+ ob1_hide = ob1.hide_get()
+ ob1_hidev = ob1.hide_viewport
+ ob1_hider = ob1.hide_render
+ ob1.hide_set(False)
+ ob1.hide_viewport = False
+ ob1.hide_render = False
+
+ components = []
+ if component_mode == 'COLLECTION':
+ dict_components = {}
+ meta_object = True
+ for _ob1 in component_coll.objects:
+ if _ob1 == ob: continue
+ if _ob1.type in ('MESH', 'CURVE','SURFACE','FONT','META'):
+ if _ob1.type == 'META':
+ if meta_object: meta_object = False
+ else: continue
+ dict_components[_ob1.name] = _ob1
+ for k in sorted(dict_components):
+ components.append(dict_components[k])
+ elif component_mode == 'OBJECT':
+ components.append(ob1)
if ob0.type == 'META':
base_ob = convert_object_to_mesh(ob0, False, True)
else:
base_ob = ob0.copy()
- base_ob.data = ob0.data.copy()
- bpy.context.collection.objects.link(base_ob)
+ 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 = bpy.context.scene.frame_current
+ 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()
-
- #new_ob.location = ob.location
- #new_ob.matrix_world = ob.matrix_world
+ scene = context.scene
+ for mod in base_ob.modifiers:
+ if mod.type == 'CLOTH':
+ override = {'scene': scene, 'active_object': base_ob, 'point_cache': mod.point_cache}
+ bpy.ops.ptcache.bake(override, bake=True)
+ break
base_ob.modifiers.update()
- bpy.ops.object.select_all(action='DESELECT')
+
+ # clear vertex groups before creating new ones
+ if ob not in components: ob.vertex_groups.clear()
+
+ 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)
+ remove_temp_objects()
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+
iter_objects = [base_ob]
- #base_ob = new_ob#.copy()
+ ob_location = ob.location
+ ob_matrix_world = ob.matrix_world
+
+ #if ob not in components:
+ ob.data.clear_geometry() # Faster with heavy geometries (from previous tessellations)
for iter in range(iterations):
+ tess_props['generator'] = base_ob
+
+ if iter > 0 and len(iter_objects) == 0: break
+ if iter > 0 and normals_mode in ('SHAPEKEYS','OBJECT'):
+ tess_props['normals_mode'] = 'VERTS'
same_iteration = []
matched_materials = []
- 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:
- try:
- mat = base_ob.material_slots[m_id].material
- ob1 = bpy.data.objects[mat.name]
- material_id = m_id
- matched_materials.append(m_id)
- bool_material_id = True
- except:
- continue
- if com_modifiers:
- data1 = simple_to_mesh(ob1)
- else: data1 = ob1.data.copy()
- n_edges1 = len(data1.edges)
-
- if iter != 0: gen_modifiers = True
- if fill_mode == 'PATCH':
- new_ob = tessellate_patch(
- base_ob, ob1, offset, zscale, com_modifiers, mode, scale_mode,
- rotation_mode, random_seed, bool_vertex_group,
- bool_selection, bool_shapekeys, bool_material_id, material_id,
- bounds_x, bounds_y
- )
- else:
- new_ob = tessellate_original(
- base_ob, ob1, offset, zscale, gen_modifiers,
- com_modifiers, mode, scale_mode, rotation_mode,
- random_seed, fill_mode, bool_vertex_group,
- bool_selection, bool_shapekeys, bool_material_id,
- material_id, normals_mode, bounds_x, bounds_y
- )
- if type(new_ob) is bpy.types.Object:
- bpy.context.view_layer.objects.active = new_ob
- else:
- continue
- 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 type(new_ob) == str: break
-
- if bool_multi_components and type(new_ob) not in (int,str):
- same_iteration.append(new_ob)
- new_ob.select_set(True)
- bpy.context.view_layer.objects.active = new_ob
-
- if type(new_ob) == str: break
-
- #bpy.data.objects.remove(base_ob)
- if bool_multi_components:
- bpy.context.view_layer.update()
- bpy.context.view_layer.objects.active.select_set(True)
- for o in bpy.data.objects:
- if o in same_iteration:
- o.select_set(True)
- o.location = ob.location
+
+ if component_mode == 'MATERIALS':
+ components = []
+ objects_keys = bpy.data.objects.keys()
+ for mat_slot in base_ob.material_slots:
+ mat_name = mat_slot.material.name
+ if mat_name in objects_keys:
+ ob1 = bpy.data.objects[mat_name]
+ if ob1.type in ('MESH', 'CURVE','SURFACE','FONT','META'):
+ components.append(bpy.data.objects[mat_name])
+ matched_materials.append(mat_name)
+ else:
+ components.append(None)
else:
- try:
- o.select_set(False)
- except: pass
- bpy.ops.object.join()
- new_ob = bpy.context.view_layer.objects.active
+ components.append(None)
+ tess_props['component'] = components
+ # patch subdivisions for additional iterations
+ if iter > 0 and fill_mode == 'PATCH':
+ temp_mod = base_ob.modifiers.new('Tissue_Subsurf', type='SUBSURF')
+ temp_mod.levels = patch_subs
+
+ # patch tessellation
+ tissue_time(None,"Tessellate iteration...",levels=1)
+ tt = time.time()
+ same_iteration = tessellate_patch(tess_props)
+ tissue_time(tt, "Tessellate iteration",levels=1)
+
+ tt = time.time()
+
+ # if empty or error, continue
+ #if type(same_iteration) != list:#is not bpy.types.Object and :
+ # return {'CANCELLED'}
+
+ for id, new_ob in enumerate(same_iteration):
+ # rename, make active and change transformations
+ new_ob.name = '_tissue_tmp_{}_{}'.format(iter,id)
new_ob.select_set(True)
- new_ob.data.update()
+ context.view_layer.objects.active = new_ob
+ new_ob.location = ob_location
+ new_ob.matrix_world = ob_matrix_world
+
+ base_ob.location = ob_location
+ base_ob.matrix_world = ob_matrix_world
+ # join together multiple components iterations
+ if type(same_iteration) == list:
+ if len(same_iteration) == 0:
+ remove_temp_objects()
+ tissue_time(None,"Can't Tessellate :-(",levels=0)
+ return {'CANCELLED'}
+ if len(same_iteration) > 1:
+ #join_objects(context, same_iteration)
+ new_ob = join_objects(same_iteration)
+
+ if type(same_iteration) in (int,str):
+ new_ob = same_iteration
+ if iter == 0:
+ try:
+ bpy.data.objects.remove(iter_objects[0])
+ iter_objects = []
+ except: continue
+ continue
- #try:
- # combine object
+ # 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()
-
+ if (fill_mode == 'PATCH' or gen_modifiers) and iter == 0:
+ last_mesh = simple_to_mesh(base_ob)#(ob0)
+ else:
+ last_mesh = iter_objects[-1].data.copy()
bm.from_mesh(last_mesh)
bm.faces.ensure_lookup_table()
- if bool_multi_components:
+ if component_mode == 'MATERIALS':
remove_materials = matched_materials
elif bool_material_id:
remove_materials = [material_id]
else: remove_materials = []
if bool_selection:
- remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select]
+ if component_mode == 'MATERIALS' 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)
+ bm.free()
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)
- #bpy.context.collection.objects.unlink(base_ob)
if iter < iterations-1: new_ob.data = base_ob.data
-
+ # store new iteration and set transformations
iter_objects.append(new_ob)
- new_ob.location = ob.location
- new_ob.matrix_world = ob.matrix_world
- try:
- bpy.data.objects.remove(bpy.data.objects['_Tessellation_Base'])
- except: pass
- base_ob.name = "_Tessellation_Base"
+ base_ob.name = '_tissue_tmp_base'
elif combine_mode == 'ALL':
base_ob = new_ob.copy()
- iter_objects.append(new_ob)
- new_ob.location = ob.location
- new_ob.matrix_world = ob.matrix_world
+ iter_objects = [new_ob] + iter_objects
else:
if base_ob != new_ob:
bpy.data.objects.remove(base_ob)
base_ob = new_ob
iter_objects = [new_ob]
+ if iter > 0:# and fill_mode == 'PATCH':
+ base_ob.modifiers.clear()#remove(temp_mod)
+
+ # Combine
+ if combine_mode != 'LAST' and len(iter_objects) > 1:
+ if base_ob not in iter_objects and type(base_ob) == bpy.types.Object:
+ bpy.data.objects.remove(base_ob)
+ new_ob = join_objects(iter_objects)
+ new_ob.modifiers.clear()
+ iter_objects = [new_ob]
+
+ tissue_time(tt, "Combine tessellations", levels=1)
+
+ if merge:
+ new_ob.active_shape_key_index = 0
+ use_bmesh = not (bool_shapekeys and fill_mode == 'PATCH' and component_mode != 'OBJECT')
+ merged = merge_components(new_ob, ob.tissue_tessellate, use_bmesh)
+ if merged == 'bridge_error':
+ message = "Can't make the bridge!"
+ ob.tissue_tessellate.warning_message_merge = message
+
+ base_ob = new_ob #context.view_layer.objects.active
+
+ tt = time.time()
+
if new_ob == 0:
- #for m, vis in zip(ob.modifiers, mod_visibility): m.show_viewport = vis
- message = "Zero faces selected in the Base mesh!"
+ #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: bpy.data.objects.remove(o)
- bpy.context.view_layer.objects.active = ob
+ 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
@@ -2551,18 +2217,6 @@ class update_tessellate(Operator):
self.report({'ERROR'}, message)
return {'CANCELLED'}
- new_ob.location = ob.location
- new_ob.matrix_world = ob.matrix_world
-
- ### REPEAT
- if combine_mode != 'LAST' and len(iter_objects)>0:
- if base_ob not in iter_objects: bpy.data.objects.remove(base_ob)
- for o in iter_objects:
- o.location = ob.location
- o.select_set(True)
- bpy.ops.object.join()
- new_ob.data.update()
-
# update data and preserve name
if ob.type != 'MESH':
loc, matr = ob.location, ob.matrix_world
@@ -2570,73 +2224,36 @@ class update_tessellate(Operator):
ob.location, ob.matrix_world = loc, matr
data_name = ob.data.name
old_data = ob.data
- ob.data = new_ob.data
- bpy.data.meshes.remove(old_data)
+ old_data.name = '_tissue_tmp_old_data'
+ #ob.data = bpy.data.meshes.new_from_object(new_ob)#
+ linked_objects = [o for o in bpy.data.objects if o.data == old_data]
+
+ for o in linked_objects:
+ o.data = new_ob.data
+ if len(linked_objects) > 1:
+ copy_tessellate_props(ob, o)
+
+ #ob.data = new_ob.data
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')
+ for vg in new_ob.vertex_groups:
+ if not vg.name in ob.vertex_groups.keys():
+ ob.vertex_groups.new(name=vg.name)
- selected_objects = [o for o in bpy.context.selected_objects]
+ selected_objects = [o for o in context.selected_objects]
for o in selected_objects: o.select_set(False)
ob.select_set(True)
- bpy.context.view_layer.objects.active = ob
- bpy.data.objects.remove(new_ob)
+ context.view_layer.objects.active = 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=False, 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()
- if cap_faces:
- 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)
- bpy.ops.mesh.edge_face_add()
- if open_edges_crease != 0:
- bpy.ops.transform.edge_crease(value=open_edges_crease)
-
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.object.mode_set(mode='OBJECT')
+ is_multiple = iterations > 1 or combine_mode != 'LAST'# or bool_multi_components
+ if merge and is_multiple:
+ use_bmesh = not (bool_shapekeys and fill_mode == 'PATCH' and component_mode != 'OBJECT')
+ merge_components(new_ob, ob.tissue_tessellate, use_bmesh)
if bool_smooth: bpy.ops.object.shade_smooth()
- ####values = [True] * len(ob.data.polygons)
- ####ob.data.polygons.foreach_set("use_smooth", values)
-
- #for m, vis in zip(ob.modifiers, mod_visibility): m.show_viewport = vis
-
- end_time = time.time()
- print('Tissue: object "{}" tessellated in {:.4f} sec'.format(ob.name, end_time-start_time))
for mesh in bpy.data.meshes:
if not mesh.users: bpy.data.meshes.remove(mesh)
@@ -2645,13 +2262,6 @@ class update_tessellate(Operator):
try: o.select_set(True)
except: pass
- bpy.ops.object.mode_set(mode=starting_mode)
-
- # clean objects
- for o in bpy.data.objects:
- if o.name not in context.view_layer.objects and "temp" in o.name:
- bpy.data.objects.remove(o)
-
ob.tissue_tessellate.error_message = ""
# Restore Base visibility
@@ -2659,14 +2269,22 @@ class update_tessellate(Operator):
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
+ if component_mode == 'OBJECT':
+ 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)
+
+ remove_temp_objects()
+
+ tissue_time(tt, "Closing tessellation", levels=1)
+
+ tissue_time(start_time,'Tessellation of "{}"'.format(ob.name),levels=0)
return {'FINISHED'}
def check(self, context):
@@ -2674,10 +2292,10 @@ class update_tessellate(Operator):
class TISSUE_PT_tessellate(Panel):
bl_label = "Tissue Tools"
- bl_category = "Edit"
+ bl_category = "Tissue"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
- bl_options = {'DEFAULT_CLOSED'}
+ #bl_options = {'DEFAULT_OPEN'}
@classmethod
def poll(cls, context):
@@ -2687,50 +2305,75 @@ class TISSUE_PT_tessellate(Panel):
layout = self.layout
col = layout.column(align=True)
- col.label(text="Tessellate:")
- col.operator("object.tessellate")
- col.operator("object.dual_mesh_tessellated")
+ col.label(text="Generate:")
+ row = col.row(align=True)
+ row.operator("object.tissue_tessellate", text='Tessellate', icon='OBJECT_DATA').component_mode = 'OBJECT'
+ tss = row.operator("object.tissue_tessellate", text='', icon='OUTLINER_COLLECTION')
+ tss.component_mode = 'COLLECTION'
+ tss.component_coll = context.collection.name
+ row.operator("object.tissue_tessellate", text='', icon='MATERIAL').component_mode = 'MATERIALS'
+ #col.operator("object.tissue_tessellate_multi", text='Tessellate Multi')
+ col.operator("object.dual_mesh_tessellated", text='Dual Mesh', icon='SEQ_CHROMA_SCOPE')
+ col.separator()
+
+ #col.label(text="Curves:")
+ col.operator("object.tissue_convert_to_curve", icon='OUTLINER_OB_CURVE', text="Convert to Curve")
+ #row.operator("object.tissue_convert_to_curve_update", icon='FILE_REFRESH', text='')
+
col.separator()
- #col = layout.column(align=True)
- #col.label(text="Tessellate Edit:")
- #col.operator("object.settings_tessellate")
- col.operator("object.update_tessellate", icon='FILE_REFRESH')
+ col.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') #####
- #col = layout.column(align=True)
- col.operator("mesh.rotate_face", icon='NDOF_TURN')
+ 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_flip", text='Flip', icon='UV_SYNC_SELECT')
+ 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.dual_mesh", icon='SEQ_CHROMA_SCOPE')
+ col.operator("object.polyhedra_wireframe", icon='MOD_WIREFRAME', text='Polyhedra Wireframe')
col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
- act = context.active_object
+ 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")
+ col.operator("object.tissue_weight_streamlines", icon="ANIM")
+
+ col.separator()
+ col.label(text="Materials:")
+ col.operator("object.random_materials", icon='COLOR')
+ col.operator("object.weight_to_materials", icon='GROUP_VERTEX')
+
+ col.separator()
+ col.label(text="Utils:")
+ col.operator("render.tissue_render_animation", icon='RENDER_ANIMATION')
class TISSUE_PT_tessellate_object(Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data"
- bl_label = "Tissue - Tessellate"
+ bl_label = "Tissue Tessellate"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
- try: return context.object.type == 'MESH'
+ 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')
+ tissue_props = ob.tissue
- try:
- bool_tessellated = props.generator or props.component != None
- ob0 = props.generator
- ob1 = props.component
- except: bool_tessellated = False
+ bool_tessellated = tissue_props.tissue_type == 'TESSELLATE'
layout = self.layout
if not bool_tessellated:
layout.label(text="The selected object is not a Tessellated object",
@@ -2743,250 +2386,770 @@ class TISSUE_PT_tessellate_object(Panel):
row = col.row(align=True)
set_tessellate_handler(self,context)
- set_animatable_fix_handler(self,context)
- row.prop(props, "bool_run", text="Animatable")
- row.operator("object.update_tessellate", icon='FILE_REFRESH')
-
- col = layout.column(align=True)
- row = col.row(align=True)
- row.label(text="BASE :")
- row.label(text="COMPONENT :")
- row = col.row(align=True)
-
+ ###### set_animatable_fix_handler(self,context)
+ row.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') ####
+ lock_icon = 'LOCKED' if tissue_props.bool_lock else 'UNLOCKED'
+ #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
+ deps_icon = 'LINKED' if tissue_props.bool_dependencies else 'UNLINKED'
+ row.prop(tissue_props, "bool_dependencies", text="", icon=deps_icon)
+ row.prop(tissue_props, "bool_lock", text="", icon=lock_icon)
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")
+ col2.prop(tissue_props, "bool_run", text="",icon='TIME')
+ col2.enabled = not tissue_props.bool_lock
+ #layout.use_property_split = True
+ #layout.use_property_decorate = False # No animation.
+ col = layout.column(align=True)
+ col.label(text='Base object:')
row = col.row(align=True)
+ row.prop_search(props, "generator", context.scene, "objects")
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')
+ col2.prop(props, "gen_modifiers", text='Use Modifiers',icon='MODIFIER')
+ '''
try:
- if not (props.component.modifiers or props.component.data.shape_keys):
+ if not (props.generator.modifiers or props.generator.data.shape_keys):
col2.enabled = False
except:
col2.enabled = False
- col.separator()
+ '''
+ #col.separator()
- # Fill and Rotation
- row = col.row(align=True)
- row.label(text="Fill Mode:")
- row.separator()
- row.label(text="Rotation:")
- row = col.row(align=True)
+ layout.use_property_split = False
+ # Fill
+ col = layout.column(align=True)
+ col.label(text="Fill Mode:")
# fill
- row.prop(props, "fill_mode", text="", icon='NONE', expand=False,
+ 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)
- row.separator()
- # rotation
- col2 = row.column(align=True)
- col2.prop(props, "rotation_mode", text="", icon='NONE', expand=False,
- 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")
- if props.rotation_mode == 'RANDOM':
- #row = col.row(align=True)
- col2.prop(props, "random_seed")
- if props.rotation_mode == 'UV':
- uv_error = False
- if props.fill_mode == 'FAN':
- row = col.row(align=True)
- row.label(text="UV rotation doesn't work in FAN mode",
- icon='ERROR')
- uv_error = True
- 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
+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
+ layout = self.layout
+ 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)
+ col.separator()
+ row = col.row(align=True)
+ row.prop(props, "fill_frame", icon='NONE')
+ show_frame_mat = props.component_mode == 'MATERIALS' or props.bool_material_id
+ col2 = row.column(align=True)
+ col2.prop(props, "fill_frame_mat", icon='NONE')
+ col2.enabled = props.fill_frame and show_frame_mat
+ row = col.row(align=True)
+ row.prop(props, "frame_boundary", text='Boundary', icon='NONE')
+ col2 = row.column(align=True)
+ col2.prop(props, "boundary_mat_offset", icon='NONE')
+ col2.enabled = props.frame_boundary and show_frame_mat
+
+
+class TISSUE_PT_tessellate_component(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Components"
+ #bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE'
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+
+ layout = self.layout
+ col = layout.column(align=True)
+ col.label(text='Component Mode:')
+ row = col.row(align=True)
+ row.prop(props, "component_mode", icon='NONE', expand=True,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ if props.component_mode == 'OBJECT':
+ col.separator()
+ row = col.row(align=True)
+ row.prop_search(props, "component", context.scene, "objects")
+ 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
+ '''
+ elif props.component_mode == 'COLLECTION':
+ col.separator()
+
+ if props.component_coll in list(bpy.data.collections):
+ components = []
+ for o in props.component_coll.objects:
+ if o.type in allowed_objects() and o is not ob:
+ components.append(o.name)
+ n_comp = len(components)
+ if n_comp == 0:
+ col.label(text="Can't find components in the Collection.", icon='ERROR')
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:
+ text = "{} Component{}".format(n_comp,"s" if n_comp>1 else "")
row = col.row(align=True)
- row.label(text="Default rotation will be used instead",
- icon='INFO')
+ row.label(text=text, icon='OBJECT_DATA')
+ row.prop(props, "com_modifiers", text='Use Modifiers',icon='MODIFIER')
+ else:
+ col.label(text="Please, chose one Collection.", icon='ERROR')
- # component XY
+ col.separator()
row = col.row(align=True)
- row.label(text="Component Coordinates:")
+ row.prop_search(props,'component_coll',bpy.data,'collections')
+ col2 = row.column(align=True)
+ col2.prop(props, "coll_rand_seed")
+ col = layout.column(align=True)
row = col.row(align=True)
- row.prop(props, "mode", expand=True)
-
- if props.mode != 'BOUNDS':
+ ob0 = props.generator
+ row.prop_search(props, 'vertex_group_distribution',
+ ob0, "vertex_groups", text='')
+ col2 = row.column(align=True)
+ row2 = col2.row(align=True)
+ row2.prop(props, "invert_vertex_group_distribution", text="",
+ toggle=True, icon='ARROW_LEFTRIGHT')
+ row2.prop(props, "vertex_group_distribution_factor")
+ row2.enabled = props.vertex_group_distribution in ob0.vertex_groups.keys()
+ if props.fill_mode == 'FAN': col.prop(props, "consistent_wedges")
+ else:
+ components = []
+ for mat in props.generator.material_slots.keys():
+ if mat in bpy.data.objects.keys():
+ if bpy.data.objects[mat].type in allowed_objects():
+ components.append(mat)
+ n_comp = len(components)
+ if n_comp == 0:
+ col.label(text="Can't find components from the materials.", icon='ERROR')
+ else:
col.separator()
+ text = "{} Component{}".format(n_comp,"s" if n_comp>1 else "")
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.label(text=text, icon='OBJECT_DATA')
+ row.prop(props, "com_modifiers", text='Use Modifiers',icon='MODIFIER')
+ if props.fill_mode != 'FRAME':
+ col.separator()
+ col.separator()
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)
-
- # component Z
- col.label(text="Thickness:")
- 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)
+ row.label(text="Boundary Faces:")
+ row.prop(props, "boundary_mat_offset", icon='NONE')
+ row = col.row(align=True)
+ row.prop(props, "boundary_variable_offset", text='Variable Offset', icon='NONE')
+ row.prop(props, "auto_rotate_boundary", icon='NONE')
+ col.separator()
- # Direction
- row = col.row(align=True)
- row.label(text="Direction:")
+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 = "Components Coordinates"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE'
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ layout = self.layout
+
+ 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, "normals_mode", text="Direction", icon='NONE', expand=True,
+ 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.enabled = props.fill_mode != 'PATCH'
- # merge
- col = layout.column(align=True)
row = col.row(align=True)
- row.prop(props, "merge")
- if props.merge:
- row.prop(props, "merge_thres")
+ 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.tissue_type == 'TESSELLATE'
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ layout = self.layout
+ # 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.separator()
row = col.row(align=True)
- row.prop(props, "bool_smooth")
- if props.merge:
- col2 = row.column(align=True)
- col2.prop(props, "bool_dissolve_seams")
- #if props.component.type != 'MESH': col2.enabled = False
+ row.separator()
+ row.separator()
+ row.separator()
+ row.prop_search(props, 'vertex_group_rotation',
+ ob0, "vertex_groups", text='Vertex Group')
+ col2 = row.column(align=True)
+ col2.prop(props, "invert_vertex_group_rotation", text="", toggle=True, icon='ARROW_LEFTRIGHT')
+ col2.enabled = props.vertex_group_rotation in ob0.vertex_groups.keys()
+ col.separator()
+ 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, "rand_seed")
+ col.prop(props, "rand_step")
+ 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: return context.object.tissue.tissue_type == 'TESSELLATE'
+ except: return False
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+
+ layout = self.layout
+ #layout.use_property_split = True
+ 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':
row = col.row(align=True)
- row.prop(props, "cap_faces")
- if props.cap_faces:
- col2 = row.column(align=True)
- col2.prop(props, "open_edges_crease", text="Crease")
+ row.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)
+ row.enabled = not props.use_origin_offset
+ col.prop(props, 'use_origin_offset')
- # Advanced Settings
- col = layout.column(align=True)
- col.separator()
+ col.separator()
+ row = col.row(align=True)
+ ob0 = props.generator
+ row.prop_search(props, 'vertex_group_thickness',
+ ob0, "vertex_groups", text='')
+ col2 = row.column(align=True)
+ row2 = col2.row(align=True)
+ row2.prop(props, "invert_vertex_group_thickness", text="",
+ toggle=True, icon='ARROW_LEFTRIGHT')
+ row2.prop(props, "vertex_group_thickness_factor")
+ row2.enabled = props.vertex_group_thickness in ob0.vertex_groups.keys()
+
+class TISSUE_PT_tessellate_direction(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Thickness Direction"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ return context.object.tissue.tissue_type == 'TESSELLATE'
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ layout = self.layout
+ ob0 = props.generator
+ #layout.use_property_split = True
+ col = layout.column(align=True)
+ 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)
+ if props.normals_mode == 'OBJECT':
col.separator()
row = col.row(align=True)
- row.prop(props, "bool_advanced", icon='SETTINGS')
- if props.bool_advanced:
- 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
- # DATA #
- col = layout.column(align=True)
- col.label(text="Morphing:")
+ row.prop_search(props, "target", context.scene, "objects", text='Target')
+ if props.warning_message_thickness != '':
+ col.separator()
+ col.label(text=props.warning_message_thickness, icon='ERROR')
+ if props.normals_mode != 'FACES':
+ col.separator()
+ col.prop(props, "smooth_normals")
+ if props.smooth_normals:
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.prop(props, "smooth_normals_iter")
row.separator()
+ row.prop_search(props, 'vertex_group_smooth_normals',
+ ob0, "vertex_groups", text='')
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
+ col2.prop(props, "invert_vertex_group_smooth_normals", text="", toggle=True, icon='ARROW_LEFTRIGHT')
+ col2.enabled = props.vertex_group_smooth_normals in ob0.vertex_groups.keys()
+ if props.normals_mode == 'VERTS':
+ col.separator()
+ row = col.row(align=True)
+ row.prop(props, "normals_x")
+ row.prop(props, "normals_y")
+ row.prop(props, "normals_z")
+ row = col.row(align=True)
+ row.prop_search(props, 'vertex_group_scale_normals',
+ ob0, "vertex_groups", text='')
+ col2 = row.column(align=True)
+ col2.prop(props, "invert_vertex_group_scale_normals", text="", toggle=True, icon='ARROW_LEFTRIGHT')
+ col2.enabled = props.vertex_group_scale_normals in ob0.vertex_groups.keys()
+ if props.normals_mode in ('OBJECT', 'SHAPEKEYS'):
+ col.separator()
+ row = col.row(align=True)
+ row.prop(props, "even_thickness")
+ if props.even_thickness: row.prop(props, "even_thickness_iter")
- # 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_multi_components", icon='MOD_TINT')
- if not allow_multi:
- col2.enabled = False
- col.separator()
- row = col.row(align=True)
- col2 = row.column(align=True)
- col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
- #if props.bool_material_id or props.bool_selection or props.bool_multi_components:
- #col2 = row.column(align=True)
- # col2.prop(props, "bool_combine")
- 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
+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:
+ return context.object.tissue.tissue_type == 'TESSELLATE'
+ except:
+ return False
- # TRANSFER DATA ### OFF
- if props.fill_mode != 'PATCH' and False:
- col = layout.column(align=True)
- col.label(text="Component Data:")
+ 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
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+ col = layout.column(align=True)
+ if props.merge:
+ col.prop(props, "merge_thres")
+ col.prop(props, "merge_open_edges_only")
+ col.prop(props, "bool_dissolve_seams")
+ col.prop(props, "close_mesh")
+ if props.close_mesh in ('BRIDGE', 'BRIDGE_CAP'):
+ col.separator()
+ if props.close_mesh == 'BRIDGE_CAP':
+ if props.vertex_group_bridge_owner == 'BASE': ob_bridge = ob0
+ else: ob_bridge = ob1
row = col.row(align=True)
+ row.prop_search(props, 'vertex_group_bridge',
+ ob_bridge, "vertex_groups")
+ row.prop(props, "invert_vertex_group_bridge", text="",
+ toggle=True, icon='ARROW_LEFTRIGHT')
+ row = col.row(align=True)
+ row.prop(props, "vertex_group_bridge_owner", expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
col2 = row.column(align=True)
- col2.prop(props, "bool_materials", icon='MATERIAL_DATA')
- row.separator()
- col2 = row.column(align=True)
- if props.fill_mode == 'PATCH':
- col.enabled = False
- col.label(text='Not needed in Patch mode', icon='INFO')
-
+ row2 = col2.row(align=True)
+ col.prop(props, "bridge_edges_crease", text="Crease")
+ col.prop(props, "bridge_material_offset", text='Material Offset')
+ '''
+ if props.close_mesh == 'BRIDGE' and False:
+ col.separator()
+ col.prop(props, "bridge_cuts")
+ col.prop(props, "bridge_smoothness")
+ '''
+ if props.close_mesh in ('CAP', 'BRIDGE_CAP'):
+ #row = col.row(align=True)
col.separator()
- row = col.row(align=True)
- row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
- row.prop(props, 'iterations', text='Repeat', icon='SETTINGS')
+ if props.close_mesh == 'BRIDGE_CAP':
+ if props.vertex_group_cap_owner == 'BASE': ob_cap = ob0
+ else: ob_cap = ob1
+ row = col.row(align=True)
+ row.prop_search(props, 'vertex_group_cap',
+ ob_cap, "vertex_groups")
+ row.prop(props, "invert_vertex_group_cap", text="",
+ toggle=True, icon='ARROW_LEFTRIGHT')
+ row = col.row(align=True)
+ row.prop(props, "vertex_group_cap_owner", expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ col.prop(props, "open_edges_crease", text="Crease")
+ col.prop(props, "cap_material_offset", text='Material Offset')
+ if props.warning_message_merge:
col.separator()
- row = col.row(align=True)
- row.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 rotate_face(Operator):
- bl_idname = "mesh.rotate_face"
- bl_label = "Rotate Faces"
- bl_description = "Rotate selected faces and update tessellated meshes"
+ col.label(text=props.warning_message_merge, icon='ERROR')
+
+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: return context.object.tissue.tissue_type == 'TESSELLATE'
+ except: return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ layout = self.layout
+ allow_shapekeys = not props.com_modifiers
+
+ if tessellated(ob):
+ ob0 = props.generator
+ 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="Component's Shape Keys cannot be used together with Component's 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:
+ return context.object.tissue.tissue_type == 'TESSELLATE'
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+
+ layout = self.layout
+ #layout.use_property_split = True
+ #layout.use_property_decorate = False # No animation.
+ allow_multi = False
+ allow_shapekeys = not props.com_modifiers
+ ob0 = props.generator
+ 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 Index")
+ #if props.bool_material_id and not props.component_mode == 'MATERIALS':
+ #col2 = row.column(align=True)
+ col2.prop(props, "material_id")
+ #if props.component_mode == 'MATERIALS':
+ # 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:
+ return context.object.tissue.tissue_type == 'TESSELLATE'
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ layout = self.layout
+ 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='', 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 = "Tissue Rotate Faces Right"
+ bl_description = "Rotate clockwise selected faces and update tessellated meshes"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
- return context.mode == 'EDIT_MESH'
+ 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 = bpy.context.active_object
+ 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)
+ bm.free()
+ 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
+
+ #override = {'object': o, 'mode': 'OBJECT', 'selected_objects': [o]}
+ if not o.tissue.bool_lock:
+ 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_flip(Operator):
+ bl_idname = "mesh.tissue_rotate_face_flip"
+ bl_label = "Tissue Rotate Faces Flip"
+ bl_description = "Fully rotate 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[:]
+ nrot = int(len(vs)/2)
+ vs2 = vs[-nrot:]+vs[:-nrot]
+ 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)
+ bm.free()
+ 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
+
+ #override = {'object': o, 'mode': 'OBJECT', 'selected_objects': [o]}
+ if not o.tissue.bool_lock:
+ 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 = "Tissue 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)
@@ -3005,18 +3168,619 @@ class rotate_face(Operator):
# trigger UI update
bmesh.update_edit_mesh(me)
+ bm.free()
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()]:
- bpy.context.view_layer.objects.active = o
- bpy.ops.object.update_tessellate()
+ context.view_layer.objects.active = o
+ if not o.tissue.bool_lock:
+ bpy.ops.object.tissue_update_tessellate()
o.select_set(False)
ob.select_set(True)
- bpy.context.view_layer.objects.active = ob
+ 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 + props['boundary_mat_offset']]
+ 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]
+ 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 + props['boundary_mat_offset'])
+ 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 + props['boundary_mat_offset']]
+ 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]
+ 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['fill_frame_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()
+ bm.free()
+ return new_ob
+
+def reduce_to_quads(ob, props):
+ '''
+ Convert an input object to a mesh with polygons that have maximum 4 vertices
+ '''
+ new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True)
+ me = new_ob.data
+
+ # Check if there are polygons with more than 4 sides
+ np_sides = get_attribute_numpy(me.polygons, 'loop_total')
+ mask = np_sides > 4
+ if not np.any(mask):
+ if props['boundary_mat_offset'] != 0 or props['boundary_variable_offset']:
+ bm=bmesh.new()
+ bm.from_mesh(me)
+ bm = offset_boundary_materials(
+ bm,
+ boundary_mat_offset = props['boundary_mat_offset'],
+ boundary_variable_offset = props['boundary_variable_offset'],
+ auto_rotate_boundary = props['auto_rotate_boundary'])
+ bm.to_mesh(me)
+ bm.free()
+ me.update()
+ return new_ob
+
+ # create bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ np_faces = np.array(bm.faces)
+ np_faces = np_faces[mask]
+
+ new_faces = []
+ for f in np_faces:
+ verts = list(f.verts)
+ while True:
+ n_verts = len(verts)
+ if n_verts < 3: break
+ elif n_verts == 3:
+ face_verts = [verts[-2], verts.pop(-1), verts.pop(0)]
+ else:
+ face_verts = [verts[-2], verts.pop(-1), verts.pop(0), verts[0]]
+ new_face = bm.faces.new(face_verts)
+ new_face.material_index = f.material_index
+ new_face.select = f.select
+ new_faces.append(new_face)
+
+ for f in np_faces: bm.faces.remove(f)
+
+ bm = offset_boundary_materials(
+ bm,
+ boundary_mat_offset = props['boundary_mat_offset'],
+ boundary_variable_offset = props['boundary_variable_offset'],
+ auto_rotate_boundary = props['auto_rotate_boundary'])
+
+ bm.to_mesh(me)
+ bm.free()
+ me.update()
+ return new_ob
+
+def convert_to_fan(ob, props, add_id_layer=False):
+ new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True)
+ bm = bmesh.new()
+ bm.from_mesh(new_ob.data)
+ if add_id_layer:
+ bm.faces.ensure_lookup_table()
+ lay = bm.faces.layers.int.new("id")
+ for i,f in enumerate(bm.faces): f[lay] = i
+ bmesh.ops.poke(bm, faces=bm.faces)#, quad_method, ngon_method)
+ bm = offset_boundary_materials(
+ bm,
+ boundary_mat_offset = props['boundary_mat_offset'],
+ boundary_variable_offset = props['boundary_variable_offset'],
+ auto_rotate_boundary = props['auto_rotate_boundary'])
+ bm.to_mesh(new_ob.data)
+ new_ob.data.update()
+ bm.free()
+ return new_ob
+
+def convert_to_triangles(ob, props):
+ new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True)
+ bm = bmesh.new()
+ bm.from_mesh(new_ob.data)
+ bmesh.ops.triangulate(bm, faces=bm.faces, quad_method='FIXED', ngon_method='BEAUTY')
+
+ bm = offset_boundary_materials(
+ bm,
+ boundary_mat_offset = props['boundary_mat_offset'],
+ boundary_variable_offset = props['boundary_variable_offset'],
+ auto_rotate_boundary = props['auto_rotate_boundary'])
+
+ bm.to_mesh(new_ob.data)
+ new_ob.data.update()
+ bm.free()
+ return new_ob
+
+def merge_components(ob, props, use_bmesh):
+
+ if not use_bmesh and False:
+ skip = True
+ ob.active_shape_key_index = 1
+ if ob.data.shape_keys != None:
+ for sk in ob.data.shape_keys.key_blocks:
+ if skip:
+ skip = False
+ continue
+ sk.mute = True
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if ob.data.shape_keys != None:
+ for sk in ob.data.shape_keys.key_blocks:
+ sk.mute = False
+ ob.data.update()
+
+ 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=props.merge_thres, use_unselected=False)
+
+ if props.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 props.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 props.close_mesh == 'CAP':
+ if props.open_edges_crease != 0:
+ bpy.ops.transform.edge_crease(value=props.open_edges_crease)
+ bpy.ops.mesh.edge_face_add()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for f in ob.data.polygons:
+ if f.select: f.material_index += props.cap_material_offset
+ elif props.close_mesh == 'BRIDGE':
+ try:
+ if props.bridge_edges_crease != 0:
+ bpy.ops.transform.edge_crease(value=props.bridge_edges_crease)
+ bpy.ops.mesh.bridge_edge_loops(
+ type='PAIRS',
+ number_cuts=props.bridge_cuts,
+ interpolation='SURFACE',
+ smoothness=props.bridge_smoothness)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for f in ob.data.polygons:
+ if f.select: f.material_index += props.bridge_material_offset
+ except: pass
+ elif props.close_mesh == 'BRIDGE_CAP':
+ # BRIDGE
+ try:
+ bpy.ops.object.mode_set(mode='OBJECT')
+ vg = ob.vertex_groups[props.vertex_group_bridge]
+ weight = get_weight_numpy(vg, len(ob.data.vertices))
+ for e in ob.data.edges:
+ if weight[e.vertices[0]]*weight[e.vertices[1]] < 1:
+ e.select = False
+ bpy.ops.object.mode_set(mode='EDIT')
+ if props.bridge_edges_crease != 0:
+ bpy.ops.transform.edge_crease(value=props.bridge_edges_crease)
+ bpy.ops.mesh.bridge_edge_loops(
+ type='PAIRS',
+ number_cuts=props.bridge_cuts,
+ interpolation='SURFACE',
+ smoothness=props.bridge_smoothness)
+ for f in ob.data.polygons:
+ if f.select: f.material_index += props.bridge_material_offset
+ bpy.ops.mesh.select_all(action='DESELECT')
+ 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)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ except: pass
+ # CAP
+ try:
+ bpy.ops.object.mode_set(mode='OBJECT')
+ vg = ob.vertex_groups[props.vertex_group_cap]
+ weight = get_weight_numpy(vg, len(ob.data.vertices))
+ for e in ob.data.edges:
+ if weight[e.vertices[0]]*weight[e.vertices[1]] < 1:
+ e.select = False
+ bpy.ops.object.mode_set(mode='EDIT')
+ if props.open_edges_crease != 0:
+ bpy.ops.transform.edge_crease(value=props.open_edges_crease)
+ bpy.ops.mesh.edge_face_add()
+ for f in ob.data.polygons:
+ if f.select: f.material_index += props.cap_material_offset
+ bpy.ops.object.mode_set(mode='OBJECT')
+ except: pass
+ else:
+ bm = bmesh.new()
+ bm.from_mesh(ob.data.copy())
+ if props.merge_open_edges_only:
+ boundary_verts = [v for v in bm.verts if v.is_boundary or v.is_wire]
+ else:
+ boundary_verts = bm.verts
+ bmesh.ops.remove_doubles(bm, verts=boundary_verts, dist=props.merge_thres)
+
+ if props.bool_dissolve_seams:
+ seam_edges = [e for e in bm.edges if e.seam]
+ bmesh.ops.dissolve_edges(bm, edges=seam_edges, use_verts=True, use_face_split=False)
+ if props.close_mesh != 'NONE':
+ bm.edges.ensure_lookup_table()
+ # set crease
+ crease_layer = bm.edges.layers.crease.verify()
+ boundary_edges = [e for e in bm.edges if e.is_boundary or e.is_wire]
+ if props.close_mesh == 'BRIDGE':
+ try:
+ for e in boundary_edges:
+ e[crease_layer] = props.bridge_edges_crease
+ closed = bmesh.ops.bridge_loops(bm, edges=boundary_edges, use_pairs=True)
+ for f in closed['faces']: f.material_index += props.bridge_material_offset
+ except:
+ bm.to_mesh(ob.data)
+ return 'bridge_error'
+ elif props.close_mesh == 'CAP':
+ for e in boundary_edges:
+ e[crease_layer] = props.open_edges_crease
+ closed = bmesh.ops.holes_fill(bm, edges=boundary_edges)
+ for f in closed['faces']: f.material_index += props.cap_material_offset
+ elif props.close_mesh == 'BRIDGE_CAP':
+ # BRIDGE
+ dvert_lay = bm.verts.layers.deform.active
+ try:
+ dvert_lay = bm.verts.layers.deform.active
+ group_index = ob.vertex_groups[props.vertex_group_bridge].index
+ bw = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
+ if props.invert_vertex_group_bridge: bw = 1-bw
+ bridge_edges = [e for e in boundary_edges if bw[e.verts[0].index]*bw[e.verts[1].index] >= 1]
+ for e in bridge_edges:
+ e[crease_layer] = props.bridge_edges_crease
+ closed = bmesh.ops.bridge_loops(bm, edges=bridge_edges, use_pairs=True)
+ for f in closed['faces']: f.material_index += props.bridge_material_offset
+ boundary_edges = [e for e in bm.edges if e.is_boundary]
+ except: pass
+ # CAP
+ try:
+ dvert_lay = bm.verts.layers.deform.active
+ group_index = ob.vertex_groups[props.vertex_group_cap].index
+ bw = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
+ if props.invert_vertex_group_cap: bw = 1-bw
+ cap_edges = [e for e in boundary_edges if bw[e.verts[0].index]*bw[e.verts[1].index] >= 1]
+ for e in cap_edges:
+ e[crease_layer] = props.open_edges_crease
+ closed = bmesh.ops.holes_fill(bm, edges=cap_edges)
+ for f in closed['faces']: f.material_index += props.cap_material_offset
+ except: pass
+ bm.to_mesh(ob.data)
+
+class tissue_render_animation(Operator):
+ bl_idname = "render.tissue_render_animation"
+ bl_label = "Tissue Render Animation"
+ bl_description = "Turnaround for issues related to animatable tessellation"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ start = True
+ path = ""
+ timer = None
+
+ def invoke(self, context, event):
+ self.start = True
+ return context.window_manager.invoke_props_dialog(self)
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.label(text="All frames will be rendered in the background.")
+ col.label(text="Press ESC to abort.")
+
+ def modal(self, context, event):
+ '''
+ # check render format
+ format = context.scene.render.image_settings.file_format
+ if format in ('FFMPEG', 'AVI_RAW', 'AVI_JPEG'):
+ message = "Please use an image format as render output"
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+ '''
+ remove_tessellate_handler()
+ scene = context.scene
+ if event.type == 'ESC' or scene.frame_current >= scene.frame_end:
+ scene.render.filepath = self.path
+ # set again the handler
+ blender_handlers = bpy.app.handlers.frame_change_post
+ blender_handlers.append(anim_tessellate)
+ blender_handlers.append(reaction_diffusion_scene)
+ context.window_manager.event_timer_remove(self.timer)
+ if event.type == 'ESC':
+ print("Tissue: Render Animation aborted.")
+ return {'CANCELLED'}
+ else:
+ print("Tissue: Render Animation completed!")
+ return {'FINISHED'}
+ else:
+ self.execute(context)
+ return {'RUNNING_MODAL'}
+
+ def execute(self, context):
+ # check output format
+ format = context.scene.render.image_settings.file_format
+ if format in ('FFMPEG', 'AVI_RAW', 'AVI_JPEG'):
+ message = "Please use an image format as render output"
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+
+ scene = context.scene
+ if self.start:
+ remove_tessellate_handler()
+ reaction_diffusion_remove_handler(self, context)
+ scene = context.scene
+ scene.frame_current = scene.frame_start
+ self.path = scene.render.filepath
+ context.window_manager.modal_handler_add(self)
+ self.timer = context.window_manager.event_timer_add(0.1, window = context.window)
+ self.start = False
+ else:
+ scene.frame_current += scene.frame_step
+ anim_tessellate(scene)
+ reaction_diffusion_scene(scene)
+ scene.render.filepath = "{}{:04d}".format(self.path,scene.frame_current)
+ bpy.ops.render.render(write_still=True)
+ return {'RUNNING_MODAL'}
+
+def offset_boundary_materials(bm, boundary_mat_offset=0, boundary_variable_offset=False, auto_rotate_boundary=False):
+ if boundary_mat_offset != 0 or boundary_variable_offset:
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+ bound_faces = []
+ bound_verts_value = [0]*len(bm.faces)
+ bound_edges_value = [0]*len(bm.faces)
+ shift_faces = [0]*len(bm.faces)
+ # store boundaries informations
+ for v in bm.verts:
+ if v.is_boundary:
+ for f in v.link_faces:
+ bound_faces.append(f)
+ bound_verts_value[f.index] += 1
+ for e in bm.edges:
+ if e.is_boundary:
+ for f in e.link_faces:
+ bound_edges_value[f.index] += 1
+ # Set material index offset
+ if boundary_variable_offset:
+ for f in bm.faces:
+ if bound_verts_value[f.index] > 0:
+ f.material_index += boundary_mat_offset
+ if bound_verts_value[f.index] == bound_edges_value[f.index]+1:
+ f.material_index += bound_verts_value[f.index]
+ else:
+ for f in bm.faces:
+ if bound_edges_value[f.index] > 0:
+ f.material_index += boundary_mat_offset
+ if auto_rotate_boundary:
+ rotate_faces = []
+ new_verts_all = []
+ for f in bm.faces:
+ val = bound_verts_value[f.index]
+ val2 = bound_edges_value[f.index]
+ if val > 0 and val2 == val-1 and val < len(f.verts):
+ pattern = [v.is_boundary for v in f.verts]
+ new_verts = [v for v in f.verts]
+ while True:
+ mult = 1
+ _pattern = pattern[val//2+1:] + pattern[:val//2+1]
+ for p in _pattern[-val:]: mult*=p
+ if mult == 1: break
+ pattern = pattern[-1:] + pattern[:-1]
+ new_verts = new_verts[-1:] + new_verts[:-1]
+ new_verts_all.append(new_verts)
+ rotate_faces.append(f)
+ if val == 4 and val2 == 3:
+ pattern = [e.is_boundary for e in f.edges]
+ new_verts = [v for v in f.verts]
+ while True:
+ mult = 1
+ _pattern = pattern[val2//2+1:] + pattern[:val2//2+1]
+ for p in _pattern[-val2:]: mult*=p
+ if mult == 1: break
+ pattern = pattern[-1:] + pattern[:-1]
+ new_verts = new_verts[-1:] + new_verts[:-1]
+ new_verts_all.append(new_verts)
+ rotate_faces.append(f)
+ for f, new_verts in zip(rotate_faces, new_verts_all):
+ material_index = f.material_index
+ bm.faces.remove(f)
+ f2 = bm.faces.new(new_verts)
+ f2.select = True
+ f2.material_index = material_index
+ bm.normal_update()
+ return bm