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:
authormeta-androcto <meta.androcto1@gmail.com>2019-07-01 14:06:16 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-07-01 14:06:16 +0300
commit498d912a9d5854c0cfc1f54ed8b4216d442b89f1 (patch)
tree66e565094b4eec07c2e05c9106724ee5e4649454 /mesh_tissue/tessellate_numpy.py
parentfffaf5d2759d38d4166f608eab8871fcd59a7e11 (diff)
mesh_tissue: initial update 2.80
Diffstat (limited to 'mesh_tissue/tessellate_numpy.py')
-rw-r--r--mesh_tissue/tessellate_numpy.py3589
1 files changed, 2515 insertions, 1074 deletions
diff --git a/mesh_tissue/tessellate_numpy.py b/mesh_tissue/tessellate_numpy.py
index e5f6db5c..9526d115 100644
--- a/mesh_tissue/tessellate_numpy.py
+++ b/mesh_tissue/tessellate_numpy.py
@@ -42,78 +42,548 @@ from bpy.props import (
FloatProperty,
IntProperty,
StringProperty,
+ PointerProperty
)
from mathutils import Vector
import numpy as np
from math import sqrt
-import random
-
-
-def lerp(a, b, t):
- return a + (b - a) * t
-
-
-def lerp2(v1, v2, v3, v4, v):
- v12 = v1 + (v2 - v1) * v.x
- v43 = v4 + (v3 - v4) * v.x
- return v12 + (v43 - v12) * v.y
-
+import random, time
+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
+ 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 lerp3(v1, v2, v3, v4, v):
- loc = lerp2(v1.co, v2.co, v3.co, v4.co, v)
- nor = lerp2(v1.normal, v2.normal, v3.normal, v4.normal, v)
- nor.normalize()
- return loc + nor * v.z
+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",
+ 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.\nUsefull for "
+ + "for branching systems. Dangerous!",
+ update = anim_tessellate_active
+ )
+ bool_combine : BoolProperty(
+ name="Combine unused",
+ default=False,
+ description="Combine the generated geometry with unused faces",
+ update = anim_tessellate_active
+ )
+ bool_advanced : BoolProperty(
+ name="Advanced Settings",
+ default=False,
+ description="Show more settings"
+ )
+ normals_mode : EnumProperty(
+ items=(
+ ('VERTS', '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 tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
- scale_mode, rotation_mode, rand_seed, fill_mode,
- bool_vertex_group, bool_selection, bool_shapekeys):
+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)
- old_me0 = ob0.data # Store generator mesh
-
- if gen_modifiers or com_modifiers:
- depsgraph = bpy.context.evaluated_depsgraph_get()
- else:
- depsgraph = None
- me0_owner = None
- if gen_modifiers: # Apply generator modifiers
- me0_owner = ob0.evaluated_get(depsgraph)
- me0 = me0_owner.to_mesh()
- else:
- me0 = ob0.data
- ob0.data = me0
- base_polygons = []
+ ob0 = convert_object_to_mesh(_ob0)
+ me0 = _ob0.data
# Check if zero faces are selected
- if bool_selection:
- for p in ob0.data.polygons:
- if p.select:
- base_polygons.append(p)
- else:
- base_polygons = ob0.data.polygons
- if len(base_polygons) == 0:
- return 0
+ 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
+
+ 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
- # Apply component modifiers
- me1_owner = ob1.evaluated_get(depsgraph)
- if com_modifiers:
- me1 = me1_owner.to_mesh()
- else:
- me1 = ob1.data
+ if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
- verts0 = me0.vertices # Collect generator vertices
+ # 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)
- # n_edges = len(me1.edges)
- # n_faces = len(me1.polygons)
-
- # Component transformations
- # loc = ob1.location
- # dim = ob1.dimensions
- # scale = ob1.scale
# Create empty lists
new_verts = []
@@ -145,112 +615,763 @@ def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
# adaptive XY
verts1 = []
for v in me1.vertices:
- if mode == "ADAPTIVE":
+ 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
- else:
+ elif mode == 'LOCAL':
vert = v.co.xyz
- vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
- verts1.append(vert)
+ 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]
+
+ patch_faces = 4**levels
+ sides = int(sqrt(patch_faces))
+ sides0 = sides-2
+ patch_faces0 = int((sides-2)**2)
+ n_patches = int(len(me0.polygons)/patch_faces)
+ if len(me0.polygons)%patch_faces != 0:
+ #ob0.data = old_me0
+ return "topology_error"
+
+ new_verts = []
+ new_edges = []
+ new_faces = []
+
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ new_patch = None
+
+ # All vertex group
+ if bool_vertex_group:
+ try:
+ weight = []
+ for vg in ob0.vertex_groups:
+ _weight = []
+ for v in me0.vertices:
+ try:
+ _weight.append(vg.weight(v.index))
+ except:
+ _weight.append(0)
+ weight.append(_weight)
+ except:
+ bool_vertex_group = False
+
+ # Adaptive Z
+ if scale_mode == 'ADAPTIVE':
+ 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))
+
+ random.seed(rand_seed)
+ bool_correct = False
+
+ _faces = [[[0] for ii in range(sides)] for jj in range(sides)]
+ _verts = [[[0] for ii in range(sides+1)] for jj in range(sides+1)]
+
+ for i in range(n_patches):
+ poly = me0.polygons[i*patch_faces]
+ if bool_selection and not poly.select: continue
+ if bool_material_id and not poly.material_index == material_id: continue
+
+ bool_correct = True
+ new_patch = bpy.data.objects.new("patch", me1.copy())
+ bpy.context.collection.objects.link(new_patch)
+
+ new_patch.select_set(True)
+ bpy.context.view_layer.objects.active = new_patch
+
+ for area in bpy.context.screen.areas:
+ for space in area.spaces:
+ try: new_patch.local_view_set(space, True)
+ except: pass
+
+ # Vertex Group
+ if bool_vertex_group:
+ for vg in ob0.vertex_groups:
+ new_patch.vertex_groups.new(name=vg.name)
+
+ # find patch faces
+ faces = _faces.copy()
+ verts = _verts.copy()
+ shift1 = sides
+ shift2 = sides*2-1
+ shift3 = sides*3-2
+ for j in range(patch_faces):
+ if j < patch_faces0:
+ if levels == 0:
+ u = j%sides0
+ v = j//sides0
+ else:
+ u = j%sides0+1
+ v = j//sides0+1
+ elif j < patch_faces0 + shift1:
+ u = j-patch_faces0
+ v = 0
+ elif j < patch_faces0 + shift2:
+ u = sides-1
+ v = j-(patch_faces0 + sides)+1
+ elif j < patch_faces0 + shift3:
+ jj = j-(patch_faces0 + shift2)
+ u = sides-jj-2
+ v = sides-1
+ else:
+ jj = j-(patch_faces0 + shift3)
+ u = 0
+ v = sides-jj-2
+ face = me0.polygons[j+i*patch_faces]
+ faces[u][v] = face
+ verts[u][v] = verts0[face.vertices[0]]
+ if u == sides-1:
+ verts[sides][v] = verts0[face.vertices[1]]
+ if v == sides-1:
+ verts[u][sides] = verts0[face.vertices[3]]
+ if u == v == sides-1:
+ verts[sides][sides] = verts0[face.vertices[2]]
+
+ # Random rotation
+ if rotation_mode == 'RANDOM':
+ 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)]
+
+ # 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")
+
+ if bool_shapekeys:
+ for sk in ob1.data.shape_keys.key_blocks:
+ 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
+
+ # grid coordinates
+ u = int(sk_vert[0]//step)
+ v = int(sk_vert[1]//step)
+ u1 = min(u+1, sides)
+ v1 = min(v+1, sides)
+ if mode != 'BOUNDS':
+ if u > sides-1:
+ u = sides-1
+ u1 = sides
+ if u < 0:
+ u = 0
+ u1 = 1
+ if v > sides-1:
+ v = sides-1
+ v1 = sides
+ if v < 0:
+ v = 0
+ v1 = 1
+ v00 = verts[u][v]
+ v10 = verts[u1][v]
+ v01 = verts[u][v1]
+ v11 = verts[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
+ 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
- # component vertices
- vs1 = np.array([v for v in verts1]).reshape(len(verts1), 3, 1)
- vx = vs1[:, 0]
- vy = vs1[:, 1]
- vz = vs1[:, 2]
+ 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
+ try:
+ #new_ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y += 1
+ try:
+ #new_ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y += 1
+ except: pass
+
+
+ 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 = [[i for i in e.vertices] for e in me1.edges if e.is_loose]
+ 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
- shapekeys = []
- do_shapekeys = False
- if me1.shape_keys is not None and bool_shapekeys:
- if len(me1.shape_keys.key_blocks) > 1:
+ 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
- # Read active key
- active_key = ob1.active_shape_key_index
- if active_key == 0:
- active_key = 1
+ if basis:
+ basis = False
+ continue
+
+ # Apply component modifiers
+ if com_modifiers:
+ sk_ob = convert_object_to_mesh(_ob1)
+ sk_data = sk_ob.data
+ source = sk_data.vertices
+ else:
+ source = sk.data
- for v in me1.shape_keys.key_blocks[active_key].data:
- if mode == "ADAPTIVE":
+ 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
- else:
+ 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[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \
- zscale
+ #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 = key1[:, 0]
- vy_key = key1[:, 1]
- vz_key = key1[:, 2]
+ vx_key.append(key1[:, 0])
+ vy_key.append(key1[:, 1])
+ vz_key.append(key1[:, 2])
+ #sk_np.append([])
- # Active vertex group
+ # All vertex group
if bool_vertex_group:
try:
weight = []
- group_index = ob0.vertex_groups.active_index
- active_vertex_group = ob0.vertex_groups[group_index]
- for v in me0.vertices:
- try:
- weight.append(active_vertex_group.weight(v.index))
- except:
- weight.append(0)
+ vertex_groups = ob0.vertex_groups
+ for vg in vertex_groups:
+ _weight = []
+ for v in me0.vertices:
+ try:
+ _weight.append(vg.weight(v.index))
+ #print(vg.weight(v.index))
+ #_weight.append(v.groups[0])
+ 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:
- # if bool_selection and not p.select: continue
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:
- center_weight = sum([weight[i] for i in p.vertices]) / len(p.vertices)
- weight.append(center_weight)
+ for w in weight:
+ center_weight = sum([w[i] for i in p.vertices]) / len(p.vertices)
+ w.append(center_weight)
for i in range(len(p.vertices)):
fan_polygons.append((p.vertices[i],
p.vertices[(i + 1) % len(p.vertices)],
last_vert, last_vert))
- # if bool_selection: selected_faces.append(p.select)
+
+ if bool_material_id: fan_material.append(p.material_index)
+ if bool_selection: fan_select.append(p.select)
+ 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
+ me0 = fan_me.copy()
+ bpy.data.meshes.remove(fan_me)
verts0 = me0.vertices
base_polygons = me0.polygons
- """
- for i in range(len(selected_faces)):
- fan_me.polygons[i].select = selected_faces[i]
- """
+ 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 = []
@@ -258,211 +1379,333 @@ def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
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 i in shifted_vertices:
- try:
- ws0.append(weight[i])
- except:
- ws0.append(0)
-
- ws0 = np.array(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' and len(ob0.data.uv_layers) > 0 and \
- fill_mode != 'FAN':
- i = p.index
- 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 +
+ 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)
- 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]
+ if len(p.vertices) > 3:
+ v03 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count + 3].uv)
else:
- vertUV = p.vertices[3:] + p.vertices[:3]
- else:
- if(dot1203 < 0):
- vertUV = p.vertices[:]
+ v03 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count].uv)
+ v1203 = v03 - v12
+ v1203.normalize()
+
+ vertUV = []
+ dot1203 = v1203.x
+ dot0132 = v0132.x
+ if(abs(dot1203) < abs(dot0132)):
+ if (dot0132 > 0):
+ vertUV = p.vertices[1:] + p.vertices[:1]
+ else:
+ vertUV = p.vertices[3:] + p.vertices[:3]
else:
- 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 i in vertUV:
- try:
- ws0.append(weight[i])
- except:
- ws0.append(0)
- ws0 = np.array(ws0)
-
- count += len(p.vertices)
+ 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
- else:
+ 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 i in p.vertices:
- try:
- ws0.append(weight[i])
- except:
- ws0.append(0)
- ws0 = np.array(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]
- # considering only 4 vertices
- vs0 = np.array((vs0[0], vs0[1], vs0[2], vs0[-1]))
- nvs0 = np.array((nvs0[0], nvs0[1], nvs0[2], nvs0[-1]))
-
- # 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
- nv0 = nvs0[0] + (nvs0[1] - nvs0[0]) * vx
- nv1 = nvs0[3] + (nvs0[2] - nvs0[3]) * vx
- nv2 = nv0 + (nv1 - nv0) * vy
# vertex z to normal
- v3 = v2 + nv2 * vz * (sqrt(p.area) if scale_mode == "ADAPTIVE" else 1)
+ 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
if bool_vertex_group:
- ws0 = np.array((ws0[0], ws0[1], ws0[2], ws0[-1]))
- # Interpolate vertex weight
- w0 = ws0[0] + (ws0[1] - ws0[0]) * vx
- w1 = ws0[3] + (ws0[2] - ws0[3]) * vx
- w2 = w0 + (w1 - w0) * vy
-
- # Shapekeys
- if do_shapekeys:
- # remapped vertex coordinates
- v0 = vs0[0] + (vs0[1] - vs0[0]) * vx_key
- v1 = vs0[3] + (vs0[2] - vs0[3]) * vx_key
- v2 = v0 + (v1 - v0) * vy_key
- # remapped vertex normal
- nv0 = nvs0[0] + (nvs0[1] - nvs0[0]) * vx_key
- nv1 = nvs0[3] + (nvs0[2] - nvs0[3]) * vx_key
- nv2 = nv0 + (nv1 - nv0) * vy_key
- # vertex z to normal
- v3_key = v2 + nv2 * vz_key * (sqrt(p.area) if
- scale_mode == "ADAPTIVE" else 1)
- v3 = v3 + (v3_key - v3) * w2
-
- if j == 0:
- new_verts_np = v3
- if bool_vertex_group:
- new_vertex_group_np = w2
- else:
- # Appending vertices
- new_verts_np = np.concatenate((new_verts_np, v3), axis=0)
- # Appending vertex group
- if bool_vertex_group:
- new_vertex_group_np = np.concatenate((new_vertex_group_np, w2),
- axis=0)
- # Appending faces
- for p in fs1:
- new_faces.append([i + n_verts * j for i in p])
- # Appending edges
- for e in es1:
- new_edges.append([i + n_verts * j for i in e])
+ 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))
+
+ 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, new_faces)
+ 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:
- new_ob.vertex_groups.new(name="generator_group")
- for i in range(len(new_vertex_group_np)):
- new_ob.vertex_groups["generator_group"].add([i],
- new_vertex_group_np[i],
- "ADD")
- ob0.data = old_me0
+ 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)
- if me0_owner:
- me0_owner.to_mesh_clear()
+ # 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)
- if me1_owner:
- me1_owner.to_mesh_clear()
+ # MATERIALS
+ for slot in ob1.material_slots: new_ob.data.materials.append(slot.material)
- return new_ob
+ 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() ###
-def store_parameters(operator, ob):
- ob.tissue_tessellate.generator = operator.generator
- ob.tissue_tessellate.component = 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
- return ob
-
+ try:
+ bpy.data.objects.remove(new_ob1)
+ except: pass
-class tissue_tessellate_prop(PropertyGroup):
- generator: StringProperty()
- component: StringProperty()
- offset: FloatProperty()
- zscale: FloatProperty(default=1)
- merge: BoolProperty()
- merge_thres: FloatProperty()
- gen_modifiers: BoolProperty()
- com_modifiers: BoolProperty()
- mode: StringProperty()
- rotation_mode: StringProperty()
- scale_mode: StringProperty()
- fill_mode: StringProperty()
- bool_random: BoolProperty()
- random_seed: IntProperty()
- vertexgroup: StringProperty()
- bool_vertex_group: BoolProperty()
- bool_selection: BoolProperty()
- bool_shapekeys: BoolProperty()
+ bpy.data.objects.remove(ob0)
+ bpy.data.meshes.remove(me0)
+ bpy.data.objects.remove(ob1)
+ bpy.data.meshes.remove(me1)
+ return new_ob
class tessellate(Operator):
@@ -472,179 +1715,277 @@ class tessellate(Operator):
"faces, adapting the shape to the different faces")
bl_options = {'REGISTER', 'UNDO'}
- object_name: StringProperty(
+
+ object_name : StringProperty(
name="",
description="Name of the generated object"
)
- zscale: FloatProperty(
+ zscale : FloatProperty(
name="Scale",
default=1,
soft_min=0,
soft_max=10,
description="Scale factor for the component thickness"
)
- scale_mode: EnumProperty(
- items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Proportional", "")),
- default='CONSTANT',
+ scale_mode : EnumProperty(
+ items=(
+ ('CONSTANT', "Constant", "Uniform thickness"),
+ ('ADAPTIVE', "Proportional", "Preserve component's proportions")
+ ),
+ default='ADAPTIVE',
name="Z-Scale according to faces size"
)
- offset: FloatProperty(
+ offset : FloatProperty(
name="Surface Offset",
- default=0,
+ default=1,
min=-1, max=1,
soft_min=-1,
soft_max=1,
description="Surface offset"
)
- mode: EnumProperty(
- items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Adaptive", "")),
- default='ADAPTIVE',
+ mode : EnumProperty(
+ items=(
+ ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
+ ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
+ ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
+ default='BOUNDS',
name="Component Mode"
)
- rotation_mode: EnumProperty(
- items=(('RANDOM', "Random", ""),
- ('UV', "Active UV", ""),
- ('DEFAULT', "Default", "")),
+ rotation_mode : EnumProperty(
+ items=(('RANDOM', "Random", "Random faces rotation"),
+ ('UV', "Active UV", "Face rotation is based on UV coordinates"),
+ ('DEFAULT', "Default", "Default rotation")),
default='DEFAULT',
name="Component Rotation"
)
- fill_mode: EnumProperty(
- items=(('QUAD', "Quad", ""), ('FAN', "Fan", "")),
+ 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"
)
- gen_modifiers: BoolProperty(
+ combine_mode : EnumProperty(
+ items=(
+ ('LAST', 'Last', 'Show only the last iteration'),
+ ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
+ ('ALL', 'All', 'Combine the result of all iterations')),
+ default='LAST',
+ name="Combine Mode",
+ )
+ gen_modifiers : BoolProperty(
name="Generator Modifiers",
default=False,
- description="Apply modifiers to base object"
+ description="Apply Modifiers and Shape Keys to the base object"
)
- com_modifiers: BoolProperty(
+ com_modifiers : BoolProperty(
name="Component Modifiers",
default=False,
- description="Apply modifiers to component object"
+ description="Apply Modifiers and Shape Keys to the component object"
)
- merge: BoolProperty(
+ merge : BoolProperty(
name="Merge",
default=False,
description="Merge vertices in adjacent duplicates"
)
- merge_thres: FloatProperty(
+ merge_thres : FloatProperty(
name="Distance",
default=0.001,
soft_min=0,
soft_max=10,
description="Limit below which to merge vertices"
)
- generator: StringProperty(
- name="",
- description="Base object for the tessellation"
- )
- component: StringProperty(
- name="",
- description="Component object for the tessellation"
- )
- bool_random: BoolProperty(
+ bool_random : BoolProperty(
name="Randomize",
default=False,
description="Randomize component rotation"
)
- random_seed: IntProperty(
+ random_seed : IntProperty(
name="Seed",
default=0,
soft_min=0,
soft_max=10,
description="Random seed"
)
- bool_vertex_group: BoolProperty(
- name="Map Vertex Group",
+ bool_vertex_group : BoolProperty(
+ name="Map Vertex Groups",
default=False,
- description="Map the active "
- "Vertex Group from the Base object to generated geometry"
+ description="Transfer all Vertex Groups from Base object"
)
- bool_selection: BoolProperty(
+ bool_selection : BoolProperty(
name="On selected Faces",
default=False,
description="Create Tessellation only on selected faces"
)
- bool_shapekeys: BoolProperty(
+ bool_shapekeys : BoolProperty(
name="Use Shape Keys",
default=False,
- description="Use component's active Shape Key according to "
- "active Vertex Group of the base object"
+ description="Transfer Component's Shape Keys. If the name of Vertex "
+ "Groups and Shape Keys are the same, they will be "
+ "automatically combined"
)
- working_on = ""
-
- @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()
+ bool_smooth : BoolProperty(
+ name="Smooth Shading",
+ default=False,
+ description="Output faces with smooth shading rather than flat shaded"
+ )
+ bool_materials : BoolProperty(
+ name="Transfer Materials",
+ default=True,
+ description="Preserve component's materials"
+ )
+ generator : StringProperty(
+ name="",
+ description="Base object for the tessellation",
+ default = ""
+ )
+ component : StringProperty(
+ name="",
+ description="Component object for the tessellation",
+ default = ""
+ )
+ bool_material_id : BoolProperty(
+ name="Tessellation on Material ID",
+ default=False,
+ description="Apply the component only on the selected Material"
+ )
+ bool_dissolve_seams : BoolProperty(
+ name="Dissolve Seams",
+ default=False,
+ description="Dissolve all seam edges"
+ )
+ material_id : IntProperty(
+ name="Material ID",
+ default=0,
+ min=0,
+ description="Material ID"
+ )
+ iterations : IntProperty(
+ name="Iterations",
+ default=1,
+ min=1,
+ soft_max=5,
+ description="Automatically repeat the Tessellation using the "
+ + "generated geometry as new base object.\nUsefull for "
+ + "for branching systems. Dangerous!"
+ )
+ bool_combine : BoolProperty(
+ name="Combine unused",
+ default=False,
+ description="Combine the generated geometry with unused faces"
+ )
+ bool_advanced : BoolProperty(
+ name="Advanced Settings",
+ default=False,
+ description="Show more settings"
+ )
+ normals_mode : EnumProperty(
+ items=(
+ ('VERTS', 'Along Normals', 'Consistent direction based on vertices normal'),
+ ('FACES', 'Individual Faces', 'Based on individual faces normal')),
+ default='VERTS',
+ name="Direction"
+ )
+ bool_multi_components : BoolProperty(
+ name="Multi Components",
+ default=False,
+ description="Combine different components according to materials name"
+ )
+ bounds_x : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default X coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
+ default='EXTEND',
+ name="Bounds X",
+ )
+ bounds_y : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default Y coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
+ default='EXTEND',
+ name="Bounds Y",
+ )
+ cap_faces : BoolProperty(
+ name="Cap Holes",
+ default=False,
+ description="Cap open edges loops"
+ )
+ open_edges_crease : FloatProperty(
+ name="Open Edges Crease",
+ default=0,
+ min=0,
+ max=1,
+ description="Automatically set crease for open edges"
+ )
+ working_on : ""
def draw(self, context):
+ allowed_obj = ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
+ '''
try:
bool_working = self.working_on == self.object_name and \
self.working_on != ""
except:
bool_working = False
+ '''
+
+ bool_working = False
+ bool_allowed = False
+ ob0 = None
+ ob1 = None
sel = bpy.context.selected_objects
+ if len(sel) == 1:
+ try:
+ ob0 = sel[0].tissue_tessellate.generator
+ ob1 = sel[0].tissue_tessellate.component
+ self.generator = ob0.name
+ self.component = ob1.name
+ bool_working = True
+ bool_allowed = True
+ except:
+ pass
- bool_meshes = False
if len(sel) == 2:
- bool_meshes = True
+ bool_allowed = True
for o in sel:
- if o.type != 'MESH':
- bool_meshes = False
+ if o.type not in allowed_obj:
+ bool_allowed = False
if len(sel) != 2 and not bool_working:
layout = self.layout
layout.label(icon='INFO')
layout.label(text="Please, select two different objects")
layout.label(text="Select first the Component object, then select")
- layout.label(text="the Base mesh.")
- elif not bool_meshes and not bool_working:
+ layout.label(text="the Base object.")
+ elif not bool_allowed and not bool_working:
layout = self.layout
layout.label(icon='INFO')
- layout.label(text="Please, select two Mesh objects")
+ layout.label(text="Only Mesh, Curve, Surface or Text objects are allowed")
else:
- try:
- ob0 = bpy.data.Objects[self.generator]
- except:
+ if ob0 == ob1 == None:
ob0 = bpy.context.active_object
self.generator = ob0.name
-
- for o in sel:
- if (o.name == ob0.name or o.type != 'MESH'):
- continue
- else:
- ob1 = o
- self.component = o.name
- self.no_component = False
- break
-
- # Checks for Tool Shelf panel, it lost the original Selection
- if bpy.context.active_object.name == self.object_name:
- # checks if the objects were deleted
- if self.check_gen_comp(
- bpy.context.active_object.tissue_tessellate.component):
- ob1 = bpy.data.objects[
- bpy.context.active_object.tissue_tessellate.component
- ]
- self.component = ob1.name
-
- if self.check_gen_comp(
- bpy.context.active_object.tissue_tessellate.generator):
- ob0 = bpy.data.objects[
- bpy.context.active_object.tissue_tessellate.generator
- ]
- self.generator = ob0.name
- self.no_component = False
+ for o in sel:
+ if o != ob0:
+ ob1 = o
+ self.component = o.name
+ self.no_component = False
+ break
# new object name
if self.object_name == "":
if self.generator == "":
self.object_name = "Tessellation"
else:
- self.object_name = self.generator + "_Tessellation"
+ #self.object_name = self.generator + "_Tessellation"
+ self.object_name = "Tessellation"
layout = self.layout
# Base and Component
@@ -652,100 +1993,54 @@ class tessellate(Operator):
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")
-
- if not self.check_gen_comp(self.generator) or \
- len(bpy.data.objects[self.generator].modifiers) == 0:
+ 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
- col2 = row.column(align=True)
- col2.prop(self, "com_modifiers", text="Use Modifiers")
-
- if not self.check_gen_comp(self.component) or \
- len(bpy.data.objects[self.component].modifiers) == 0:
- col2.enabled = False
- self.com_modifiers = False
- # On selected faces
- row = col.row(align=True)
- row.prop(self, "bool_selection", text="On selected Faces")
- col.separator()
-
- # General
- col = layout.column(align=True)
- col.label(text="New Object Name:")
- col.prop(self, "object_name")
-
- # Count number of faces
- if self.gen_modifiers or self.com_modifiers:
- depsgraph = context.evaluated_depsgraph_get()
- else:
- depsgraph = None
+ # Component Modifiers
+ row.separator()
+ col3 = row.column(align=True)
+ col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
+ component = bpy.data.objects[self.component]
try:
- polygons = 0
- me_temp_owner = None
- if self.gen_modifiers:
- me_temp_owner = ob0.evaluated_get(depsgraph)
- me_temp = me_temp_owner.to_mesh()
- else:
- me_temp_owner = None
- me_temp = ob0.data
-
- for p in me_temp.polygons:
- if not self.bool_selection or p.select:
- if self.fill_mode == "FAN":
- polygons += len(p.vertices)
- else:
- polygons += 1
-
- if me_temp_owner:
- me_temp_owner.to_mesh_clear()
-
- if self.com_modifiers:
- me_temp_owner = bpy.data.objects[self.component].evaluated_get(depsgraph)
- me_temp = me_temp_owner.to_mesh()
- else:
- me_temp_owner = None
- me_temp = bpy.data.objects[self.component].data
- polygons *= len(me_temp.polygons)
- if me_temp_owner:
- me_temp_owner.to_mesh_clear()
-
- str_polygons = '{:0,.0f}'.format(polygons)
- if polygons > 200000:
- col.label(text=str_polygons + " polygons will be created!",
- icon='ERROR')
- else:
- col.label(text=str_polygons + " faces will be created!",
- icon='INFO')
+ if not (component.modifiers or component.data.shape_keys):
+ col3.enabled = False
+ self.com_modifiers = False
except:
- pass
+ col3.enabled = False
+ self.com_modifiers = False
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)
-
- # Fill
+ #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)
- row.separator()
# Rotation
- row.prop(
+ row.separator()
+ 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':
- row = col.row(align=True)
- row.prop(self, "random_seed")
+ col2.prop(self, "random_seed")
+
if self.rotation_mode == 'UV':
uv_error = False
if self.fill_mode == 'FAN':
@@ -754,14 +2049,19 @@ class tessellate(Operator):
icon='ERROR')
uv_error = True
- if not self.check_gen_comp(self.generator) or \
- len(bpy.data.objects[self.generator].data.uv_layers) == 0:
+ if ob0.type != 'MESH':
row = col.row(align=True)
- check_name = bpy.data.objects[self.generator].name if \
- self.check_gen_comp(self.generator) else "None"
- row.label(text="'" + check_name +
- "' doesn't have UV Maps", icon='ERROR')
+ row.label(
+ text="UV rotation supported only for Mesh objects",
+ icon='ERROR')
uv_error = True
+ else:
+ if len(ob0.data.uv_layers) == 0:
+ row = col.row(align=True)
+ check_name = self.generator
+ row.label(text="'" + check_name +
+ "' doesn't have UV Maps", icon='ERROR')
+ uv_error = True
if uv_error:
row = col.row(align=True)
row.label(text="Default rotation will be used instead",
@@ -769,15 +2069,31 @@ class tessellate(Operator):
# Component XY
row = col.row(align=True)
- row.label(text="Component XY:")
+ row.label(text="Component Coordinates:")
row = col.row(align=True)
row.prop(
self, "mode", text="Component XY", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
+ if self.mode != 'BOUNDS':
+ col.separator()
+ row = col.row(align=True)
+ row.label(text="X:")
+ row.prop(
+ self, "bounds_x", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ row = col.row(align=True)
+ row.label(text="Y:")
+ row.prop(
+ self, "bounds_y", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
# Component Z
- col.label(text="Component Z:")
+ col.label(text="Thickness:")
row = col.row(align=True)
row.prop(
self, "scale_mode", text="Scale Mode", icon='NONE', expand=True,
@@ -787,166 +2103,184 @@ class tessellate(Operator):
self, "zscale", text="Scale", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
- col.prop(
- self, "offset", text="Offset", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
+ 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)
- # ADVANCED
- col = layout.column(align=True)
- col.label(text="Advanced Settings:")
- # vertex group + shape keys
row = col.row(align=True)
- col2 = row.column(align=True)
- col2.prop(self, "bool_vertex_group")
-
- if self.check_gen_comp(self.generator) and \
- len(bpy.data.objects[self.generator].vertex_groups) == 0:
- col2.enabled = False
- self.bool_vertex_group = False
-
- col2 = row.column(align=True)
-
- if not self.check_gen_comp(self.generator) or \
- not self.check_gen_comp(self.component):
- return
+ 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
- col2.prop(self, "bool_shapekeys", text="Use Shape Keys")
+ 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")
- if len(bpy.data.objects[self.generator].vertex_groups) == 0 or \
- bpy.data.objects[self.component].data.shape_keys is None:
- col2.enabled = False
- self.bool_shapekeys = False
- elif len(bpy.data.objects[self.generator].vertex_groups) == 0 or \
- bpy.data.objects[self.component].data.shape_keys is not None:
- if len(bpy.data.objects[
- self.component].data.shape_keys.key_blocks) < 2:
+ # 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
- self.bool_shapekeys = 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
+
+ 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.context.active_object
- self.generator = ob0.name
+ ob0 = bpy.data.objects[self.generator]
+ ob1 = bpy.data.objects[self.component]
except:
- self.report({'ERROR'}, "A Generator mesh object must be selected")
-
- # component object
- sel = bpy.context.selected_objects
- no_component = True
- for o in sel:
- if (o.name == ob0.name or o.type != 'MESH'):
- continue
- else:
- ob1 = o
- self.component = o.name
- no_component = False
- break
-
- # Checks for Tool Shelf panel, it lost the original Selection
- if bpy.context.active_object == self.object_name:
- ob1 = bpy.data.objects[
- bpy.context.active_object.tissue_tessellate.component]
- self.component = ob1.name
- ob0 = bpy.data.objects[
- bpy.context.active_object.tissue_tessellate.generator]
- self.generator = ob0.name
- no_component = False
-
- if no_component:
- # self.report({'ERROR'}, "A component mesh object must be selected")
return {'CANCELLED'}
- # 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"
+ # Check if existing object with same name
+ names = [o.name for o in bpy.data.objects]
+ if self.object_name in names:
+ count_name = 1
+ while True:
+ test_name = self.object_name + '.{:03d}'.format(count_name)
+ if not (test_name in names):
+ self.object_name = test_name
+ break
+ count_name += 1
- if bpy.data.objects[self.component].type != 'MESH':
- message = "Component must be Mesh Objects!"
+ if ob1.type not in allowed_obj:
+ message = "Component must be Mesh, Curve, Surface, Text or Meta object!"
self.report({'ERROR'}, message)
- self.component = ""
+ self.component = None
- if bpy.data.objects[self.generator].type != 'MESH':
- message = "Generator must be Mesh Objects!"
+ if ob0.type not in allowed_obj:
+ message = "Generator must be Mesh, Curve, Surface, Text or Meta object!"
self.report({'ERROR'}, message)
self.generator = ""
- if self.component != "" and 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')
- new_ob = tassellate(
- ob0, ob1, self.offset, self.zscale, self.gen_modifiers,
- self.com_modifiers, self.mode, self.scale_mode,
- self.rotation_mode, self.random_seed, self.fill_mode,
- self.bool_vertex_group, self.bool_selection,
- self.bool_shapekeys
- )
-
- if new_ob == 0:
- message = "Zero faces selected in the Base mesh!"
- self.report({'ERROR'}, message)
- return {'CANCELLED'}
-
- new_ob.name = self.object_name
- # new_ob = bpy.data.objects.new(self.object_name, new_me)
-
- new_ob.location = ob0.location
- new_ob.matrix_world = ob0.matrix_world
-
- bpy.context.collection.objects.link(new_ob)
- new_ob.select_set(True)
- bpy.context.view_layer.objects.active = new_ob
- if self.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=self.merge_thres, use_unselected=False)
- 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)
- self.object_name = new_ob.name
- self.working_on = self.object_name
-
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.object.mode_set(mode='OBJECT')
-
- # MATERIALS
- try:
- # create materials list
- polygon_materials = [p.material_index for p in ob1.data.polygons] * int(
- len(new_ob.data.polygons) / len(ob1.data.polygons))
- # assign old material
- component_materials = [slot.material for slot in ob1.material_slots]
- for i in range(len(component_materials)):
- bpy.ops.object.material_slot_add()
- bpy.context.object.material_slots[i].material = \
- component_materials[i]
- for i in range(len(new_ob.data.polygons)):
- new_ob.data.polygons[i].material_index = polygon_materials[i]
- except:
- pass
+ 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
- return {'FINISHED'}
-
- def check(self, context):
- return True
+ return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@@ -963,9 +2297,10 @@ class update_tessellate(Operator):
@classmethod
def poll(cls, context):
- try:
- return context.active_object.tissue_tessellate.generator != "" and \
- context.active_object.tissue_tessellate.component != ""
+ #try:
+ try: #context.object == None: return False
+ return context.object.tissue_tessellate.generator != None and \
+ context.object.tissue_tessellate.component != None
except:
return False
@@ -975,7 +2310,9 @@ class update_tessellate(Operator):
return checking in bpy.data.objects.keys()
def execute(self, context):
- ob = bpy.context.active_object
+ start_time = time.time()
+
+ ob = bpy.context.object
if not self.go:
generator = ob.tissue_tessellate.generator
component = ob.tissue_tessellate.component
@@ -994,35 +2331,287 @@ class update_tessellate(Operator):
bool_selection = ob.tissue_tessellate.bool_selection
bool_shapekeys = ob.tissue_tessellate.bool_shapekeys
mode = ob.tissue_tessellate.mode
+ bool_smooth = ob.tissue_tessellate.bool_smooth
+ bool_materials = ob.tissue_tessellate.bool_materials
+ bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams
+ bool_material_id = ob.tissue_tessellate.bool_material_id
+ material_id = ob.tissue_tessellate.material_id
+ iterations = ob.tissue_tessellate.iterations
+ bool_combine = ob.tissue_tessellate.bool_combine
+ normals_mode = ob.tissue_tessellate.normals_mode
+ bool_advanced = ob.tissue_tessellate.bool_advanced
+ bool_multi_components = ob.tissue_tessellate.bool_multi_components
+ combine_mode = ob.tissue_tessellate.combine_mode
+ bounds_x = ob.tissue_tessellate.bounds_x
+ bounds_y = ob.tissue_tessellate.bounds_y
+ cap_faces = ob.tissue_tessellate.cap_faces
+ open_edges_crease = ob.tissue_tessellate.open_edges_crease
- if not self.check_gen_comp(generator) or \
- not self.check_gen_comp(component):
- self.report({'ERROR'},
- "Base or Component Objects are missing from the data "
- "(Most likely deleted or renamed)")
- return {'CANCELLED'}
-
- if (generator == "" or component == ""):
+ try:
+ generator.name
+ component.name
+ except:
self.report({'ERROR'},
"Active object must be Tessellate before Update")
return {'CANCELLED'}
- ob0 = bpy.data.objects[generator]
- ob1 = bpy.data.objects[component]
+ # Solve Local View issues
+ local_spaces = []
+ local_ob0 = []
+ local_ob1 = []
+ for area in bpy.context.screen.areas:
+ for space in area.spaces:
+ print(space)
+ try:
+ if ob.local_view_get(space):
+ local_spaces.append(space)
+ local_ob0 = ob0.local_view_get(space)
+ ob0.local_view_set(space, True)
+ local_ob1 = ob1.local_view_get(space)
+ ob1.local_view_set(space, True)
+ except:
+ pass
- temp_ob = tassellate(
- ob0, ob1, offset, zscale, gen_modifiers, com_modifiers,
- mode, scale_mode, rotation_mode, random_seed, fill_mode,
- bool_vertex_group, bool_selection, bool_shapekeys
- )
+ starting_mode = bpy.context.object.mode
+ #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT'
+ bpy.ops.object.mode_set(mode='OBJECT')
- if temp_ob == 0:
+ ob0 = generator
+ ob1 = component
+ auto_layer_collection()
+
+ ob0_hide = ob0.hide_get()
+ ob0_hidev = ob0.hide_viewport
+ ob0_hider = ob0.hide_render
+ ob1_hide = ob1.hide_get()
+ ob1_hidev = ob1.hide_viewport
+ ob1_hider = ob1.hide_render
+ ob0.hide_set(False)
+ ob0.hide_viewport = False
+ ob0.hide_render = False
+ ob1.hide_set(False)
+ ob1.hide_viewport = False
+ ob1.hide_render = False
+
+ if ob0.type == 'META':
+ base_ob = convert_object_to_mesh(ob0, False, True)
+ else:
+ base_ob = ob0.copy()
+ base_ob.data = ob0.data.copy()
+ bpy.context.collection.objects.link(base_ob)
+
+ # 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
+ 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
+ base_ob.modifiers.update()
+ bpy.ops.object.select_all(action='DESELECT')
+ iter_objects = [base_ob]
+ #base_ob = new_ob#.copy()
+
+ for iter in range(iterations):
+ 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
+ else:
+ try:
+ o.select_set(False)
+ except: pass
+ bpy.ops.object.join()
+ new_ob = bpy.context.view_layer.objects.active
+ new_ob.select_set(True)
+ new_ob.data.update()
+
+ #try:
+ # combine object
+ if (bool_selection or bool_material_id) and combine_mode == 'UNUSED':
+ # remove faces from last mesh
+ bm = bmesh.new()
+
+ last_mesh = iter_objects[-1].data.copy()
+
+ bm.from_mesh(last_mesh)
+ bm.faces.ensure_lookup_table()
+ if bool_multi_components:
+ remove_materials = matched_materials
+ elif bool_material_id:
+ remove_materials = [material_id]
+ else: remove_materials = []
+ if bool_selection:
+ 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.material_index in remove_materials]
+ bmesh.ops.delete(bm, geom=remove_faces, context='FACES')
+ bm.to_mesh(last_mesh)
+ last_mesh.update()
+
+ 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]
+
+ 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
+
+ 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"
+ 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
+ else:
+ if base_ob != new_ob:
+ bpy.data.objects.remove(base_ob)
+ base_ob = new_ob
+ iter_objects = [new_ob]
+
+ 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.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
+ ob.select_set(True)
+ message = errors[new_ob]
+ ob.tissue_tessellate.error_message = message
+ bpy.ops.object.mode_set(mode=starting_mode)
self.report({'ERROR'}, message)
return {'CANCELLED'}
- ob.data = temp_ob.data
- bpy.data.objects.remove(temp_ob)
+ 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
+ ob = convert_object_to_mesh(ob,False,True)
+ ob.location, ob.matrix_world = loc, matr
+ data_name = ob.data.name
+ old_data = ob.data
+ ob.data = new_ob.data
+ bpy.data.meshes.remove(old_data)
+ ob.data.name = data_name
+
+ # copy vertex group
+ if bool_vertex_group:
+ for vg in new_ob.vertex_groups:
+ if not vg.name in ob.vertex_groups.keys():
+ ob.vertex_groups.new(name=vg.name)
+ new_vg = ob.vertex_groups[vg.name]
+ for i in range(len(ob.data.vertices)):
+ try:
+ weight = vg.weight(i)
+ except:
+ weight = 0
+ new_vg.add([i], weight, 'REPLACE')
+
+ selected_objects = [o for o in bpy.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)
+
if merge:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(
@@ -1030,516 +2619,378 @@ class update_tessellate(Operator):
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')
- # MATERIALS
- try:
- # create materials list
- polygon_materials = [p.material_index for p in ob1.data.polygons] * int(
- len(ob.data.polygons) / len(ob1.data.polygons))
- # assign old material
- component_materials = [slot.material for slot in ob1.material_slots]
- for i in range(len(component_materials)):
- bpy.ops.object.material_slot_add()
- bpy.context.object.material_slots[i].material = \
- component_materials[i]
- for i in range(len(ob.data.polygons)):
- ob.data.polygons[i].material_index = polygon_materials[i]
- except:
- pass
+ 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)
+
+ for o in selected_objects:
+ 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
+ ob0.hide_set(ob0_hide)
+ ob0.hide_viewport = ob0_hidev
+ ob0.hide_render = ob0_hider
+ # Restore Component visibility
+ ob1.hide_set(ob1_hide)
+ ob1.hide_viewport = ob1_hidev
+ ob1.hide_render = ob1_hider
+ # Restore Local visibility
+ for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1):
+ ob0.local_view_set(space, local0)
+ ob1.local_view_set(space, local1)
+ print(local_ob0)
+ print(local_ob1)
return {'FINISHED'}
def check(self, context):
return True
-
-class settings_tessellate(Operator):
- bl_idname = "object.settings_tessellate"
- bl_label = "Settings"
- bl_description = ("Update the tessellated mesh according to base and component changes\n"
- "Allow also to change tessellation's parameters")
- bl_options = {'REGISTER', 'UNDO'}
-
- object_name: StringProperty(
- name="",
- description="Name of the generated object"
- )
- zscale: FloatProperty(
- name="Scale",
- default=1,
- soft_min=0,
- soft_max=10,
- description="Scale factor for the component thickness"
- )
- scale_mode: EnumProperty(
- items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Proportional", "")),
- default='ADAPTIVE',
- name="Scale variation"
- )
- offset: FloatProperty(
- name="Surface Offset",
- default=0,
- min=-1, max=1,
- soft_min=-1,
- soft_max=1,
- description="Surface offset"
- )
- mode: EnumProperty(
- items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Adaptive", "")),
- default='ADAPTIVE',
- name="Component Mode"
- )
- rotation_mode: EnumProperty(
- items=(('RANDOM', "Random", ""), ('UV', "Active UV", ""),
- ('DEFAULT', "Default", "")),
- default='DEFAULT',
- name="Component Rotation"
- )
- fill_mode: EnumProperty(
- items=(('QUAD', "Quad", ""), ('FAN', "Fan", "")),
- default='QUAD',
- name="Fill Mode"
- )
- gen_modifiers: BoolProperty(
- name="Generator Modifiers",
- default=False,
- description="Apply modifiers to base object"
- )
- com_modifiers: BoolProperty(
- name="Component Modifiers",
- default=False,
- description="Apply modifiers to component object"
- )
- merge: BoolProperty(
- name="Merge",
- default=False,
- description="Merge vertices in adjacent duplicates"
- )
- merge_thres: FloatProperty(
- name="Distance",
- default=0.001,
- soft_min=0,
- soft_max=10,
- description="Limit below which to merge vertices"
- )
- generator: StringProperty(
- name="",
- description="Base object for the tessellation"
- )
- component: StringProperty(
- name="",
- description="Component object for the tessellation"
- )
- bool_random: BoolProperty(
- name="Randomize",
- default=False,
- description="Randomize component rotation"
- )
- random_seed: IntProperty(
- name="Seed",
- default=0,
- soft_min=0,
- soft_max=10,
- description="Random seed"
- )
- bool_vertex_group: BoolProperty(
- name="Map Vertex Group",
- default=False,
- description="Map on generated "
- "geometry the active Vertex Group from the base object"
- )
- bool_selection: BoolProperty(
- name="On selected Faces",
- default=False,
- description="Create Tessellation only on select faces"
- )
- bool_shapekeys: BoolProperty(
- name="Use Shape Keys",
- default=False,
- description="Use component's active Shape Key according to active "
- "Vertex Group of the base object"
- )
- go = False
+class TISSUE_PT_tessellate(Panel):
+ bl_label = "Tissue Tools"
+ bl_category = "Tissue"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ #bl_options = {'DEFAULT_OPEN'}
@classmethod
def poll(cls, context):
- try:
- return context.active_object.tissue_tessellate.generator != "" and \
- context.active_object.tissue_tessellate.component != ""
- except:
- return False
-
- @staticmethod
- def check_gen_comp(checking):
- # note pass the stored name key in here to check it out
- return checking in bpy.data.objects.keys()
+ return context.mode in {'OBJECT', 'EDIT_MESH'}
def draw(self, context):
layout = self.layout
- ob0 = bpy.context.active_object
-
- if not self.go:
- self.generator = ob0.tissue_tessellate.generator
- self.component = ob0.tissue_tessellate.component
- self.zscale = ob0.tissue_tessellate.zscale
- self.scale_mode = ob0.tissue_tessellate.scale_mode
- self.rotation_mode = ob0.tissue_tessellate.rotation_mode
- self.offset = ob0.tissue_tessellate.offset
- self.merge = ob0.tissue_tessellate.merge
- self.merge_thres = ob0.tissue_tessellate.merge_thres
- self.gen_modifiers = ob0.tissue_tessellate.gen_modifiers
- self.com_modifiers = ob0.tissue_tessellate.com_modifiers
- self.bool_random = ob0.tissue_tessellate.bool_random
- self.random_seed = ob0.tissue_tessellate.random_seed
- self.fill_mode = ob0.tissue_tessellate.fill_mode
- self.bool_vertex_group = ob0.tissue_tessellate.bool_vertex_group
- self.bool_selection = ob0.tissue_tessellate.bool_selection
- self.bool_shapekeys = ob0.tissue_tessellate.bool_shapekeys
- self.mode = ob0.tissue_tessellate.mode
-
- # start drawing
- layout = self.layout
- # check for keys in data - as the objects can be deleted or renamed
- if not self.check_gen_comp(self.generator) or \
- not self.check_gen_comp(self.component):
-
- layout.label(text="Base or Component Objects are missing from the data",
- icon="INFO")
- layout.label(text="(Most likely deleted or renamed)",
- icon="BLANK1")
- layout.label(text="Settings could not be altered anymore",
- icon="BLANK1")
- layout.label(text="Please re-run Tessellate with two new selected objects",
- icon="BLANK1")
- return
-
- # ob0 = bpy.context.active_object
- # Base and Component
col = layout.column(align=True)
- row = col.row(align=True)
- row.label(text="BASE :")
- row.label(text="COMPONENT :")
- row = col.row(align=True)
-
- col2 = row.column(align=True)
- col2.prop_search(self, "generator", bpy.data, "objects")
- row.separator()
- col2 = row.column(align=True)
- col2.prop_search(self, "component", bpy.data, "objects")
-
- row = col.row(align=True)
- col2 = row.column(align=True)
- col2.prop(self, "gen_modifiers", text="Use Modifiers")
-
- if len(bpy.data.objects[self.generator].modifiers) == 0:
- col2.enabled = False
- self.gen_modifiers = False
-
- col2 = row.column(align=True)
- col2.prop(self, "com_modifiers", text="Use Modifiers")
-
- if len(bpy.data.objects[self.component].modifiers) == 0:
- col2.enabled = False
- self.com_modifiers = False
-
- # On selected faces
- row = col.row(align=True)
- row.prop(self, "bool_selection", text="On selected Faces")
+ col.label(text="Tessellate:")
+ col.operator("object.tessellate")
+ col.operator("object.dual_mesh_tessellated")
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')
- if self.gen_modifiers or self.com_modifiers:
- depsgraph = context.evaluated_depsgraph_get()
-
- # Count number of faces
- try:
- polygons = 0
- me_temp_owner = None
- if self.gen_modifiers:
- me_temp_owner = bpy.data.objects[self.generator].evaluated_get(depsgraph)
- me_temp = me_temp_owner.to_mesh()
- else:
- me_temp_owner = None
- me_temp = bpy.data.objects[self.generator].data
-
- for p in me_temp.polygons:
- if not self.bool_selection or p.select:
- if self.fill_mode == "FAN":
- polygons += len(p.vertices)
- else:
- polygons += 1
-
- if me_temp_owner:
- me_temp_owner.to_mesh_clear()
-
- if self.com_modifiers:
- me_temp_owner = bpy.data.objects[self.component].evaluated_get(depsgraph)
- me_temp = me_temp_owner.to_mesh()
- else:
- me_temp_owner = None
- me_temp = bpy.data.objects[self.component].data
- polygons *= len(me_temp.polygons)
-
- if me_temp_owner:
- me_temp_owner.to_mesh_clear()
+ #col = layout.column(align=True)
+ col.operator("mesh.rotate_face", icon='NDOF_TURN')
- str_polygons = '{:0,.0f}'.format(polygons)
- if polygons > 200000:
- col.label(text=str_polygons + " polygons will be created!",
- icon='ERROR')
- else:
- col.label(text=str_polygons + " faces will be created!",
- icon='INFO')
- except:
- pass
col.separator()
+ col.label(text="Other:")
+ col.operator("object.dual_mesh")
+ col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
- # Fill and Rotation
- row = col.row(align=True)
- row.label(text="Fill Mode:")
- row.separator()
- row.label(text="Rotation:")
- row = col.row(align=True)
-
- # fill
- 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)
- row.separator()
-
- # rotation
- row.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':
- row = col.row(align=True)
- row.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
-
- if len(bpy.data.objects[self.generator].data.uv_layers) == 0:
- row = col.row(align=True)
- row.label(text="'" + bpy.data.objects[self.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')
-
- # component XY
- row = col.row(align=True)
- row.label(text="Component XY:")
- 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)
-
- # component Z
- col.label(text="Component Z:")
- 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)
- 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)
-
- # 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)
-
- # ADVANCED #
- col = layout.column(align=True)
- tessellate.rotation_mode
-
- col.label(text="Advanced Settings:")
- # vertex group + shape keys
- row = col.row(align=True)
- col2 = row.column(align=True)
- col2.prop(self, "bool_vertex_group")
-
- if len(bpy.data.objects[self.generator].vertex_groups) == 0:
- col2.enabled = False
- self.bool_vertex_group = False
- col2 = row.column(align=True)
- col2.prop(self, "bool_shapekeys", text="Use Shape Keys")
-
- if len(bpy.data.objects[self.generator].vertex_groups) == 0 or \
- bpy.data.objects[self.component].data.shape_keys is None:
- col2.enabled = False
- self.bool_shapekeys = False
- elif len(bpy.data.objects[self.generator].vertex_groups) == 0 or \
- bpy.data.objects[self.component].data.shape_keys is not None:
- if len(bpy.data.objects[self.component].data.shape_keys.key_blocks) < 2:
- col2.enabled = False
- self.bool_shapekeys = False
- self.go = True
-
- def execute(self, context):
- self.ob = bpy.context.active_object
-
- if not self.go:
- self.generator = self.ob.tissue_tessellate.generator
- self.component = self.ob.tissue_tessellate.component
- self.zscale = self.ob.tissue_tessellate.zscale
- self.scale_mode = self.ob.tissue_tessellate.scale_mode
- self.rotation_mode = self.ob.tissue_tessellate.rotation_mode
- self.offset = self.ob.tissue_tessellate.offset
- self.merge = self.ob.tissue_tessellate.merge
- self.merge_thres = self.ob.tissue_tessellate.merge_thres
- self.gen_modifiers = self.ob.tissue_tessellate.gen_modifiers
- self.com_modifiers = self.ob.tissue_tessellate.com_modifiers
- self.bool_random = self.ob.tissue_tessellate.bool_random
- self.random_seed = self.ob.tissue_tessellate.random_seed
- self.fill_mode = self.ob.tissue_tessellate.fill_mode
- self.bool_vertex_group = self.ob.tissue_tessellate.bool_vertex_group
- self.bool_selection = self.ob.tissue_tessellate.bool_selection
- self.bool_shapekeys = self.ob.tissue_tessellate.bool_shapekeys
-
- if not self.check_gen_comp(self.generator) or \
- not self.check_gen_comp(self.component):
- # do nothing as the Warning was already done in it UI
- return {'CANCELLED'}
-
- if (self.generator == "" or self.component == ""):
- self.report({'ERROR'},
- "Active object must be Tessellated before Update")
- return {'CANCELLED'}
-
- if (bpy.data.objects[self.generator].type != 'MESH'):
- self.report({'ERROR'}, "Base object must be a Mesh")
- return {'CANCELLED'}
-
- if (bpy.data.objects[self.component].type != 'MESH'):
- self.report({'ERROR'}, "Component object must be a Mesh")
- return {'CANCELLED'}
-
- ob0 = bpy.data.objects[self.generator]
- ob1 = bpy.data.objects[self.component]
+ act = context.active_object
+ if act and act.type == 'MESH':
+ col.operator("object.uv_to_mesh", icon="UV")
- temp_ob = tassellate(
- ob0, ob1, self.offset, self.zscale, self.gen_modifiers,
- self.com_modifiers, self.mode, self.scale_mode, self.rotation_mode,
- self.random_seed, self.fill_mode, self.bool_vertex_group,
- self.bool_selection, self.bool_shapekeys
- )
- if temp_ob == 0:
- message = "Zero faces selected in the Base mesh"
- self.report({'ERROR'}, message)
-
- return {'CANCELLED'}
+class TISSUE_PT_tessellate_object(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_label = "Tissue - Tessellate"
+ bl_options = {'DEFAULT_CLOSED'}
- # Transfer mesh data
- self.ob.data = temp_ob.data
+ @classmethod
+ def poll(cls, context):
+ try: return context.object.type == 'MESH'
+ except: return False
- # Create object in order to transfer vertex group
- bpy.context.collection.objects.link(temp_ob)
- temp_ob.select_set(True)
- bpy.context.view_layer.objects.active = temp_ob
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
- bpy.ops.object.vertex_group_copy_to_linked()
- except:
- pass
-
- bpy.context.scene.objects.unlink(temp_ob)
- bpy.data.objects.remove(temp_ob)
- bpy.context.view_layer.objects.active = self.ob
-
- if self.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=self.merge_thres, use_unselected=False)
- bpy.ops.object.mode_set(mode='OBJECT')
- self.ob = store_parameters(self, self.ob)
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.object.mode_set(mode='OBJECT')
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ if not bool_tessellated:
+ layout.label(text="The selected object is not a Tessellated object",
+ icon='INFO')
+ else:
+ if props.error_message != "":
+ layout.label(text=props.error_message,
+ icon='ERROR')
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.prop(props, "bool_run", text="Animatable")
+ row.operator("object.update_tessellate", icon='FILE_REFRESH')
- # MATERIALS
- try:
- # create materials list
- polygon_materials = [p.material_index for p in ob1.data.polygons] * \
- int(len(self.ob.data.polygons) / len(ob1.data.polygons))
- # assign old material
- component_materials = [slot.material for slot in ob1.material_slots]
- for i in range(len(component_materials)):
- bpy.ops.object.material_slot_add()
- bpy.context.object.material_slots[i].material = \
- component_materials[i]
- for i in range(len(self.ob.data.polygons)):
- self.ob.data.polygons[i].material_index = polygon_materials[i]
- except:
- pass
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.label(text="BASE :")
+ row.label(text="COMPONENT :")
+ row = col.row(align=True)
- return {'FINISHED'}
+ col2 = row.column(align=True)
+ col2.prop_search(props, "generator", context.scene, "objects")
+ row.separator()
+ col2 = row.column(align=True)
+ col2.prop_search(props, "component", context.scene, "objects")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
+ row.separator()
+ try:
+ if not (ob0.modifiers or ob0.data.shape_keys) or props.fill_mode == 'PATCH':
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ col2 = row.column(align=True)
+ col2.prop(props, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
+ try:
+ if not (props.component.modifiers or props.component.data.shape_keys):
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ col.separator()
- def check(self, context):
- return True
+ # Fill and Rotation
+ row = col.row(align=True)
+ row.label(text="Fill Mode:")
+ row.separator()
+ row.label(text="Rotation:")
+ row = col.row(align=True)
- def invoke(self, context, event):
- return context.window_manager.invoke_props_dialog(self, width=400)
+ # fill
+ row.prop(props, "fill_mode", text="", icon='NONE', expand=False,
+ 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)
-class tessellate_panel(Panel):
- bl_label = "Tissue"
- bl_category = "Create"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_options = {'DEFAULT_CLOSED'}
+ if props.rotation_mode == 'RANDOM':
+ #row = col.row(align=True)
+ col2.prop(props, "random_seed")
- @classmethod
- def poll(cls, context):
- return context.mode in {'OBJECT', 'EDIT_MESH'}
+ 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
+ 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')
- def draw(self, context):
- layout = self.layout
+ # component XY
+ row = col.row(align=True)
+ row.label(text="Component Coordinates:")
+ row = col.row(align=True)
+ row.prop(props, "mode", expand=True)
- col = layout.column(align=True)
- col.label(text="Tessellate Add:")
- col.operator("object.tessellate")
+ if props.mode != 'BOUNDS':
+ col.separator()
+ row = col.row(align=True)
+ row.label(text="X:")
+ row.prop(
+ props, "bounds_x", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
- col = layout.column(align=True)
- col.label(text="Tessellate Edit:")
- col.operator("object.settings_tessellate")
- col.operator("object.update_tessellate")
+ 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)
+
+ # Direction
+ row = col.row(align=True)
+ row.label(text="Direction:")
+ row = col.row(align=True)
+ row.prop(
+ props, "normals_mode", text="Direction", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ row.enabled = props.fill_mode != 'PATCH'
- col = layout.column(align=True)
- col.operator("mesh.rotate_face")
+ # merge
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.prop(props, "merge")
+ if props.merge:
+ row.prop(props, "merge_thres")
+ 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
- col.separator()
- col.label(text="Other:")
- col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
+ 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")
- act = context.active_object
- if act and act.type == 'MESH':
- col.operator("object.uv_to_mesh", icon="GROUP_UVS")
+ # Advanced Settings
+ col = layout.column(align=True)
+ col.separator()
+ 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 = 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
+
+ # 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
+ # TRANFER DATA ### OFF
+ if props.fill_mode != 'PATCH' and False:
+ col = layout.column(align=True)
+ col.label(text="Component Data:")
+ row = col.row(align=True)
+ 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')
+
+ col.separator()
+ row = col.row(align=True)
+ row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
+ row.prop(props, 'iterations', text='Repeat', icon='SETTINGS')
+ 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"
@@ -1554,45 +3005,35 @@ class rotate_face(Operator):
def execute(self, context):
ob = bpy.context.active_object
me = ob.data
- bpy.ops.object.mode_set(mode='OBJECT')
- for p in [f for f in me.polygons if f.select]:
- p.vertices = p.vertices[1:] + p.vertices[:1]
+ bm = bmesh.from_edit_mesh(me)
+ mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.flip_normals()
- bpy.ops.mesh.flip_normals()
- me.update(calc_edges=True)
+ for face in bm.faces:
+ if (face.select):
+ vs = face.verts[:]
+ vs2 = vs[1:]+vs[:1]
+ material_index = face.material_index
+ bm.faces.remove(face)
+ f2 = bm.faces.new(vs2)
+ f2.select = True
+ f2.material_index = material_index
+ bm.normal_update()
+
+ # trigger UI update
+ bmesh.update_edit_mesh(me)
+ ob.select_set(False)
# update tessellated meshes
bpy.ops.object.mode_set(mode='OBJECT')
for o in [obj for obj in bpy.data.objects if
- obj.tissue_tessellate.generator == ob.name]:
+ obj.tissue_tessellate.generator == ob and obj.visible_get()]:
bpy.context.view_layer.objects.active = o
bpy.ops.object.update_tessellate()
+ o.select_set(False)
+ ob.select_set(True)
bpy.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 register():
- bpy.utils.register_class(tissue_tessellate_prop)
- bpy.utils.register_class(tessellate)
- bpy.utils.register_class(update_tessellate)
- bpy.utils.register_class(settings_tessellate)
- bpy.utils.register_class(tessellate_panel)
- bpy.utils.register_class(rotate_face)
-
-
-def unregister():
- bpy.utils.unregister_class(tissue_tessellate_prop)
- bpy.utils.unregister_class(tessellate)
- bpy.utils.unregister_class(update_tessellate)
- bpy.utils.unregister_class(settings_tessellate)
- bpy.utils.unregister_class(tessellate_panel)
- bpy.utils.unregister_class(rotate_face)
-
-
-if __name__ == "__main__":
- register()