diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/BOOKMARKS.TXT b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/BOOKMARKS.TXT
new file mode 100644
index 00000000..97d6298e
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/BOOKMARKS.TXT
@@ -0,0 +1,2 @@
+[Bookmarks]
+[Recent]
\ No newline at end of file
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/PLATFORM_SUPPORT.TXT b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/PLATFORM_SUPPORT.TXT
new file mode 100644
index 00000000..a8e98863
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/PLATFORM_SUPPORT.TXT
@@ -0,0 +1 @@
+{NVIDIA Corporation/GeForce GTX 460/PCIe/SSE2/4.5.0 NVIDIA 391.35}=SUPPORTED
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/RECENT-FILES.TXT b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/RECENT-FILES.TXT
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/RECENT-FILES.TXT
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/STARTUP.BLEND b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/STARTUP.BLEND
new file mode 100644
index 00000000..f73bcd96
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/STARTUP.BLEND differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/USERPREF.BLEND b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/USERPREF.BLEND
new file mode 100644
index 00000000..3ba30706
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/USERPREF.BLEND differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/DATAFILES/STUDIOLIGHTS/MATCAP/ЯXƎ..O_PAϽTAM_O.LƧO.O_REDAHS_ETIHW_TNEIDARG_ENISOC_TOOR_REWOP_57864084_91_O_19_48046875_POWER_ROOT_COSINE_GRADIENT_WHITE_SHADER_O.OSL.O_MATCAP_O..EXR b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/DATAFILES/STUDIOLIGHTS/MATCAP/ЯXƎ..O_PAϽTAM_O.LƧO.O_REDAHS_ETIHW_TNEIDARG_ENISOC_TOOR_REWOP_57864084_91_O_19_48046875_POWER_ROOT_COSINE_GRADIENT_WHITE_SHADER_O.OSL.O_MATCAP_O..EXR
new file mode 100644
index 00000000..e98a32ff
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/DATAFILES/STUDIOLIGHTS/MATCAP/ЯXƎ..O_PAϽTAM_O.LƧO.O_REDAHS_ETIHW_TNEIDARG_ENISOC_TOOR_REWOP_57864084_91_O_19_48046875_POWER_ROOT_COSINE_GRADIENT_WHITE_SHADER_O.OSL.O_MATCAP_O..EXR differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/DATAFILES/STUDIOLIGHTS/STUDIO/LƧ.SL b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/DATAFILES/STUDIOLIGHTS/STUDIO/LƧ.SL
new file mode 100644
index 00000000..b536ef2d
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/DATAFILES/STUDIOLIGHTS/STUDIO/LƧ.SL
@@ -0,0 +1,48 @@
+version 1
+light_ambient.x 0.000000
+light_ambient.y 0.000000
+light_ambient.z 0.000000
+light[0].flag 1
+light[0].smooth 0.000000
+light[0].col.x 1.000000
+light[0].col.y 1.000000
+light[0].col.z 1.000000
+light[0].spec.x 0.000000
+light[0].spec.y 0.000000
+light[0].spec.z 0.000000
+light[0].vec.x -0.000000
+light[0].vec.y -0.000000
+light[0].vec.z 1.000000
+light[1].flag 0
+light[1].smooth 0.000000
+light[1].col.x 0.521083
+light[1].col.y 0.538226
+light[1].col.z 0.538226
+light[1].spec.x 0.599030
+light[1].spec.y 0.599030
+light[1].spec.z 0.599030
+light[1].vec.x -0.406780
+light[1].vec.y 0.203390
+light[1].vec.z 0.890597
+light[2].flag 0
+light[2].smooth 0.478261
+light[2].col.x 0.038403
+light[2].col.y 0.034357
+light[2].col.z 0.049530
+light[2].spec.x 0.106102
+light[2].spec.y 0.125981
+light[2].spec.z 0.158523
+light[2].vec.x -0.135593
+light[2].vec.y 0.101695
+light[2].vec.z 0.985532
+light[3].flag 0
+light[3].smooth 0.200000
+light[3].col.x 0.090838
+light[3].col.y 0.082080
+light[3].col.z 0.072255
+light[3].spec.x 0.106535
+light[3].spec.y 0.084771
+light[3].spec.z 0.066080
+light[3].vec.x 0.624519
+light[3].vec.y -0.562067
+light[3].vec.z -0.542269
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ADD_MESH_CLUSTER.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ADD_MESH_CLUSTER.PY
new file mode 100644
index 00000000..c5515b7e
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ADD_MESH_CLUSTER.PY
@@ -0,0 +1,1304 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import io
+import math
+import os
+import copy
+from math import pi, cos, sin, tan, sqrt
+from mathutils import Vector, Matrix
+from copy import copy
+
+# -----------------------------------------------------------------------------
+# Atom, stick and element data
+
+
+# This is a list that contains some data of all possible elements. The structure
+# is as follows:
+#
+# 1, "Hydrogen", "H", [0.0,0.0,1.0], 0.32, 0.32, 0.32 , -1 , 1.54 means
+#
+# No., name, short name, color, radius (used), radius (covalent), radius (atomic),
+#
+# charge state 1, radius (ionic) 1, charge state 2, radius (ionic) 2, ... all
+# charge states for any atom are listed, if existing.
+# The list is fixed and cannot be changed ... (see below)
+
+ATOM_CLUSTER_ELEMENTS_DEFAULT = (
+( 1, "Hydrogen", "H", ( 1.0, 1.0, 1.0, 1.0), 0.32, 0.32, 0.79 , -1 , 1.54 ),
+( 2, "Helium", "He", ( 0.85, 1.0, 1.0, 1.0), 0.93, 0.93, 0.49 ),
+( 3, "Lithium", "Li", ( 0.8, 0.50, 1.0, 1.0), 1.23, 1.23, 2.05 , 1 , 0.68 ),
+( 4, "Beryllium", "Be", ( 0.76, 1.0, 0.0, 1.0), 0.90, 0.90, 1.40 , 1 , 0.44 , 2 , 0.35 ),
+( 5, "Boron", "B", ( 1.0, 0.70, 0.70, 1.0), 0.82, 0.82, 1.17 , 1 , 0.35 , 3 , 0.23 ),
+( 6, "Carbon", "C", ( 0.56, 0.56, 0.56, 1.0), 0.77, 0.77, 0.91 , -4 , 2.60 , 4 , 0.16 ),
+( 7, "Nitrogen", "N", ( 0.18, 0.31, 0.97, 1.0), 0.75, 0.75, 0.75 , -3 , 1.71 , 1 , 0.25 , 3 , 0.16 , 5 , 0.13 ),
+( 8, "Oxygen", "O", ( 1.0, 0.05, 0.05, 1.0), 0.73, 0.73, 0.65 , -2 , 1.32 , -1 , 1.76 , 1 , 0.22 , 6 , 0.09 ),
+( 9, "Fluorine", "F", ( 0.56, 0.87, 0.31, 1.0), 0.72, 0.72, 0.57 , -1 , 1.33 , 7 , 0.08 ),
+(10, "Neon", "Ne", ( 0.70, 0.89, 0.96, 1.0), 0.71, 0.71, 0.51 , 1 , 1.12 ),
+(11, "Sodium", "Na", ( 0.67, 0.36, 0.94, 1.0), 1.54, 1.54, 2.23 , 1 , 0.97 ),
+(12, "Magnesium", "Mg", ( 0.54, 1.0, 0.0, 1.0), 1.36, 1.36, 1.72 , 1 , 0.82 , 2 , 0.66 ),
+(13, "Aluminium", "Al", ( 0.74, 0.65, 0.65, 1.0), 1.18, 1.18, 1.82 , 3 , 0.51 ),
+(14, "Silicon", "Si", ( 0.94, 0.78, 0.62, 1.0), 1.11, 1.11, 1.46 , -4 , 2.71 , -1 , 3.84 , 1 , 0.65 , 4 , 0.42 ),
+(15, "Phosphorus", "P", ( 1.0, 0.50, 0.0, 1.0), 1.06, 1.06, 1.23 , -3 , 2.12 , 3 , 0.44 , 5 , 0.35 ),
+(16, "Sulfur", "S", ( 1.0, 1.0, 0.18, 1.0), 1.02, 1.02, 1.09 , -2 , 1.84 , 2 , 2.19 , 4 , 0.37 , 6 , 0.30 ),
+(17, "Chlorine", "Cl", ( 0.12, 0.94, 0.12, 1.0), 0.99, 0.99, 0.97 , -1 , 1.81 , 5 , 0.34 , 7 , 0.27 ),
+(18, "Argon", "Ar", ( 0.50, 0.81, 0.89, 1.0), 0.98, 0.98, 0.88 , 1 , 1.54 ),
+(19, "Potassium", "K", ( 0.56, 0.25, 0.83, 1.0), 2.03, 2.03, 2.77 , 1 , 0.81 ),
+(20, "Calcium", "Ca", ( 0.23, 1.0, 0.0, 1.0), 1.74, 1.74, 2.23 , 1 , 1.18 , 2 , 0.99 ),
+(21, "Scandium", "Sc", ( 0.90, 0.90, 0.90, 1.0), 1.44, 1.44, 2.09 , 3 , 0.73 ),
+(22, "Titanium", "Ti", ( 0.74, 0.76, 0.78, 1.0), 1.32, 1.32, 2.00 , 1 , 0.96 , 2 , 0.94 , 3 , 0.76 , 4 , 0.68 ),
+(23, "Vanadium", "V", ( 0.65, 0.65, 0.67, 1.0), 1.22, 1.22, 1.92 , 2 , 0.88 , 3 , 0.74 , 4 , 0.63 , 5 , 0.59 ),
+(24, "Chromium", "Cr", ( 0.54, 0.6, 0.78, 1.0), 1.18, 1.18, 1.85 , 1 , 0.81 , 2 , 0.89 , 3 , 0.63 , 6 , 0.52 ),
+(25, "Manganese", "Mn", ( 0.61, 0.47, 0.78, 1.0), 1.17, 1.17, 1.79 , 2 , 0.80 , 3 , 0.66 , 4 , 0.60 , 7 , 0.46 ),
+(26, "Iron", "Fe", ( 0.87, 0.4, 0.2, 1.0), 1.17, 1.17, 1.72 , 2 , 0.74 , 3 , 0.64 ),
+(27, "Cobalt", "Co", ( 0.94, 0.56, 0.62, 1.0), 1.16, 1.16, 1.67 , 2 , 0.72 , 3 , 0.63 ),
+(28, "Nickel", "Ni", ( 0.31, 0.81, 0.31, 1.0), 1.15, 1.15, 1.62 , 2 , 0.69 ),
+(29, "Copper", "Cu", ( 0.78, 0.50, 0.2, 1.0), 1.17, 1.17, 1.57 , 1 , 0.96 , 2 , 0.72 ),
+(30, "Zinc", "Zn", ( 0.49, 0.50, 0.69, 1.0), 1.25, 1.25, 1.53 , 1 , 0.88 , 2 , 0.74 ),
+(31, "Gallium", "Ga", ( 0.76, 0.56, 0.56, 1.0), 1.26, 1.26, 1.81 , 1 , 0.81 , 3 , 0.62 ),
+(32, "Germanium", "Ge", ( 0.4, 0.56, 0.56, 1.0), 1.22, 1.22, 1.52 , -4 , 2.72 , 2 , 0.73 , 4 , 0.53 ),
+(33, "Arsenic", "As", ( 0.74, 0.50, 0.89, 1.0), 1.20, 1.20, 1.33 , -3 , 2.22 , 3 , 0.58 , 5 , 0.46 ),
+(34, "Selenium", "Se", ( 1.0, 0.63, 0.0, 1.0), 1.16, 1.16, 1.22 , -2 , 1.91 , -1 , 2.32 , 1 , 0.66 , 4 , 0.50 , 6 , 0.42 ),
+(35, "Bromine", "Br", ( 0.65, 0.16, 0.16, 1.0), 1.14, 1.14, 1.12 , -1 , 1.96 , 5 , 0.47 , 7 , 0.39 ),
+(36, "Krypton", "Kr", ( 0.36, 0.72, 0.81, 1.0), 1.31, 1.31, 1.24 ),
+(37, "Rubidium", "Rb", ( 0.43, 0.18, 0.69, 1.0), 2.16, 2.16, 2.98 , 1 , 1.47 ),
+(38, "Strontium", "Sr", ( 0.0, 1.0, 0.0, 1.0), 1.91, 1.91, 2.45 , 2 , 1.12 ),
+(39, "Yttrium", "Y", ( 0.58, 1.0, 1.0, 1.0), 1.62, 1.62, 2.27 , 3 , 0.89 ),
+(40, "Zirconium", "Zr", ( 0.58, 0.87, 0.87, 1.0), 1.45, 1.45, 2.16 , 1 , 1.09 , 4 , 0.79 ),
+(41, "Niobium", "Nb", ( 0.45, 0.76, 0.78, 1.0), 1.34, 1.34, 2.08 , 1 , 1.00 , 4 , 0.74 , 5 , 0.69 ),
+(42, "Molybdenum", "Mo", ( 0.32, 0.70, 0.70, 1.0), 1.30, 1.30, 2.01 , 1 , 0.93 , 4 , 0.70 , 6 , 0.62 ),
+(43, "Technetium", "Tc", ( 0.23, 0.61, 0.61, 1.0), 1.27, 1.27, 1.95 , 7 , 0.97 ),
+(44, "Ruthenium", "Ru", ( 0.14, 0.56, 0.56, 1.0), 1.25, 1.25, 1.89 , 4 , 0.67 ),
+(45, "Rhodium", "Rh", ( 0.03, 0.49, 0.54, 1.0), 1.25, 1.25, 1.83 , 3 , 0.68 ),
+(46, "Palladium", "Pd", ( 0.0, 0.41, 0.52, 1.0), 1.28, 1.28, 1.79 , 2 , 0.80 , 4 , 0.65 ),
+(47, "Silver", "Ag", ( 0.75, 0.75, 0.75, 1.0), 1.34, 1.34, 1.75 , 1 , 1.26 , 2 , 0.89 ),
+(48, "Cadmium", "Cd", ( 1.0, 0.85, 0.56, 1.0), 1.48, 1.48, 1.71 , 1 , 1.14 , 2 , 0.97 ),
+(49, "Indium", "In", ( 0.65, 0.45, 0.45, 1.0), 1.44, 1.44, 2.00 , 3 , 0.81 ),
+(50, "Tin", "Sn", ( 0.4, 0.50, 0.50, 1.0), 1.41, 1.41, 1.72 , -4 , 2.94 , -1 , 3.70 , 2 , 0.93 , 4 , 0.71 ),
+(51, "Antimony", "Sb", ( 0.61, 0.38, 0.70, 1.0), 1.40, 1.40, 1.53 , -3 , 2.45 , 3 , 0.76 , 5 , 0.62 ),
+(52, "Tellurium", "Te", ( 0.83, 0.47, 0.0, 1.0), 1.36, 1.36, 1.42 , -2 , 2.11 , -1 , 2.50 , 1 , 0.82 , 4 , 0.70 , 6 , 0.56 ),
+(53, "Iodine", "I", ( 0.58, 0.0, 0.58, 1.0), 1.33, 1.33, 1.32 , -1 , 2.20 , 5 , 0.62 , 7 , 0.50 ),
+(54, "Xenon", "Xe", ( 0.25, 0.61, 0.69, 1.0), 1.31, 1.31, 1.24 ),
+(55, "Caesium", "Cs", ( 0.34, 0.09, 0.56, 1.0), 2.35, 2.35, 3.35 , 1 , 1.67 ),
+(56, "Barium", "Ba", ( 0.0, 0.78, 0.0, 1.0), 1.98, 1.98, 2.78 , 1 , 1.53 , 2 , 1.34 ),
+(57, "Lanthanum", "La", ( 0.43, 0.83, 1.0, 1.0), 1.69, 1.69, 2.74 , 1 , 1.39 , 3 , 1.06 ),
+(58, "Cerium", "Ce", ( 1.0, 1.0, 0.78, 1.0), 1.65, 1.65, 2.70 , 1 , 1.27 , 3 , 1.03 , 4 , 0.92 ),
+(59, "Praseodymium", "Pr", ( 0.85, 1.0, 0.78, 1.0), 1.65, 1.65, 2.67 , 3 , 1.01 , 4 , 0.90 ),
+(60, "Neodymium", "Nd", ( 0.78, 1.0, 0.78, 1.0), 1.64, 1.64, 2.64 , 3 , 0.99 ),
+(61, "Promethium", "Pm", ( 0.63, 1.0, 0.78, 1.0), 1.63, 1.63, 2.62 , 3 , 0.97 ),
+(62, "Samarium", "Sm", ( 0.56, 1.0, 0.78, 1.0), 1.62, 1.62, 2.59 , 3 , 0.96 ),
+(63, "Europium", "Eu", ( 0.38, 1.0, 0.78, 1.0), 1.85, 1.85, 2.56 , 2 , 1.09 , 3 , 0.95 ),
+(64, "Gadolinium", "Gd", ( 0.27, 1.0, 0.78, 1.0), 1.61, 1.61, 2.54 , 3 , 0.93 ),
+(65, "Terbium", "Tb", ( 0.18, 1.0, 0.78, 1.0), 1.59, 1.59, 2.51 , 3 , 0.92 , 4 , 0.84 ),
+(66, "Dysprosium", "Dy", ( 0.12, 1.0, 0.78, 1.0), 1.59, 1.59, 2.49 , 3 , 0.90 ),
+(67, "Holmium", "Ho", ( 0.0, 1.0, 0.61, 1.0), 1.58, 1.58, 2.47 , 3 , 0.89 ),
+(68, "Erbium", "Er", ( 0.0, 0.90, 0.45, 1.0), 1.57, 1.57, 2.45 , 3 , 0.88 ),
+(69, "Thulium", "Tm", ( 0.0, 0.83, 0.32, 1.0), 1.56, 1.56, 2.42 , 3 , 0.87 ),
+(70, "Ytterbium", "Yb", ( 0.0, 0.74, 0.21, 1.0), 1.74, 1.74, 2.40 , 2 , 0.93 , 3 , 0.85 ),
+(71, "Lutetium", "Lu", ( 0.0, 0.67, 0.14, 1.0), 1.56, 1.56, 2.25 , 3 , 0.85 ),
+(72, "Hafnium", "Hf", ( 0.30, 0.76, 1.0, 1.0), 1.44, 1.44, 2.16 , 4 , 0.78 ),
+(73, "Tantalum", "Ta", ( 0.30, 0.65, 1.0, 1.0), 1.34, 1.34, 2.09 , 5 , 0.68 ),
+(74, "Tungsten", "W", ( 0.12, 0.58, 0.83, 1.0), 1.30, 1.30, 2.02 , 4 , 0.70 , 6 , 0.62 ),
+(75, "Rhenium", "Re", ( 0.14, 0.49, 0.67, 1.0), 1.28, 1.28, 1.97 , 4 , 0.72 , 7 , 0.56 ),
+(76, "Osmium", "Os", ( 0.14, 0.4, 0.58, 1.0), 1.26, 1.26, 1.92 , 4 , 0.88 , 6 , 0.69 ),
+(77, "Iridium", "Ir", ( 0.09, 0.32, 0.52, 1.0), 1.27, 1.27, 1.87 , 4 , 0.68 ),
+(78, "Platinum", "Pt", ( 0.81, 0.81, 0.87, 1.0), 1.30, 1.30, 1.83 , 2 , 0.80 , 4 , 0.65 ),
+(79, "Gold", "Au", ( 1.0, 0.81, 0.13, 1.0), 1.34, 1.34, 1.79 , 1 , 1.37 , 3 , 0.85 ),
+(80, "Mercury", "Hg", ( 0.72, 0.72, 0.81, 1.0), 1.49, 1.49, 1.76 , 1 , 1.27 , 2 , 1.10 ),
+(81, "Thallium", "Tl", ( 0.65, 0.32, 0.30, 1.0), 1.48, 1.48, 2.08 , 1 , 1.47 , 3 , 0.95 ),
+(82, "Lead", "Pb", ( 0.34, 0.34, 0.38, 1.0), 1.47, 1.47, 1.81 , 2 , 1.20 , 4 , 0.84 ),
+(83, "Bismuth", "Bi", ( 0.61, 0.30, 0.70, 1.0), 1.46, 1.46, 1.63 , 1 , 0.98 , 3 , 0.96 , 5 , 0.74 ),
+(84, "Polonium", "Po", ( 0.67, 0.36, 0.0, 1.0), 1.46, 1.46, 1.53 , 6 , 0.67 ),
+(85, "Astatine", "At", ( 0.45, 0.30, 0.27, 1.0), 1.45, 1.45, 1.43 , -3 , 2.22 , 3 , 0.85 , 5 , 0.46 ),
+(86, "Radon", "Rn", ( 0.25, 0.50, 0.58, 1.0), 1.00, 1.00, 1.34 ),
+(87, "Francium", "Fr", ( 0.25, 0.0, 0.4, 1.0), 1.00, 1.00, 1.00 , 1 , 1.80 ),
+(88, "Radium", "Ra", ( 0.0, 0.49, 0.0, 1.0), 1.00, 1.00, 1.00 , 2 , 1.43 ),
+(89, "Actinium", "Ac", ( 0.43, 0.67, 0.98, 1.0), 1.00, 1.00, 1.00 , 3 , 1.18 ),
+(90, "Thorium", "Th", ( 0.0, 0.72, 1.0, 1.0), 1.65, 1.65, 1.00 , 4 , 1.02 ),
+(91, "Protactinium", "Pa", ( 0.0, 0.63, 1.0, 1.0), 1.00, 1.00, 1.00 , 3 , 1.13 , 4 , 0.98 , 5 , 0.89 ),
+(92, "Uranium", "U", ( 0.0, 0.56, 1.0, 1.0), 1.42, 1.42, 1.00 , 4 , 0.97 , 6 , 0.80 ),
+(93, "Neptunium", "Np", ( 0.0, 0.50, 1.0, 1.0), 1.00, 1.00, 1.00 , 3 , 1.10 , 4 , 0.95 , 7 , 0.71 ),
+(94, "Plutonium", "Pu", ( 0.0, 0.41, 1.0, 1.0), 1.00, 1.00, 1.00 , 3 , 1.08 , 4 , 0.93 ),
+(95, "Americium", "Am", ( 0.32, 0.36, 0.94, 1.0), 1.00, 1.00, 1.00 , 3 , 1.07 , 4 , 0.92 ),
+(96, "Curium", "Cm", ( 0.47, 0.36, 0.89, 1.0), 1.00, 1.00, 1.00 ),
+(97, "Berkelium", "Bk", ( 0.54, 0.30, 0.89, 1.0), 1.00, 1.00, 1.00 ),
+(98, "Californium", "Cf", ( 0.63, 0.21, 0.83, 1.0), 1.00, 1.00, 1.00 ),
+(99, "Einsteinium", "Es", ( 0.70, 0.12, 0.83, 1.0), 1.00, 1.00, 1.00 ),
+(100, "Fermium", "Fm", ( 0.70, 0.12, 0.72, 1.0), 1.00, 1.00, 1.00 ),
+(101, "Mendelevium", "Md", ( 0.70, 0.05, 0.65, 1.0), 1.00, 1.00, 1.00 ),
+(102, "Nobelium", "No", ( 0.74, 0.05, 0.52, 1.0), 1.00, 1.00, 1.00 ),
+(103, "Lawrencium", "Lr", ( 0.78, 0.0, 0.4, 1.0), 1.00, 1.00, 1.00 ),
+(104, "Vacancy", "Vac", ( 0.5, 0.5, 0.5, 1.0), 1.00, 1.00, 1.00),
+(105, "Default", "Default", ( 1.0, 1.0, 1.0, 1.0), 1.00, 1.00, 1.00),
+(106, "Stick", "Stick", ( 0.5, 0.5, 0.5, 1.0), 1.00, 1.00, 1.00),
+)
+
+# This list here contains all data of the elements and will be used during
+# runtime. It is a list of classes.
+# During executing Atomic Blender, the list will be initialized with the fixed
+# data from above via the class structure below (CLASS_atom_pdb_Elements). We
+# have then one fixed list (above), which will never be changed, and a list of
+# classes with same data. The latter can be modified via loading a separate
+# custom data file.
+ATOM_CLUSTER_ELEMENTS = []
+ATOM_CLUSTER_ALL_ATOMS = []
+
+# This is the class, which stores the properties for one element.
+class CLASS_atom_cluster_Elements(object):
+ __slots__ = ('number', 'name', 'short_name', 'color', 'radii', 'radii_ionic')
+ def __init__(self, number, name, short_name, color, radii, radii_ionic):
+ self.number = number
+ self.name = name
+ self.short_name = short_name
+ self.color = color
+ self.radii = radii
+ self.radii_ionic = radii_ionic
+
+# This is the class, which stores the properties of one atom.
+class CLASS_atom_cluster_atom(object):
+ __slots__ = ('location')
+ def __init__(self, location):
+ self.location = location
+
+# -----------------------------------------------------------------------------
+# Read atom data
+
+def DEF_atom_read_atom_data():
+
+ del ATOM_CLUSTER_ELEMENTS[:]
+
+ for item in ATOM_CLUSTER_ELEMENTS_DEFAULT:
+
+ # All three radii into a list
+ radii = [item[4],item[5],item[6]]
+ # The handling of the ionic radii will be done later. So far, it is an
+ # empty list.
+ radii_ionic = []
+
+ li = CLASS_atom_cluster_Elements(item[0],item[1],item[2],item[3],
+ radii,radii_ionic)
+ ATOM_CLUSTER_ELEMENTS.append(li)
+
+
+# -----------------------------------------------------------------------------
+# Routines for shapes
+
+def vec_in_sphere(atom_pos,size, skin):
+
+ regular = True
+ inner = True
+
+ if atom_pos.length > size/2.0:
+ regular = False
+
+ if atom_pos.length < (size/2.0)*(1-skin):
+ inner = False
+
+ return (regular, inner)
+
+
+def vec_in_parabole(atom_pos, height, diameter):
+
+ regular = True
+ inner = True
+
+ px = atom_pos[0]
+ py = atom_pos[1]
+ pz = atom_pos[2] + height/2.0
+
+ a = diameter / sqrt(4 * height)
+
+
+ if pz < 0.0:
+ return (False, False)
+ if px == 0.0 and py == 0.0:
+ return (True, True)
+
+ if py == 0.0:
+ y = 0.0
+ x = a * a * pz / px
+ z = x * x / (a * a)
+ else:
+ y = pz * py * a * a / (px*px + py*py)
+ x = y * px / py
+ z = (x*x + y*y) / (a * a)
+
+ if( atom_pos.length > sqrt(x*x+y*y+z*z) ):
+ regular = False
+
+ return (regular, inner)
+
+
+def vec_in_pyramide_square(atom_pos, size, skin):
+
+ """
+ Please, if possible leave all this! The code documents the
+ mathemetical way of cutting a pyramide with square base.
+
+ P1 = Vector((-size/2, 0.0, -size/4))
+ P2 = Vector((0.0, -size/2, -size/4))
+ P4 = Vector((size/2, 0.0, -size/4))
+ P5 = Vector((0.0, size/2, -size/4))
+ P6 = Vector((0.0, 0.0, size/4))
+
+ # First face
+ v11 = P1 - P2
+ v12 = P1 - P6
+ n1 = v11.cross(v12)
+ g1 = -n1 * P1
+
+ # Second face
+ v21 = P6 - P4
+ v22 = P6 - P5
+ n2 = v21.cross(v22)
+ g2 = -n2 * P6
+
+ # Third face
+ v31 = P1 - P5
+ v32 = P1 - P6
+ n3 = v32.cross(v31)
+ g3 = -n3 * P1
+
+ # Forth face
+ v41 = P6 - P2
+ v42 = P2 - P4
+ n4 = v41.cross(v42)
+ g4 = -n4 * P2
+
+ # Fith face, base
+ v51 = P2 - P1
+ v52 = P2 - P4
+ n5 = v51.cross(v52)
+ g5 = -n5 * P2
+ """
+
+ # A much faster way for calculation:
+ size2 = size * size
+ size3 = size2 * size
+ n1 = Vector((-1/4, -1/4, 1/4)) * size2
+ g1 = -1/16 * size3
+ n2 = Vector(( 1/4, 1/4, 1/4)) * size2
+ g2 = g1
+ n3 = Vector((-1/4, 1/4, 1/4)) * size2
+ g3 = g1
+ n4 = Vector(( 1/4, -1/4, 1/4)) * size2
+ g4 = g1
+ n5 = Vector(( 0.0, 0.0, -1/2)) * size2
+ g5 = -1/8 * size3
+
+ distance_plane_1 = abs((n1 @ atom_pos - g1)/n1.length)
+ on_plane_1 = (atom_pos - n1 * (distance_plane_1/n1.length)).length
+ distance_plane_2 = abs((n2 @ atom_pos - g2)/n2.length)
+ on_plane_2 = (atom_pos - n2 * (distance_plane_2/n2.length)).length
+ distance_plane_3 = abs((n3 @ atom_pos - g3)/n3.length)
+ on_plane_3 = (atom_pos - n3 * (distance_plane_3/n3.length)).length
+ distance_plane_4 = abs((n4 @ atom_pos - g4)/n4.length)
+ on_plane_4 = (atom_pos - n4 * (distance_plane_4/n4.length)).length
+ distance_plane_5 = abs((n5 @ atom_pos - g5)/n5.length)
+ on_plane_5 = (atom_pos - n5 * (distance_plane_5/n5.length)).length
+
+ regular = True
+ inner = True
+ if(atom_pos.length > on_plane_1):
+ regular = False
+ if(atom_pos.length > on_plane_2):
+ regular = False
+ if(atom_pos.length > on_plane_3):
+ regular = False
+ if(atom_pos.length > on_plane_4):
+ regular = False
+ if(atom_pos.length > on_plane_5):
+ regular = False
+
+ if skin == 1.0:
+ return (regular, inner)
+
+ size = size * (1.0 - skin)
+
+ size2 = size * size
+ size3 = size2 * size
+ n1 = Vector((-1/4, -1/4, 1/4)) * size2
+ g1 = -1/16 * size3
+ n2 = Vector(( 1/4, 1/4, 1/4)) * size2
+ g2 = g1
+ n3 = Vector((-1/4, 1/4, 1/4)) * size2
+ g3 = g1
+ n4 = Vector(( 1/4, -1/4, 1/4)) * size2
+ g4 = g1
+ n5 = Vector(( 0.0, 0.0, -1/2)) * size2
+ g5 = -1/8 * size3
+
+ distance_plane_1 = abs((n1 @ atom_pos - g1)/n1.length)
+ on_plane_1 = (atom_pos - n1 * (distance_plane_1/n1.length)).length
+ distance_plane_2 = abs((n2 @ atom_pos - g2)/n2.length)
+ on_plane_2 = (atom_pos - n2 * (distance_plane_2/n2.length)).length
+ distance_plane_3 = abs((n3 @ atom_pos - g3)/n3.length)
+ on_plane_3 = (atom_pos - n3 * (distance_plane_3/n3.length)).length
+ distance_plane_4 = abs((n4 @ atom_pos - g4)/n4.length)
+ on_plane_4 = (atom_pos - n4 * (distance_plane_4/n4.length)).length
+ distance_plane_5 = abs((n5 @ atom_pos - g5)/n5.length)
+ on_plane_5 = (atom_pos - n5 * (distance_plane_5/n5.length)).length
+
+ inner = False
+ if(atom_pos.length > on_plane_1):
+ inner = True
+ if(atom_pos.length > on_plane_2):
+ inner = True
+ if(atom_pos.length > on_plane_3):
+ inner = True
+ if(atom_pos.length > on_plane_4):
+ inner = True
+ if(atom_pos.length > on_plane_5):
+ inner = True
+
+ return (regular, inner)
+
+
+def vec_in_pyramide_hex_abc(atom_pos, size, skin):
+
+ a = size/2.0
+ #c = size/2.0*cos((30/360)*2.0*pi)
+ c = size * 0.4330127020
+ #s = size/2.0*sin((30/360)*2.0*pi)
+ s = size * 0.25
+ #h = 2.0 * (sqrt(6.0)/3.0) * c
+ h = 1.632993162 * c
+
+ """
+ Please, if possible leave all this! The code documents the
+ mathemetical way of cutting a tetraeder.
+
+ P1 = Vector((0.0, a, 0.0))
+ P2 = Vector(( -c, -s, 0.0))
+ P3 = Vector(( c, -s, 0.0))
+ P4 = Vector((0.0, 0.0, h))
+ C = (P1+P2+P3+P4)/4.0
+ P1 = P1 - C
+ P2 = P2 - C
+ P3 = P3 - C
+ P4 = P4 - C
+
+ # First face
+ v11 = P1 - P2
+ v12 = P1 - P4
+ n1 = v11.cross(v12)
+ g1 = -n1 * P1
+
+ # Second face
+ v21 = P2 - P3
+ v22 = P2 - P4
+ n2 = v21.cross(v22)
+ g2 = -n2 * P2
+
+ # Third face
+ v31 = P3 - P1
+ v32 = P3 - P4
+ n3 = v31.cross(v32)
+ g3 = -n3 * P3
+
+ # Forth face
+ v41 = P2 - P1
+ v42 = P2 - P3
+ n4 = v41.cross(v42)
+ g4 = -n4 * P1
+ """
+
+ n1 = Vector(( -h*(a+s), c*h, c*a ))
+ g1 = -1/2*c*(a*h+s*h)
+ n2 = Vector(( 0, -2*c*h, 2*c*s ))
+ g2 = -1/2*c*(a*h+s*h)
+ n3 = Vector(( h*(a+s), c*h, a*c ))
+ g3 = -1/2*c*(a*h+s*h)
+ n4 = Vector(( 0, 0, -2*c*(s+a) ))
+ g4 = -1/2*h*c*(s+a)
+
+ distance_plane_1 = abs((n1 @ atom_pos - g1)/n1.length)
+ on_plane_1 = (atom_pos - n1 * (distance_plane_1/n1.length)).length
+ distance_plane_2 = abs((n2 @ atom_pos - g2)/n2.length)
+ on_plane_2 = (atom_pos - n2 * (distance_plane_2/n2.length)).length
+ distance_plane_3 = abs((n3 @ atom_pos - g3)/n3.length)
+ on_plane_3 = (atom_pos - n3 * (distance_plane_3/n3.length)).length
+ distance_plane_4 = abs((n4 @ atom_pos - g4)/n4.length)
+ on_plane_4 = (atom_pos - n4 * (distance_plane_4/n4.length)).length
+
+ regular = True
+ inner = True
+ if(atom_pos.length > on_plane_1):
+ regular = False
+ if(atom_pos.length > on_plane_2):
+ regular = False
+ if(atom_pos.length > on_plane_3):
+ regular = False
+ if(atom_pos.length > on_plane_4):
+ regular = False
+
+ if skin == 1.0:
+ return (regular, inner)
+
+ size = size * (1.0 - skin)
+
+ a = size/2.0
+ #c = size/2.0*cos((30/360)*2.0*pi)
+ c= size * 0.4330127020
+ #s = size/2.0*sin((30/360)*2.0*pi)
+ s = size * 0.25
+ #h = 2.0 * (sqrt(6.0)/3.0) * c
+ h = 1.632993162 * c
+
+ n1 = Vector(( -h*(a+s), c*h, c*a ))
+ g1 = -1/2*c*(a*h+s*h)
+ n2 = Vector(( 0, -2*c*h, 2*c*s ))
+ g2 = -1/2*c*(a*h+s*h)
+ n3 = Vector(( h*(a+s), c*h, a*c ))
+ g3 = -1/2*c*(a*h+s*h)
+ n4 = Vector(( 0, 0, -2*c*(s+a) ))
+ g4 = -1/2*h*c*(s+a)
+
+ distance_plane_1 = abs((n1 @ atom_pos - g1)/n1.length)
+ on_plane_1 = (atom_pos - n1 * (distance_plane_1/n1.length)).length
+ distance_plane_2 = abs((n2 @ atom_pos - g2)/n2.length)
+ on_plane_2 = (atom_pos - n2 * (distance_plane_2/n2.length)).length
+ distance_plane_3 = abs((n3 @ atom_pos - g3)/n3.length)
+ on_plane_3 = (atom_pos - n3 * (distance_plane_3/n3.length)).length
+ distance_plane_4 = abs((n4 @ atom_pos - g4)/n4.length)
+ on_plane_4 = (atom_pos - n4 * (distance_plane_4/n4.length)).length
+
+ inner = False
+ if(atom_pos.length > on_plane_1):
+ inner = True
+ if(atom_pos.length > on_plane_2):
+ inner = True
+ if(atom_pos.length > on_plane_3):
+ inner = True
+ if(atom_pos.length > on_plane_4):
+ inner = True
+
+ return (regular, inner)
+
+
+
+def vec_in_octahedron(atom_pos,size, skin):
+
+ regular = True
+ inner = True
+
+ """
+ Please, if possible leave all this! The code documents the
+ mathemetical way of cutting an octahedron.
+
+ P1 = Vector((-size/2, 0.0, 0.0))
+ P2 = Vector((0.0, -size/2, 0.0))
+ P3 = Vector((0.0, 0.0, -size/2))
+ P4 = Vector((size/2, 0.0, 0.0))
+ P5 = Vector((0.0, size/2, 0.0))
+ P6 = Vector((0.0, 0.0, size/2))
+
+ # First face
+ v11 = P2 - P1
+ v12 = P2 - P3
+ n1 = v11.cross(v12)
+ g1 = -n1 * P2
+
+ # Second face
+ v21 = P1 - P5
+ v22 = P1 - P3
+ n2 = v21.cross(v22)
+ g2 = -n2 * P1
+
+ # Third face
+ v31 = P1 - P2
+ v32 = P1 - P6
+ n3 = v31.cross(v32)
+ g3 = -n3 * P1
+
+ # Forth face
+ v41 = P6 - P2
+ v42 = P2 - P4
+ n4 = v41.cross(v42)
+ g4 = -n4 * P2
+
+ # Fith face
+ v51 = P2 - P3
+ v52 = P2 - P4
+ n5 = v51.cross(v52)
+ g5 = -n5 * P2
+
+ # Six face
+ v61 = P6 - P4
+ v62 = P6 - P5
+ n6 = v61.cross(v62)
+ g6 = -n6 * P6
+
+ # Seventh face
+ v71 = P5 - P4
+ v72 = P5 - P3
+ n7 = v71.cross(v72)
+ g7 = -n7 * P5
+
+ # Eigth face
+ v81 = P1 - P5
+ v82 = P1 - P6
+ n8 = v82.cross(v81)
+ g8 = -n8 * P1
+ """
+
+ # A much faster way for calculation:
+ size2 = size * size
+ size3 = size2 * size
+ n1 = Vector((-1/4, -1/4, -1/4)) * size2
+ g1 = -1/8 * size3
+ n2 = Vector((-1/4, 1/4, -1/4)) * size2
+ g2 = g1
+ n3 = Vector((-1/4, -1/4, 1/4)) * size2
+ g3 = g1
+ n4 = Vector(( 1/4, -1/4, 1/4)) * size2
+ g4 = g1
+ n5 = Vector(( 1/4, -1/4, -1/4)) * size2
+ g5 = g1
+ n6 = Vector(( 1/4, 1/4, 1/4)) * size2
+ g6 = g1
+ n7 = Vector(( 1/4, 1/4, -1/4)) * size2
+ g7 = g1
+ n8 = Vector((-1/4, 1/4, 1/4)) * size2
+ g8 = g1
+
+ distance_plane_1 = abs((n1 @ atom_pos - g1)/n1.length)
+ on_plane_1 = (atom_pos - n1 * (distance_plane_1/n1.length)).length
+ distance_plane_2 = abs((n2 @ atom_pos - g2)/n2.length)
+ on_plane_2 = (atom_pos - n2 * (distance_plane_2/n2.length)).length
+ distance_plane_3 = abs((n3 @ atom_pos - g3)/n3.length)
+ on_plane_3 = (atom_pos - n3 * (distance_plane_3/n3.length)).length
+ distance_plane_4 = abs((n4 @ atom_pos - g4)/n4.length)
+ on_plane_4 = (atom_pos - n4 * (distance_plane_4/n4.length)).length
+ distance_plane_5 = abs((n5 @ atom_pos - g5)/n5.length)
+ on_plane_5 = (atom_pos - n5 * (distance_plane_5/n5.length)).length
+ distance_plane_6 = abs((n6 @ atom_pos - g6)/n6.length)
+ on_plane_6 = (atom_pos - n6 * (distance_plane_6/n6.length)).length
+ distance_plane_7 = abs((n7 @ atom_pos - g7)/n7.length)
+ on_plane_7 = (atom_pos - n7 * (distance_plane_7/n7.length)).length
+ distance_plane_8 = abs((n8 @ atom_pos - g8)/n8.length)
+ on_plane_8 = (atom_pos - n8 * (distance_plane_8/n8.length)).length
+
+ if(atom_pos.length > on_plane_1):
+ regular = False
+ if(atom_pos.length > on_plane_2):
+ regular = False
+ if(atom_pos.length > on_plane_3):
+ regular = False
+ if(atom_pos.length > on_plane_4):
+ regular = False
+ if(atom_pos.length > on_plane_5):
+ regular = False
+ if(atom_pos.length > on_plane_6):
+ regular = False
+ if(atom_pos.length > on_plane_7):
+ regular = False
+ if(atom_pos.length > on_plane_8):
+ regular = False
+
+ if skin == 1.0:
+ return (regular, inner)
+
+ size = size * (1.0 - skin)
+
+ size2 = size * size
+ size3 = size2 * size
+ n1 = Vector((-1/4, -1/4, -1/4)) * size2
+ g1 = -1/8 * size3
+ n2 = Vector((-1/4, 1/4, -1/4)) * size2
+ g2 = g1
+ n3 = Vector((-1/4, -1/4, 1/4)) * size2
+ g3 = g1
+ n4 = Vector(( 1/4, -1/4, 1/4)) * size2
+ g4 = g1
+ n5 = Vector(( 1/4, -1/4, -1/4)) * size2
+ g5 = g1
+ n6 = Vector(( 1/4, 1/4, 1/4)) * size2
+ g6 = g1
+ n7 = Vector(( 1/4, 1/4, -1/4)) * size2
+ g7 = g1
+ n8 = Vector((-1/4, 1/4, 1/4)) * size2
+ g8 = g1
+
+ distance_plane_1 = abs((n1 @ atom_pos - g1)/n1.length)
+ on_plane_1 = (atom_pos - n1 * (distance_plane_1/n1.length)).length
+ distance_plane_2 = abs((n2 @ atom_pos - g2)/n2.length)
+ on_plane_2 = (atom_pos - n2 * (distance_plane_2/n2.length)).length
+ distance_plane_3 = abs((n3 @ atom_pos - g3)/n3.length)
+ on_plane_3 = (atom_pos - n3 * (distance_plane_3/n3.length)).length
+ distance_plane_4 = abs((n4 @ atom_pos - g4)/n4.length)
+ on_plane_4 = (atom_pos - n4 * (distance_plane_4/n4.length)).length
+ distance_plane_5 = abs((n5 @ atom_pos - g5)/n5.length)
+ on_plane_5 = (atom_pos - n5 * (distance_plane_5/n5.length)).length
+ distance_plane_6 = abs((n6 @ atom_pos - g6)/n6.length)
+ on_plane_6 = (atom_pos - n6 * (distance_plane_6/n6.length)).length
+ distance_plane_7 = abs((n7 @ atom_pos - g7)/n7.length)
+ on_plane_7 = (atom_pos - n7 * (distance_plane_7/n7.length)).length
+ distance_plane_8 = abs((n8 @ atom_pos - g8)/n8.length)
+ on_plane_8 = (atom_pos - n8 * (distance_plane_8/n8.length)).length
+
+ inner = False
+ if(atom_pos.length > on_plane_1):
+ inner = True
+ if(atom_pos.length > on_plane_2):
+ inner = True
+ if(atom_pos.length > on_plane_3):
+ inner = True
+ if(atom_pos.length > on_plane_4):
+ inner = True
+ if(atom_pos.length > on_plane_5):
+ inner = True
+ if(atom_pos.length > on_plane_6):
+ inner = True
+ if(atom_pos.length > on_plane_7):
+ inner = True
+ if(atom_pos.length > on_plane_8):
+ inner = True
+
+ return (regular, inner)
+
+
+def vec_in_truncated_octahedron(atom_pos,size, skin):
+
+ regular = True
+ inner = True
+
+ # The normal octahedron
+ size2 = size * size
+ size3 = size2 * size
+ n1 = Vector((-1/4, -1/4, -1/4)) * size2
+ g1 = -1/8 * size3
+ n2 = Vector((-1/4, 1/4, -1/4)) * size2
+ g2 = g1
+ n3 = Vector((-1/4, -1/4, 1/4)) * size2
+ g3 = g1
+ n4 = Vector(( 1/4, -1/4, 1/4)) * size2
+ g4 = g1
+ n5 = Vector(( 1/4, -1/4, -1/4)) * size2
+ g5 = g1
+ n6 = Vector(( 1/4, 1/4, 1/4)) * size2
+ g6 = g1
+ n7 = Vector(( 1/4, 1/4, -1/4)) * size2
+ g7 = g1
+ n8 = Vector((-1/4, 1/4, 1/4)) * size2
+ g8 = g1
+
+ distance_plane_1 = abs((n1 @ atom_pos - g1)/n1.length)
+ on_plane_1 = (atom_pos - n1 * (distance_plane_1/n1.length)).length
+ distance_plane_2 = abs((n2 @ atom_pos - g2)/n2.length)
+ on_plane_2 = (atom_pos - n2 * (distance_plane_2/n2.length)).length
+ distance_plane_3 = abs((n3 @ atom_pos - g3)/n3.length)
+ on_plane_3 = (atom_pos - n3 * (distance_plane_3/n3.length)).length
+ distance_plane_4 = abs((n4 @ atom_pos - g4)/n4.length)
+ on_plane_4 = (atom_pos - n4 * (distance_plane_4/n4.length)).length
+ distance_plane_5 = abs((n5 @ atom_pos - g5)/n5.length)
+ on_plane_5 = (atom_pos - n5 * (distance_plane_5/n5.length)).length
+ distance_plane_6 = abs((n6 @ atom_pos - g6)/n6.length)
+ on_plane_6 = (atom_pos - n6 * (distance_plane_6/n6.length)).length
+ distance_plane_7 = abs((n7 @ atom_pos - g7)/n7.length)
+ on_plane_7 = (atom_pos - n7 * (distance_plane_7/n7.length)).length
+ distance_plane_8 = abs((n8 @ atom_pos - g8)/n8.length)
+ on_plane_8 = (atom_pos - n8 * (distance_plane_8/n8.length)).length
+
+ # Here are the 6 additional faces
+ # pp = (size/2.0) - (sqrt(2.0)/2.0) * ((size/sqrt(2.0))/3.0)
+ pp = size / 3.0
+
+ n_1 = Vector((1.0,0.0,0.0))
+ n_2 = Vector((-1.0,0.0,0.0))
+ n_3 = Vector((0.0,1.0,0.0))
+ n_4 = Vector((0.0,-1.0,0.0))
+ n_5 = Vector((0.0,0.0,1.0))
+ n_6 = Vector((0.0,0.0,-1.0))
+
+ distance_plane_1b = abs((n_1 @ atom_pos + pp)/n_1.length)
+ on_plane_1b = (atom_pos - n_1 * (distance_plane_1b/n_1.length)).length
+ distance_plane_2b = abs((n_2 @ atom_pos + pp)/n_2.length)
+ on_plane_2b = (atom_pos - n_2 * (distance_plane_2b/n_2.length)).length
+ distance_plane_3b = abs((n_3 @ atom_pos + pp)/n_3.length)
+ on_plane_3b = (atom_pos - n_3 * (distance_plane_3b/n_3.length)).length
+ distance_plane_4b = abs((n_4 @ atom_pos + pp)/n_4.length)
+ on_plane_4b = (atom_pos - n_4 * (distance_plane_4b/n_4.length)).length
+ distance_plane_5b = abs((n_5 @ atom_pos + pp)/n_5.length)
+ on_plane_5b = (atom_pos - n_5 * (distance_plane_5b/n_5.length)).length
+ distance_plane_6b = abs((n_6 @ atom_pos + pp)/n_6.length)
+ on_plane_6b = (atom_pos - n_6 * (distance_plane_6b/n_6.length)).length
+
+ if(atom_pos.length > on_plane_1):
+ regular = False
+ if(atom_pos.length > on_plane_2):
+ regular = False
+ if(atom_pos.length > on_plane_3):
+ regular = False
+ if(atom_pos.length > on_plane_4):
+ regular = False
+ if(atom_pos.length > on_plane_5):
+ regular = False
+ if(atom_pos.length > on_plane_6):
+ regular = False
+ if(atom_pos.length > on_plane_7):
+ regular = False
+ if(atom_pos.length > on_plane_8):
+ regular = False
+ if(atom_pos.length > on_plane_1b):
+ regular = False
+ if(atom_pos.length > on_plane_2b):
+ regular = False
+ if(atom_pos.length > on_plane_3b):
+ regular = False
+ if(atom_pos.length > on_plane_4b):
+ regular = False
+ if(atom_pos.length > on_plane_5b):
+ regular = False
+ if(atom_pos.length > on_plane_6b):
+ regular = False
+
+ if skin == 1.0:
+ return (regular, inner)
+
+ size = size * (1.0 - skin)
+
+ # The normal octahedron
+ size2 = size * size
+ size3 = size2 * size
+ n1 = Vector((-1/4, -1/4, -1/4)) * size2
+ g1 = -1/8 * size3
+ n2 = Vector((-1/4, 1/4, -1/4)) * size2
+ g2 = g1
+ n3 = Vector((-1/4, -1/4, 1/4)) * size2
+ g3 = g1
+ n4 = Vector(( 1/4, -1/4, 1/4)) * size2
+ g4 = g1
+ n5 = Vector(( 1/4, -1/4, -1/4)) * size2
+ g5 = g1
+ n6 = Vector(( 1/4, 1/4, 1/4)) * size2
+ g6 = g1
+ n7 = Vector(( 1/4, 1/4, -1/4)) * size2
+ g7 = g1
+ n8 = Vector((-1/4, 1/4, 1/4)) * size2
+ g8 = g1
+
+ distance_plane_1 = abs((n1 @ atom_pos - g1)/n1.length)
+ on_plane_1 = (atom_pos - n1 * (distance_plane_1/n1.length)).length
+ distance_plane_2 = abs((n2 @ atom_pos - g2)/n2.length)
+ on_plane_2 = (atom_pos - n2 * (distance_plane_2/n2.length)).length
+ distance_plane_3 = abs((n3 @ atom_pos - g3)/n3.length)
+ on_plane_3 = (atom_pos - n3 * (distance_plane_3/n3.length)).length
+ distance_plane_4 = abs((n4 @ atom_pos - g4)/n4.length)
+ on_plane_4 = (atom_pos - n4 * (distance_plane_4/n4.length)).length
+ distance_plane_5 = abs((n5 @ atom_pos - g5)/n5.length)
+ on_plane_5 = (atom_pos - n5 * (distance_plane_5/n5.length)).length
+ distance_plane_6 = abs((n6 @ atom_pos - g6)/n6.length)
+ on_plane_6 = (atom_pos - n6 * (distance_plane_6/n6.length)).length
+ distance_plane_7 = abs((n7 @ atom_pos - g7)/n7.length)
+ on_plane_7 = (atom_pos - n7 * (distance_plane_7/n7.length)).length
+ distance_plane_8 = abs((n8 @ atom_pos - g8)/n8.length)
+ on_plane_8 = (atom_pos - n8 * (distance_plane_8/n8.length)).length
+
+ # Here are the 6 additional faces
+ # pp = (size/2.0) - (sqrt(2.0)/2.0) * ((size/sqrt(2.0))/3.0)
+ pp = size / 3.0
+
+ n_1 = Vector((1.0,0.0,0.0))
+ n_2 = Vector((-1.0,0.0,0.0))
+ n_3 = Vector((0.0,1.0,0.0))
+ n_4 = Vector((0.0,-1.0,0.0))
+ n_5 = Vector((0.0,0.0,1.0))
+ n_6 = Vector((0.0,0.0,-1.0))
+
+ distance_plane_1b = abs((n_1 @ atom_pos + pp)/n_1.length)
+ on_plane_1b = (atom_pos - n_1 * (distance_plane_1b/n_1.length)).length
+ distance_plane_2b = abs((n_2 @ atom_pos + pp)/n_2.length)
+ on_plane_2b = (atom_pos - n_2 * (distance_plane_2b/n_2.length)).length
+ distance_plane_3b = abs((n_3 @ atom_pos + pp)/n_3.length)
+ on_plane_3b = (atom_pos - n_3 * (distance_plane_3b/n_3.length)).length
+ distance_plane_4b = abs((n_4 @ atom_pos + pp)/n_4.length)
+ on_plane_4b = (atom_pos - n_4 * (distance_plane_4b/n_4.length)).length
+ distance_plane_5b = abs((n_5 @ atom_pos + pp)/n_5.length)
+ on_plane_5b = (atom_pos - n_5 * (distance_plane_5b/n_5.length)).length
+ distance_plane_6b = abs((n_6 @ atom_pos + pp)/n_6.length)
+ on_plane_6b = (atom_pos - n_6 * (distance_plane_6b/n_6.length)).length
+
+ inner = False
+
+ if(atom_pos.length > on_plane_1):
+ inner = True
+ if(atom_pos.length > on_plane_2):
+ inner = True
+ if(atom_pos.length > on_plane_3):
+ inner = True
+ if(atom_pos.length > on_plane_4):
+ inner = True
+ if(atom_pos.length > on_plane_5):
+ inner = True
+ if(atom_pos.length > on_plane_6):
+ inner = True
+ if(atom_pos.length > on_plane_7):
+ inner = True
+ if(atom_pos.length > on_plane_8):
+ inner = True
+ if(atom_pos.length > on_plane_1b):
+ inner = True
+ if(atom_pos.length > on_plane_2b):
+ inner = True
+ if(atom_pos.length > on_plane_3b):
+ inner = True
+ if(atom_pos.length > on_plane_4b):
+ inner = True
+ if(atom_pos.length > on_plane_5b):
+ inner = True
+ if(atom_pos.length > on_plane_6b):
+ inner = True
+
+ return (regular, inner)
+
+# -----------------------------------------------------------------------------
+# Routines for lattices
+
+def create_hexagonal_abcabc_lattice(ctype, size, skin, lattice):
+
+ atom_number_total = 0
+ atom_number_drawn = 0
+ y_displ = 0
+ z_displ = 0
+
+ """
+ e = (1/sqrt(2.0)) * lattice
+ f = sqrt(3.0/4.0) * e
+ df1 = (e/2.0) * tan((30.0/360.0)*2.0*pi)
+ df2 = (e/2.0) / cos((30.0/360.0)*2.0*pi)
+ g = sqrt(2.0/3.0) * e
+ """
+
+ e = 0.7071067810 * lattice
+ f = 0.8660254038 * e
+ df1 = 0.2886751348 * e
+ df2 = 0.5773502690 * e
+ g = 0.8164965810 * e
+
+ if ctype == "parabolid_abc":
+ # size = height, skin = diameter
+ number_x = int(skin/(2*e))+4
+ number_y = int(skin/(2*f))+4
+ number_z = int(size/(2*g))
+ else:
+ number_x = int(size/(2*e))+4
+ number_y = int(size/(2*f))+4
+ number_z = int(size/(2*g))+1+4
+
+
+ for k in range(-number_z,number_z+1):
+ for j in range(-number_y,number_y+1):
+ for i in range(-number_x,number_x+1):
+ atom = Vector((float(i)*e,float(j)*f,float(k)*g))
+
+ if y_displ == 1:
+ if z_displ == 1:
+ atom[0] += e/2.0
+ else:
+ atom[0] -= e/2.0
+ if z_displ == 1:
+ atom[0] -= e/2.0
+ atom[1] += df1
+ if z_displ == 2:
+ atom[0] += 0.0
+ atom[1] += df2
+
+ if ctype == "sphere_hex_abc":
+ message = vec_in_sphere(atom, size, skin)
+ elif ctype == "pyramide_hex_abc":
+ # size = height, skin = diameter
+ message = vec_in_pyramide_hex_abc(atom, size, skin)
+ elif ctype == "parabolid_abc":
+ message = vec_in_parabole(atom, size, skin)
+
+ if message[0] == True and message[1] == True:
+ atom_add = CLASS_atom_cluster_atom(atom)
+ ATOM_CLUSTER_ALL_ATOMS.append(atom_add)
+ atom_number_total += 1
+ atom_number_drawn += 1
+ if message[0] == True and message[1] == False:
+ atom_number_total += 1
+
+ if y_displ == 1:
+ y_displ = 0
+ else:
+ y_displ = 1
+
+ y_displ = 0
+ if z_displ == 0:
+ z_displ = 1
+ elif z_displ == 1:
+ z_displ = 2
+ else:
+ z_displ = 0
+
+ print("Atom positions calculated")
+
+ return (atom_number_total, atom_number_drawn)
+
+
+def create_hexagonal_abab_lattice(ctype, size, skin, lattice):
+
+ atom_number_total = 0
+ atom_number_drawn = 0
+ y_displ = "even"
+ z_displ = "even"
+
+ """
+ e = (1/sqrt(2.0)) * lattice
+ f = sqrt(3.0/4.0) * e
+ df = (e/2.0) * tan((30.0/360.0)*2*pi)
+ g = sqrt(2.0/3.0) * e
+ """
+
+ e = 0.7071067814 * lattice
+ f = 0.8660254038 * e
+ df = 0.2886751348 * e
+ g = 0.8164965810 * e
+
+
+ if ctype == "parabolid_ab":
+ # size = height, skin = diameter
+ number_x = int(skin/(2*e))+4
+ number_y = int(skin/(2*f))+4
+ number_z = int(size/(2*g))
+ else:
+ number_x = int(size/(2*e))+4
+ number_y = int(size/(2*f))+4
+ number_z = int(size/(2*g))+1+4
+
+
+ for k in range(-number_z,number_z+1):
+ for j in range(-number_y,number_y+1):
+ for i in range(-number_x,number_x+1):
+
+ atom = Vector((float(i)*e,float(j)*f,float(k)*g))
+
+ if "odd" in y_displ:
+ if "odd" in z_displ:
+ atom[0] += e/2.0
+ else:
+ atom[0] -= e/2.0
+ if "odd" in z_displ:
+ atom[0] -= e/2.0
+ atom[1] += df
+
+ if ctype == "sphere_hex_ab":
+ message = vec_in_sphere(atom, size, skin)
+ elif ctype == "parabolid_ab":
+ # size = height, skin = diameter
+ message = vec_in_parabole(atom, size, skin)
+
+ if message[0] == True and message[1] == True:
+ atom_add = CLASS_atom_cluster_atom(atom)
+ ATOM_CLUSTER_ALL_ATOMS.append(atom_add)
+ atom_number_total += 1
+ atom_number_drawn += 1
+ if message[0] == True and message[1] == False:
+ atom_number_total += 1
+
+ if "even" in y_displ:
+ y_displ = "odd"
+ else:
+ y_displ = "even"
+
+ y_displ = "even"
+ if "even" in z_displ:
+ z_displ = "odd"
+ else:
+ z_displ = "even"
+
+ print("Atom positions calculated")
+
+ return (atom_number_total, atom_number_drawn)
+
+
+def create_square_lattice(ctype, size, skin, lattice):
+
+ atom_number_total = 0
+ atom_number_drawn = 0
+
+ if ctype == "parabolid_square":
+ # size = height, skin = diameter
+ number_k = int(size/(2.0*lattice))
+ number_j = int(skin/(2.0*lattice)) + 5
+ number_i = int(skin/(2.0*lattice)) + 5
+ else:
+ number_k = int(size/(2.0*lattice))
+ number_j = int(size/(2.0*lattice))
+ number_i = int(size/(2.0*lattice))
+
+
+ for k in range(-number_k,number_k+1):
+ for j in range(-number_j,number_j+1):
+ for i in range(-number_i,number_i+1):
+
+ atom = Vector((float(i),float(j),float(k))) * lattice
+
+ if ctype == "sphere_square":
+ message = vec_in_sphere(atom, size, skin)
+ elif ctype == "pyramide_square":
+ message = vec_in_pyramide_square(atom, size, skin)
+ elif ctype == "parabolid_square":
+ # size = height, skin = diameter
+ message = vec_in_parabole(atom, size, skin)
+ elif ctype == "octahedron":
+ message = vec_in_octahedron(atom, size, skin)
+ elif ctype == "truncated_octahedron":
+ message = vec_in_truncated_octahedron(atom,size, skin)
+
+ if message[0] == True and message[1] == True:
+ atom_add = CLASS_atom_cluster_atom(atom)
+ ATOM_CLUSTER_ALL_ATOMS.append(atom_add)
+ atom_number_total += 1
+ atom_number_drawn += 1
+ if message[0] == True and message[1] == False:
+ atom_number_total += 1
+
+ print("Atom positions calculated")
+
+ return (atom_number_total, atom_number_drawn)
+
+
+
+# -----------------------------------------------------------------------------
+# Routine for the icosahedron
+
+
+# Note that the icosahedron needs a special treatment since it requires a
+# non-common crystal lattice. The faces are (111) facets and the geometry
+# is five-fold. So far, a max size of 8217 atoms can be chosen.
+# More details about icosahedron shaped clusters can be found in:
+#
+# 1. C. Mottet, G. Tréglia, B. Legrand, Surface Science 383 (1997) L719-L727
+# 2. C. R. Henry, Surface Science Reports 31 (1998) 231-325
+
+# The following code is a translation from an existing Fortran code into Python.
+# The Fortran code has been created by Christine Mottet and translated by me
+# (Clemens Barth).
+
+# Although a couple of code lines are non-typical for Python, it is best to
+# leave the code as is.
+#
+# To do:
+#
+# 1. Unlimited cluster size
+# 2. Skin effect
+
+def create_icosahedron(size, lattice):
+
+ natot = int(1 + (10*size*size+15*size+11)*size/3)
+
+ x = list(range(natot+1))
+ y = list(range(natot+1))
+ z = list(range(natot+1))
+
+ xs = list(range(12+1))
+ ys = list(range(12+1))
+ zs = list(range(12+1))
+
+ xa = [[[ [] for i in range(12+1)] for j in range(12+1)] for k in range(20+1)]
+ ya = [[[ [] for i in range(12+1)] for j in range(12+1)] for k in range(20+1)]
+ za = [[[ [] for i in range(12+1)] for j in range(12+1)] for k in range(20+1)]
+
+ naret = [[ [] for i in range(12+1)] for j in range(12+1)]
+ nfacet = [[[ [] for i in range(12+1)] for j in range(12+1)] for k in range(12+1)]
+
+ rac2 = sqrt(2.0)
+ rac5 = sqrt(5.0)
+ tdef = (rac5+1.0)/2.0
+
+ rapp = sqrt(2.0*(1.0-tdef/(tdef*tdef+1.0)))
+ nats = 2 * (5*size*size+1)
+ nat = 13
+ epsi = 0.01
+
+ x[1] = 0.0
+ y[1] = 0.0
+ z[1] = 0.0
+
+ for i in range(2, 5+1):
+ z[i] = 0.0
+ y[i+4] = 0.0
+ x[i+8] = 0.0
+
+ for i in range(2, 3+1):
+ x[i] = tdef
+ x[i+2] = -tdef
+ x[i+4] = 1.0
+ x[i+6] = -1.0
+ y[i+8] = tdef
+ y[i+10] = -tdef
+
+ for i in range(2, 4+1, 2):
+ y[i] = 1.0
+ y[i+1] = -1.0
+ z[i+4] = tdef
+ z[i+5] = -tdef
+ z[i+8] = 1.0
+ z[i+9] = -1.0
+
+ xdef = rac2 / sqrt(tdef * tdef + 1)
+
+ for i in range(2, 13+1):
+ x[i] = x[i] * xdef / 2.0
+ y[i] = y[i] * xdef / 2.0
+ z[i] = z[i] * xdef / 2.0
+
+ if size > 1:
+
+ for n in range (2, size+1):
+ ifacet = 0
+ iaret = 0
+ inatf = 0
+ for i in range(1, 12+1):
+ for j in range(1, 12+1):
+ naret[i][j] = 0
+ for k in range (1, 12+1):
+ nfacet[i][j][k] = 0
+
+ nl1 = 6
+ nl2 = 8
+ nl3 = 9
+ k1 = 0
+ k2 = 0
+ k3 = 0
+ k12 = 0
+ for i in range(1, 12+1):
+ nat += 1
+ xs[i] = n * x[i+1]
+ ys[i] = n * y[i+1]
+ zs[i] = n * z[i+1]
+ x[nat] = xs[i]
+ y[nat] = ys[i]
+ z[nat] = zs[i]
+ k1 += 1
+
+ for i in range(1, 12+1):
+ for j in range(2, 12+1):
+ if j <= i:
+ continue
+
+ xij = xs[j] - xs[i]
+ yij = ys[j] - ys[i]
+ zij = zs[j] - zs[i]
+ xij2 = xij * xij
+ yij2 = yij * yij
+ zij2 = zij * zij
+ dij2 = xij2 + yij2 + zij2
+ dssn = n * rapp / rac2
+ dssn2 = dssn * dssn
+ diffij = abs(dij2-dssn2)
+ if diffij >= epsi:
+ continue
+
+ for k in range(3, 12+1):
+ if k <= j:
+ continue
+
+ xjk = xs[k] - xs[j]
+ yjk = ys[k] - ys[j]
+ zjk = zs[k] - zs[j]
+ xjk2 = xjk * xjk
+ yjk2 = yjk * yjk
+ zjk2 = zjk * zjk
+ djk2 = xjk2 + yjk2 + zjk2
+ diffjk = abs(djk2-dssn2)
+ if diffjk >= epsi:
+ continue
+
+ xik = xs[k] - xs[i]
+ yik = ys[k] - ys[i]
+ zik = zs[k] - zs[i]
+ xik2 = xik * xik
+ yik2 = yik * yik
+ zik2 = zik * zik
+ dik2 = xik2 + yik2 + zik2
+ diffik = abs(dik2-dssn2)
+ if diffik >= epsi:
+ continue
+
+ if nfacet[i][j][k] != 0:
+ continue
+
+ ifacet += 1
+ nfacet[i][j][k] = ifacet
+
+ if naret[i][j] == 0:
+ iaret += 1
+ naret[i][j] = iaret
+ for l in range(1,n-1+1):
+ nat += 1
+ xa[i][j][l] = xs[i]+l*(xs[j]-xs[i]) / n
+ ya[i][j][l] = ys[i]+l*(ys[j]-ys[i]) / n
+ za[i][j][l] = zs[i]+l*(zs[j]-zs[i]) / n
+ x[nat] = xa[i][j][l]
+ y[nat] = ya[i][j][l]
+ z[nat] = za[i][j][l]
+
+ if naret[i][k] == 0:
+ iaret += 1
+ naret[i][k] = iaret
+ for l in range(1, n-1+1):
+ nat += 1
+ xa[i][k][l] = xs[i]+l*(xs[k]-xs[i]) / n
+ ya[i][k][l] = ys[i]+l*(ys[k]-ys[i]) / n
+ za[i][k][l] = zs[i]+l*(zs[k]-zs[i]) / n
+ x[nat] = xa[i][k][l]
+ y[nat] = ya[i][k][l]
+ z[nat] = za[i][k][l]
+
+ if naret[j][k] == 0:
+ iaret += 1
+ naret[j][k] = iaret
+ for l in range(1, n-1+1):
+ nat += 1
+ xa[j][k][l] = xs[j]+l*(xs[k]-xs[j]) / n
+ ya[j][k][l] = ys[j]+l*(ys[k]-ys[j]) / n
+ za[j][k][l] = zs[j]+l*(zs[k]-zs[j]) / n
+ x[nat] = xa[j][k][l]
+ y[nat] = ya[j][k][l]
+ z[nat] = za[j][k][l]
+
+ for l in range(2, n-1+1):
+ for ll in range(1, l-1+1):
+ xf = xa[i][j][l]+ll*(xa[i][k][l]-xa[i][j][l]) / l
+ yf = ya[i][j][l]+ll*(ya[i][k][l]-ya[i][j][l]) / l
+ zf = za[i][j][l]+ll*(za[i][k][l]-za[i][j][l]) / l
+ nat += 1
+ inatf += 1
+ x[nat] = xf
+ y[nat] = yf
+ z[nat] = zf
+ k3 += 1
+
+ atom_number_total = 0
+ atom_number_drawn = 0
+
+ for i in range (1,natot+1):
+
+ atom = Vector((x[i],y[i],z[i])) * lattice
+
+ atom_add = CLASS_atom_cluster_atom(atom)
+ ATOM_CLUSTER_ALL_ATOMS.append(atom_add)
+ atom_number_total += 1
+ atom_number_drawn += 1
+
+ return (atom_number_total, atom_number_drawn)
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_CALC_FUNC.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_CALC_FUNC.PY
new file mode 100644
index 00000000..5b5ed5ee
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_CALC_FUNC.PY
@@ -0,0 +1,164 @@
+# -*- coding: utf-8 -*-
+import bpy
+import math
+import random
+
+from mathutils import Matrix
+from mathutils import Vector
+
+from . import cfg
+
+
+def at_random_fill(min, max):
+ first = random.uniform(min, max)
+ second = random.uniform(min, max)
+ if first <= second:
+ return(first, second)
+ else:
+ return(second, first)
+
+
+def at_random(seed, totalc, totalr, mint, maxt, mins, maxs, minr, maxr, btr, bsc, brot, uniform,
+ tr1, tr2, sc1, sc2, r1, r2, pivot, varia, valign):
+ """Random function for translation, scale and rotation,
+ seed : seed for random
+ totalc : number of elements in column
+ totalr : number of elements in row
+ mint : minimum for translation
+ maxt : maximum for translation
+ mins : minimum for scale
+ maxs : maximum for scale
+ minr : minimum for rotation
+ maxr : maximun for rotation
+ btr : (boolean) use translation or not
+ bsc : (boolean) use scale or not
+ brot : (boolean) use rotation or not
+ uniform : (boolean) use uniform scale or not
+ tr1 : translation offset of the column
+ tr2 : translation offset of the row
+ sc1 : scale offset of the column
+ sc2 : scale offset of the row
+ r1 : rotation offset of the column
+ r2 : rotation offset of the row
+ pivot : pivot
+ varia : variation of rows
+ valign : Vector of align of rows
+ """
+ random.seed(seed)
+ tr, sc, rot = [0, 0, 0], [0, 0, 0], [0, 0, 0]
+ xyz_vec = (x_axis(), y_axis(), z_axis())
+ ref_name = cfg.atools_objs[0][0]
+ for j in range(totalr):
+ for k in range(totalc + j*varia):
+ elem_name = cfg.atools_objs[j][k]
+ if elem_name == ref_name:
+ continue
+ elem = bpy.data.objects[elem_name]
+ for i in range(3):
+ tr[i] = random.uniform(mint[i], maxt[i])
+ sc[i] = random.uniform(mins[i]/100, maxs[i]/100)
+ rot[i] = random.uniform(minr[i], maxr[i])
+ if uniform:
+ sc[0] = sc[1] = sc[2]
+ mt = Matrix.Translation(tr)
+ ms = Matrix.Scale(sc[0], 4, (1, 0, 0)) @ Matrix.Scale(sc[1], 4, (0, 1, 0)) @ Matrix.Scale(sc[2], 4, (0, 0, 1))
+ mr = Matrix.Rotation(rot[0], 4, (1, 0, 0)) @ Matrix.Rotation(rot[1], 4, (0, 1, 0)) @ Matrix.Rotation(rot[2], 4, (0, 0, 1))
+
+ # recalculate the position...
+ vt, vs, vr = tsr(cfg.ref_mtx, k, j, tr1, tr2, sc1, sc2, Vector(r1), Vector(r2), valign)
+
+ if pivot is not None:
+ emat = at_all_in_one(cfg.ref_mtx, vr, xyz_vec, vt, vs, pivot.location)
+ else:
+ emat = at_all_in_one(cfg.ref_mtx, vr, xyz_vec, vt, vs, cfg.ref_mtx.translation)
+ elem.matrix_world = emat
+ if btr:
+ elem.matrix_world @= mt
+ if bsc:
+ elem.matrix_world @= ms
+ if brot:
+ elem.matrix_world @= mr
+
+def x_axis():
+ """Get the x axis"""
+ return Vector((1.0, 0.0, 0.0))
+
+
+def y_axis():
+ """Get the y axis"""
+ return Vector((0.0, 1.0, 0.0))
+
+
+def z_axis():
+ """Get the z axis"""
+ return Vector((0.0, 0.0, 1.0))
+
+
+def xyz_axis():
+ """Get the xyz axis"""
+ return Vector((1.0, 1.0, 1.0))
+
+
+def at_all_in_one(ref, angle, vecxyz, vec_tr, vec_sc, pivot):
+ """Return the matrix of transformations"""
+ # Matrix is composed by location @ rotation @ scale
+ loc_ref, rot_ref, sc_ref = ref.decompose()
+ # ref_location = bpy.data.objects[cfg.atools_objs[0][0]].location
+
+ loc_ma = Matrix.Translation(loc_ref)
+ rot_ma = rot_ref.to_matrix().to_4x4()
+ sc_ma = Matrix.Scale(sc_ref[0], 4, (1, 0, 0)) @ Matrix.Scale(sc_ref[1], 4, (0, 1, 0)) @ Matrix.Scale(sc_ref[2], 4, (0, 0, 1))
+
+ mt = Matrix.Translation(pivot - loc_ref)
+ mr = Matrix.Rotation(angle[0], 4, vecxyz[0]) @ Matrix.Rotation(angle[1], 4, vecxyz[1]) @ Matrix.Rotation(angle[2], 4, vecxyz[2])
+ mra = mt @ mr @ mt.inverted()
+
+ trm = Matrix.Translation(vec_tr)
+ scm = Matrix.Scale(vec_sc[0], 4, (1, 0, 0)) @ Matrix.Scale(vec_sc[1], 4, (0, 1, 0)) @ Matrix.Scale(vec_sc[2], 4, (0, 0, 1))
+
+ if pivot == loc_ref:
+ mw = loc_ma @ rot_ma @ trm @ scm @ sc_ma @ mr
+ else:
+ mw = loc_ma @ mra @ rot_ma @ trm @ scm @ sc_ma
+ return mw
+
+
+def fill_rotation(context):
+ prop = context.scene.arraytools_prop
+ offset = prop.rot_offset
+
+ for i in range(3):
+ if offset[i] == 0.0:
+ prop.rot_min[i], prop.rot_max[i] = at_random_fill(-math.pi, math.pi)
+ else:
+ prop.rot_min[i], prop.rot_max[i] = at_random_fill(-offset[i]*2, offset[i]*2)
+
+
+def sum_serie(n, factor):
+ """Return the sum of the serie 1+2+3+4+...+n
+ with a factor
+ """
+ return ((n * (n - 1)) / 2) * factor
+
+
+# (T)ranslate (S)cale (R)otation vector
+def tsr(mat, col, row, tcol, trow, scol, srow, rcol, rrow, ralign):
+ """Retrieve the translation, scale and rotation vector according
+ to the position in the array
+ mat : matrix of the reference object
+ col : position in column
+ row : position in row
+ tcol : translate offset in column
+ trow : translate offset in row
+ scol : scale offset in column
+ srow : scale offset in row
+ rcol : rotation offset in column
+ rrow : rotation offset in row
+ ralign : row align
+ """
+ translate = col * tcol + row * trow + row * ralign
+ rotate = col * Vector(rcol) + row * Vector(rrow)
+ s1 = col * (mat.to_scale() - (scol/100))
+ s2 = row * (mat.to_scale() - (srow/100))
+ scale = xyz_axis() - s1 - s2
+ return translate, scale, rotate
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_INTERFACE.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_INTERFACE.PY
new file mode 100644
index 00000000..57af0098
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_INTERFACE.PY
@@ -0,0 +1,1223 @@
+# -*- coding: utf-8 -*-
+import bpy
+import math
+
+from bpy.types import PropertyGroup
+from mathutils import Vector
+
+from . import cfg
+from . import at_panel
+from . import at_operators
+from . at_calc_func import(
+ x_axis,
+ y_axis,
+ z_axis,
+ xyz_axis,
+ at_all_in_one,
+ at_random,
+ sum_serie,
+ tsr
+)
+
+"""not used yet
+if check on update, may really slow the addon """
+def check_list(alist):
+ """check all the objects"""
+ for elem in alist:
+ if elem in bpy.data.objects:
+ pass
+ else:
+ cfg.display_error(str(elem)+" isn't valid.")
+ print("Check_list : a name isn't valid ", elem)
+ return False
+ return True
+
+
+def elem_in_row(column, row, indice):
+ """Number of elements in a row"""
+ elements = column + (row - 1) * indice
+ # print("row elements =", elements)
+ return elements
+
+
+# ---------------------------- Properties ---------------------------
+class ArrayTools_props(PropertyGroup):
+ """Properties for array tools"""
+
+ def add_in_column(self, row, nb_column=-1):
+ """Add nb_column element(s) in each row"""
+ column = cfg.at_count_values[0]
+ if nb_column == -1:
+ nb_column = cfg.at_count_values[1] - column
+
+ ref_name = cfg.atools_objs[0][0]
+ if ref_name in bpy.data.objects:
+ ref_obj = bpy.data.objects[ref_name]
+ # update the ref_mtx if object's transforms have changed
+ cfg.ref_mtx = ref_obj.matrix_world.copy()
+ # with offset no need to replace all elements, only the last
+ if self.is_tr_off_last:
+ for i in range(row):
+ col = column + i*self.alter
+ for j in range(col, col + nb_column):
+ objcp = ref_obj.copy()
+ array_col = bpy.data.collections.get(cfg.col_name)
+ array_col.objects.link(objcp)
+ if self.is_copy:
+ objcp.data = ref_obj.data.copy()
+ cfg.atools_objs[i].append(objcp.name)
+
+ self.transforms_lsr(j, i, cfg.ref_mtx, objcp.name)
+ # update the global ui
+ tr, sc, rot = self.calc_global()
+ self.up_ui_tr_global(tr)
+ self.up_ui_sc_global(sc)
+ self.up_ui_rot_global(rot)
+
+ else: # replace all elements
+ for i in range(row):
+ col = column + i*self.alter
+ for j in range(col, col + nb_column):
+ objcp = ref_obj.copy()
+ array_col = bpy.data.collections.get(cfg.col_name)
+ array_col.objects.link(objcp)
+ if self.is_copy:
+ objcp.data = ref_obj.data.copy()
+ cfg.atools_objs[i].append(objcp.name)
+ self.update_global(bpy.context)
+ del objcp
+ del ref_obj
+ else:
+ message = "Problem with reference object's name."
+ cfg.display_error(message)
+ print("Error in 'add_in_column' : ", message)
+
+
+ def del_in_column(self, row, nb_column=-1):
+ """Remove nb_column element(s) in each row"""
+ if nb_column == -1:
+ nb_column = cfg.at_count_values[0] - cfg.at_count_values[1]
+ array_col = bpy.data.collections.get(cfg.col_name)
+ for i in range(row-1, -1, -1):
+ for j in range(nb_column):
+ del_name = cfg.atools_objs[i].pop()
+ if del_name in bpy.data.objects:
+ obj = bpy.data.objects[del_name]
+ array_col.objects.unlink(obj)
+ bpy.data.objects.remove(obj, do_unlink=True)
+ else:
+ cfg.display_error(del_name + " doesn't exist anymore.")
+ print("Error in 'del_in_column' : ", del_name)
+
+ # if no more element in list, remove the row
+ if not cfg.atools_objs[i]:
+ cfg.atools_objs.pop()
+ self.up_ui_updateRow(row - 1)
+ continue
+ if not self.is_tr_off_last:
+ # if global is used last
+ self.update_global(bpy.context)
+ else:
+ tr, sc, rot = self.calc_global()
+ self.up_ui_tr_global(tr)
+ self.up_ui_sc_global(sc)
+ self.up_ui_rot_global(rot)
+
+
+ def add_in_col_alter(self, row, nb_column):
+ """Add elements in all rows except the first for variation"""
+ array_col = bpy.data.collections.get(cfg.col_name)
+ ref_name = cfg.atools_objs[0][0]
+ column = self.count
+ if ref_name in bpy.data.objects:
+ ref_obj = bpy.data.objects[ref_name]
+ cfg.ref_mtx = ref_obj.matrix_world.copy()
+ if self.is_tr_off_last:
+ for i in range(1, row):
+ for j in range(column, column + i * nb_column):
+ objcp = ref_obj.copy()
+ array_col = bpy.data.collections.get(cfg.col_name)
+ array_col.objects.link(objcp)
+ if self.is_copy:
+ objcp.data = ref_obj.data.copy()
+ cfg.atools_objs[i].append(objcp.name)
+ # print("objs=", cfg.atools_objs)
+
+ self.update_offset(bpy.context)
+ else: # replace all elements
+ for i in range(1, row):
+ for j in range(column, column + i * nb_column):
+ objcp = ref_obj.copy()
+ array_col = bpy.data.collections.get(cfg.col_name)
+ array_col.objects.link(objcp)
+ if self.is_copy:
+ objcp.data = ref_obj.data.copy()
+ cfg.atools_objs[i].append(objcp.name)
+ self.update_global(bpy.context)
+ del objcp
+ del ref_obj
+ else:
+ message = "Problem with reference object's name."
+ cfg.display_error(message)
+ print("Error in 'add_in_column' : ", message)
+
+
+ def del_in_col_alter(self, row, nb_column):
+ """Remove elements in all rows except the first"""
+ array_col = bpy.data.collections.get(cfg.col_name)
+ for i in range(row -1 , 0, -1):
+ for j in range(nb_column * i):
+ del_name = cfg.atools_objs[i].pop()
+ # print("del name=", del_name)
+ if del_name in bpy.data.objects:
+ obj = bpy.data.objects[del_name]
+ array_col.objects.unlink(obj)
+ bpy.data.objects.remove(obj, do_unlink=True)
+ else:
+ cfg.display_error(del_name + " doesn't exist anymore.")
+ print("Error in 'del_in_column' : ", del_name)
+ if self.is_tr_off_last:
+ self.update_offset(bpy.context)
+ else:
+ self.update_global(bpy.context)
+
+ def add_in_row(self, column, nb_row=-1):
+ """Add column elements in nb_row new row(s)"""
+ row = cfg.at_row_values[0]
+ if nb_row == -1:
+ nb_row = cfg.at_row_values[1] - row
+
+ ref_name = cfg.atools_objs[0][0]
+ if ref_name in bpy.data.objects:
+ ref_obj = bpy.data.objects[ref_name]
+ cfg.ref_mtx = ref_obj.matrix_world.copy()
+ if self.is_tr_off_last:
+ for i in range(row, row + nb_row):
+ cfg.atools_objs.append([])
+ for j in range(column + i*self.alter):
+ objcp = ref_obj.copy()
+ array_col = bpy.data.collections.get(cfg.col_name)
+ array_col.objects.link(objcp)
+ if self.is_copy:
+ objcp.data = ref_obj.data.copy()
+ cfg.atools_objs[i].append(objcp.name)
+ self.transforms_lsr(j, i, cfg.ref_mtx, objcp.name)
+ else:
+ for i in range(row, row + nb_row):
+ cfg.atools_objs.append([])
+ for j in range(column):
+ objcp = ref_obj.copy()
+ array_col = bpy.data.collections.get(cfg.col_name)
+ array_col.objects.link(objcp)
+ if self.is_copy:
+ objcp.data = ref_obj.data.copy()
+ cfg.atools_objs[i].append(objcp.name)
+ self.update_global(bpy.context)
+ else:
+ message = "Problem with reference object's name."
+ cfg.display_error(message)
+ print("Error in 'add in row' : ", message)
+
+
+ def del_in_row(self, nb_row=-1):
+ """Remove nb_row row(s) : (column * nb_row) elements"""
+ if nb_row == -1:
+ nb_row = cfg.at_row_values[0] - cfg.at_row_values[1]
+ array_col = bpy.data.collections.get(cfg.col_name)
+ for i in range(nb_row):
+ names = cfg.atools_objs.pop()
+ for del_name in names:
+ if del_name in bpy.data.objects:
+ obj = bpy.data.objects[del_name]
+ array_col.objects.unlink(obj)
+ bpy.data.objects.remove(obj, do_unlink=True)
+ else:
+ cfg.display_error(del_name + " doesn't exist anymore.")
+ print("Error in 'del_in_column' : ", del_name)
+
+
+ def at_del_all(self, del_rall):
+ """Delete all copies and remove objects from lists
+ del_rall : boolean, True to del reference object from list
+ """
+ array_col = bpy.data.collections.get(cfg.col_name)
+ ref_name = cfg.atools_objs[0][0]
+ for i in range(self.row):
+ names = cfg.atools_objs.pop()
+ for obj_name in reversed(names):
+ if obj_name == ref_name:
+ continue
+ # test if object exist
+ if obj_name in bpy.data.objects:
+ obj = bpy.data.objects[obj_name]
+ array_col.objects.unlink(obj)
+ bpy.data.objects.remove(obj, do_unlink=True)
+ else:
+ cfg.display_error(obj_name + " not exist!")
+ print("Error in 'del_all' : ", obj_name)
+
+ if del_rall:
+ cfg.atools_objs.clear()
+
+ # removing the collection if empty
+ if not array_col.objects:
+ bpy.data.collections.remove(array_col)
+ else:
+ cfg.atools_objs.append([ref_name])
+ # print("Del_all done!")
+
+ # ----------------------- UI update -----------------------------
+ # ---------------------------------------------------------------
+ # ----------------------- count update --------------------------
+ def updateCount(self, context):
+ """update the number of element(s) in column"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ cfg.add_count(int(self.count))
+ cfg.del_count()
+
+ # cfg.count_values[0] always store old count value
+ difference = self.count - cfg.at_count_values[0]
+
+ self.update_infos()
+
+ if difference > 0:
+ self.add_in_column(self.row, difference)
+ elif difference < 0:
+ self.del_in_column(self.row, -difference)
+ # print("objs =", cfg.atools_objs)
+
+
+ def up_ui_updateCount(self, val):
+ """Update the value of the property count in UI"""
+ self.is_prog_change = True
+ self.count = val
+
+ # ----------------------- row update ----------------------------
+ def update_row(self, context):
+ """Update row property"""
+ cfg.add_row(self.row)
+ cfg.del_row()
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ if self.alter < 0 and cfg.maxrow < self.row:
+ cfg.display_error("Maximun rows for these setting is : " + str(cfg.maxrow))
+ self.up_ui_updateRow(cfg.maxrow)
+ return
+
+ # cfg.at_row_values[0] always store old row value
+ difference = self.row - cfg.at_row_values[0]
+ if difference > 0:
+ self.add_in_row(self.count, difference)
+ elif difference < 0:
+ self.del_in_row(-difference)
+
+ line = elem_in_row(self.count, self.row, self.alter)
+
+ self.update_infos()
+
+ def up_ui_updateRow(self, val):
+ """Update the value of the property row in UI"""
+ self.is_prog_change = True
+ self.row = val
+
+ def update_alter(self, context):
+ """Update alter property"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ # alter must have at least 2 rows
+ if self.row == 1 and self.alter != 0:
+ cfg.display_error("Add more rows first.")
+ self.up_ui_updateAlter(0)
+ return
+ if self.alter < 0:
+ # (column + (row-1)* variation) is the number of elements
+ # of the last row and must be at least >= 1
+ alter = int((1 - self.count) / (self.row - 1))
+ if self.alter < alter:
+ cfg.display_error("Min variation is '"+str(alter)+"' for these settings.")
+ self.up_ui_updateAlter(alter)
+ return
+
+ cfg.add_alter(self.alter)
+ cfg.del_alter()
+ self.update_ralign()
+
+ difference = self.alter - cfg.at_alter[0]
+ if difference > 0:
+ self.add_in_col_alter(self.row, difference)
+ elif difference < 0:
+ self.del_in_col_alter(self.row, -difference)
+ # print(f"count={self.count}, row={self.row}, alter={self.alter}")
+ line = elem_in_row(self.count, self.row, self.alter)
+ # print("elems in row =", line)
+
+ self.update_infos()
+
+
+ def up_ui_updateAlter(self, val):
+ """Update the value of the property alter in UI"""
+ self.is_prog_change = True
+ self.alter = val
+
+
+ def update_ralign(self):
+ """Update the value of ralign"""
+ decal = -self.alter * self.tr_offset
+ if self.align == 'LEFT':
+ self.ralign = Vector((0.0, 0.0, 0.0))
+ elif self.align == 'CENTER':
+ self.ralign = decal / 2
+ elif self.align == 'RIGHT':
+ self.ralign = decal
+
+
+ def update_align(self, context):
+ """According to the value of align, calculate ralign"""
+ self.update_ralign()
+
+ if self.is_tr_off_last:
+ self.update_offset(bpy.context)
+ else:
+ self.update_global(bpy.context)
+
+
+ def update_infos(self):
+ """Update properties total and erow"""
+ sum = sum_serie(self.row, self.alter)
+ square = self.count * self.row
+ if self.alter >= 0:
+ cfg.maxrow = self.row
+ else:
+ ca = self.count // -self.alter
+ cfg.maxrow = ca if self.count % self.alter == 0 else ca + 1
+ self.total = str(int(square + sum))
+ self.erow = str(elem_in_row(self.count, self.row, self.alter))
+
+ # ----------------------- translation update --------------------
+ def up_ui_tr_offset(self, val):
+ """Update the value of the property tr_offset in UI"""
+ self.is_prog_change = True
+ self.tr_offset = val
+
+ def up_ui_tr_global(self, val):
+ """Update the value of the property tr_global in UI"""
+ self.is_prog_change = True
+ self.tr_global = val
+
+ # ----------------------- scale update --------------------------
+ def up_ui_sc_offset(self, val):
+ """Update the value of the property sc_offset in UI"""
+ self.is_prog_change = True
+ self.sc_offset = val
+
+ def up_ui_sc_global(self, val):
+ """Update the value of the property sc_global in UI"""
+ self.is_prog_change = True
+ self.sc_global = val
+
+ # ----------------------- rotation update -----------------------
+ def up_ui_rot_offset(self, val):
+ """Update the value of the property rot_offset in UI"""
+ self.is_prog_change = True
+ self.rot_offset = val
+
+ def up_ui_rot_global(self, val):
+ """Update the value of the property rot_global in UI"""
+ self.is_prog_change = True
+ self.rot_global = val
+
+ # ---------------------------------------------------------------
+ def calc_global(self):
+ """Calculate global for column"""
+ tg = (self.count-1) * self.tr_offset
+ sg = (xyz_axis() - (self.count-1) *
+ (cfg.ref_mtx.to_scale() - (self.sc_offset/100))) * 100
+ rg = self.count * Vector(self.rot_offset)
+ return tg,sg,rg
+
+
+ def transforms_lsr(self, column, row, mat, ename):
+ """Calculate transforms according to the position of the element
+ column : indice of the element's column
+ row : indice of the element's row
+ mat : matrix of the reference object
+ ename : element's name to put in place
+ """
+ localxyz = (x_axis(), y_axis(), z_axis())
+
+ translate, scaling, rotate = tsr(mat, column, row, self.tr_offset, self.tr_second,
+ self.sc_offset, self.sc_second, self.rot_offset, self.rot_second, self.ralign)
+ if ename in bpy.data.objects:
+ obj = bpy.data.objects[ename]
+ if self.at_pivot is not None:
+ obj.matrix_world = at_all_in_one(mat, rotate, localxyz, translate,
+ scaling, self.at_pivot.location)
+ else:
+ obj.matrix_world = at_all_in_one(mat, rotate, localxyz, translate,
+ scaling, mat.translation)
+
+
+ def apply_transforms(self, matx, nb_column, nb_row, tr, sc, rot):
+ """Move, scale and rotate the selected elements
+ tr : translation offset of the first row
+ sc : scale offset of the first row
+ rot : rotation offset of the first row
+ return global transforms
+ """
+ # local axis always (1,0,0) (0,1,0) (0,0,1)
+ localxyz = (x_axis(), y_axis(), z_axis())
+
+ ref_scale = matx.to_scale()
+ # duplicate code but avoid looping the test
+ if self.at_pivot is not None:
+ for i in range(nb_row):
+ for j in range(nb_column + i*self.alter):
+ elem = cfg.atools_objs[i][j]
+ if elem in bpy.data.objects:
+ obj = bpy.data.objects[elem]
+ else:
+ cfg.display_error(elem + " no more exist !")
+ print("Error in 'apply_transforms', name no more exist : ", elem)
+ continue
+ t_off, s_off, r_off = tsr(matx, j, i, tr, self.tr_second, sc,
+ self.sc_second, rot, self.rot_second, self.ralign)
+
+ obj.matrix_world = at_all_in_one(matx, r_off,
+ localxyz, t_off, s_off, self.at_pivot.location)
+ else:
+ for i in range(nb_row):
+ for j in range(nb_column + i*self.alter):
+ ref_loc = cfg.ref_mtx.translation
+ elem = cfg.atools_objs[i][j]
+ if elem in bpy.data.objects:
+ obj = bpy.data.objects[elem]
+ else:
+ cfg.display_error(elem + " no more exist !")
+ print("Error in 'apply_transforms', name no more exist : ", elem)
+ continue
+ t_off, s_off, r_off = tsr(matx, j, i, tr, self.tr_second, sc,
+ self.sc_second, rot, self.rot_second, self.ralign)
+
+ obj.matrix_world = at_all_in_one(matx, r_off,
+ localxyz, t_off, s_off, ref_loc)
+ tr_col,sc_col,rot_col = self.calc_global()
+ return(tr_col, sc_col, rot_col)
+
+ def update_offset(self, context):
+ """Update for all offsets"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else: # user change offset
+ self.is_tr_off_last = True
+
+ ref_name = cfg.atools_objs[0][0]
+ if bpy.data.objects[ref_name]:
+ cfg.ref_mtx = bpy.data.objects[ref_name].matrix_world.copy()
+ aloc, asc, arot = self.apply_transforms(cfg.ref_mtx, self.count, self.row,
+ self.tr_offset, self.sc_offset, Vector(self.rot_offset))
+
+ # since offset changes, global too
+ self.up_ui_tr_global(aloc)
+ self.up_ui_sc_global(asc)
+ self.up_ui_rot_global(arot)
+
+
+ def update_global(self, context):
+ """Update for all globals"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else: # user change global
+ self.is_tr_off_last = False
+
+ ref_name = cfg.atools_objs[0][0]
+ if bpy.data.objects[ref_name]:
+ cfg.ref_mtx = bpy.data.objects[ref_name].matrix_world.copy()
+ ref_scale = cfg.ref_mtx.to_scale()
+
+ translation_offset = Vector(self.tr_global) / (self.count - 1)
+ scale_offset = ref_scale - ((ref_scale-(self.sc_global/100)) / (self.count - 1))
+ rotation_offset = Vector(self.rot_global) / self.count
+
+ self.apply_transforms(cfg.ref_mtx, self.count, self.row, translation_offset,
+ Vector(scale_offset)*100, rotation_offset)
+
+ # since global changes, offset too
+ self.up_ui_tr_offset(translation_offset)
+ self.up_ui_sc_offset(Vector(scale_offset*100))
+ self.up_ui_rot_offset(rotation_offset)
+
+
+ def update_second(self, context):
+ """Update the secondary transforms"""
+ ref_name = cfg.atools_objs[0][0]
+ if bpy.data.objects[ref_name]:
+ cfg.ref_mtx = bpy.data.objects[ref_name].matrix_world.copy()
+ self.apply_transforms(cfg.ref_mtx, self.count, self.row, self.tr_offset,
+ self.sc_offset, self.rot_offset)
+
+
+ # ----------------------- is_copy update ------------------------
+ def up_ui_is_copy(self):
+ """Update the value of the property is_copy in UI"""
+ self.is_prog_change = True
+ self.is_copy = False
+
+
+ def update_is_copy(self, context):
+ """Allow a copy or duplicate(copy link by default)"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ if self.is_copy: # no need to rebuild all
+ for i in range(self.row):
+ for j in range(self.count):
+ if i == 0 and j == 0:
+ continue
+ ref_name = cfg.atools_objs[0][0]
+ elem_name = cfg.atools_objs[i][j]
+ bpy.data.objects[elem_name].data = bpy.data.objects[ref_name].data.copy()
+ else: # since the value change (now duplicate), need to rebuild
+ count = self.count
+ row = self.row
+ ref_name = cfg.atools_objs[0][0]
+ array_col = bpy.data.collections.get(cfg.col_name)
+
+ # DO NOT USE BLENDER CRASH WITH IT
+ # self.at_del_all(False)
+
+ bpy.ops.object.delete({"selected_objects": array_col.objects})
+ cfg.atools_objs.clear()
+ cfg.atools_objs.append([ref_name])
+
+ ref_obj = bpy.data.objects[ref_name]
+ for i in range(row):
+ if i != 0:
+ cfg.atools_objs.append([])
+ for j in range(count + i*self.alter):
+ objcp = ref_obj.copy()
+ array_col.objects.link(objcp)
+ cfg.atools_objs[i].append(objcp.name)
+ del objcp
+ del ref_obj
+
+ if self.is_tr_off_last:
+ self.update_offset(bpy.context)
+ else:
+ self.update_global(bpy.context)
+
+ print("Rebuild done!")
+
+ # ----------------------- random part ---------------------------
+ # ---------------------------------------------------------------
+ def update_seed(self, context):
+ if self.at_mode == 'ADV':
+ sc_min = (self.sc_min_x, self.sc_min_y, self.sc_min_z)
+ sc_max = (self.sc_max_x, self.sc_max_y, self.sc_max_z)
+ at_random(self.at_seed, self.count, self.row, self.tr_min, self.tr_max, sc_min,
+ sc_max, self.rot_min, self.rot_max, self.at_is_tr, self.at_is_sc, self.at_is_rot,
+ self.sc_all, self.tr_offset, self.tr_second, self.sc_offset, self.sc_second,
+ self.rot_offset, self.rot_second, self.at_pivot, self.alter, self.ralign)
+ else: # simple mode
+ vec = xyz_axis()
+ tr = self.tr_rand * vec
+ sc = self.sc_rand * vec
+ rot = self.rot_rand * vec
+ at_random(self.at_seed, self.count, self.row, -tr, tr, sc, 100*vec, -rot, rot,
+ self.at_is_tr, self.at_is_sc, self.at_is_rot, False, self.tr_offset,
+ self.tr_second, self.sc_offset, self.sc_second, self.rot_offset,
+ self.rot_second, self.at_pivot, self.alter, self.ralign)
+
+
+ def update_rtr(self, context):
+ """rtr in simple mode update adv mode"""
+ self.tr_max = self.tr_rand * Vector((1.0, 1.0, 1.0))
+ self.tr_min = self.tr_rand * Vector((-1.0, -1.0, -1.0))
+
+
+ def update_rsc(self, context):
+ """rsc in simple mode update adv mode"""
+ self.sc_max_x, self.sc_max_y, self.sc_max_z = (100.0, 100.0, 100.0)
+ rand = self.sc_rand
+ self.sc_min_x = rand
+ self.sc_min_y = rand
+ self.sc_min_z = rand
+
+
+ def update_rrot(self, context):
+ """rrot in simple mode update adv mode"""
+ self.rot_max = self.rot_rand * Vector((1.0, 1.0, 1.0))
+ self.rot_min = self.rot_rand * Vector((-1.0, -1.0, -1.0))
+
+
+ def up_ui_sc_min_x(self, val):
+ """Update the value of the property sc_min_x in UI"""
+ self.is_prog_change = True
+ self.sc_min_x = val
+
+
+ def up_ui_sc_min_y(self, val):
+ """Update the value of the property sc_min_y in UI"""
+ self.is_prog_change = True
+ self.sc_min_y = val
+
+
+ def up_ui_sc_min_z(self, val):
+ """Update the value of the property sc_min_z in UI"""
+ self.is_prog_change = True
+ self.sc_min_z = val
+
+
+ def up_ui_sc_max_x(self, val):
+ """Update the value of the property sc_max_x in UI"""
+ self.is_prog_change = True
+ self.sc_max_x = val
+
+
+ def up_ui_sc_max_y(self, val):
+ """Update the value of the property sc_max_y in UI"""
+ self.is_prog_change = True
+ self.sc_max_y = val
+
+
+ def up_ui_sc_max_z(self, val):
+ """Update the value of the property sc_max_z in UI"""
+ self.is_prog_change = True
+ self.sc_max_z = val
+
+ # -------------- update min and max -----------------------------
+ # if user enter a max value < min, change min and vice versa
+ def up_tr_min(self, context):
+ """Update tr_max if tr_min is higher"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ for i in range(3):
+ if self.tr_min[i] > self.tr_max[i]:
+ self.is_prog_change = True
+ self.tr_max[i] = self.tr_min[i]
+
+
+ def up_tr_max(self, context):
+ """Update tr_min if tr_max is lower"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ for i in range(3):
+ if self.tr_min[i] > self.tr_max[i]:
+ self.is_prog_change = True
+ self.tr_min[i] = self.tr_max[i]
+
+
+ def up_sc_min_x(self, context):
+ """Update sc_max_x if sc_min_x is higher"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ test = self.sc_min_x > self.sc_max_x
+ if test and self.sc_all:
+ # case : min > max and uniform = True
+ self.up_ui_sc_max_x(self.sc_min_x)
+ # with uniform : min_x = min_y = min_z same for max_
+ self.up_ui_sc_min_y(self.sc_min_x)
+ self.up_ui_sc_min_z(self.sc_min_x)
+ self.up_ui_sc_max_y(self.sc_min_x)
+ self.up_ui_sc_max_z(self.sc_min_x)
+ elif self.sc_all:
+ # case : min < max and uniform = True
+ self.up_ui_sc_min_y(self.sc_min_x)
+ self.up_ui_sc_min_z(self.sc_min_x)
+ self.up_ui_sc_max_y(self.sc_max_x)
+ self.up_ui_sc_max_z(self.sc_max_x)
+ elif test:
+ # case : min > max and uniform = False
+ self.up_ui_sc_max_x(self.sc_min_x)
+
+ def up_sc_min_y(self, context):
+ """Update sc_max_y if sc_min_y is higher"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ test = self.sc_min_y > self.sc_max_y
+ if test and self.sc_all:
+ # case : min > max and uniform = True
+ self.up_ui_sc_max_y(self.sc_min_y)
+ # with uniform : min_x = min_y = min_z same for max_
+ self.up_ui_sc_min_x(self.sc_min_y)
+ self.up_ui_sc_min_z(self.sc_min_y)
+ self.up_ui_sc_max_x(self.sc_min_y)
+ self.up_ui_sc_max_y(self.sc_min_y)
+ elif self.sc_all:
+ # case : min < max and uniform = True
+ self.up_ui_sc_min_x(self.sc_min_y)
+ self.up_ui_sc_min_z(self.sc_min_y)
+ self.up_ui_sc_max_x(self.sc_max_y)
+ self.up_ui_sc_max_z(self.sc_max_y)
+ elif test:
+ # case : min > max and uniform = False
+ self.up_ui_sc_max_y(self.sc_min_y)
+
+ def up_sc_min_z(self, context):
+ """Update sc_max_z if sc_min_z is higher"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ test = self.sc_min_z > self.sc_max_z
+ if test and self.sc_all:
+ # case : min > max and uniform = True
+ self.up_ui_sc_max_z(self.sc_min_z)
+ # with uniform : min_x = min_y = min_z same for max_
+ self.up_ui_sc_min_x(self.sc_min_z)
+ self.up_ui_sc_min_y(self.sc_min_z)
+ self.up_ui_sc_max_x(self.sc_min_z)
+ self.up_ui_sc_max_y(self.sc_min_z)
+ elif self.sc_all:
+ # case : min < max and uniform = True
+ self.up_ui_sc_min_x(self.sc_min_z)
+ self.up_ui_sc_min_y(self.sc_min_z)
+ self.up_ui_sc_max_x(self.sc_max_z)
+ self.up_ui_sc_max_y(self.sc_max_z)
+ elif test:
+ # case : min > max and uniform = False
+ self.up_ui_sc_max_y(self.sc_min_z)
+
+ def up_sc_max_x(self, context):
+ """Update sc_min_x if sc_max_x is lower"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ test = self.sc_min_x > self.sc_max_x
+ if test and self.sc_all:
+ # case : min > max and uniform = True
+ self.up_ui_sc_min_x(self.sc_max_x)
+ # with uniform : min_x = min_y = min_z same for max_
+ self.up_ui_sc_max_y(self.sc_max_x)
+ self.up_ui_sc_max_z(self.sc_max_x)
+ self.up_ui_sc_min_y(self.sc_max_x)
+ self.up_ui_sc_min_z(self.sc_max_x)
+ elif self.sc_all:
+ # case : min < max and uniform = True
+ self.up_ui_sc_max_y(self.sc_max_x)
+ self.up_ui_sc_max_z(self.sc_max_x)
+ self.up_ui_sc_min_y(self.sc_min_x)
+ self.up_ui_sc_min_z(self.sc_min_x)
+ elif test:
+ # case : min > max and uniform = False
+ self.up_ui_sc_min_x(self.sc_max_x)
+
+ def up_sc_max_y(self, context):
+ """Update sc_min_y if sc_max_y is lower"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ test = self.sc_min_y > self.sc_max_y
+ if test and self.sc_all:
+ # case : min > max and uniform = True
+ self.up_ui_sc_min_y(self.sc_max_y)
+ # with uniform : min_x = min_y = min_z same for max_
+ self.up_ui_sc_max_x(self.sc_max_y)
+ self.up_ui_sc_max_z(self.sc_max_y)
+ self.up_ui_sc_min_x(self.sc_max_y)
+ self.up_ui_sc_min_z(self.sc_max_y)
+ elif self.sc_all:
+ # case : min < max and uniform = True
+ self.up_ui_sc_max_x(self.sc_max_y)
+ self.up_ui_sc_max_z(self.sc_max_y)
+ self.up_ui_sc_min_x(self.sc_min_y)
+ self.up_ui_sc_min_z(self.sc_min_y)
+ elif test:
+ # case : min > max and uniform = False
+ self.up_ui_sc_min_y(self.sc_max_y)
+
+ def up_sc_max_z(self, context):
+ """Update sc_min_z if sc_max_z is lower"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ test = self.sc_min_z > self.sc_max_z
+ if test and self.sc_all:
+ # case : min > max and uniform = True
+ self.up_ui_sc_min_z(self.sc_max_z)
+ # with uniform : min_x = min_y = min_z same for max_
+ self.up_ui_sc_max_x(self.sc_max_z)
+ self.up_ui_sc_max_y(self.sc_max_z)
+ self.up_ui_sc_min_x(self.sc_max_z)
+ self.up_ui_sc_min_y(self.sc_max_z)
+ elif self.sc_all:
+ # case : min < max and uniform = True
+ self.up_ui_sc_max_x(self.sc_max_z)
+ self.up_ui_sc_max_y(self.sc_max_z)
+ self.up_ui_sc_min_x(self.sc_min_z)
+ self.up_ui_sc_min_y(self.sc_min_z)
+ elif test:
+ # case : min > max and uniform = False
+ self.up_ui_sc_min_z(self.sc_max_z)
+
+ def up_rot_min(self, context):
+ """Update rot_max if rot_min is higher"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ for i in range(3):
+ if self.rot_min[i] > self.rot_max[i]:
+ self.is_prog_change = True
+ self.rot_max[i] = self.rot_min[i]
+
+ def up_rot_max(self, context):
+ """Update rot_min if rot_max is lower"""
+ if self.is_prog_change:
+ self.is_prog_change = False
+ else:
+ for i in range(3):
+ if self.rot_min[i] > self.rot_max[i]:
+ self.is_prog_change = True
+ self.rot_min[i] = self.rot_max[i]
+
+ # ----------------------- reset all properties ------------------
+ def up_ui_reset(self):
+ """Reset all UI properties"""
+ self.up_ui_updateCount(2)
+ self.up_ui_updateRow(1)
+ self.up_ui_is_copy()
+ self.up_ui_tr_offset(Vector((2.0, 0.0, 0.0)))
+ self.up_ui_tr_global(Vector((2.0, 0.0, 0.0)))
+ self.up_ui_sc_offset((100, 100, 100))
+ self.up_ui_sc_global((100, 100, 100))
+ self.up_ui_rot_offset(Vector((0.0, 0.0, 0.0)))
+ self.up_ui_rot_global(Vector((0.0, 0.0, 0.0)))
+ self.up_ui_updateAlter(0)
+ self.total = "2"
+ self.erow = "2"
+
+
+ count: bpy.props.IntProperty(
+ name='Count',
+ description="Number of elements, original count as one",
+ default=2,
+ soft_min=2,
+ update=updateCount
+ )
+
+ row: bpy.props.IntProperty(
+ name="Row",
+ description="Number of row(s)",
+ default=1,
+ soft_min=1,
+ soft_max=100,
+ update=update_row
+ )
+
+ """Allow a variation in the row :
+ if row gets n elements, row +1 will get (n + variation) elements
+ only if n + variation > 0
+ """
+ alter: bpy.props.IntProperty(
+ name=" Row variation",
+ description="""Variation in the number of elements in a row. (between -5 and 5).
+ \n Be careful with it""",
+ default=0,
+ soft_min=-5,
+ soft_max=5,
+ update=update_alter
+ )
+
+ total: bpy.props.StringProperty(
+ name="Total",
+ description="Total of elements in array",
+ default="2"
+ )
+
+ erow: bpy.props.StringProperty(
+ description="Number of elements in the current row.",
+ default="2"
+ )
+
+ # if alter <> 0, how align the rows
+ align: bpy.props.EnumProperty(
+ name='Align',
+ description="Align of rows when variation is not zero",
+ items=[
+ ('LEFT', 'Left', "Align to the left", 'ALIGN_LEFT', 0),
+ ('CENTER', 'Center', "Align to the center", 'ALIGN_CENTER', 1),
+ ('RIGHT', 'Right', "Align to the right", 'ALIGN_RIGHT', 2)
+ ],
+ default='LEFT',
+ update=update_align
+ )
+
+ # Vector alignment depends on align
+ ralign: bpy.props.FloatVectorProperty(
+ subtype='TRANSLATION',
+ unit='LENGTH',
+ default=(0.0, 0.0, 0.0)
+ )
+
+ # booleans use to know if user or prog change the value to avoid continuous loop
+ is_prog_change: bpy.props.BoolProperty(default=False) # True if prog change value
+
+ # which one between offset and global user calls last, True is offset, False global
+ is_tr_off_last: bpy.props.BoolProperty(default=True)
+
+ # True if addon is initialised
+ already_start: bpy.props.BoolProperty(default=False)
+
+ # if the user need a single copy or a duplicate (link object)
+ is_copy: bpy.props.BoolProperty(
+ name="Copy only",
+ description="Duplicate or copy, default is duplicate",
+ default=False,
+ update=update_is_copy
+ )
+
+ # translation vector offset
+ tr_offset: bpy.props.FloatVectorProperty(
+ name='Offset',
+ description="Distance between elements",
+ default=(2.0, 0.0, 0.0),
+ subtype='TRANSLATION',
+ unit='LENGTH',
+ precision=2,
+ step=50,
+ options={'ANIMATABLE'},
+ update=update_offset
+ )
+
+ # global translation distance
+ tr_global: bpy.props.FloatVectorProperty(
+ name='Global',
+ description="Distance between the original and the last element",
+ default=(2.0, 0.0, 0.0),
+ subtype='TRANSLATION',
+ unit='LENGTH',
+ precision=2,
+ step=50,
+ options={'ANIMATABLE'},
+ update=update_global
+ )
+
+ tr_second: bpy.props.FloatVectorProperty(
+ name="Translation",
+ description="Additional offset distance for rows",
+ default=(0.0, 0.0, 0.0),
+ subtype='TRANSLATION',
+ unit='LENGTH',
+ precision=2,
+ step=50,
+ update=update_second
+ )
+
+ at_pivot: bpy.props.PointerProperty(
+ name='Pivot',
+ description="Object you want as pivot point. If none, pivot point is the object's origine",
+ type=bpy.types.Object
+ )
+
+ # scaling vector offset
+ sc_offset: bpy.props.FloatVectorProperty(
+ name='Offset',
+ description="Incremental scale of the next elements",
+ default=(100.0, 100.0, 100.0),
+ subtype='XYZ',
+ precision=1,
+ step=100,
+ options={'ANIMATABLE'},
+ update=update_offset
+ )
+
+ # global scaling
+ sc_global: bpy.props.FloatVectorProperty(
+ name='Global',
+ description="Scale of the last element",
+ default=(100.0, 100.0, 100.0),
+ subtype='XYZ',
+ precision=1,
+ step=100,
+ options={'ANIMATABLE'},
+ update=update_global
+ )
+
+ sc_second: bpy.props.FloatVectorProperty(
+ name='Scale',
+ description="Additionnal scale for rows",
+ default=(100.0, 100.0, 100.0),
+ subtype='XYZ',
+ precision=1,
+ step=100,
+ options={'ANIMATABLE'},
+ update=update_second
+ )
+ # rotation vector offset
+ rot_offset: bpy.props.FloatVectorProperty(
+ name='Offset',
+ description="Angle between each element",
+ default=(0.0, 0.0, 0.0),
+ subtype='XYZ',
+ unit='ROTATION',
+ step=500, # = 5
+ options={'ANIMATABLE'},
+ update=update_offset
+ )
+
+ # global rotation
+ rot_global: bpy.props.FloatVectorProperty(
+ name='Global',
+ description="Maximum angle from the reference to the last element",
+ default=(0.0, 0.0, 0.0),
+ subtype='XYZ',
+ unit='ROTATION',
+ step=500, # = 5
+ options={'ANIMATABLE'},
+ update=update_global
+ )
+
+ rot_second: bpy.props.FloatVectorProperty(
+ name='Rotation',
+ description="Additionnal rotation for rows",
+ default=(0.0, 0.0, 0.0),
+ subtype='XYZ',
+ unit='ROTATION',
+ step=500,
+ options={'ANIMATABLE'},
+ update=update_second
+ )
+
+ # ----------------------- random part ---------------------------
+ at_seed: bpy.props.IntProperty(
+ name='Seed',
+ description="Seed value for random",
+ soft_min=0,
+ default=0,
+ update=update_seed
+ )
+
+ at_mode: bpy.props.EnumProperty(
+ name="Mode",
+ description="Choose between simple mode or advanced",
+ items=(('SIM', 'Simple', "Simple mode"),
+ ('ADV', 'Advanced', "Advanced mode")),
+ default='SIM'
+ )
+
+ at_is_tr: bpy.props.BoolProperty(
+ name="Add translation",
+ description="Add translation in random?",
+ default=False
+ )
+
+ at_is_sc: bpy.props.BoolProperty(
+ name="Add scale",
+ description="Add scale in random?",
+ default=False
+ )
+
+ at_is_rot: bpy.props.BoolProperty(
+ name="Add rotation",
+ description="Add rotation in random?",
+ default=False
+ )
+
+ tr_min: bpy.props.FloatVectorProperty(
+ name="min",
+ description="Minimum random value for translation",
+ unit='LENGTH',
+ default=(0.0, 0.0, 0.0),
+ update=up_tr_min
+ )
+
+ tr_max: bpy.props.FloatVectorProperty(
+ name="max",
+ description="Maximum random value for translation",
+ unit='LENGTH',
+ default=(0.0, 0.0, 0.0),
+ update=up_tr_max
+ )
+
+ tr_rand: bpy.props.FloatProperty(
+ name="Translation",
+ description="Random values for all axis",
+ unit='LENGTH',
+ default=0.0,
+ update=update_rtr
+ )
+
+ sc_all: bpy.props.BoolProperty(
+ name="uniform scale",
+ description="Uniform or non uniform scale, default is non uniform.",
+ default=False
+ )
+
+ sc_min_x: bpy.props.IntProperty(
+ name="min",
+ description="Minimum random value for x scale",
+ default=100,
+ update=up_sc_min_x
+ )
+
+ sc_min_y: bpy.props.IntProperty(
+ name="min",
+ description="Minimum random value for y scale",
+ default=100,
+ update=up_sc_min_y
+ )
+
+ sc_min_z: bpy.props.IntProperty(
+ name="min",
+ description="Minimum random value for z scale",
+ default=100,
+ update=up_sc_min_z
+ )
+
+ sc_max_x: bpy.props.IntProperty(
+ name="max",
+ description="Maximum random value for x scale",
+ default=100,
+ update=up_sc_max_x
+ )
+
+ sc_max_y: bpy.props.IntProperty(
+ name="max",
+ description="Maximum random value for y scale",
+ default=100,
+ update=up_sc_max_y
+ )
+
+ sc_max_z: bpy.props.IntProperty(
+ name="max",
+ description="Maximum random value for z scale",
+ default=100,
+ update=up_sc_max_z
+ )
+
+ sc_rand: bpy.props.IntProperty(
+ name="Scale",
+ description="Random scale value for all axis",
+ default=100,
+ update=update_rsc
+ )
+
+ rot_min: bpy.props.FloatVectorProperty(
+ name="min",
+ description="Minimum random value for rotation",
+ unit='ROTATION',
+ default=(0.0, 0.0, 0.0),
+ update=up_rot_min
+ )
+
+ rot_max: bpy.props.FloatVectorProperty(
+ name="max",
+ description="Maximum random value for rotation",
+ unit='ROTATION',
+ default=(0.0, 0.0, 0.0),
+ update=up_rot_max
+ )
+
+ rot_rand: bpy.props.FloatProperty(
+ name="Rotation",
+ description="Random rotation for all axis",
+ unit='ROTATION',
+ default=0.0,
+ update=update_rrot
+ )
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_OPERATORS.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_OPERATORS.PY
new file mode 100644
index 00000000..46894c7b
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_OPERATORS.PY
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+# ---------------------------- Operators ------------------------
+import bpy
+import math
+
+from mathutils import Vector
+
+from . import cfg
+from . import at_interface
+from . at_calc_func import at_random_fill, fill_rotation
+
+
+class OBJECT_OT_at_start(bpy.types.Operator):
+ """Start and init the addon"""
+ bl_idname = 'scene.at_op'
+ bl_label = "Start array"
+
+ @classmethod
+ def poll(cls, context):
+ return not context.scene.arraytools_prop.already_start
+
+ def execute(self, context):
+ cfg.init_array_tool(context)
+ return {'FINISHED'}
+
+
+class OBJECT_OT_at_done(bpy.types.Operator):
+ """Apply the settings"""
+ bl_idname = 'scene.at_done'
+ bl_label = "Done !"
+
+ def execute(self, context):
+ cfg.atools_objs.clear()
+ #cfg.at_mtx_list.clear()
+ array_col = bpy.data.collections.get(cfg.col_name)
+ cfg.col_name = "Array_collection"
+ context.scene.arraytools_prop.up_ui_reset()
+ context.scene.arraytools_prop.already_start = False
+ return {'FINISHED'}
+
+
+class OBJECT_OT_at_cancel(bpy.types.Operator):
+ """Cancel the settings"""
+ bl_idname = 'scene.at_cancel'
+ bl_label = "Cancel"
+
+ def execute(self, context):
+ scn = context.scene
+ scn.arraytools_prop.at_del_all(True)
+ scn.arraytools_prop.up_ui_reset()
+ scn.arraytools_prop.already_start = False
+ cfg.col_name = "Array_collection"
+ return {'FINISHED'}
+
+
+class OBJECT_OT_fill_tr(bpy.types.Operator):
+ """Fill the random translation fields"""
+ bl_idname = 'scene.fill_tr'
+ bl_label = "Fill"
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ offset = prop.tr_offset
+
+ for i in range(3):
+ if offset[i] == 0.0:
+ prop.tr_min[i], prop.tr_max[i] = at_random_fill(-3.0, 3.0)
+ else:
+ prop.tr_min[i], prop.tr_max[i] = at_random_fill(-offset[i]/2, offset[i]/2)
+ return{'FINISHED'}
+
+
+class OBJECT_OT_fill_sc(bpy.types.Operator):
+ """Fill the random scale fields"""
+ bl_idname = 'scene.fill_sc'
+ bl_label = "Fill"
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ offset = prop.sc_offset
+
+ if 100 in [offset[0], offset[1], offset[2]]:
+ prop.sc_min_x, prop.sc_max_x = at_random_fill(40.0, 120.0)
+ prop.sc_min_y, prop.sc_max_y = at_random_fill(40.0, 120.0)
+ prop.sc_min_z, prop.sc_max_z = at_random_fill(40.0, 120.0)
+ else:
+ rand = [(100 - offset[i]) / 2 for i in range(3)]
+ print(rand)
+ prop.sc_min_x, prop.sc_max_x = at_random_fill(offset[0]-rand[0], offset[0]+rand[0])
+ prop.sc_min_y, prop.sc_max_y = at_random_fill(offset[1]-rand[1], offset[1]+rand[1])
+ prop.sc_min_z, prop.sc_max_z = at_random_fill(offset[2]-rand[2], offset[2]+rand[2])
+ if prop.sc_all:
+ prop.sc_min_x = prop.sc_min_y = prop.sc_min_z
+ prop.sc_max_x = prop.sc_max_y = prop.sc_max_z
+ return {'FINISHED'}
+
+
+class OBJECT_OT_fill_rot(bpy.types.Operator):
+ """Fill the random rotation fields"""
+ bl_idname = 'scene.fill_rot'
+ bl_label = "Fill"
+
+ def execute(self, context):
+ fill_rotation(context)
+ return {'FINISHED'}
+
+
+class OBJECT_OT_x360(bpy.types.Operator):
+ """Quick 360 degrees on X axis"""
+ bl_idname = 'scene.x360'
+ bl_label = "360"
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ prop.tr_offset = Vector((0.0, 0.0, 0.0))
+ prop.rot_global = Vector((math.pi/180*360, 0.0, 0.0))
+ return{'FINISHED'}
+
+
+class OBJECT_OT_y360(bpy.types.Operator):
+ """Quick 360 degrees on Y axis"""
+ bl_idname = 'scene.y360'
+ bl_label = "360"
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ prop.tr_offset = Vector((0.0, 0.0, 0.0))
+ prop.rot_global = Vector((0.0, math.pi/180*360, 0.0))
+ return{'FINISHED'}
+
+
+class OBJECT_OT_z360(bpy.types.Operator):
+ """Quick 360 degrees on Z axis"""
+ bl_idname = 'scene.z360'
+ bl_label = "360"
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ prop.tr_offset = Vector((0.0, 0.0, 0.0))
+ prop.rot_global = Vector((0.0, 0.0, math.pi/180*360))
+ return{'FINISHED'}
+
+
+class OBJECT_OT_reset_tr(bpy.types.Operator):
+ """Reset the settings of random translation"""
+ bl_idname = 'scene.at_reset_tr'
+ bl_label = 'Reset'
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ prop.tr_min[0], prop.tr_min[1], prop.tr_min[2] = 0.0, 0.0, 0.0
+ prop.tr_max[0], prop.tr_max[1], prop.tr_max[2] = 0.0, 0.0, 0.0
+
+ # if operator is used many times
+ # get weird result != 0 with vector
+ # prop.tr_max = Vector((0.0, 0.0, 0.0))
+ return {'FINISHED'}
+
+
+class OBJECT_OT_reset_sc(bpy.types.Operator):
+ """Reset the settings of random scale"""
+ bl_idname = 'scene.at_reset_sc'
+ bl_label = 'Reset'
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ prop.sc_min_x, prop.sc_min_y, prop.sc_min_z = 100, 100, 100
+ prop.sc_max_x, prop.sc_max_y, prop.sc_max_z = 100, 100, 100
+ return{'FINISHED'}
+
+
+class OBJECT_OT_reset_rot(bpy.types.Operator):
+ """Reset the settings of random rotation"""
+ bl_idname = 'scene.at_reset_rot'
+ bl_label = 'Reset'
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ prop.rot_min[0], prop.rot_min[1], prop.rot_min[2] = 0.0, 0.0, 0.0
+ prop.rot_max[0], prop.rot_max[1], prop.rot_max[2] = 0.0, 0.0, 0.0
+ return{'FINISHED'}
+
+
+class OBJECT_OT_reset_second(bpy.types.Operator):
+ """Reset the settings of row options"""
+ bl_idname = 'scene.at_reset_second'
+ bl_label = 'Reset'
+
+ def execute(self, context):
+ prop = context.scene.arraytools_prop
+ prop.tr_second = (0,0,0)
+ prop.sc_second = (100,100,100)
+ prop.rot_second = (0,0,0)
+ return {'FINISHED'}
+
+
+class OBJECT_OT_error(bpy.types.Operator):
+ """Draw a message box to display error"""
+ bl_idname = "info.at_error"
+ bl_label = "Message info"
+
+ info: bpy.props.StringProperty(
+ name = "Message",
+ description = "Display a message",
+ default = ''
+ )
+
+ def execute(self, context):
+ self.report({'INFO'}, self.info)
+ print(self.info)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label(text=self.info)
+ layout.label(text="")
\ No newline at end of file
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_PANEL.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_PANEL.PY
new file mode 100644
index 00000000..9f2e890f
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_PANEL.PY
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+from bpy.types import Panel
+
+from . import cfg
+
+# ---------------------------- Panel --------------------------------
+class UIPANEL_PT_def(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "Array Tools"
+
+
+class UIPANEL_PT_trans(UIPANEL_PT_def):
+ """Panel containing the settings for translation, scale and rotation array"""
+ bl_label = "Array Tools"
+
+ @classmethod
+ def poll(cls, context):
+ return (len(context.selected_objects) > 0 and (context.object.mode == 'OBJECT'))
+
+ def draw(self, context):
+ layout = self.layout
+ scn = context.scene
+ my_prop = scn.arraytools_prop
+
+ row = layout.row()
+ row.operator('scene.at_op')
+ row = layout.row()
+ if not my_prop.already_start:
+ row.alignment = 'CENTER'
+ row.label(text="~ Click to begin ~")
+ else:
+ row.prop(my_prop, 'is_copy')
+ row.prop(my_prop, 'count')
+ box = layout.box()
+ box.label(text="Translation")
+ col = box.column()
+ split = col.split()
+ split.prop(my_prop, 'tr_offset')
+ split.prop(my_prop, 'tr_global')
+
+ row = layout.row()
+ row.prop(my_prop, 'at_pivot')
+
+ box = layout.box()
+ box.label(text="Scaling (%)")
+ col = box.column()
+ split = col.split()
+ split.prop(my_prop, 'sc_offset')
+ split.prop(my_prop, 'sc_global')
+
+ box = layout.box()
+ if scn.unit_settings.system_rotation == 'DEGREES':
+ box.label(text="Rotation (degrees)")
+ else:
+ box.label(text="Rotation (radians)")
+ split = box.split(factor=0.08)
+
+ col = split.column(align=True)
+ col.label(text='')
+ col.operator('scene.x360', text='X')
+ col.operator('scene.y360', text='Y')
+ col.operator('scene.z360', text='Z')
+
+ col = split.column()
+ col.prop(my_prop, 'rot_offset')
+ col = split.column()
+ col.prop(my_prop, 'rot_global')
+
+ box = layout.box()
+ row = box.row()
+ row.scale_y = 1.5
+ row.operator('scene.at_done')
+ row.operator('scene.at_cancel')
+
+ row = box.row()
+ row.scale_y = 0.3
+ row.alignment = 'CENTER'
+ row.label(text="~ Tansforms are NOT applied ~")
+
+
+class UIPANEL_PT_rows(UIPANEL_PT_def):
+ """Panel containing the row options"""
+ bl_parent_id = 'UIPANEL_PT_trans'
+ bl_label = 'Rows options'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ my_prop = context.scene.arraytools_prop
+
+ if my_prop.already_start:
+ row = layout.row()
+ row.prop(my_prop, 'count')
+ row.prop(my_prop, 'row')
+ row = layout.row()
+
+ row.scale_y = 0.8
+ row.prop(my_prop, 'align', icon_only=True, expand=True)
+ row.prop(my_prop, 'alter')
+ row = layout.row()
+
+ row.alignment = 'CENTER'
+ row.scale_x = 1.5
+ row.scale_y = 0.6
+ row.label(text=" - Offset settings -")
+ row.scale_x = 0.8
+ row.operator('scene.at_reset_second')
+
+ layout.use_property_split = True
+
+ col = layout.column()
+ row = col.row(align=True)
+ row.prop(my_prop, 'tr_second')
+ col = layout.column()
+ row = col.row(align=True)
+ row.prop(my_prop, 'sc_second')
+ col = layout.column()
+ row = col.row(align=True)
+ row.prop(my_prop, 'rot_second')
+
+ row = layout.row()
+ row.scale_y = 0.5
+ row.label(text="Total : " + my_prop.total + " | current row : " + my_prop.erow)
+ """
+ box = layout.box()
+ box.prop(my_prop, 'tr_second')
+ #row = layout.row()
+ box.prop(my_prop, 'sc_second')
+ #row = layout.row()
+ box.prop(my_prop, 'rot_second')
+ """
+
+
+class UIPANEL_PT_options(UIPANEL_PT_def):
+ """Panel containing the random options"""
+ bl_parent_id = 'UIPANEL_PT_trans'
+ bl_label = 'Random options'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ my_prop = context.scene.arraytools_prop
+
+ layout.enabled = my_prop.already_start
+ row = layout.row()
+ row.alignment = 'CENTER'
+ row.prop(my_prop, 'at_seed')
+ row = layout.row()
+ row.prop(my_prop, 'at_mode', expand=True)
+ row = layout.row()
+ if my_prop.at_mode == 'SIM':
+ row.prop(my_prop, 'at_is_tr')
+ row = layout.row()
+ row.prop(my_prop, 'tr_rand')
+ row = layout.row()
+ row.prop(my_prop, 'at_is_sc')
+ row = layout.row()
+ row.prop(my_prop, 'sc_rand')
+ row = layout.row()
+ row.prop(my_prop, 'at_is_rot')
+ row = layout.row()
+ row.prop(my_prop, 'rot_rand')
+ else:
+ row.label(text=' ')
+ row.label(text='X')
+ row.label(text='Y')
+ row.label(text='Z')
+ row = layout.row()
+ row.prop(my_prop, 'at_is_tr')
+ row.scale_x = 0.5
+ row.scale_y = 0.7
+ row.operator('scene.at_reset_tr')
+ row.operator('scene.fill_tr')
+ row = layout.row()
+ row.prop(my_prop, 'tr_min')
+ row = layout.row()
+ row.prop(my_prop, 'tr_max')
+ row = layout.row()
+
+ row.prop(my_prop, 'at_is_sc')
+ row.scale_x = 0.5
+ row.scale_y = 0.7
+ row.operator('scene.at_reset_sc')
+ row.operator('scene.fill_sc')
+ row = layout.row()
+ row.alignment = "CENTER"
+ row.scale_y = 0.7
+ row.prop(my_prop, 'sc_all')
+ row = layout.row(align=True)
+ row.label(text='min:')
+ row.prop(my_prop, 'sc_min_x', text='')
+ row.prop(my_prop, 'sc_min_y', text='')
+ row.prop(my_prop, 'sc_min_z', text='')
+ row = layout.row(align=True)
+ row.label(text='max:')
+ row.prop(my_prop, 'sc_max_x', text='')
+ row.prop(my_prop, 'sc_max_y', text='')
+ row.prop(my_prop, 'sc_max_z', text='')
+
+ row = layout.row()
+ row.prop(my_prop, "at_is_rot")
+ row.scale_x = 0.5
+ row.scale_y = 0.7
+ row.operator('scene.at_reset_rot')
+ row.operator('scene.fill_rot')
+ row = layout.row()
+ row.prop(my_prop, 'rot_min')
+ row = layout.row()
+ row.prop(my_prop, 'rot_max')
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/CFG.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/CFG.PY
new file mode 100644
index 00000000..097261b7
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/CFG.PY
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+import bpy
+
+# count values, contains only 2 values : old count and current
+at_count_values = []
+# row value, contains old row and current
+at_row_values = []
+# alter values, contains old and current
+at_alter = []
+# maximun row according to column and alter
+maxrow = 1
+# list of the copies / list of lists
+atools_objs = []
+ref_mtx = [] # reference matrix
+# collection name
+col_name = "Array_collection"
+
+
+def init_array_tool(context):
+ """Initialisation of the array tools"""
+ global at_count_values
+ global at_row_values
+ global at_alter
+ global atools_objs
+ global ref_mtx
+ global col_name
+
+ prop = context.scene.arraytools_prop
+ name = col_name
+ i = 1
+ collect = bpy.data.collections.get(col_name)
+ # create and link the new collection
+ if collect is None:
+ array_col = bpy.data.collections.new(col_name)
+ bpy.context.scene.collection.children.link(array_col)
+ else:
+ # if a collection already exist, create a new one
+ while bpy.data.collections.get(name) is not None:
+ name = col_name + str(i)
+ i += 1
+ array_col = bpy.data.collections.new(name)
+ bpy.context.scene.collection.children.link(array_col)
+ col_name = name
+
+ if not prop.already_start:
+ at_count_values = [1, 2]
+ at_row_values = [0, 1]
+ at_alter = [0, 0]
+ active = context.active_object
+ prop.already_start = True
+ prop.is_tr_off_last = True
+ if active is not None:
+ atools_objs.append([active.name])
+ ref_mtx = active.matrix_world.copy()
+ del active
+ prop.add_in_column(prop.row)
+ # no need anymore
+ else:
+ print("No object selected")
+ else:
+ print("Already started!")
+
+
+def add_count(value):
+ """Save the current count"""
+ global at_count_values
+ at_count_values.append(value)
+
+
+def del_count():
+ """Del the previous count"""
+ global at_count_values
+ del at_count_values[0]
+
+
+def add_row(value):
+ """Save the current row"""
+ global at_row_values
+ at_row_values.append(value)
+
+
+def del_row():
+ """ Del the previous row value"""
+ global at_row_values
+ del at_row_values[0]
+
+
+def add_alter(value):
+ """save the current variation"""
+ global at_alter
+ at_alter.append(value)
+
+
+def del_alter():
+ """Remove previous variation"""
+ global at_alter
+ del at_alter[0]
+
+
+def display_error(msg):
+ """Call the operator to display an error message"""
+ bpy.ops.info.at_error('INVOKE_DEFAULT', info = msg)
+
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__INIT__.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__INIT__.PY
new file mode 100644
index 00000000..9fd1d327
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__INIT__.PY
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+import bpy
+
+from . import cfg
+from . import at_interface
+
+bl_info = {
+ "name": "Array_tools",
+ "author": "Elreenys",
+ "description": "Tools to create array of objects",
+ "blender": (2, 80, 0),
+ "version": (1, 2, 1),
+ "location": "View3D > sidebar > array tools tab",
+ "category": "Object"
+}
+
+classes = (
+ at_operators.OBJECT_OT_at_start,
+ at_operators.OBJECT_OT_at_cancel,
+ at_operators.OBJECT_OT_at_done,
+ at_operators.OBJECT_OT_fill_tr,
+ at_operators.OBJECT_OT_fill_sc,
+ at_operators.OBJECT_OT_fill_rot,
+ at_operators.OBJECT_OT_x360,
+ at_operators.OBJECT_OT_y360,
+ at_operators.OBJECT_OT_z360,
+ at_operators.OBJECT_OT_reset_tr,
+ at_operators.OBJECT_OT_reset_sc,
+ at_operators.OBJECT_OT_reset_rot,
+ at_operators.OBJECT_OT_reset_second,
+ at_operators.OBJECT_OT_error,
+ at_panel.UIPANEL_PT_trans,
+ at_panel.UIPANEL_PT_rows,
+ at_panel.UIPANEL_PT_options,
+ at_interface.ArrayTools_props
+)
+
+
+def register():
+ scene = bpy.types.Scene
+ pp = bpy.props.PointerProperty
+
+ for cls in classes:
+ bpy.utils.register_class(cls)
+ scene.arraytools_prop = pp(type=at_interface.ArrayTools_props)
+
+
+def unregister():
+ del bpy.types.Scene.arraytools_prop
+ for cls in reversed(classes):
+ bpy.utils.unregister_class(cls)
+
+
+if __name__ == '__main__':
+ register()
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_CALC_FUNC.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_CALC_FUNC.CPYTHON-37.PYC
new file mode 100644
index 00000000..018749b4
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_CALC_FUNC.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_INTERFACE.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_INTERFACE.CPYTHON-37.PYC
new file mode 100644
index 00000000..acafa98c
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_INTERFACE.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_OPERATORS.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_OPERATORS.CPYTHON-37.PYC
new file mode 100644
index 00000000..4bf6527d
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_OPERATORS.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_PANEL.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_PANEL.CPYTHON-37.PYC
new file mode 100644
index 00000000..16ac25c2
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_PANEL.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/CFG.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/CFG.CPYTHON-37.PYC
new file mode 100644
index 00000000..b4f3f4aa
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/CFG.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/__INIT__.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/__INIT__.CPYTHON-37.PYC
new file mode 100644
index 00000000..747b8cc2
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/__INIT__.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/MESH_SOLIDIFY_WIREFRAME.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/MESH_SOLIDIFY_WIREFRAME.PY
new file mode 100644
index 00000000..f8532311
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/MESH_SOLIDIFY_WIREFRAME.PY
@@ -0,0 +1,298 @@
+#!BPY
+
+bl_info = {
+ "name": "Solidify Wireframe",
+ "author": "Yorik van Havre, Alejandro Sierra, Howard Trickey",
+ "description": "Turns the selected edges of a mesh into solid geometry",
+ "version": (2, 3),
+ "blender": (2, 5, 8),
+ "category": "Mesh",
+ "location": "Mesh > Solidify Wireframe",
+ "warning": '',
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Modeling/Solidify_Wireframe",
+ "tracker_url": "http://projects.blender.org/tracker/?func=detail&group_id=153&aid=26997&atid=467",
+ }
+
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See th
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+import bpy, mathutils
+
+cube_faces = [ [0,3,2,1], [5,6,7,4], [0,1,5,4],
+ [7,6,2,3], [2,6,5,1], [0,4,7,3] ]
+cube_normals = [ mathutils.Vector((0,0,-1)),
+ mathutils.Vector((0,0,1)),
+ mathutils.Vector((0,-1,0)),
+ mathutils.Vector((0,1,0)),
+ mathutils.Vector((1,0,0)),
+ mathutils.Vector((-1,0,0)) ]
+
+def create_cube(me, v, d):
+ x = v.co.x
+ y = v.co.y
+ z = v.co.z
+ coords=[ [x-d,y-d,z-d], [x+d,y-d,z-d], [x+d,y+d,z-d], [x-d,y+d,z-d],
+ [x-d,y-d,z+d], [x+d,y-d,z+d], [x+d,y+d,z+d], [x-d,y+d,z+d] ]
+ for coord in coords:
+ me.vertices.add(1)
+ me.vertices[-1].co = mathutils.Vector(coord)
+
+def norm_dot(e, k, fnorm, me):
+ v = me.vertices[e[1]].co - me.vertices[e[0]].co
+ if k == 1:
+ v = -v
+ v.normalize()
+ return v * fnorm
+
+def fill_cube_face(me, index, f):
+ return [index + cube_faces[f][i] for i in range(4)]
+
+# Coords of jth point of face f in cube instance i
+def cube_face_v(me, f, i, j):
+ return me.vertices[i + cube_faces[f][j]].co
+
+def cube_face_center(me, f, i):
+ return 0.5 * (cube_face_v(me, f, i, 0) + \
+ cube_face_v(me, f, i, 2))
+
+# Return distance between points on two faces when
+# each point is projected onto the plane that goes through
+# the face center and is perpendicular to the line
+# through the face centers.
+def projected_dist(me, i1, i2, f1, f2, j1, j2):
+ f1center = cube_face_center(me, f1, i1)
+ f2center = cube_face_center(me, f2, i2)
+ axis_norm = (f2center - f1center).normalized()
+ v1 = cube_face_v(me, f1, i1, j1)
+ v2 = cube_face_v(me, f2, i2, j2)
+ v1proj = v1 - (axis_norm * (v1 - f1center)) * axis_norm
+ v2proj = v2 - (axis_norm * (v2 - f2center)) * axis_norm
+ return (v2proj - v1proj).length
+
+def skin_edges(me, i1, i2, f1, f2):
+ # Connect verts starting at i1 forming cube face f1
+ # to those starting at i2 forming cube face f2.
+ # Need to find best alignment to avoid a twist.
+ shortest_length = 1e6
+ f2_start_index = 0
+ for i in range(4):
+ x = projected_dist(me, i1, i2, f1, f2, 0, i)
+ if x < shortest_length:
+ shortest_length = x
+ f2_start_index = i
+ ans = []
+ j = f2_start_index
+ for i in range(4):
+ fdata = [i1 + cube_faces[f1][i],
+ i2 + cube_faces[f2][j],
+ i2 + cube_faces[f2][(j + 1) % 4],
+ i1 + cube_faces[f1][(i - 1) % 4]]
+ if fdata[3] == 0:
+ fdata = [fdata[3]] + fdata[0:3]
+ ans.extend(fdata)
+ j = (j - 1) % 4
+ return ans
+
+
+# Return map: v -> list of length len(node_normals) where
+# each element of the list is either None (no assignment)
+# or ((v0, v1), 0 or 1) giving an edge and direction that face is assigned to.
+def find_assignment(me, edges, vert_edges, node_normals):
+ nf = len(node_normals)
+ feasible = {}
+ for e in edges:
+ for k in (0, 1):
+ fds = [(f, norm_dot(e, k, node_normals[f], me)) for f in range(nf)]
+ feasible[(e, k)] = [fd for fd in fds if fd[1] > 0.01]
+ assignment = {}
+ for v, ves in vert_edges.items():
+ assignment[v] = best_assignment(ves, feasible, nf)
+ return assignment
+
+def best_assignment(ves, feasible, nf):
+ apartial = [ None ] * nf
+ return best_assign_help(ves, feasible, apartial, 0.0)[0]
+
+def best_assign_help(ves, feasible, apartial, sumpartial):
+ if len(ves) == 0:
+ return (apartial, sumpartial)
+ else:
+ ek0 = ves[0]
+ vesrest = ves[1:]
+ feas = feasible[ek0]
+ bestsum = 0
+ besta = None
+ for (f, d) in feas:
+ if apartial[f] is None:
+ ap = apartial[:]
+ ap[f] = ek0
+ # sum up d**2 to penalize smaller d's more
+ sp = sumpartial + d*d
+ (a, s) = best_assign_help(vesrest, feasible, ap, sp)
+ if s > bestsum:
+ bestsum = s
+ besta = a
+ if besta:
+ return (besta, bestsum)
+ else:
+ # not feasible to assign e0, k0; try to assign rest
+ return best_assign_help(vesrest, feasible, apartial, sumpartial)
+
+def assigned_face(e, assignment):
+ (v0, v1), dir = e
+ a = assignment[v1]
+ for j, ee in enumerate(a):
+ if e == ee:
+ return j
+ return -1
+
+def create_wired_mesh(me2, me, thick):
+ edges = []
+ vert_edges = {}
+ for be in me.edges:
+ if be.select and not be.hide:
+ e = (be.key[0], be.key[1])
+ edges.append(e)
+ for k in (0, 1):
+ if e[k] not in vert_edges:
+ vert_edges[e[k]] = []
+ vert_edges[e[k]].append((e, k))
+
+ assignment = find_assignment(me, edges, vert_edges, cube_normals)
+
+ # Create the geometry
+ n_idx = {}
+ for v in assignment:
+ vpos = me.vertices[v]
+ index = len(me2.vertices)
+ # We need to associate each node with the new geometry
+ n_idx[v] = index
+ # Geometry for the nodes, each one a cube
+ create_cube(me2, vpos, thick)
+
+ # Skin using the new geometry
+ cfaces = []
+ for k, f in assignment.items():
+ # Skin the nodes
+ for i in range(len(cube_faces)):
+ if f[i] is None:
+ cfaces.extend(fill_cube_face(me2, n_idx[k], i))
+ else:
+ (v0, v1), dir = f[i]
+ # only skin between edges in forward direction
+ # to avoid making doubles
+ if dir == 1:
+ # but first make sure other end actually assigned
+ i2 = assigned_face(((v0, v1), 0), assignment)
+ if i2 == -1:
+ cfaces.extend(fill_cube_face(me2, n_idx[k], i))
+ continue
+ i2 = assigned_face(((v0, v1), 1), assignment)
+ if i2 != -1:
+ cfaces.extend(skin_edges(me2, n_idx[v0], n_idx[v1], i, i2))
+ else:
+ # assignment failed for this edge
+ cfaces.extend(fill_cube_face(me2, n_idx[k], i))
+
+ # adding faces to the mesh
+ me2.faces.add(len(cfaces) // 4)
+ me2.faces.foreach_set("vertices_raw", cfaces)
+ me2.update(calc_edges=True)
+
+# panel containing tools
+class VIEW3D_PT_tools_SolidifyWireframe(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_context = "mesh_edit"
+ bl_label = "Solidify Wireframe"
+
+ def draw(self, context):
+ active_obj = context.active_object
+ layout = self.layout
+ col = layout.column(align=True)
+ col.operator("mesh.solidify_wireframe", text="Solidify")
+ col.prop(context.scene, "swThickness")
+ col.prop(context.scene, "swSelectNew")
+
+# a class for your operator
+class SolidifyWireframe(bpy.types.Operator):
+ '''Turns the selected edges of a mesh into solid objects'''
+ bl_idname = "mesh.solidify_wireframe"
+ bl_label = "Solidify Wireframe"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def invoke(self, context, event):
+ return self.execute(context)
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.active_object
+ return ob and ob.type == 'MESH'
+
+ def execute(self, context):
+ # Get the active object
+ ob_act = context.active_object
+ # getting current edit mode
+ currMode = ob_act.mode
+ # switching to object mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.select_all(action='DESELECT')
+ # getting mesh data
+ mymesh = ob_act.data
+ #getting new mesh
+ newmesh = bpy.data.meshes.new(mymesh.name + " wire")
+ obj = bpy.data.objects.new(newmesh.name,newmesh)
+ obj.location = ob_act.location
+ obj.rotation_euler = ob_act.rotation_euler
+ obj.scale = ob_act.scale
+ context.scene.objects.link(obj)
+ create_wired_mesh(newmesh, mymesh, context.scene.swThickness)
+
+ # restoring original editmode if needed
+ if context.scene.swSelectNew:
+ obj.select = True
+ context.scene.objects.active = obj
+ else:
+ bpy.ops.object.mode_set(mode=currMode)
+
+ # returning after everything is done
+ return {'FINISHED'}
+
+# Register the operator
+def solidifyWireframe_menu_func(self, context):
+ self.layout.operator(SolidifyWireframe.bl_idname, text="Solidify Wireframe", icon='PLUGIN')
+
+# Add "Solidify Wireframe" menu to the "Mesh" menu.
+def register():
+ bpy.utils.register_module(__name__)
+ bpy.types.Scene.swThickness = bpy.props.FloatProperty(name="Thickness",
+ description="Thickness of the skinned edges",
+ default=0.02)
+ bpy.types.Scene.swSelectNew = bpy.props.BoolProperty(name="Select wire",
+ description="If checked, the wire object will be selected after creation",
+ default=True)
+ bpy.types.VIEW3D_MT_edit_mesh_edges.append(solidifyWireframe_menu_func)
+
+# Remove "Solidify Wireframe" menu entry from the "Mesh" menu.
+def unregister():
+ bpy.utils.register_module(__name__)
+ del bpy.types.Scene.swThickness
+ bpy.types.VIEW3D_MT_edit_mesh_edges.remove(solidifyWireframe_menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/OBJECT_RENDER_WIRE.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/OBJECT_RENDER_WIRE.PY
new file mode 100644
index 00000000..4ffb03fa
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/OBJECT_RENDER_WIRE.PY
@@ -0,0 +1,421 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See th
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# object_render_wire.py liero, meta-androcto,
+# Yorik van Havre, Alejandro Sierra, Howard Trickey
+# ***** END GPL LICENCE BLOCK *****
+
+bl_info = {
+ "name": "Render Wireframe",
+ "author": "Community",
+ "description": " WireRender & WireSoild modes",
+ "version": (2, 3),
+ "blender": (2, 63, 0),
+ "location": "Object > Render Wireframe",
+ "warning": '',
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=26997',
+ 'category': 'Object'}
+
+import bpy, mathutils
+
+cube_faces = [ [0,3,2,1], [5,6,7,4], [0,1,5,4],
+ [7,6,2,3], [2,6,5,1], [0,4,7,3] ]
+cube_normals = [ mathutils.Vector((0,0,-1)),
+ mathutils.Vector((0,0,1)),
+ mathutils.Vector((0,-1,0)),
+ mathutils.Vector((0,1,0)),
+ mathutils.Vector((1,0,0)),
+ mathutils.Vector((-1,0,0)) ]
+
+def create_cube(me, v, d):
+ x = v.co.x
+ y = v.co.y
+ z = v.co.z
+ coords=[ [x-d,y-d,z-d], [x+d,y-d,z-d], [x+d,y+d,z-d], [x-d,y+d,z-d],
+ [x-d,y-d,z+d], [x+d,y-d,z+d], [x+d,y+d,z+d], [x-d,y+d,z+d] ]
+ for coord in coords:
+ me.vertices.add(1)
+ me.vertices[-1].co = mathutils.Vector(coord)
+
+def norm_dot(e, k, fnorm, me):
+ v = me.vertices[e[1]].co - me.vertices[e[0]].co
+ if k == 1:
+ v = -v
+ v.normalize()
+ return v * fnorm
+
+def fill_cube_face(me, index, f):
+ return [index + cube_faces[f][i] for i in range(4)]
+
+# Coords of jth point of face f in cube instance i
+def cube_face_v(me, f, i, j):
+ return me.vertices[i + cube_faces[f][j]].co
+
+def cube_face_center(me, f, i):
+ return 0.5 * (cube_face_v(me, f, i, 0) + \
+ cube_face_v(me, f, i, 2))
+
+# Return distance between points on two faces when
+# each point is projected onto the plane that goes through
+# the face center and is perpendicular to the line
+# through the face centers.
+def projected_dist(me, i1, i2, f1, f2, j1, j2):
+ f1center = cube_face_center(me, f1, i1)
+ f2center = cube_face_center(me, f2, i2)
+ axis_norm = (f2center - f1center).normalized()
+ v1 = cube_face_v(me, f1, i1, j1)
+ v2 = cube_face_v(me, f2, i2, j2)
+ v1proj = v1 - (axis_norm * (v1 - f1center)) * axis_norm
+ v2proj = v2 - (axis_norm * (v2 - f2center)) * axis_norm
+ return (v2proj - v1proj).length
+
+def skin_edges(me, i1, i2, f1, f2):
+ # Connect verts starting at i1 forming cube face f1
+ # to those starting at i2 forming cube face f2.
+ # Need to find best alignment to avoid a twist.
+ shortest_length = 1e6
+ f2_start_index = 0
+ for i in range(4):
+ x = projected_dist(me, i1, i2, f1, f2, 0, i)
+ if x < shortest_length:
+ shortest_length = x
+ f2_start_index = i
+ ans = []
+ j = f2_start_index
+ for i in range(4):
+ fdata = [i1 + cube_faces[f1][i],
+ i2 + cube_faces[f2][j],
+ i2 + cube_faces[f2][(j + 1) % 4],
+ i1 + cube_faces[f1][(i - 1) % 4]]
+ if fdata[3] == 0:
+ fdata = [fdata[3]] + fdata[0:3]
+ ans.extend(fdata)
+ j = (j - 1) % 4
+ return ans
+
+
+# Return map: v -> list of length len(node_normals) where
+# each element of the list is either None (no assignment)
+# or ((v0, v1), 0 or 1) giving an edge and direction that face is assigned to.
+def find_assignment(me, edges, vert_edges, node_normals):
+ nf = len(node_normals)
+ feasible = {}
+ for e in edges:
+ for k in (0, 1):
+ fds = [(f, norm_dot(e, k, node_normals[f], me)) for f in range(nf)]
+ feasible[(e, k)] = [fd for fd in fds if fd[1] > 0.01]
+ assignment = {}
+ for v, ves in vert_edges.items():
+ assignment[v] = best_assignment(ves, feasible, nf)
+ return assignment
+
+def best_assignment(ves, feasible, nf):
+ apartial = [ None ] * nf
+ return best_assign_help(ves, feasible, apartial, 0.0)[0]
+
+def best_assign_help(ves, feasible, apartial, sumpartial):
+ if len(ves) == 0:
+ return (apartial, sumpartial)
+ else:
+ ek0 = ves[0]
+ vesrest = ves[1:]
+ feas = feasible[ek0]
+ bestsum = 0
+ besta = None
+ for (f, d) in feas:
+ if apartial[f] is None:
+ ap = apartial[:]
+ ap[f] = ek0
+ # sum up d**2 to penalize smaller d's more
+ sp = sumpartial + d*d
+ (a, s) = best_assign_help(vesrest, feasible, ap, sp)
+ if s > bestsum:
+ bestsum = s
+ besta = a
+ if besta:
+ return (besta, bestsum)
+ else:
+ # not feasible to assign e0, k0; try to assign rest
+ return best_assign_help(vesrest, feasible, apartial, sumpartial)
+
+def assigned_face(e, assignment):
+ (v0, v1), dir = e
+ a = assignment[v1]
+ for j, ee in enumerate(a):
+ if e == ee:
+ return j
+ return -1
+
+def create_wired_mesh(me2, me, thick):
+ edges = []
+ vert_edges = {}
+ for be in me.edges:
+ if be.select and not be.hide:
+ e = (be.key[0], be.key[1])
+ edges.append(e)
+ for k in (0, 1):
+ if e[k] not in vert_edges:
+ vert_edges[e[k]] = []
+ vert_edges[e[k]].append((e, k))
+
+ assignment = find_assignment(me, edges, vert_edges, cube_normals)
+
+ # Create the geometry
+ n_idx = {}
+ for v in assignment:
+ vpos = me.vertices[v]
+ index = len(me2.vertices)
+ # We need to associate each node with the new geometry
+ n_idx[v] = index
+ # Geometry for the nodes, each one a cube
+ create_cube(me2, vpos, thick)
+
+ # Skin using the new geometry
+ cfaces = []
+ for k, f in assignment.items():
+ # Skin the nodes
+ for i in range(len(cube_faces)):
+ if f[i] is None:
+ cfaces.extend(fill_cube_face(me2, n_idx[k], i))
+ else:
+ (v0, v1), dir = f[i]
+ # only skin between edges in forward direction
+ # to avoid making doubles
+ if dir == 1:
+ # but first make sure other end actually assigned
+ i2 = assigned_face(((v0, v1), 0), assignment)
+ if i2 == -1:
+ cfaces.extend(fill_cube_face(me2, n_idx[k], i))
+ continue
+ i2 = assigned_face(((v0, v1), 1), assignment)
+ if i2 != -1:
+ cfaces.extend(skin_edges(me2, n_idx[v0], n_idx[v1], i, i2))
+ else:
+ # assignment failed for this edge
+ cfaces.extend(fill_cube_face(me2, n_idx[k], i))
+
+ # adding faces to the mesh
+ me2.tessfaces.add(len(cfaces) // 4)
+ me2.tessfaces.foreach_set("vertices_raw", cfaces)
+ me2.update(calc_edges=True)
+
+# Add built in wireframe
+def wire_add(mallas):
+ if mallas:
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.context.scene.objects.active = mallas[0]
+ for o in mallas: o.select = True
+ bpy.ops.object.duplicate()
+ obj, sce = bpy.context.object, bpy.context.scene
+ for mod in obj.modifiers: obj.modifiers.remove(mod)
+ bpy.ops.object.join()
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.wireframe(thickness=0.005)
+ bpy.ops.object.mode_set()
+ for mat in obj.material_slots: bpy.ops.object.material_slot_remove()
+ if 'wire_object' in sce.objects.keys():
+ sce.objects.get('wire_object').data = obj.data
+ sce.objects.get('wire_object').matrix_world = mallas[0].matrix_world
+ sce.objects.unlink(obj)
+ else:
+ obj.name = 'wire_object'
+ obj.data.materials.append(bpy.data.materials.get('mat_wireobj'))
+
+ return{'FINISHED'}
+'''
+class VIEW3D_PT_tools_SolidifyWireframe(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_context = "mesh_edit"
+ bl_label = "Solidify Wireframe"
+
+ def draw(self, context):
+ active_obj = context.active_object
+ layout = self.layout
+ col = layout.column(align=True)
+ col.operator("mesh.solidify_wireframe", text="Solidify")
+ col.prop(context.scene, "swThickness")
+ col.prop(context.scene, "swSelectNew")
+'''
+# a class for your operator
+class SolidifyWireframe(bpy.types.Operator):
+ """Turns the selected edges of a mesh into solid objects"""
+ bl_idname = "mesh.solidify_wireframe"
+ bl_label = "Solidify Wireframe"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def invoke(self, context, event):
+ return self.execute(context)
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.active_object
+ return ob and ob.type == 'MESH'
+
+ def execute(self, context):
+ # Get the active object
+ ob_act = context.active_object
+ # getting current edit mode
+ currMode = ob_act.mode
+ # switching to object mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.select_all(action='DESELECT')
+ # getting mesh data
+ mymesh = ob_act.data
+ #getting new mesh
+ newmesh = bpy.data.meshes.new(mymesh.name + " wire")
+ obj = bpy.data.objects.new(newmesh.name,newmesh)
+ obj.location = ob_act.location
+ obj.rotation_euler = ob_act.rotation_euler
+ obj.scale = ob_act.scale
+ context.scene.objects.link(obj)
+ create_wired_mesh(newmesh, mymesh, context.scene.swThickness)
+
+ # restoring original editmode if needed
+ if context.scene.swSelectNew:
+ obj.select = True
+ context.scene.objects.active = obj
+ else:
+ bpy.ops.object.mode_set(mode=currMode)
+
+ # returning after everything is done
+ return {'FINISHED'}
+
+class WireMaterials(bpy.types.Operator):
+ bl_idname = 'scene.wire_render'
+ bl_label = 'Apply Materials'
+ bl_description = 'Set Up Materials for a Wire Render'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ wm = bpy.context.window_manager
+ sce = bpy.context.scene
+
+ if 'mat_clay' not in bpy.data.materials:
+ mat = bpy.data.materials.new('mat_clay')
+ mat.specular_intensity = 0
+ else: mat = bpy.data.materials.get('mat_clay')
+ mat.diffuse_color = wm.col_clay
+ mat.use_shadeless = wm.shadeless_mat
+
+ if 'mat_wire' not in bpy.data.materials:
+ mat = bpy.data.materials.new('mat_wire')
+ mat.specular_intensity = 0
+ mat.use_transparency = True
+ mat.type = 'WIRE'
+ mat.offset_z = 0.05
+ else: mat = bpy.data.materials.get('mat_wire')
+ mat.diffuse_color = wm.col_wire
+ mat.use_shadeless = wm.shadeless_mat
+
+ try: bpy.ops.object.mode_set()
+ except: pass
+
+ if wm.selected_meshes: objetos = bpy.context.selected_objects
+ else: objetos = sce.objects
+
+ mallas = [o for o in objetos if o.type == 'MESH' and o.is_visible(sce) and o.name != 'wire_object']
+
+ for obj in mallas:
+ sce.objects.active = obj
+ print ('procesando >', obj.name)
+ obj.show_wire = wm.wire_view
+ for mat in obj.material_slots:
+ bpy.ops.object.material_slot_remove()
+ obj.data.materials.append(bpy.data.materials.get('mat_wire'))
+ obj.data.materials.append(bpy.data.materials.get('mat_clay'))
+ obj.material_slots.data.active_material_index = 1
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.object.material_slot_assign()
+ bpy.ops.object.mode_set()
+
+ if wm.wire_object:
+ if 'mat_wireobj' not in bpy.data.materials:
+ mat = bpy.data.materials.new('mat_wireobj')
+ mat.specular_intensity = 0
+ else: mat = bpy.data.materials.get('mat_wireobj')
+ mat.diffuse_color = wm.col_wire
+ mat.use_shadeless = wm.shadeless_mat
+ wire_add(mallas)
+
+ return{'FINISHED'}
+
+class PanelWMat(bpy.types.Panel):
+ bl_label = 'Setup Wire Render'
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ wm = bpy.context.window_manager
+ active_obj = context.active_object
+ layout = self.layout
+
+ column = layout.column(align=True)
+ column.prop(wm, 'col_clay')
+ column.prop(wm, 'col_wire')
+ column = layout.column(align=True)
+ column.prop(wm, 'selected_meshes')
+ column.prop(wm, 'shadeless_mat')
+ column.prop(wm, 'wire_view')
+ column.prop(wm, 'wire_object')
+ column.separator()
+ column.operator('scene.wire_render')
+ column.label(text='- - - - - - - - - - - - - - - - - - - - - -')
+ col = layout.column(align=True)
+ column.label(text='Solid WireFrame')
+ layout.operator("mesh.solidify_wireframe", text="Create Mesh Object")
+ col.prop(context.scene, "swThickness")
+ col.prop(context.scene, "swSelectNew")
+bpy.types.WindowManager.selected_meshes = bpy.props.BoolProperty(name='Selected Meshes', default=False, description='Apply materials to Selected Meshes / All Visible Meshes')
+bpy.types.WindowManager.shadeless_mat = bpy.props.BoolProperty(name='Shadeless', default=False, description='Generate Shadeless Materials')
+bpy.types.WindowManager.col_clay = bpy.props.FloatVectorProperty(name='', description='Clay Color', default=(1.0, 0.9, 0.8), min=0, max=1, step=1, precision=3, subtype='COLOR_GAMMA', size=3)
+bpy.types.WindowManager.col_wire = bpy.props.FloatVectorProperty(name='', description='Wire Color', default=(0.1 ,0.0 ,0.0), min=0, max=1, step=1, precision=3, subtype='COLOR_GAMMA', size=3)
+bpy.types.WindowManager.wire_view = bpy.props.BoolProperty(name='Viewport Wires', default=False, description='Overlay wires display over solid in Viewports')
+bpy.types.WindowManager.wire_object = bpy.props.BoolProperty(name='Create Mesh Object', default=False, description='Add a Wire Object to scene to be able to render wires in Cycles')
+bpy.types.Scene.swThickness = bpy.props.FloatProperty(name="Thickness", description="Thickness of the skinned edges", default=0.01)
+bpy.types.Scene.swSelectNew = bpy.props.BoolProperty(name="Select wire", description="If checked, the wire object will be selected after creation", default=True)
+
+# Register the operator
+def solidifyWireframe_menu_func(self, context):
+ self.layout.operator(SolidifyWireframe.bl_idname, text="Solidify Wireframe", icon='PLUGIN')
+
+# Add "Solidify Wireframe" menu to the "Mesh" menu.
+def register():
+ bpy.utils.register_class(WireMaterials)
+ bpy.utils.register_class(PanelWMat)
+ bpy.utils.register_module(__name__)
+ bpy.types.Scene.swThickness = bpy.props.FloatProperty(name="Thickness",
+ description="Thickness of the skinned edges",
+ default=0.01)
+ bpy.types.Scene.swSelectNew = bpy.props.BoolProperty(name="Select wire",
+ description="If checked, the wire object will be selected after creation",
+ default=True)
+ bpy.types.VIEW3D_MT_edit_mesh_edges.append(solidifyWireframe_menu_func)
+
+# Remove "Solidify Wireframe" menu entry from the "Mesh" menu.
+def unregister():
+ bpy.utils.unregister_class(WireMaterials)
+ bpy.utils.unregister_class(PanelWMat)
+ bpy.utils.unregister_module(__name__)
+ del bpy.types.Scene.swThickness
+ bpy.types.VIEW3D_MT_edit_mesh_edges.remove(solidifyWireframe_menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/PRECISE_RENDER_BORDER_ADJUST_V1-3.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/PRECISE_RENDER_BORDER_ADJUST_V1-3.PY
new file mode 100644
index 00000000..c459f96c
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/PRECISE_RENDER_BORDER_ADJUST_V1-3.PY
@@ -0,0 +1,158 @@
+######################################################################################################
+# A simple add-on to allows the user to precisly place the border render region (Ctrl+B in cam view) #
+# using numerical input, witch can be animated #
+# Actualy uncommented (see further version) #
+# Author: Lapineige #
+# License: GPL v3 #
+######################################################################################################
+
+
+############# Add-on description (used by Blender)
+
+bl_info = {
+ "name": "Precise Render Border Adjust",
+ "description": 'Allows to modify and animate the "Border Render" region with numerical input.',
+ "author": "Lapineige",
+ "version": (1, 3),
+ "blender": (2, 71, 0),
+ "location": "Properties > Render > Precise Render Border Adjust (panel)",
+ "warning": "", # used for warning icon and text in addons panel
+ "wiki_url": "http://le-terrier-de-lapineige.over-blog.com/2014/07/precise-render-border-adjust-mon-add-on-pour-positionner-precisement-le-border-render.html",
+ "tracker_url": "http://blenderclan.tuxfamily.org/html/modules/newbb/viewtopic.php?topic_id=42159",
+ "category": "Render"}
+
+##############
+
+import bpy
+
+bpy.types.Scene.x_min_pixels = bpy.props.IntProperty(min=0, description="Minimum X value (in pixel) for the render border")
+bpy.types.Scene.x_max_pixels = bpy.props.IntProperty(min=0, description="Maximum X value (in pixel) for the render border")
+bpy.types.Scene.y_min_pixels = bpy.props.IntProperty(min=0, description="Minimum Y value (in pixel) for the render border")
+bpy.types.Scene.y_max_pixels = bpy.props.IntProperty(min=0, description="Maximum Y value (in pixel) for the render border")
+
+
+class PreciseRenderBorderAdjust(bpy.types.Panel):
+ """Creates the tools in a Panel, in the scene context of the properties editor"""
+ bl_label = "Precise Render Border Adjust"
+ bl_idname = "Precise_Render_Border_Adjust"
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "render"
+
+ def draw(self, context):
+ layout = self.layout
+
+ scene = context.scene
+
+ if not scene.render.use_border:
+ sub = layout.split(percentage=0.7)
+ sub.label(icon="ERROR", text="Border Render not activated:")
+ sub.prop(scene.render, "use_border")
+
+ sub = layout.column()
+ row = sub.row()
+ row.label(text="")
+ row.prop(scene.render, "border_max_y", text="Max", slider=True)
+ row.label(text="")
+ row = sub.row(align=True)
+ row.prop(scene.render, "border_min_x", text="Min", slider=True)
+ row.prop(scene.render, "border_max_x", text="Max", slider=True)
+ row = sub.row()
+ row.label(text="")
+ row.prop(scene.render, "border_min_y", text="Min", slider=True)
+ row.label(text="")
+
+ row = layout.row()
+ row.label(text="Convert values to pixels:")
+ row.operator("render.bordertopixels", text="Border -> Pixels")
+
+ layout.label(text="Pixels position X:")
+ row = layout.row(align=True)
+ row.prop(scene, "x_min_pixels", text="Min")
+ row.prop(scene, "x_max_pixels", text="Max")
+ layout.label(text="Pixels position Y:")
+ row = layout.row(align=True)
+ row.prop(scene, "y_min_pixels", text="Min")
+ row.prop(scene, "y_max_pixels", text="Max")
+
+ layout.label(icon="INFO", text="Don't forget to apply pixels values")
+ row = layout.row()
+ row.operator("render.pixelstoborder", text="Pixels -> Border")
+
+class PixelsToBorder(bpy.types.Operator):
+ """ Convert the pixel value into the proportion needed by the Blender native property """
+ bl_idname = "render.pixelstoborder"
+ bl_label = "Convert Pixels to Border proportion"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ C = bpy.context
+
+ X = C.scene.render.resolution_x
+ Y = C.scene.render.resolution_y
+
+ C.scene.render.border_min_x = C.scene.x_min_pixels / X
+ C.scene.render.border_max_x = C.scene.x_max_pixels / X
+ C.scene.render.border_min_y = C.scene.y_min_pixels / Y
+ C.scene.render.border_max_y = C.scene.y_max_pixels / Y
+
+ if C.scene.x_min_pixels > X:
+ C.scene.x_min_pixels = X
+ if C.scene.x_max_pixels > X:
+ C.scene.x_max_pixels = X
+ if C.scene.y_min_pixels > Y:
+ C.scene.y_min_pixels = Y
+ if C.scene.y_max_pixels > Y:
+ C.scene.y_max_pixels = Y
+
+ return {'FINISHED'}
+
+class BorderToPixels(bpy.types.Operator):
+ """ Convert the Blender native property value to pixels"""
+ bl_idname = "render.bordertopixels"
+ bl_label = "Convert border values to pixels"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ C = bpy.context
+
+ X = C.scene.render.resolution_x
+ Y = C.scene.render.resolution_y
+
+ C.scene.x_min_pixels = int(C.scene.render.border_min_x * X)
+ C.scene.x_max_pixels = int(C.scene.render.border_max_x * X)
+ C.scene.y_min_pixels = int(C.scene.render.border_min_y * Y)
+ C.scene.y_max_pixels = int(C.scene.render.border_max_y * Y)
+
+ return {'FINISHED'}
+
+def register():
+ bpy.utils.register_class(PreciseRenderBorderAdjust)
+ bpy.utils.register_class(PixelsToBorder)
+ bpy.utils.register_class(BorderToPixels)
+
+
+def unregister():
+ bpy.utils.unregister_class(PreciseRenderBorderAdjust)
+ bpy.utils.unregister_class(PixelsToBorder)
+ bpy.utils.unregister_class(BorderToPixels)
+
+
+if __name__ == "__main__":
+ C = bpy.context
+
+ X = C.scene.render.resolution_x
+ Y = C.scene.render.resolution_y
+
+ C.scene.x_min_pixels = 0
+ C.scene.x_max_pixels = X
+ C.scene.y_min_pixels = 0
+ C.scene.y_max_pixels = Y
+
+ register()
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/RENDER-BORDER.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/RENDER-BORDER.PY
new file mode 100644
index 00000000..7599116a
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/RENDER-BORDER.PY
@@ -0,0 +1,307 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Render Border",
+ "description": "Render Border",
+ "author": "Christian Brinkmann, David Boho",
+ "version": (0, 0, 5),
+ "blender": (2, 80, 0),
+ "tracker_url": "https://github.com/p2or/blender-renderborder",
+ "location": "Camera > Properties > Data > Render Border",
+ "category": "Render"
+}
+
+import bpy
+from bpy.app.handlers import persistent
+
+
+def round_pixels(pixel_float):
+ return round(pixel_float, 2)
+
+def calc_normalized(pixels_int, pixel_max):
+ return pixels_int / pixel_max if pixel_max else 0.0
+
+def calc_pixels(normalized_float, pixel_max):
+ return normalized_float * pixel_max
+
+def calc_width(res_x, min_x, max_x):
+ return res_x * max_x - res_x * min_x
+
+def calc_height(res_y, min_y, max_y):
+ return res_y * max_y - res_y * min_y
+
+def calc_centerX(res_x, min_x, width):
+ return res_x * min_x + width / 2
+
+def calc_centerY(res_y, min_y, height):
+ return res_y * min_y + height / 2
+
+
+# ------------------------------------------------------------------------
+# Properties
+# ------------------------------------------------------------------------
+
+class RenderBorder(bpy.types.PropertyGroup):
+
+ # static member
+ _rd = None
+ _resX = _resY = _minX = _maxX = _minY = _maxY = 0
+ _width = _height = _centerX = _centerY = 0
+
+ def set_centerX(self, value):
+ diffX = calc_normalized((value - self._centerX), self._resX)
+ self._rd.border_min_x += diffX
+ self._rd.border_max_x += diffX
+ RenderBorder._minX = calc_pixels(self._rd.border_min_x, self._resX)
+ RenderBorder._maxX = calc_pixels(self._rd.border_max_x, self._resX)
+ RenderBorder._width = calc_width(self._resX, self._rd.border_min_x, self._rd.border_max_x)
+ RenderBorder._centerX = value
+
+ def set_centerY(self, value):
+ diffY = calc_normalized((value - self._centerY), self._resY)
+ self._rd.border_min_y += diffY
+ self._rd.border_max_y += diffY
+ RenderBorder._minY = calc_pixels(self._rd.border_min_y, self._resY)
+ RenderBorder._maxY = calc_pixels(self._rd.border_max_y, self._resY)
+ RenderBorder._height = calc_height(self._resY, self._rd.border_min_y, self._rd.border_max_y)
+ RenderBorder._centerY = value
+
+ def set_minX(self, value):
+ self._rd.border_min_x = calc_normalized(value, self._resX)
+ RenderBorder._minX = round_pixels(calc_pixels(self._rd.border_min_x, self._resX))
+ RenderBorder._width = calc_width(self._resX, self._rd.border_min_x, self._rd.border_max_x)
+ RenderBorder._centerX = calc_centerX(self._resX, self._rd.border_min_x, self._width)
+
+ def set_maxX(self, value):
+ self._rd.border_max_x = calc_normalized(value, self._resX)
+ RenderBorder._maxX = round_pixels(calc_pixels(self._rd.border_max_x, self._resX))
+ RenderBorder._width = calc_width(self._resX, self._rd.border_min_x, self._rd.border_max_x)
+ RenderBorder._centerX = calc_centerX(self._resX, self._rd.border_min_x, self._width)
+
+ def set_minY(self, value):
+ self._rd.border_min_y = calc_normalized(value, self._resY)
+ RenderBorder._minY = round_pixels(calc_pixels(self._rd.border_min_y, self._resY))
+ RenderBorder._height = calc_height(self._resY, self._rd.border_min_y, self._rd.border_max_y)
+ RenderBorder._centerY = calc_centerY(self._resY, self._rd.border_min_y, self._height)
+
+ def set_maxY(self, value):
+ self._rd.border_max_y = calc_normalized(value, self._resY)
+ RenderBorder._maxY = round_pixels(calc_pixels(self._rd.border_max_y, self._resY))
+ RenderBorder._height = calc_height(self._resY, self._rd.border_min_y, self._rd.border_max_y)
+ RenderBorder._centerY = calc_centerY(self._resY, self._rd.border_min_y, self._height)
+
+ def set_useBorder(self, value):
+ self._rd.use_border = value
+
+ def get_centerX(self):
+ return RenderBorder._centerX
+
+ def get_centerY(self):
+ return RenderBorder._centerY
+
+ def get_minX(self):
+ return RenderBorder._minX
+
+ def get_maxX(self):
+ return RenderBorder._maxX
+
+ def get_minY(self):
+ return RenderBorder._minY
+
+ def get_maxY(self):
+ return RenderBorder._maxY
+
+ def get_width(self):
+ return abs(round_pixels(RenderBorder._width))
+
+ def get_height(self):
+ return abs(round_pixels(RenderBorder._height))
+
+ def get_useBorder(self):
+ bpy.ops.rborder.init_border()
+ return self._rd.use_border
+
+ center_x : bpy.props.IntProperty(
+ name = "Center X",
+ description = ("Horizontal center of the render border box"),
+ min = 0, default = 0, get=get_centerX, set=set_centerX )
+
+ center_y : bpy.props.IntProperty(
+ name = "Center Y",
+ description = ("Vertical center of the render border box"),
+ min = 0, default = 0, get=get_centerY, set=set_centerY )
+
+ width : bpy.props.IntProperty(
+ name = "Width",
+ description = ("Width of render border box"),
+ min = 0, default = 0, get=get_width)
+
+ height : bpy.props.IntProperty(
+ name = "Height",
+ description = ("Height of render border box"),
+ min = 0, default = 0, get=get_height)
+
+ min_x : bpy.props.IntProperty(
+ description = ("Pixel distance between the left edge "
+ "of the camera border and the left "
+ "side of the render border box"),
+ name = "Min X", min = 0, default = 0, get=get_minX, set=set_minX )
+
+ max_x : bpy.props.IntProperty(
+ description = ("Pixel distance between the right edge "
+ "of the camera border and the right "
+ "side of the render border box"),
+ name = "Max X",min = 0, default = 0, get=get_maxX, set=set_maxX )
+
+ min_y : bpy.props.IntProperty(
+ description = ("Pixel distance between the bottom edge "
+ "of the camera border and the bottom "
+ "edge of the render border box"),
+ name = "Min Y", min = 0, default = 0, get=get_minY, set=set_minY )
+
+ max_y : bpy.props.IntProperty(
+ description = ("Pixel distance between the top edge "
+ "of the camera border and the top "
+ "edge of the render border box"),
+ name = "Max Y", min = 0, default = 0, get=get_maxY, set=set_maxY )
+
+ use_rborder : bpy.props.BoolProperty(
+ name = "Use render border", description = "Use render border",
+ get=get_useBorder, set=set_useBorder)
+
+
+# ------------------------------------------------------------------------
+# Operators
+# ------------------------------------------------------------------------
+
+class RBORDER_OT_init_border(bpy.types.Operator):
+ bl_idname = "rborder.init_border"
+ bl_label = "Init Render Border"
+ bl_options = {'INTERNAL'}
+
+ def execute(self, context):
+ scn = context.scene
+ RenderBorder._rd = scn.render
+ RenderBorder._resX = scn.render.resolution_x
+ RenderBorder._resY = scn.render.resolution_y
+
+ rbx = scn.renderborder
+ rbx.min_x = round_pixels(calc_pixels(scn.render.border_min_x, scn.render.resolution_x))
+ rbx.min_y = round_pixels(calc_pixels(scn.render.border_min_y, scn.render.resolution_y))
+ rbx.max_x = round_pixels(calc_pixels(scn.render.border_max_x, scn.render.resolution_x))
+ rbx.max_y = round_pixels(calc_pixels(scn.render.border_max_y, scn.render.resolution_y))
+ return {'FINISHED'}
+
+
+class RBORDER_OT_reset_border(bpy.types.Operator):
+ bl_idname = "rborder.reset_border"
+ bl_label = "Reset Render Border"
+ bl_description = "Fit render border to the current camera resolution"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ scn = context.scene
+ rbx = scn.renderborder
+ rbx.min_x = 0
+ rbx.min_y = 0
+ rbx.max_x = scn.render.resolution_x
+ rbx.max_y = scn.render.resolution_y
+ self.report({'INFO'}, "Render Border adapted")
+ return {'FINISHED'}
+
+
+# ------------------------------------------------------------------------
+# Panel
+# ------------------------------------------------------------------------
+
+class RBORDER_PT_camera(bpy.types.Panel):
+ bl_label = "Render Border"
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object.type == "CAMERA"
+
+ def draw_header(self, context):
+ scn = context.scene
+ rbx = scn.renderborder
+ self.layout.prop(rbx, "use_rborder", text="")
+
+ def draw(self, context):
+ scn = context.scene
+ rbx = scn.renderborder
+ layout = self.layout
+
+ row = layout.row()
+ col = row.column(align=True)
+ rowsub = col.row(align=True)
+ rowsub.prop(rbx, "min_x", text="X")
+ rowsub.prop(rbx, "max_x", text="R")
+ rowsub = col.row(align=True)
+ rowsub.prop(rbx, "min_y", text="Y")
+ rowsub.prop(rbx, "max_y", text="T")
+ col.prop(rbx, "center_x")
+ col.prop(rbx, "center_y")
+ col.operator("rborder.reset_border", text="Reset Render Border", icon='FILE_REFRESH')
+ row = layout.row()
+ col = layout.column(align=True)
+ rowsub = col.row(align=True)
+ rowsub = row.split(factor=0.3, align=True)
+ rowsub.prop(scn.render, "use_crop_to_border", text="Crop Image")
+ rowsub.alignment = 'RIGHT'
+ rowsub.label(text="Width: {}px Height: {}px".format(rbx.width, rbx.height))
+
+
+# ------------------------------------------------------------------------
+# Registration
+# ------------------------------------------------------------------------
+
+@persistent
+def init_renderborder_member(dummy):
+ bpy.ops.rborder.init_border()
+
+
+classes = (
+ RenderBorder,
+ RBORDER_OT_init_border,
+ RBORDER_OT_reset_border,
+ RBORDER_PT_camera
+)
+
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
+ bpy.types.Scene.renderborder = bpy.props.PointerProperty(type=RenderBorder)
+ bpy.app.handlers.load_post.append(init_renderborder_member)
+
+def unregister():
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+
+ bpy.app.handlers.load_post.remove(init_renderborder_member)
+ del bpy.types.Scene.renderborder
+
+
+if __name__ == "__main__":
+ register()
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/RA_DRAW_UI.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/RA_DRAW_UI.PY
new file mode 100644
index 00000000..42251d2e
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/RA_DRAW_UI.PY
@@ -0,0 +1,125 @@
+import bpy
+import gpu
+import blf
+from gpu_extras.batch import batch_for_shader
+
+
+def RA_modal_Draw(self, context, prefs):
+ height = bpy.context.region.height
+ width = bpy.context.region.width
+ CO = context.object
+
+ font_id = 0
+
+ #+ text
+ if CO.RA_Unq_mode == True:
+ blf.color (font_id,0.9,0.32,0.35,1)
+ else:
+ blf.color (font_id,0.85,0.85,0.85,1)
+ #* Offset
+ blf.position(font_id, (width/2) - 200, (height/2) - 250, 0)
+ blf.size(font_id, 20, 60)
+ blf.draw(font_id, ("{} {}".format("Offset: ",str(round(CO.RA_Offset, 2)))) )
+
+ #* Object Selectable
+ blf.position(font_id, (width/2) + 50, (height/2) - 250, 0)
+
+ blf.draw(font_id, ("{} {}".format("Selectable: ",str(CO.RA_Sel_Status))) )
+
+ #* Object Number "Count"
+ blf.position(font_id, (width/2) - 50, (height/2) - 250, 0)
+ if CO.RA_Unq_mode == True:
+ blf.color (font_id,0.5,0.5,0.5,1)
+ else:
+ blf.color (font_id,0.85,0.85,0.85,1)
+
+ blf.draw(font_id, ("{} {}".format("Count: ",str(round(CO.RA_ObjNum, 2)))) )
+ #* Show/Hide Help
+ blf.color (font_id,1,1,1,1)
+ text = "Show/Hide Help 'H'"
+ blf.position(font_id, (width/2 - blf.dimensions(font_id, text)[0] / 2), (height/2) - 230, 0)
+
+ blf.draw(font_id, text)
+ #+--------------------------------------------------------------+#
+ #* Unique Mode
+ blf.color (font_id,0.8,0.4,0.0,1)
+ text = "Unique Mode: "
+ blf.position(font_id, (width/2 - 84), (height/2) - 270, 0)
+ blf.draw(font_id, text)
+ #-------------------------#
+ if CO.RA_Unq_mode == True:
+ blf.color (font_id,0.1,0.94,0.4,1)
+ unq_text = "Active"
+ else:
+ blf.color (font_id,0.6,0.1,0.0,1)
+ unq_text = "--------"
+ blf.position(font_id, (width/2 + 34), (height/2) - 270, 0)
+ blf.draw(font_id, unq_text)
+ #+--------------------------------------------------------------+#
+ #* Help
+ blf.color (font_id,0.6,1,0.6,1)
+ if prefs.modal_help == True:
+ lines = ["Reset 'R'",
+ "Apply 'A'",
+ "Join 'J' ends radial mode and merges all objects",
+ "Grab 'G'",
+ "Unique Mode 'Q' unlinks objects data block",
+ "'RMB' and Esc to Cancel",
+ "'Shift' to snap offset",
+ "'Mouse Wheel' Increase/Decrease Count"
+ ]
+ for index, l in enumerate(lines):
+ text = l
+ blf.position(font_id, (width/2) - 200, (height/2 -200) + 20 * index, 0)
+
+ blf.draw(font_id, text)
+
+def RA_draw_B(self, context, prefs):
+ height = bpy.context.region.height
+ width = bpy.context.region.width
+ CO = bpy.context.object
+ #+-----------------------------------------------------------------------+#
+ vertices = (
+ (width/2 - 80 , height/2 - 215),(width/2 + 80, height/2 - 215),
+ (width/2 - 90, height/2 - 233),( width/2 + 90, height/2 - 233) )
+
+ indices = (
+ (0, 1, 2), (2, 1, 3))
+
+ shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
+ batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
+
+ shader.bind()
+
+ shader.uniform_float("color", (0.8,0.4,0.0,1))
+ batch.draw(shader)
+ #+-----------------------------------------------------------------------+#
+ vertices = (
+ (width/2 - 216 , height/2 - 234),(width/2 + 206, height/2 - 234),
+ (width/2 - 220, height/2 - 254),( width/2 + 200, height/2 - 254) )
+
+ indices = (
+ (0, 1, 2), (2, 1, 3))
+
+ shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
+ batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
+
+
+
+ shader.bind()
+ shader.uniform_float("color", (0.15,0.15,0.15,1))
+ batch.draw(shader)
+ #+-----------------------------------------------------------------------+#
+ vertices = (
+ (width/2 - 96 , height/2 - 253),(width/2 + 96, height/2 - 253),
+ (width/2 - 86, height/2 - 274),( width/2 + 86, height/2 - 274) )
+
+ indices = (
+ (0, 1, 2), (2, 1, 3))
+
+ shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
+ batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
+
+ shader.bind()
+ shader.uniform_float("color", (0.15,0.15,0.15,1))
+ batch.draw(shader)
\ No newline at end of file
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__INIT__.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__INIT__.PY
new file mode 100644
index 00000000..0b4143ba
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__INIT__.PY
@@ -0,0 +1,666 @@
+
+import bpy,math,mathutils,blf,rna_keymap_ui
+
+from .RA_draw_ui import *
+from mathutils import Matrix
+from bpy.types import (
+ PropertyGroup,
+ Menu
+ )
+from bpy.props import (
+ IntProperty,
+ FloatProperty,
+ BoolProperty
+ )
+#// join objects option in modal operator
+#// Reset array option in modal operator
+#// Modal operator Ui
+#// add Radial Array hotkey
+#// preferences add hotkey in addon preferences menu
+#// addon menu ui
+#// add modal selectable toggle
+#// add modal apply option
+#// add modal ui tooltips
+#// add make unique
+#// add create collection toggle
+
+
+bl_info = {
+ "name" : "R.Array",
+ "author" : "Syler",
+ "version": (0, 0, 1, 2),
+ "description": "Adds Radial Array Operator",
+ "blender" : (2, 80, 0),
+ "category" : "Object"
+}
+#+ handle the keymap
+addon_keymaps = []
+
+def add_hotkey():
+ #* Ctrl Q call R_Array
+ wm = bpy.context.window_manager
+ km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')
+ kmi = km.keymap_items.new(R_Array.bl_idname, 'Q', 'PRESS', ctrl=True)
+ addon_keymaps.append(km)
+
+def remove_hotkey():
+ wm = bpy.context.window_manager
+ for km in addon_keymaps:
+ wm.keyconfigs.addon.keymaps.remove(km)
+ # clear the list
+ del addon_keymaps[:]
+#--------------------------------------------------------------------------------------#
+def RA_Update_Sel_Status(self, context):
+ if self.RA_Sel_Status == True:
+ for ob in self.RA_Parent.children:
+ ob.hide_select = False
+ if self.RA_Sel_Status == False:
+ for ob in self.RA_Parent.children:
+ ob.hide_select = True
+
+def RA_Update_ObjNum(self, context):
+
+ if self.RA_Status == True:
+
+ if len(self.RA_Parent.children) == self.RA_ObjNum:
+ pass
+
+ #+ Add Objects
+ if len(self.RA_Parent.children) < self.RA_ObjNum:
+ object_list = []
+ object_to_copy = self.RA_Parent.children[0]
+ # append already existing objects to object list
+ for c in self.RA_Parent.children:
+ object_list.append(c)
+
+
+ for i in range (len(self.RA_Parent.children), self.RA_ObjNum):
+ object_list.append(object_to_copy.copy())
+
+
+
+ # Add Objects To Collection
+ for index, ob in enumerate(object_list):
+
+ # Reset Matrix
+ ob.matrix_basis = mathutils.Matrix()
+
+ # set object location to RA_Parent + RA_Offset
+ ob.location[1] = self.RA_Parent.location[1] + self.RA_Parent.RA_Offset
+ # create angle variable
+ angle = math.radians(360/self.RA_Parent.RA_ObjNum)
+
+ # rotate object
+ R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z')
+ T = mathutils.Matrix.Translation([0, 0, 0])
+ M = T @ R @ T.inverted()
+ ob.location = M @ ob.location
+ ob.rotation_euler.rotate(M)
+
+
+ # Parent Object
+ ob.parent = self.RA_Parent
+ self.RA_Parent.matrix_parent_inverse = ob.matrix_world.inverted()
+ ob.RA_Parent = self.RA_Parent
+
+ # make objects selectable/unselectable
+ if self.RA_Sel_Status == True:
+ ob.hide_select = False
+ if self.RA_Sel_Status == False:
+ ob.hide_select = True
+
+ # Change Object Name
+ ob.name = "RA - " + self.RA_Name + " - " + str(index)
+ # set RA Status
+ ob.RA_Status = True
+ # Link object
+ try:
+ self.RA_Parent.users_collection[0].objects.link(ob)
+ #print ("For LINK")
+ except:
+ #print ("PASS Linking object to collection failed")
+ pass
+
+ #+ Remove Objects
+ if len(self.RA_Parent.children) > self.RA_ObjNum:
+
+ # deselect all objects
+ for d in bpy.context.view_layer.objects:
+ d.select_set(False)
+ bpy.context.view_layer.objects.active = None
+
+ # Make selectable and Select all objects that will be deleted
+ for i in range (self.RA_ObjNum, len(self.RA_Parent.children)):
+ self.RA_Parent.children[i].hide_select = False
+ self.RA_Parent.children[i].select_set(True)
+ # Delete Objects
+ bpy.ops.object.delete()
+ # select control Object
+ bpy.context.view_layer.objects.active = self.RA_Parent
+ self.RA_Parent.select_set(True)
+ for index, ob in enumerate(self.RA_Parent.children):
+ # Reset Matrix
+ ob.matrix_basis = mathutils.Matrix()
+
+ # set object location to RA_Parent + RA_Offset
+ ob.location[1] = self.RA_Parent.location[1] + self.RA_Parent.RA_Offset
+ # create angle variable
+ angle = math.radians(360/self.RA_Parent.RA_ObjNum)
+
+ # rotate object
+ R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z')
+ T = mathutils.Matrix.Translation([0, 0, 0])
+ M = T @ R @ T.inverted()
+ ob.location = M @ ob.location
+ ob.rotation_euler.rotate(M)
+
+def RA_Update_Offset(self, context):
+
+ if self.RA_Status == True:
+ for ob in self.RA_Parent.children:
+ # define variables
+ loc = mathutils.Vector((0.0, self.RA_Offset, 0.0))
+ rot = ob.rotation_euler
+ # rotate location
+ loc.rotate(rot)
+ # apply rotation
+ ob.location = loc
+ else:
+ pass
+#--------------------------------------------------------------------------------------#
+class R_Array(bpy.types.Operator):
+ bl_idname = 'sop.r_array'
+ bl_label = 'Radial Array'
+ bl_description = 'Radial Array S.Operator'
+ bl_options = {'REGISTER', 'UNDO'}
+
+
+
+
+ #?Useless !?
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+
+ #Create Bpy.context Variable
+ C = bpy.context
+ active_object = C.active_object
+
+
+ # call modal if RA_Status = True
+ try:
+ if active_object.RA_Status == True:
+ bpy.ops.sop.ra_modal('INVOKE_DEFAULT')
+ return {'FINISHED'}
+ except:
+ pass
+ # Check Selected Cancel if NOT Mesh
+ if C.selected_objects == [] or C.active_object.type != 'MESH':
+ self.report({'INFO'}, "No Mesh Selected")
+ return {'CANCELLED'}
+
+
+ # Create Variables
+ L_Objects = [] # object list
+ ob = active_object # active object reference
+ ob_collections = ob.users_collection # active Object collections
+ f_name = ob.name # Object Name
+ point = ob.location.copy() # Middle point
+ is_col_new = True
+
+
+ # Create New Collection
+ if bpy.context.preferences.addons[__name__].preferences.col_toggle == True:
+ for q in bpy.data.collections:
+ if q.name == "RA -" + f_name:
+ collection = q
+ is_col_new = False
+ try:
+ for col in ob_collections:
+ col.objects.unlink(ob)
+ collection.objects.link(ob)
+ except:
+ pass
+
+
+ if is_col_new == True:
+ # create and link new collection
+ collection = bpy.data.collections.new(name="RA -" + f_name)
+ bpy.context.scene.collection.children.link(collection)
+ print ("NEW")
+ # Move Object to collection
+ for col in ob_collections:
+ col.objects.unlink(ob)
+ collection.objects.link(ob)
+ else:
+ collection = ob_collections[0]
+
+ # Create/Location/Name/Status/set RA_Parent/Link Empty and other memery
+ empty = bpy.data.objects.new( "empty", None )
+ empty.location = point
+ empty.name = ".RA - " + ob.name + " - Control Empty"
+ empty.RA_Status = True
+ empty.RA_Parent = empty
+ empty.RA_Name = f_name
+ empty.RA_Sel_Status = bpy.context.preferences.addons[__name__].preferences.selectable
+ collection.objects.link(empty)
+
+ # Move object
+ ob.location[1] = ob.location[1] + ob.RA_Offset
+
+ # Deselect Active Object and select Control Object
+ ob.select_set(False)
+ empty.select_set(True)
+
+ # set empty as active object
+ bpy.context.view_layer.objects.active = empty
+
+ # create duplicate objects
+ for o in range(0, empty.RA_ObjNum):
+
+ if o == 0:
+ L_Objects.append(ob)
+ if o != 0:
+ L_Objects.append(ob.copy())
+ # Add Objects To Collection
+ for index, ob in enumerate(L_Objects):
+ # create angle variable
+ angle = math.radians(360/empty.RA_ObjNum)
+
+
+ # rotate object
+ R = mathutils.Matrix.Rotation(angle * (index), 4, 'Z')
+ T = mathutils.Matrix.Translation([0, 0, 0])
+ M = T @ R @ T.inverted()
+ ob.location = M @ ob.location
+ ob.rotation_euler.rotate(M)
+
+ # Parent Object
+ ob.parent = empty
+ empty.matrix_parent_inverse = ob.matrix_world.inverted()
+ ob.RA_Parent = empty
+
+ # make objects selectable/unselectable
+ if empty.RA_Sel_Status == True:
+ ob.hide_select = False
+ if empty.RA_Sel_Status == False:
+ ob.hide_select = True
+
+ # Change Object Name
+ ob.name = "RA - " + str(f_name) + " - " + str(index)
+ # Set RA Status
+ ob.RA_Status = True
+
+ # Link object
+ try:
+ collection.objects.link(ob)
+ #print ("For LINK")
+ except:
+ #print ("PASS Linking object to collection failed")
+ pass
+ bpy.ops.sop.ra_modal('INVOKE_DEFAULT')
+
+ return {'FINISHED'}
+#--------------------------------------------------------------------------------------#
+class RA_Modal(bpy.types.Operator):
+ # Change Radial Array
+ bl_idname = "sop.ra_modal"
+ bl_label = "Radial Array Modal"
+ bl_options = {"REGISTER", "UNDO", "BLOCKING", "GRAB_CURSOR", "INTERNAL"} #- add later!?
+
+ first_mouse_x: IntProperty()
+ I_RA_Offset: FloatProperty()
+ I_RA_ObjNum: IntProperty()
+ unq_mode: BoolProperty()
+
+
+ def modal(self, context, event):
+
+ # context shortcut
+ C = context
+ OB = C.object
+ context.area.tag_redraw() #?
+ prefs = bpy.context.preferences.addons[__name__].preferences
+ # -------------------------------------------------------------#
+ #+ change offset
+ if event.type == 'MOUSEMOVE' :
+ delta = self.first_mouse_x - event.mouse_x
+ if event.shift:
+ C.object.RA_Offset = round((self.I_RA_Offset + delta * 0.01))
+ else:
+ C.object.RA_Offset = self.I_RA_Offset + delta * 0.01
+ # -------------------------------------------------------------#
+ #+ add/remove Objects
+ if event.type == 'WHEELUPMOUSE' and OB.RA_Unq_mode == False:
+ OB.RA_ObjNum = OB.RA_ObjNum + 1
+
+ if event.type == 'WHEELDOWNMOUSE' and OB.RA_Unq_mode == False:
+ OB.RA_ObjNum = OB.RA_ObjNum - 1
+ # -------------------------------------------------------------#
+ #+ call the tarnslation operator
+ if event.type == 'G' and event.value == "PRESS":
+
+ C.tool_settings.use_snap = True
+ C.tool_settings.snap_elements = {'FACE'}
+ C.tool_settings.use_snap_align_rotation = True
+
+ bpy.ops.transform.translate('INVOKE_DEFAULT')
+ bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ return {'FINISHED'}
+ # -------------------------------------------------------------#
+
+ #+ join objects
+ if event.type == 'J' and event.value == "PRESS":
+ objects = OB.RA_Parent.children
+ location = OB.RA_Parent.location
+ cursor_location = bpy.context.scene.cursor.location.copy()
+
+ # deselect objects and select control object
+ for o in C.selected_objects:
+ o.select_set(False)
+ C.object.RA_Parent.hide_select = False
+ bpy.context.view_layer.objects.active = C.object.RA_Parent
+ C.object.RA_Parent.select_set(True)
+
+ # Delete control object
+ bpy.ops.object.delete()
+
+ for ob in objects:
+ ob.hide_select = False
+ ob.select_set(True)
+ bpy.context.view_layer.objects.active = objects[0]
+
+
+ bpy.context.scene.cursor.location = location
+ bpy.ops.view3d.snap_selected_to_cursor(use_offset=True)
+ bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
+ bpy.ops.object.join()
+ bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
+ bpy.context.scene.cursor.location = cursor_location
+ bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ return {'FINISHED'}
+ # -------------------------------------------------------------#
+
+ #+ Reset
+ if event.type == 'R' and event.value == "PRESS":
+
+ objects = OB.RA_Parent.children
+ name = OB.RA_Parent.RA_Name
+ # deslect all objects
+ for o in C.selected_objects:
+ o.select_set(False)
+ # select objects
+ for ob in objects:
+ if ob != objects[0]:
+ ob.hide_select = False
+ ob.select_set(True)
+ # delete objects
+ bpy.ops.object.delete()
+
+ # select object and clear parent and other memery
+ objects[0].location = objects[0].RA_Parent.location
+ objects[0].RA_Parent.select_set(True)
+ bpy.ops.object.delete()
+ objects[0].hide_select = False
+ bpy.context.view_layer.objects.active = objects[0]
+ objects[0].select_set(True)
+ objects[0].parent = None
+ objects[0].name = name
+ try:
+ del objects[0]["RA_Parent"]
+ del objects[0]["RA_Status"]
+ except:
+ pass
+
+ bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ return {'FINISHED'}
+ #+ Apply
+ if event.type == 'A' and event.value == "PRESS":
+
+ objects = OB.RA_Parent.children
+ # deslect all objects
+ for o in C.selected_objects:
+ o.select_set(False)
+ # select and delete control object
+ objects[0].RA_Parent.select_set(True)
+ bpy.ops.object.delete()
+ # select objects
+ for ob in objects:
+
+ ob.hide_select = False
+ ob.select_set(True)
+ ob.RA_Status = False
+ ob.parent = None
+
+ bpy.context.view_layer.objects.active = objects[0]
+ bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ return {'FINISHED'}
+ #+ Make Unique Mode toggle
+ if event.type == 'Q' and event.value == "PRESS":
+ objects = OB.RA_Parent.children
+ if OB.RA_Unq_mode == True:
+ for ob in objects:
+ ob.data = objects[0].data
+ OB.RA_Unq_mode = False
+ else:
+ #* make unique data
+ for ob in objects:
+ ob.data = ob.data.copy()
+ OB.RA_Unq_mode = True
+ #+ Selectable toggle
+ if event.type == 'S' and event.value == "PRESS":
+ if OB.RA_Sel_Status == True:
+ OB.RA_Sel_Status = False
+ else:
+ OB.RA_Sel_Status = True
+ #+ Help Mode toggle
+ if event.type == 'H' and event.value == "PRESS":
+ if prefs.modal_help == True:
+ prefs.modal_help = False
+ else:
+ prefs.modal_help = True
+ # -------------------------------------------------------------#
+ #+ Finish/Cancel Modal
+ elif event.type == 'LEFTMOUSE':
+ bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ return {'FINISHED'}
+
+ elif event.type in {'RIGHTMOUSE', 'ESC'}:
+ C.object.RA_Offset = self.I_RA_Offset
+ C.object.RA_ObjNum = self.I_RA_ObjNum
+ bpy.types.SpaceView3D.draw_handler_remove(self.ra_draw_b, 'WINDOW')
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ return {'CANCELLED'}
+
+ return {'RUNNING_MODAL'}
+
+ def invoke(self, context, event):
+ # context shortcut
+ C = context
+ if C.object.RA_Status == True:
+ for o in C.selected_objects:
+ o.select_set(False)
+ bpy.context.view_layer.objects.active = C.object.RA_Parent
+ C.object.RA_Parent.select_set(True)
+
+
+
+ if C.object:
+ # set initial Variable values
+ self.first_mouse_x = event.mouse_x
+ self.I_RA_Offset = C.object.RA_Offset
+ self.I_RA_ObjNum = C.object.RA_ObjNum
+ self.unq_mode = C.object.RA_Unq_mode
+ self.prefs = bpy.context.preferences.addons[__name__].preferences
+ ###-------------------------------------------###
+ args = (self, context, self.prefs)
+
+
+ self.ra_draw_b = bpy.types.SpaceView3D.draw_handler_add(RA_draw_B, args, 'WINDOW', 'POST_PIXEL')
+ self._handle = bpy.types.SpaceView3D.draw_handler_add(RA_modal_Draw, args, 'WINDOW', 'POST_PIXEL')
+
+ self.mouse_path = []
+
+ context.window_manager.modal_handler_add(self)
+ return {'RUNNING_MODAL'}
+ else:
+ self.report({'WARNING'}, "No active object, could not finish")
+ return {'CANCELLED'}
+#--------------------------------------------------------------------------------------#
+class RA_Prefs(bpy.types.AddonPreferences):
+ bl_idname = __name__
+ # here you define the addons customizable props
+ offset: bpy.props.FloatProperty(default=5)
+ objnum: bpy.props.IntProperty(default=6)
+ selectable: bpy.props.BoolProperty(default= True, description="False = Only Control Object is selectable")
+ modal_help: bpy.props.BoolProperty(default= False, description="True = Display Help text in modal")
+ col_toggle: bpy.props.BoolProperty(default= False, description="True = Create New Collection")
+ # here you specify how they are drawn
+ def draw(self, context):
+ layout = self.layout
+ box = layout.box()
+ split = box.split()
+ col = split.column()
+ # Layout ---------------------------------------------------------------- #
+ col.label(text="Default Values:")
+ col.prop(self, "offset",text="Default Offset")
+ col.prop(self, "objnum",text="Default Count")
+ col.prop(self, "selectable",text="Selectable")
+ col.prop(self, "modal_help",text="Modal Help")
+ col.label(text ="Options:")
+ col.prop(self, "col_toggle",text="Create New Collection")
+ col.label(text="Keymap:")
+
+
+ wm = bpy.context.window_manager
+ kc = wm.keyconfigs.user
+ km = kc.keymaps['Object Mode']
+ #kmi = km.keymap_items[0]
+ kmi = get_hotkey_entry_item(km, 'sop.r_array', 'sop.r_array')
+
+ if addon_keymaps:
+ km = addon_keymaps[0].active()
+ col.context_pointer_set("keymap", km)
+ rna_keymap_ui.draw_kmi([], kc, km, kmi, col, 0)
+
+
+
+def get_addon_preferences():
+ ''' quick wrapper for referencing addon preferences '''
+ addon_preferences = bpy.context.user_preferences.addons[__name__].preferences
+ return addon_preferences
+def get_hotkey_entry_item(km, kmi_name, kmi_value):
+ '''
+ returns hotkey of specific type, with specific properties.name (keymap is not a dict, so referencing by keys is not enough
+ if there are multiple hotkeys!)
+ '''
+ for i, km_item in enumerate(km.keymap_items):
+ if km.keymap_items.keys()[i] == kmi_name:
+ if km.keymap_items[i].idname == kmi_value:
+ return km_item
+ return None
+
+classes = (
+ RA_Prefs,
+ R_Array,
+ RA_Modal,
+)
+
+
+def register():
+ print ("----------------------------------")
+ print ("S.Ops Init")
+ print ("----------------------------------")
+
+ #+ add hotkey
+ add_hotkey()
+
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+ # Init Props
+
+ bpy.types.Object.RA_Parent = bpy.props.PointerProperty(
+ name="RA Parent",
+ description="RA Parent Object Reference",
+ type=bpy.types.Object
+ )
+
+ bpy.types.Object.RA_ObjNum = bpy.props.IntProperty(
+ name="RA ObjNum",
+ description="RA Object Number",
+ default = bpy.context.preferences.addons[__name__].preferences.objnum,
+ min = 1,
+ update = RA_Update_ObjNum
+ )
+
+ bpy.types.Object.RA_Offset = bpy.props.FloatProperty(
+ name="Offset",
+ description="Radial Array Offset",
+ default = bpy.context.preferences.addons[__name__].preferences.offset,
+ update = RA_Update_Offset
+ )
+
+ bpy.types.Object.RA_Status = bpy.props.BoolProperty(
+ name="Status",
+ description="Radial Array Status",
+ default = False
+ )
+
+ bpy.types.Object.RA_Sel_Status = bpy.props.BoolProperty(
+ name="Selectable",
+ description="False = Only Control Object is selectable",
+ default = bpy.context.preferences.addons[__name__].preferences.selectable,
+ update = RA_Update_Sel_Status
+ )
+
+ bpy.types.Object.RA_Unq_mode = bpy.props.BoolProperty(
+ name="Unique Mode",
+ description="True = all objects have a unique data block(Disables Count in Modal)",
+ default = False
+ )
+ bpy.types.Object.RA_Name = bpy.props.StringProperty(
+ name="Name",
+ description="Radial Array Name",
+ default = "Nameing Error"
+ )
+
+
+ print ("----------------------------------")
+ print ("S.Ops Register End")
+ print ("----------------------------------")
+
+
+def unregister():
+ print ("----------------------------------")
+ print ("S.Ops unRegister Start")
+ print ("----------------------------------")
+ #+ remove hotkey
+ remove_hotkey()
+
+ from bpy.utils import unregister_class
+ for cls in classes:
+ unregister_class(cls)
+
+
+
+
+
+ print ("----------------------------------")
+ print ("S.Ops unRegister End")
+ print ("----------------------------------")
+
+if __name__ == "__main__":
+ register()
+
+
+
+
+
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__PYCACHE__/RA_DRAW_UI.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__PYCACHE__/RA_DRAW_UI.CPYTHON-37.PYC
new file mode 100644
index 00000000..94c01852
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__PYCACHE__/RA_DRAW_UI.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__PYCACHE__/__INIT__.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__PYCACHE__/__INIT__.CPYTHON-37.PYC
new file mode 100644
index 00000000..b52d729c
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__PYCACHE__/__INIT__.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/SPACE_VIEW_3D_DISPLAY_TOOLS.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/SPACE_VIEW_3D_DISPLAY_TOOLS.PY
new file mode 100644
index 00000000..3b07823f
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/SPACE_VIEW_3D_DISPLAY_TOOLS.PY
@@ -0,0 +1,1360 @@
+# space_view_3d_display_tools.py Copyright (C) 2012, Jordi Vall-llovera
+#
+# Multiple display tools for fast navigate/interact with the viewport
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_info = {
+ "name": "Display Tools",
+ "author": "Jordi Vall-llovera Medina",
+ "version": (1, 2, 6),
+ "blender": (2, 6, 4),
+ "location": "Toolshelf",
+ "description": "Display tools for fast navigate/interact with the viewport",
+ "warning": "",
+ "wiki_url": "http://jordiart3d.blogspot.com.es/",
+ "tracker_url": "",
+ "category": "3D View"}
+
+"""
+Additional links:
+ Author Site: http://jordiart3d.blogspot.com.es/
+"""
+
+import bpy
+
+from bpy.props import IntProperty, BoolProperty, FloatProperty, EnumProperty
+
+# init delay variables
+bpy.types.Scene.Delay = bpy.props.BoolProperty(
+ default = False,
+ description = "Activate delay return to normal viewport mode")
+
+bpy.types.Scene.DelayTime = bpy.props.IntProperty(
+ default = 30,
+ min = 1,
+ max = 500,
+ soft_min = 10,
+ soft_max = 250,
+ description = "Delay time to return to normal viewport\
+ mode after move your mouse cursor")
+
+bpy.types.Scene.DelayTimeGlobal = bpy.props.IntProperty(
+ default = 30,
+ min = 1,
+ max = 500,
+ soft_min = 10,
+ soft_max = 250,
+ description = "Delay time to return to normal viewport\
+ mode after move your mouse cursor")
+
+#init variable for fast navigate
+bpy.types.Scene.EditActive = bpy.props.BoolProperty(
+ default = True,
+ description = "Activate for fast navigate in edit mode too")
+
+#Fast Navigate toggle function
+def trigger_fast_navigate(trigger):
+ scene = bpy.context.scene
+ scene.FastNavigateStop = False
+
+ if trigger == True:
+ trigger = False
+ else:
+ trigger = True
+
+#Control how to display particles during fast navigate
+def display_particles(mode):
+ scene = bpy.context.scene
+
+ if mode == True:
+ for particles in bpy.data.particles:
+ if particles.type == 'EMITTER':
+ particles.draw_method = 'DOT'
+ particles.draw_percentage = 100
+ else:
+ particles.draw_method = 'RENDER'
+ particles.draw_percentage = 100
+ else:
+ for particles in bpy.data.particles:
+ if particles.type == 'EMITTER':
+ particles.draw_method = 'DOT'
+ particles.draw_percentage = scene.ParticlesPercentageDisplay
+ else:
+ particles.draw_method = 'RENDER'
+ particles.draw_percentage = scene.ParticlesPercentageDisplay
+
+#Do repetitive fast navigate related stuff
+def fast_navigate_stuff(self, context, event):
+ scene = bpy.context.scene
+ view = context.space_data
+
+ if bpy.context.area.type != 'VIEW_3D':
+ return self.cancel(context)
+
+ if event.type == 'ESC' or event.type == 'RET' or event.type == 'SPACE':
+ return self.cancel(context)
+
+ if scene.FastNavigateStop == True:
+ return self.cancel(context)
+
+ #fast navigate while orbit/panning
+ if event.type == 'MIDDLEMOUSE':
+ if scene.Delay == True:
+ if scene.DelayTime < scene.DelayTimeGlobal:
+ scene.DelayTime += 1
+ view.viewport_shade = scene.FastMode
+ self.mode = False
+
+ #fast navigate while transform operations
+ if event.type == 'G' or event.type == 'R' or event.type == 'S':
+ if scene.Delay == True:
+ if scene.DelayTime < scene.DelayTimeGlobal:
+ scene.DelayTime += 1
+ view.viewport_shade = scene.FastMode
+ self.mode = False
+
+ #fast navigate while menu popups or duplicates
+ if event.type == 'W' or event.type == 'D' or event.type == 'L'\
+ or event.type == 'U' or event.type == 'I' or event.type == 'M'\
+ or event.type == 'A' or event.type == 'B':
+ if scene.Delay == True:
+ if scene.DelayTime < scene.DelayTimeGlobal:
+ scene.DelayTime += 1
+ view.viewport_shade = scene.FastMode
+ self.mode = False
+
+ #fast navigate while numpad navigation
+ if event.type == 'NUMPAD_PERIOD' or event.type == 'NUMPAD_1'\
+ or event.type == 'NUMPAD_2' or event.type == 'NUMPAD_3'\
+ or event.type == 'NUMPAD_4' or event.type == 'NUMPAD_5'\
+ or event.type == 'NUMPAD_6' or event.type == 'NUMPAD_7'\
+ or event.type == 'NUMPAD_8' or event.type == 'NUMPAD_9':
+ if scene.Delay == True:
+ if scene.DelayTime < scene.DelayTimeGlobal:
+ scene.DelayTime += 1
+ view.viewport_shade = scene.FastMode
+ self.mode = False
+
+ #fast navigate while zooming with mousewheel too
+ if event.type == 'WHEELUPMOUSE' or event.type == 'WHEELDOWNMOUSE':
+ scene.DelayTime = scene.DelayTimeGlobal
+ view.viewport_shade = scene.FastMode
+ self.mode = False
+
+ if event.type == 'MOUSEMOVE':
+ if scene.Delay == True:
+ if scene.DelayTime == 0:
+ scene.DelayTime = scene.DelayTimeGlobal
+ view.viewport_shade = scene.OriginalMode
+ self.mode = True
+ else:
+ view.viewport_shade = scene.OriginalMode
+ self.mode = True
+
+ if scene.Delay == True:
+ scene.DelayTime -= 1
+ if scene.DelayTime == 0:
+ scene.DelayTime = scene.DelayTimeGlobal
+ view.viewport_shade = scene.OriginalMode
+ self.mode = True
+
+ if scene.ShowParticles == False:
+ for particles in bpy.data.particles:
+ if particles.type == 'EMITTER':
+ particles.draw_method = 'NONE'
+ else:
+ particles.draw_method = 'NONE'
+ else:
+ display_particles(self.mode)
+
+#Fast Navigate operator
+class FastNavigate(bpy.types.Operator):
+ """Operator that runs Fast navigate in modal mode"""
+ bl_idname = "view3d.fast_navigate_operator"
+ bl_label = "Fast Navigate"
+ trigger = BoolProperty(default = False)
+ mode = BoolProperty(default = False)
+ scene = bpy.context.scene
+ scene.DelayTime = scene.DelayTimeGlobal
+
+ def modal(self, context, event):
+ scene = bpy.context.scene
+ view = context.space_data
+
+ if scene.EditActive == True:
+ fast_navigate_stuff(self, context ,event)
+ return {'PASS_THROUGH'}
+ else:
+ obj = context.active_object
+ if obj:
+ if obj.mode != 'EDIT':
+ fast_navigate_stuff(self, context ,event)
+ return {'PASS_THROUGH'}
+ else:
+ return {'PASS_THROUGH'}
+ else:
+ fast_navigate_stuff(self, context ,event)
+ return {'PASS_THROUGH'}
+
+ def execute(self, context):
+ context.window_manager.modal_handler_add(self)
+ trigger_fast_navigate(self.trigger)
+ return {'RUNNING_MODAL'}
+
+ def cancel(self, context):
+ scene = context.scene
+ for particles in bpy.data.particles:
+ particles.draw_percentage = scene.InitialParticles
+ return {'CANCELLED'}
+
+#Fast Navigate Stop
+def fast_navigate_stop(context):
+ scene = bpy.context.scene
+ scene.FastNavigateStop = True
+
+#Fast Navigate Stop Operator
+class FastNavigateStop(bpy.types.Operator):
+ '''Stop Fast Navigate Operator'''
+ bl_idname = "view3d.fast_navigate_stop"
+ bl_label = "Stop"
+ FastNavigateStop = IntProperty(name = "FastNavigateStop",
+ description = "Stop fast navigate mode",
+ default = 0)
+
+ def execute(self,context):
+ fast_navigate_stop(context)
+ return {'FINISHED'}
+
+#Drawtype textured
+def draw_textured(context):
+ view = context.space_data
+ view.viewport_shade = 'TEXTURED'
+ bpy.context.scene.game_settings.material_mode = 'GLSL'
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.draw_type = 'TEXTURED'
+ else:
+ for obj in selection:
+ obj.draw_type = 'TEXTURED'
+
+class DisplayTextured(bpy.types.Operator):
+ '''Display objects in textured mode'''
+ bl_idname = "view3d.display_textured"
+ bl_label = "Textured"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ draw_textured(context)
+ return {'FINISHED'}
+
+#Drawtype solid
+def draw_solid(context):
+ view = context.space_data
+ view.viewport_shade = 'TEXTURED'
+ bpy.context.scene.game_settings.material_mode = 'GLSL'
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.draw_type = 'SOLID'
+ else:
+ for obj in selection:
+ obj.draw_type = 'SOLID'
+
+class DisplaySolid(bpy.types.Operator):
+ '''Display objects in solid mode'''
+ bl_idname = "view3d.display_solid"
+ bl_label = "Solid"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ draw_solid(context)
+ return {'FINISHED'}
+
+#Drawtype wire
+def draw_wire(context):
+ view = context.space_data
+ view.viewport_shade = 'TEXTURED'
+ bpy.context.scene.game_settings.material_mode = 'GLSL'
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.draw_type = 'WIRE'
+ else:
+ for obj in selection:
+ obj.draw_type = 'WIRE'
+
+class DisplayWire(bpy.types.Operator):
+ '''Display objects in wireframe mode'''
+ bl_idname = "view3d.display_wire"
+ bl_label = "Wire"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ draw_wire(context)
+ return {'FINISHED'}
+
+#Drawtype bounds
+def draw_bounds(context):
+ view = context.space_data
+ view.viewport_shade = 'TEXTURED'
+ bpy.context.scene.game_settings.material_mode = 'GLSL'
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.draw_type = 'BOUNDS'
+ else:
+ for obj in selection:
+ obj.draw_type = 'BOUNDS'
+
+class DisplayBounds(bpy.types.Operator):
+ '''Display objects in bounds mode'''
+ bl_idname = "view3d.display_bounds"
+ bl_label = "Bounds"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ draw_bounds(context)
+ return {'FINISHED'}
+
+#Shade smooth
+def shade_smooth(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ bpy.ops.object.select_all(action = 'TOGGLE')
+ bpy.ops.object.shade_smooth()
+ bpy.ops.object.select_all(action = 'TOGGLE')
+ else:
+ obj = context.active_object
+ if obj.mode == 'OBJECT':
+ for obj in selection:
+ bpy.ops.object.shade_smooth()
+ else:
+ bpy.ops.mesh.faces_shade_smooth()
+
+class DisplayShadeSmooth(bpy.types.Operator):
+ '''Display shade smooth meshes'''
+ bl_idname = "view3d.display_shade_smooth"
+ bl_label = "Smooth"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ shade_smooth(context)
+ return {'FINISHED'}
+
+#Shade flat
+def shade_flat(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ bpy.ops.object.select_all(action = 'TOGGLE')
+ bpy.ops.object.shade_flat()
+ bpy.ops.object.select_all(action = 'TOGGLE')
+ else:
+ obj = context.active_object
+ if obj.mode == 'OBJECT':
+ for obj in selection:
+ bpy.ops.object.shade_flat()
+ else:
+ bpy.ops.mesh.faces_shade_flat()
+
+class DisplayShadeFlat(bpy.types.Operator):
+ '''Display shade flat meshes'''
+ bl_idname = "view3d.display_shade_flat"
+ bl_label = "Flat"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ shade_flat(context)
+ return {'FINISHED'}
+
+#Shadeless on
+def shadeless_on(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.materials:
+ obj.use_shadeless = True
+ else:
+ for sel in selection:
+ if sel.type == 'MESH':
+ materials = sel.data.materials
+ for mat in materials:
+ mat.use_shadeless = True
+
+class DisplayShadelessOn(bpy.types.Operator):
+ '''Display shadeless material'''
+ bl_idname = "view3d.display_shadeless_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ shadeless_on(context)
+ return {'FINISHED'}
+
+#Shadeless off
+def shadeless_off(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.materials:
+ obj.use_shadeless = False
+ else:
+ for sel in selection:
+ if sel.type == 'MESH':
+ materials = sel.data.materials
+ for mat in materials:
+ mat.use_shadeless = False
+
+class DisplayShadelessOff(bpy.types.Operator):
+ '''Display shaded material'''
+ bl_idname = "view3d.display_shadeless_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ shadeless_off(context)
+ return {'FINISHED'}
+
+#Wireframe on
+def wire_on(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.show_wire = True
+
+ for mesh in bpy.data.meshes:
+ mesh.show_all_edges = True
+ else:
+ for obj in selection:
+ obj.show_wire = True
+
+ for sel in selection:
+ if sel.type == 'MESH':
+ mesh = sel.data
+ mesh.show_all_edges = True
+
+class DisplayWireframeOn(bpy.types.Operator):
+ '''Display wireframe overlay on'''
+ bl_idname = "view3d.display_wire_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ wire_on(context)
+ return {'FINISHED'}
+
+#Wireframe off
+def wire_off(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.show_wire = False
+
+ for mesh in bpy.data.meshes:
+ mesh.show_all_edges = False
+ else:
+ for obj in selection:
+ obj.show_wire = False
+
+ for sel in selection:
+ if sel.type == 'MESH':
+ mesh = sel.data
+ mesh.show_all_edges = False
+
+class DisplayWireframeOff(bpy.types.Operator):
+ '''Display wireframe overlay off'''
+ bl_idname = "view3d.display_wire_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ wire_off(context)
+ return {'FINISHED'}
+
+#Bounds on
+def bounds_on(context):
+ scene = context.scene
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.show_bounds = True
+ obj.draw_bounds_type = scene.BoundingMode
+ else:
+ for obj in selection:
+ obj.show_bounds = True
+ obj.draw_bounds_type = scene.BoundingMode
+
+class DisplayBoundsOn(bpy.types.Operator):
+ '''Display Bounding box overlay on'''
+ bl_idname = "view3d.display_bounds_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ bounds_on(context)
+ return {'FINISHED'}
+
+#Wireframe off
+def bounds_off(context):
+ scene = context.scene
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.show_bounds = False
+ else:
+ for obj in selection:
+ obj.show_bounds = False
+
+class DisplayBoundsOff(bpy.types.Operator):
+ '''Display Bounding box overlay off'''
+ bl_idname = "view3d.display_bounds_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ bounds_off(context)
+ return {'FINISHED'}
+
+#Double Sided on
+def double_sided_on(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for mesh in bpy.data.meshes:
+ mesh.show_double_sided = True
+ else:
+ for sel in selection:
+ if sel.type == 'MESH':
+ mesh = sel.data
+ mesh.show_double_sided = True
+
+class DisplayDoubleSidedOn(bpy.types.Operator):
+ '''Turn on face double shaded mode'''
+ bl_idname = "view3d.display_double_sided_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ double_sided_on(context)
+ return {'FINISHED'}
+
+#Double Sided off
+def double_sided_off(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for mesh in bpy.data.meshes:
+ mesh.show_double_sided = False
+ else:
+ for sel in selection:
+ if sel.type == 'MESH':
+ mesh = sel.data
+ mesh.show_double_sided = False
+
+class DisplayDoubleSidedOff(bpy.types.Operator):
+ '''Turn off face double sided shade mode'''
+ bl_idname = "view3d.display_double_sided_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ double_sided_off(context)
+ return {'FINISHED'}
+
+#XRay on
+def x_ray_on(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.show_x_ray = True
+ else:
+ for obj in selection:
+ obj.show_x_ray = True
+
+class DisplayXRayOn(bpy.types.Operator):
+ '''X-Ray display on'''
+ bl_idname = "view3d.display_x_ray_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ x_ray_on(context)
+ return {'FINISHED'}
+
+#XRay off
+def x_ray_off(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ obj.show_x_ray = False
+ else:
+ for obj in selection:
+ obj.show_x_ray = False
+
+class DisplayXRayOff(bpy.types.Operator):
+ '''X-Ray display off'''
+ bl_idname = "view3d.display_x_ray_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ x_ray_off(context)
+ return {'FINISHED'}
+
+#Init properties for scene
+bpy.types.Scene.FastNavigateStop = bpy.props.BoolProperty(
+ name = "Fast Navigate Stop",
+ description = "Stop fast navigate mode",
+ default = False)
+
+bpy.types.Scene.OriginalMode = bpy.props.EnumProperty(
+ items = [('TEXTURED', 'Texture', 'Texture display mode'),
+ ('SOLID', 'Solid', 'Solid display mode')],
+ name = "Normal",
+ default = 'SOLID')
+
+bpy.types.Scene.BoundingMode = bpy.props.EnumProperty(
+ items = [('BOX', 'Box', 'Box shape'),
+ ('SPHERE', 'Sphere', 'Sphere shape'),
+ ('CYLINDER', 'Cylinder', 'Cylinder shape'),
+ ('CONE', 'Cone', 'Cone shape')],
+ name = "BB Mode")
+
+bpy.types.Scene.FastMode = bpy.props.EnumProperty(
+ items = [('WIREFRAME', 'Wireframe', 'Wireframe display'),
+ ('BOUNDBOX', 'Bounding Box', 'Bounding Box display')],
+ name = "Fast")
+
+bpy.types.Scene.ShowParticles = bpy.props.BoolProperty(
+ name = "Show Particles",
+ description = "Show or hide particles on fast navigate mode",
+ default = True)
+
+bpy.types.Scene.ParticlesPercentageDisplay = bpy.props.IntProperty(
+ name = "Display",
+ description = "Display only a percentage of particles",
+ default = 25,
+ min = 0,
+ max = 100,
+ soft_min = 0,
+ soft_max = 100,
+ subtype = 'FACTOR')
+
+bpy.types.Scene.InitialParticles = bpy.props.IntProperty(
+ name = "Count for initial particle setting before enter fast navigate",
+ description = "Display a percentage value of particles",
+ default = 100,
+ min = 0,
+ max = 100,
+ soft_min = 0,
+ soft_max = 100)
+
+#Set Render Settings
+def set_render_settings(conext):
+ scene = bpy.context.scene
+ render = bpy.context.scene.render
+ view = bpy.context.space_data
+ render.simplify_subdivision = 0
+ render.simplify_shadow_samples = 0
+ render.simplify_child_particles = 0
+ render.simplify_ao_sss = 0
+
+class DisplaySimplify(bpy.types.Operator):
+ '''Display scene simplified'''
+ bl_idname = "view3d.display_simplify"
+ bl_label = "Reset"
+
+ Mode = EnumProperty(
+ items = [('WIREFRAME', 'Wireframe', ''),
+ ('BOUNDBOX', 'Bounding Box', '')],
+ name = "Mode")
+
+ ShowParticles = BoolProperty(
+ name = "ShowParticles",
+ description = "Show or hide particles on fast navigate mode",
+ default = True)
+
+ ParticlesPercentageDisplay = IntProperty(
+ name = "Display",
+ description = "Display a percentage value of particles",
+ default = 25,
+ min = 0,
+ max = 100,
+ soft_min = 0,
+ soft_max = 100,
+ subtype = 'FACTOR')
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ set_render_settings(context)
+ return {'FINISHED'}
+
+#Display Modifiers Render on
+def modifiers_render_on(context):
+ scene = bpy.context.scene
+ bpy.types.Scene.Symplify = IntProperty(
+ name = "Integer",description = "Enter an integer")
+ scene['Simplify'] = 1
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_render = True
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_render = True
+
+class DisplayModifiersRenderOn(bpy.types.Operator):
+ '''Display modifiers in render'''
+ bl_idname = "view3d.display_modifiers_render_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_render_on(context)
+ return {'FINISHED'}
+
+#Display Modifiers Render off
+def modifiers_render_off(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_render = False
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_render = False
+
+class DisplayModifiersRenderOff(bpy.types.Operator):
+ '''Hide modifiers in render'''
+ bl_idname = "view3d.display_modifiers_render_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_render_off(context)
+ return {'FINISHED'}
+
+#Display Modifiers Viewport on
+def modifiers_viewport_on(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_viewport = True
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_viewport = True
+
+class DisplayModifiersViewportOn(bpy.types.Operator):
+ '''Display modifiers in viewport'''
+ bl_idname = "view3d.display_modifiers_viewport_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_viewport_on(context)
+ return {'FINISHED'}
+
+#Display Modifiers Viewport off
+def modifiers_viewport_off(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_viewport = False
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_viewport = False
+
+class DisplayModifiersViewportOff(bpy.types.Operator):
+ '''Hide modifiers in viewport'''
+ bl_idname = "view3d.display_modifiers_viewport_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_viewport_off(context)
+ return {'FINISHED'}
+
+#Display Modifiers Edit on
+def modifiers_edit_on(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_in_editmode = True
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_in_editmode = True
+
+class DisplayModifiersEditOn(bpy.types.Operator):
+ '''Display modifiers during edit mode'''
+ bl_idname = "view3d.display_modifiers_edit_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_edit_on(context)
+ return {'FINISHED'}
+
+#Display Modifiers Edit off
+def modifiers_edit_off(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_in_editmode = False
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_in_editmode = False
+
+class DisplayModifiersEditOff(bpy.types.Operator):
+ '''Hide modifiers during edit mode'''
+ bl_idname = "view3d.display_modifiers_edit_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_edit_off(context)
+ return {'FINISHED'}
+
+#Display Modifiers Cage on
+def modifiers_cage_on(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_on_cage = True
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_on_cage = True
+
+class DisplayModifiersCageOn(bpy.types.Operator):
+ '''Display modifiers editing cage during edit mode'''
+ bl_idname = "view3d.display_modifiers_cage_on"
+ bl_label = "On"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_cage_on(context)
+ return {'FINISHED'}
+
+#Display Modifiers Cage off
+def modifiers_cage_off(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_on_cage = False
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_on_cage = False
+
+class DisplayModifiersCageOff(bpy.types.Operator):
+ '''Hide modifiers editing cage during edit mode'''
+ bl_idname = "view3d.display_modifiers_cage_off"
+ bl_label = "Off"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_cage_off(context)
+ return {'FINISHED'}
+
+#Display Modifiers Expand
+def modifiers_expand(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_expanded = True
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_expanded = True
+
+class DisplayModifiersExpand(bpy.types.Operator):
+ '''Expand all modifiers on modifier stack'''
+ bl_idname = "view3d.display_modifiers_expand"
+ bl_label = "Expand"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_expand(context)
+ return {'FINISHED'}
+
+#Display Modifiers Collapse
+def modifiers_collapse(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ mod.show_expanded = False
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ mod.show_expanded = False
+
+class DisplayModifiersCollapse(bpy.types.Operator):
+ '''Collapse all modifiers on modifier stack'''
+ bl_idname = "view3d.display_modifiers_collapse"
+ bl_label = "Collapse"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_collapse(context)
+ return {'FINISHED'}
+
+#Apply modifiers
+def modifiers_apply(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ bpy.ops.object.select_all(action = 'TOGGLE')
+ bpy.ops.object.convert(target = 'MESH', keep_original = False)
+ bpy.ops.object.select_all(action = 'TOGGLE')
+ else:
+ for mesh in selection:
+ if mesh.type == "MESH":
+ bpy.ops.object.convert(target='MESH', keep_original = False)
+
+class DisplayModifiersApply(bpy.types.Operator):
+ '''Apply modifiers'''
+ bl_idname = "view3d.display_modifiers_apply"
+ bl_label = "Apply All"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_apply(context)
+ return {'FINISHED'}
+
+#Delete modifiers
+def modifiers_delete(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ for obj in bpy.data.objects:
+ for mod in obj.modifiers:
+ bpy.context.scene.objects.active = obj
+ bpy.ops.object.modifier_remove(modifier = mod.name)
+ else:
+ for obj in selection:
+ for mod in obj.modifiers:
+ bpy.context.scene.objects.active = obj
+ bpy.ops.object.modifier_remove(modifier = mod.name)
+
+class DisplayModifiersDelete(bpy.types.Operator):
+ '''Delete modifiers'''
+ bl_idname = "view3d.display_modifiers_delete"
+ bl_label = "Delete All"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_delete(context)
+ return {'FINISHED'}
+
+#Put dummy modifier for boost subsurf
+def modifiers_set_dummy(context):
+ selection = bpy.context.selected_objects
+
+ if not(selection):
+ print("funciona")
+ for object in bpy.data.objects:
+ if object.type == "MESH":
+ mod = object.modifiers.new(type = 'SIMPLE_DEFORM',\
+ name = "Dummy")
+ mod.factor = 0
+ else:
+ for object in selection:
+ if object.type == "MESH":
+ mod = object.modifiers.new(type = 'SIMPLE_DEFORM',\
+ name = "Dummy")
+ mod.factor = 0
+
+class DisplayAddDummy(bpy.types.Operator):
+ '''Add a dummy simple deform modifier to boost\
+ subsurf modifier viewport performance'''
+ bl_idname = "view3d.display_modifiers_set_dummy"
+ bl_label = "Put Dummy"
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ modifiers_set_dummy(context)
+ return {'FINISHED'}
+
+# main class for Fast Navigate
+class VIEW3D_PT_FastNavigate(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Fast Navigate"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ layout = self.layout
+
+ # Tools
+ scene = context.scene
+ row = layout.row(align=True)
+ row.alignment = 'LEFT'
+ row.operator("view3d.fast_navigate_operator")
+ row.operator("view3d.fast_navigate_stop")
+ layout.label("Settings :")
+ row = layout.row()
+ box = row.box()
+ box.prop(scene,"OriginalMode")
+ box.prop(scene,"FastMode")
+ box.prop(scene, "EditActive", "Edit mode")
+ box.prop(scene, "Delay")
+ box.prop(scene, "DelayTimeGlobal", "Delay time")
+ box.alignment = 'LEFT'
+ box.prop(scene,"ShowParticles")
+ box.prop(scene,"ParticlesPercentageDisplay")
+
+# main class for Display Mode
+class VIEW3D_PT_DisplayMode(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Display Mode"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ layout = self.layout
+
+ # Tools
+ col = layout.column()
+ col.alignment = 'EXPAND'
+ row = col.row()
+ row.operator("view3d.display_textured" , icon ='TEXTURE_SHADED')
+ row.operator("view3d.display_solid" , icon ='SOLID')
+ col = layout.column()
+ col.alignment = 'EXPAND'
+ row = col.row()
+ row.operator("view3d.display_wire" , icon = 'WIRE')
+ row.operator("view3d.display_bounds" , icon = 'BBOX')
+
+# main class for Shading Setup
+class VIEW3D_PT_ShadingSetup(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Shading Setup"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ layout = self.layout
+
+ # Tools
+ col = layout.column(align=True)
+ row = col.row()
+ row.operator("view3d.display_shade_smooth")
+ row.operator("view3d.display_shade_flat")
+ row = col.row()
+ row.operator("view3d.display_shadeless_on", "Shadeless On",\
+ icon = 'SOLID')
+ row.operator("view3d.display_shadeless_off",\
+ "Shadeless Off", icon = 'SOLID')
+ row = col.row()
+ row.operator("view3d.display_wire_on", "Wire On", icon = 'WIRE')
+ row.operator("view3d.display_wire_off", "Wire Off", icon = 'WIRE')
+ row = col.row()
+ row.operator("view3d.display_bounds_on", "Bounds On", icon = 'BBOX')
+ row.operator("view3d.display_bounds_off", "Bounds Off", icon = 'BBOX')
+ row = col.row()
+ row.operator("view3d.display_double_sided_on",\
+ "DSided On", icon = 'MESH_DATA')
+ row.operator("view3d.display_double_sided_off",\
+ "DSided Off", icon = 'MESH_DATA')
+ row = col.row()
+ row.operator("view3d.display_x_ray_on",\
+ "XRay On", icon = 'GHOST_ENABLED')
+ row.operator("view3d.display_x_ray_off",\
+ "XRay Off", icon = 'GHOST_ENABLED')
+ row = col.row()
+ row.separator()
+ row = col.row()
+
+ scene = context.scene
+ row.prop(scene, "BoundingMode")
+
+# main class for Scene Visualization
+class VIEW3D_PT_SceneVisualization(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Scene Visualization"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ layout = self.layout
+
+ # Tools
+ scene = context.scene
+ render = scene.render
+ space = context.space_data
+ layout.prop(space, "show_manipulator")
+ layout.prop(space, "show_outline_selected")
+ layout.prop(space, "show_only_render")
+ layout.prop(space, "show_textured_solid")
+ layout.prop(space, "show_backface_culling")
+ layout.prop(space, "show_all_objects_origin")
+ layout.prop(render,"use_simplify", "Simplify")
+ if scene.render.use_simplify == True:
+ layout.label("Settings :")
+ row = layout.row()
+ box = row.box()
+ box.prop(render, "simplify_subdivision", "Subdivision")
+ box.prop(render, "simplify_shadow_samples", "Shadow Samples")
+ box.prop(render, "simplify_child_particles", "Child Particles")
+ box.prop(render, "simplify_ao_sss", "AO and SSS")
+ layout.operator("view3d.display_simplify")
+
+# main class for Modifier Tools
+class VIEW3D_PT_ModifierTools(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Modifier Tools"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ layout = self.layout
+
+ # Tools
+ layout.label("Modifiers", icon = 'MODIFIER')
+ col = layout.column(align=True)
+ col.alignment = 'EXPAND'
+ row = col.row()
+ row.operator("view3d.display_modifiers_render_on",\
+ icon = 'RENDER_STILL')
+ row.operator("view3d.display_modifiers_render_off")
+ row.operator("view3d.display_modifiers_viewport_on",
+ icon = 'RESTRICT_VIEW_OFF')
+ row.operator("view3d.display_modifiers_viewport_off")
+ col = layout.column(align=True)
+ col.alignment = 'EXPAND'
+ row = col.row()
+ row.operator("view3d.display_modifiers_edit_on", icon = 'EDITMODE_HLT')
+ row.operator("view3d.display_modifiers_edit_off")
+ row.operator("view3d.display_modifiers_cage_on",\
+ icon = 'EDITMODE_DEHLT')
+ row.operator("view3d.display_modifiers_cage_off")
+ row = layout.row(align=True)
+ row.operator("view3d.display_modifiers_expand", icon = 'TRIA_DOWN')
+ row.operator("view3d.display_modifiers_collapse", icon = 'TRIA_RIGHT')
+ row = layout.row(align=True)
+ row.operator("view3d.display_modifiers_apply", icon = 'MODIFIER')
+ row.operator("view3d.display_modifiers_delete", icon = 'X')
+ row = layout.row(align=True)
+ row.operator("view3d.display_modifiers_set_dummy",\
+ icon = 'OUTLINER_OB_ARMATURE')
+
+# register the classes
+def register():
+ bpy.utils.register_class(FastNavigate)
+ bpy.utils.register_class(DisplayTextured)
+ bpy.utils.register_class(DisplaySolid)
+ bpy.utils.register_class(DisplayWire)
+ bpy.utils.register_class(DisplayBounds)
+ bpy.utils.register_class(DisplayWireframeOn)
+ bpy.utils.register_class(DisplayWireframeOff)
+ bpy.utils.register_class(DisplayBoundsOn)
+ bpy.utils.register_class(DisplayBoundsOff)
+ bpy.utils.register_class(DisplayShadeSmooth)
+ bpy.utils.register_class(DisplayShadeFlat)
+ bpy.utils.register_class(DisplayShadelessOn)
+ bpy.utils.register_class(DisplayShadelessOff)
+ bpy.utils.register_class(DisplayDoubleSidedOn)
+ bpy.utils.register_class(DisplayDoubleSidedOff)
+ bpy.utils.register_class(DisplayXRayOn)
+ bpy.utils.register_class(DisplayXRayOff)
+ bpy.utils.register_class(DisplayModifiersRenderOn)
+ bpy.utils.register_class(DisplayModifiersRenderOff)
+ bpy.utils.register_class(DisplayModifiersViewportOn)
+ bpy.utils.register_class(DisplayModifiersViewportOff)
+ bpy.utils.register_class(DisplayModifiersEditOn)
+ bpy.utils.register_class(DisplayModifiersEditOff)
+ bpy.utils.register_class(DisplayModifiersCageOn)
+ bpy.utils.register_class(DisplayModifiersCageOff)
+ bpy.utils.register_class(DisplayModifiersExpand)
+ bpy.utils.register_class(DisplayModifiersCollapse)
+ bpy.utils.register_class(DisplayModifiersApply)
+ bpy.utils.register_class(DisplayModifiersDelete)
+ bpy.utils.register_class(DisplayAddDummy)
+ bpy.utils.register_class(DisplaySimplify)
+ bpy.utils.register_module(__name__)
+ pass
+
+def unregister():
+ bpy.utils.unregister_class(FastNavigate)
+ bpy.utils.unregister_class(DisplayTextured)
+ bpy.utils.unregister_class(DisplaySolid)
+ bpy.utils.unregister_class(DisplayWire)
+ bpy.utils.unregister_class(DisplayBounds)
+ bpy.utils.unregister_class(DisplayShadeSmooth)
+ bpy.utils.unregister_class(DisplayShadeFlat)
+ bpy.utils.unregister_class(DisplayShadelessOn)
+ bpy.utils.unregister_class(DisplayShadelessOff)
+ bpy.utils.unregister_class(DisplayWireframeOn)
+ bpy.utils.unregister_class(DisplayWireframeOff)
+ bpy.utils.unregister_class(DisplayBoundsOn)
+ bpy.utils.unregister_class(DisplayBoundsOff)
+ bpy.utils.unregister_class(DisplayDoubleSidedOn)
+ bpy.utils.unregister_class(DisplayDoubleSidedOff)
+ bpy.utils.unregister_class(DisplayXRayOn)
+ bpy.utils.unregister_class(DisplayXRayOff)
+ bpy.utils.unregister_class(DisplayModifiersRenderOn)
+ bpy.utils.unregister_class(DisplayModifiersRenderOff)
+ bpy.utils.unregister_class(DisplayModifiersViewportOn)
+ bpy.utils.unregister_class(DisplayModifiersViewportOff)
+ bpy.utils.unregister_class(DisplayModifiersEditOn)
+ bpy.utils.unregister_class(DisplayModifiersEditOff)
+ bpy.utils.unregister_class(DisplayModifiersCageOn)
+ bpy.utils.unregister_class(DisplayModifiersCageOff)
+ bpy.utils.unregister_class(DisplayModifiersExpand)
+ bpy.utils.unregister_class(DisplayModifiersCollapse)
+ bpy.utils.unregister_class(DisplayModifiersApply)
+ bpy.utils.unregister_class(DisplayModifiersDelete)
+ bpy.utils.unregister_class(DisplayAddDummy)
+ bpy.utils.unregister_class(DisplaySimplify)
+ bpy.utils.unregister_module(__name__)
+ pass
+
+if __name__ == "__main__":
+ register()
\ No newline at end of file
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/COLORS_GROUPS_EXCHANGER.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/COLORS_GROUPS_EXCHANGER.PY
new file mode 100644
index 00000000..dee8bd62
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/COLORS_GROUPS_EXCHANGER.PY
@@ -0,0 +1,3235 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+#-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
+# #
+# Vertex Color to Vertex Group allow you to convert colors channles to weight #
+# maps. #
+# The main purpose is to use vertex colors to store information when importing #
+# files from other softwares. The script works with the active vertex color #
+# slot. #
+# For use the command "Vertex Clors to Vertex Groups" use the search bar #
+# (space bar). #
+# #
+# (c) Alessandro Zomparelli #
+# (2017) #
+# #
+# http://www.co-de-it.com/ #
+# #
+################################################################################
+
+import bpy, bmesh
+import numpy as np
+import math, timeit, time
+from math import *#pi, sin
+from statistics import mean, stdev
+from mathutils import Vector
+from numpy import *
+try: from .numba_functions import numba_reaction_diffusion
+except: pass
+try: import numexpr as ne
+except: pass
+
+from bpy.types import (
+ Operator,
+ Panel,
+ PropertyGroup,
+ )
+
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+ StringProperty,
+ FloatVectorProperty,
+ IntVectorProperty
+)
+
+from .utils import *
+
+def reaction_diffusion_add_handler(self, context):
+ # remove existing handlers
+ old_handlers = []
+ for h in bpy.app.handlers.frame_change_post:
+ if "reaction_diffusion" in str(h):
+ old_handlers.append(h)
+ for h in old_handlers: bpy.app.handlers.frame_change_post.remove(h)
+ # add new handler
+ bpy.app.handlers.frame_change_post.append(reaction_diffusion_def)
+
+class formula_prop(PropertyGroup):
+ name : StringProperty()
+ formula : StringProperty()
+ float_var : FloatVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
+ int_var : IntVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
+
+class reaction_diffusion_prop(PropertyGroup):
+ run : BoolProperty(default=False, update = reaction_diffusion_add_handler,
+ description='Compute a new iteration on frame changes. Currently is not working during Render Animation')
+
+ time_steps : bpy.props.IntProperty(
+ name="Steps", default=10, min=0, soft_max=50,
+ description="Number of Steps")
+
+ dt : bpy.props.FloatProperty(
+ name="dt", default=1, min=0, soft_max=0.2,
+ description="Time Step")
+
+ diff_a : bpy.props.FloatProperty(
+ name="Diff A", default=0.1, min=0, soft_max=2, precision=3,
+ description="Diffusion A")
+
+ diff_b : bpy.props.FloatProperty(
+ name="Diff B", default=0.05, min=0, soft_max=2, precision=3,
+ description="Diffusion B")
+
+ f : bpy.props.FloatProperty(
+ name="f", default=0.055, min=0, soft_max=0.5, precision=3,
+ description="Feed Rate")
+
+ k : bpy.props.FloatProperty(
+ name="k", default=0.062, min=0, soft_max=0.5, precision=3,
+ description="Kill Rate")
+
+ diff_mult : bpy.props.FloatProperty(
+ name="Scale", default=1, min=0, soft_max=1, max=2, precision=2,
+ description="Multiplier for the diffusion of both substances")
+
+def compute_formula(ob=None, formula="rx", float_var=(0,0,0,0,0), int_var=(0,0,0,0,0)):
+ verts = ob.data.vertices
+ n_verts = len(verts)
+
+ f1,f2,f3,f4,f5 = float_var
+ i1,i2,i3,i4,i5 = int_var
+
+ do_groups = "w[" in formula
+ do_local = "lx" in formula or "ly" in formula or "lz" in formula
+ do_global = "gx" in formula or "gy" in formula or "gz" in formula
+ do_relative = "rx" in formula or "ry" in formula or "rz" in formula
+ do_normal = "nx" in formula or "ny" in formula or "nz" in formula
+ mat = ob.matrix_world
+
+ for i in range(1000):
+ if "w["+str(i)+"]" in formula and i > len(ob.vertex_groups)-1:
+ return "w["+str(i)+"] not found"
+
+ w = []
+ for i in range(len(ob.vertex_groups)):
+ w.append([])
+ if "w["+str(i)+"]" in formula:
+ vg = ob.vertex_groups[i]
+ for v in verts:
+ try:
+ w[i].append(vg.weight(v.index))
+ except:
+ w[i].append(0)
+ w[i] = array(w[i])
+
+ start_time = timeit.default_timer()
+ # compute vertex coordinates
+ if do_local or do_relative or do_global:
+ co = [0]*n_verts*3
+ verts.foreach_get('co', co)
+ np_co = array(co).reshape((n_verts, 3))
+ lx, ly, lz = array(np_co).transpose()
+ if do_relative:
+ rx = np.interp(lx, (lx.min(), lx.max()), (0, +1))
+ ry = np.interp(ly, (ly.min(), ly.max()), (0, +1))
+ rz = np.interp(lz, (lz.min(), lz.max()), (0, +1))
+ if do_global:
+ co = [v.co for v in verts]
+ global_co = []
+ for v in co:
+ global_co.append(mat * v)
+ global_co = array(global_co).reshape((n_verts, 3))
+ gx, gy, gz = array(global_co).transpose()
+ # compute vertex normals
+ if do_normal:
+ normal = [0]*n_verts*3
+ verts.foreach_get('normal', normal)
+ normal = array(normal).reshape((n_verts, 3))
+ nx, ny, nz = array(normal).transpose()
+
+ try:
+ weight = eval(formula)
+ return weight
+ except:
+ return "There is something wrong"
+ print("Weight Formula: " + str(timeit.default_timer() - start_time))
+
+class weight_formula_wiki(bpy.types.Operator):
+ bl_idname = "scene.weight_formula_wiki"
+ bl_label = "Online Documentation"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ bpy.ops.wm.url_open(url="https://github.com/alessandro-zomparelli/tissue/wiki/Weight-Tools#weight-formula")
+ return {'FINISHED'}
+
+class weight_formula(bpy.types.Operator):
+ bl_idname = "object.weight_formula"
+ bl_label = "Weight Formula"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ ex = [
+ #'cos(arctan(nx/ny)*6 + sin(rz*30)*0.5)/2 + cos(arctan(nx/ny)*6 - sin(rz*30)*0.5 + pi/2)/2 + 0.5',
+ 'cos(arctan(nx/ny)*i1*2 + sin(rz*i3))/i2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i3))/i2 + 0.5',
+ 'cos(arctan(nx/ny)*i1*2 + sin(rz*i2))/2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i2))/2',
+ '(sin(arctan(nx/ny)*i1)*sin(nz*i1)+1)/2',
+ 'cos(arctan(nx/ny)*f1)',
+ 'cos(arctan(lx/ly)*f1 + sin(rz*f2)*f3)',
+ 'sin(nx*15) 0',
+ 'sin(nz*i1)',
+ 'w[0]**2',
+ 'sqrt((rx-0.5)**2 + (ry-0.5)**2)*2',
+ 'abs(0.5-rz)*2',
+ 'rx'
+ ]
+ ex_items = list((s,s,"") for s in ex)
+ ex_items.append(('CUSTOM', "User Formula", ""))
+
+ examples : bpy.props.EnumProperty(
+ items = ex_items, default='CUSTOM', name="Examples")
+
+ old_ex = ""
+
+ formula : bpy.props.StringProperty(
+ name="Formula", default="", description="Formula to Evaluate")
+ bl_description = ("Generate a Vertex Group based on the given formula")
+
+ slider_f01 : bpy.props.FloatProperty(
+ name="f1", default=1, description="Slider")
+ bl_description = ("Slider Float 1")
+ slider_f02 : bpy.props.FloatProperty(
+ name="f2", default=1, description="Slider")
+ bl_description = ("Slider Float 2")
+ slider_f03 : bpy.props.FloatProperty(
+ name="f3", default=1, description="Slider")
+ bl_description = ("Slider Float 3")
+ slider_f04 : bpy.props.FloatProperty(
+ name="f4", default=1, description="Slider")
+ bl_description = ("Slider Float 4")
+ slider_f05 : bpy.props.FloatProperty(
+ name="f5", default=1, description="Slider")
+ bl_description = ("Slider Float 5")
+ slider_i01 : bpy.props.IntProperty(
+ name="i1", default=1, description="Slider")
+ bl_description = ("Slider Integer 1")
+ slider_i02 : bpy.props.IntProperty(
+ name="i2", default=1, description="Slider")
+ bl_description = ("Slider Integer 2")
+ slider_i03 : bpy.props.IntProperty(
+ name="i3", default=1, description="Slider")
+ bl_description = ("Slider Integer 3")
+ slider_i04 : bpy.props.IntProperty(
+ name="i4", default=1, description="Slider")
+ bl_description = ("Slider Integer 4")
+ slider_i05 : bpy.props.IntProperty(
+ name="i5", default=1, description="Slider")
+ bl_description = ("Slider Integer 5")
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=350)
+
+ def draw(self, context):
+ layout = self.layout
+ #layout.label(text="Examples")
+ layout.prop(self, "examples", text="Examples")
+ #if self.examples == 'CUSTOM':
+ layout.label(text="Formula")
+ layout.prop(self, "formula", text="")
+ #try: self.examples = self.formula
+ #except: pass
+
+ if self.examples != self.old_ex and self.examples != 'CUSTOM':
+ self.formula = self.examples
+ self.old_ex = self.examples
+ elif self.formula != self.examples:
+ self.examples = 'CUSTOM'
+ formula = self.formula
+
+ layout.separator()
+ if "f1" in formula: layout.prop(self, "slider_f01")
+ if "f2" in formula: layout.prop(self, "slider_f02")
+ if "f3" in formula: layout.prop(self, "slider_f03")
+ if "f4" in formula: layout.prop(self, "slider_f04")
+ if "f5" in formula: layout.prop(self, "slider_f05")
+ if "i1" in formula: layout.prop(self, "slider_i01")
+ if "i2" in formula: layout.prop(self, "slider_i02")
+ if "i3" in formula: layout.prop(self, "slider_i03")
+ if "i4" in formula: layout.prop(self, "slider_i04")
+ if "i5" in formula: layout.prop(self, "slider_i05")
+
+ layout.label(text="Variables (for each vertex):")
+ layout.label(text="lx, ly, lz: Local Coordinates", icon='ORIENTATION_LOCAL')
+ layout.label(text="gx, gy, gz: Global Coordinates", icon='WORLD')
+ layout.label(text="rx, ry, rz: Local Coordinates (0 to 1)", icon='NORMALIZE_FCURVES')
+ layout.label(text="nx, ny, nz: Normal Coordinates", icon='SNAP_NORMAL')
+ layout.label(text="w[0], w[1], w[2], ... : Vertex Groups", icon="GROUP_VERTEX")
+ layout.separator()
+ layout.label(text="f1, f2, f3, f4, f5: Float Sliders", icon='MOD_HUE_SATURATION')#PROPERTIES
+ layout.label(text="i1, i2, i3, i4, i5: Integer Sliders", icon='MOD_HUE_SATURATION')
+ layout.separator()
+ #layout.label(text="All mathematical functions are based on Numpy", icon='INFO')
+ #layout.label(text="https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html", icon='INFO')
+ layout.operator("scene.weight_formula_wiki", icon="HELP")
+ #layout.label(text="(where 'i' is the index of the Vertex Group)")
+
+ def execute(self, context):
+ ob = bpy.context.active_object
+ n_verts = len(ob.data.vertices)
+ #if self.examples == 'CUSTOM':
+ # formula = self.formula
+ #else:
+ #self.formula = self.examples
+ # formula = self.examples
+
+ #f1, f2, f3, f4, f5 = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
+ #i1, i2, i3, i4, i5 = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
+ f_sliders = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
+ i_sliders = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
+
+ if self.examples != self.old_ex and self.examples != 'CUSTOM':
+ self.formula = self.examples
+ self.old_ex = self.examples
+ elif self.formula != self.examples:
+ self.examples = 'CUSTOM'
+ formula = self.formula
+
+ if formula == "": return {'FINISHED'}
+ vertex_group_name = "Formula " + formula
+ ob.vertex_groups.new(name=vertex_group_name)
+
+ weight = compute_formula(ob, formula=formula, float_var=f_sliders, int_var=i_sliders)
+ if type(weight) == str:
+ self.report({'ERROR'}, weight)
+ return {'CANCELLED'}
+
+ #start_time = timeit.default_timer()
+ weight = nan_to_num(weight)
+ if type(weight) == int or type(weight) == float:
+ for i in range(n_verts):
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ elif type(weight) == ndarray:
+ for i in range(n_verts):
+ ob.vertex_groups[-1].add([i], weight[i], 'REPLACE')
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+
+ # Store formula settings
+ new_formula = ob.formula_settings.add()
+ new_formula.name = ob.vertex_groups[-1].name
+ new_formula.formula = formula
+ new_formula.int_var = i_sliders
+ new_formula.float_var = f_sliders
+
+ #for f in ob.formula_settings:
+ # print(f.name, f.formula, f.int_var, f.float_var)
+ return {'FINISHED'}
+
+class _weight_laplacian(bpy.types.Operator):
+ bl_idname = "object._weight_laplacian"
+ bl_label = "Weight Laplacian"
+ bl_description = ("Compute the Vertex Group Laplacian")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ bounds : bpy.props.EnumProperty(
+ items=(('MANUAL', "Manual Bounds", ""),
+ ('POSITIVE', "Positive Only", ""),
+ ('NEGATIVE', "Negative Only", ""),
+ ('AUTOMATIC', "Automatic Bounds", "")),
+ default='AUTOMATIC', name="Bounds")
+
+ mode : bpy.props.EnumProperty(
+ items=(('LENGTH', "Length Weight", ""),
+ ('SIMPLE', "Simple", "")),
+ default='SIMPLE', name="Evaluation Mode")
+
+ min_def : bpy.props.FloatProperty(
+ name="Min", default=0, soft_min=-1, soft_max=0,
+ description="Laplacian value with 0 weight")
+
+ max_def : bpy.props.FloatProperty(
+ name="Max", default=0.5, soft_min=0, soft_max=5,
+ description="Laplacian value with 1 weight")
+
+ bounds_string = ""
+
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.label(text="Evaluation Mode")
+ col.prop(self, "mode", text="")
+ col.label(text="Bounds")
+ col.prop(self, "bounds", text="")
+ if self.bounds == 'MANUAL':
+ col.label(text="Strain Rate \u03B5:")
+ col.prop(self, "min_def")
+ col.prop(self, "max_def")
+ col.label(text="\u03B5" + ": from " + self.bounds_string)
+
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ group_id = ob.vertex_groups.active_index
+ input_group = ob.vertex_groups[group_id].name
+
+ group_name = "Laplacian"
+ ob.vertex_groups.new(name=group_name)
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ weight = []
+ for v in me.vertices:
+ try:
+ weight.append(ob.vertex_groups[input_group].weight(v.index))
+ except:
+ weight.append(0)
+
+ n_verts = len(bm.verts)
+ lap = [0]*n_verts
+ for e in bm.edges:
+ if self.mode == 'LENGTH':
+ length = e.calc_length()
+ if length == 0: continue
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap[id0] += weight[id1]/length - weight[id0]/length
+ lap[id1] += weight[id0]/length - weight[id1]/length
+ else:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap[id0] += weight[id1] - weight[id0]
+ lap[id1] += weight[id0] - weight[id1]
+
+ mean_lap = mean(lap)
+ stdev_lap = stdev(lap)
+ filter_lap = [i for i in lap if mean_lap-2*stdev_lap < i < mean_lap+2*stdev_lap]
+ if self.bounds == 'MANUAL':
+ min_def = self.min_def
+ max_def = self.max_def
+ elif self.bounds == 'AUTOMATIC':
+ min_def = min(filter_lap)
+ max_def = max(filter_lap)
+ self.min_def = min_def
+ self.max_def = max_def
+ elif self.bounds == 'NEGATIVE':
+ min_def = 0
+ max_def = min(filter_lap)
+ self.min_def = min_def
+ self.max_def = max_def
+ elif self.bounds == 'POSITIVE':
+ min_def = 0
+ max_def = max(filter_lap)
+ self.min_def = min_def
+ self.max_def = max_def
+ delta_def = max_def - min_def
+
+ # check undeformed errors
+ if delta_def == 0: delta_def = 0.0001
+
+ for i in range(len(lap)):
+ val = (lap[i]-min_def)/delta_def
+ if val > 0.7: print(str(val) + " " + str(lap[i]))
+ #val = weight[i] + 0.2*lap[i]
+ ob.vertex_groups[-1].add([i], val, 'REPLACE')
+ self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
+ ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+class weight_laplacian(bpy.types.Operator):
+ bl_idname = "object.weight_laplacian"
+ bl_label = "Weight Laplacian"
+ bl_description = ("Compute the Vertex Group Laplacian")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ steps : bpy.props.IntProperty(
+ name="Steps", default=10, min=0, soft_max=50,
+ description="Number of Steps")
+
+ dt : bpy.props.FloatProperty(
+ name="dt", default=0.2, min=0, soft_max=0.2,
+ description="Time Step")
+
+ diff_a : bpy.props.FloatProperty(
+ name="Diff A", default=1, min=0, soft_max=2,
+ description="Diffusion A")
+
+ diff_b : bpy.props.FloatProperty(
+ name="Diff B", default=0.5, min=0, soft_max=2,
+ description="Diffusion B")
+
+ f : bpy.props.FloatProperty(
+ name="f", default=0.055, min=0, soft_max=0.5,
+ description="Feed Rate")
+
+ k : bpy.props.FloatProperty(
+ name="k", default=0.062, min=0, soft_max=0.5,
+ description="Kill Rate")
+
+ diff_mult : bpy.props.FloatProperty(
+ name="Scale", default=1, min=0, soft_max=1, max=2, precision=2,
+ description="Multiplier for the diffusion of both substances")
+
+ bounds_string = ""
+
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ a = []
+ b = []
+ for v in me.vertices:
+ try:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ except:
+ a.append(0)
+ try:
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ except:
+ b.append(0)
+
+ a = array(a)
+ b = array(b)
+ f = self.f
+ k = self.k
+ diff_a = self.diff_a * self.diff_mult
+ diff_b = self.diff_b * self.diff_mult
+ dt = self.dt
+
+ # initialize
+ n_verts = len(bm.verts)
+ # find max number of edges for vertex
+ max_edges = 0
+ n_neighbors = []
+ id_neighbors = []
+ for v in bm.verts:
+ n_edges = len(v.link_edges)
+ max_edges = max(max_edges, n_edges)
+ n_neighbors.append(n_edges)
+ neighbors = []
+ for e in link_edges:
+ for v1 in e.verts:
+ if v != v1: neighbors.append(v1.index)
+ id_neighbors.append(neighbors)
+ n_neighbors = array(n_neighbors)
+
+
+ a = [[] for i in range(n_verts)]
+ lap_map = []
+
+ for e in bm.edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap_map[id0].append(id1)
+ lap_map[id1].append(id0)
+
+ e1 = array(e1)
+ e2 = array(e2)
+ lap_a = a[e1]
+
+ for i in range(self.steps):
+
+ lap_a = zeros((n_verts))#[0]*n_verts
+ lap_b = zeros((n_verts))#[0]*n_verts
+ for e in bm.edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap_a[id0] += a[id1] - a[id0]
+ lap_a[id1] += a[id0] - a[id1]
+ lap_b[id0] += b[id1] - b[id0]
+ lap_b[id1] += b[id0] - b[id1]
+ ab2 = a*b**2
+ a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+
+class reaction_diffusion(bpy.types.Operator):
+ bl_idname = "object.reaction_diffusion"
+ bl_label = "Reaction Diffusion"
+ bl_description = ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ steps : bpy.props.IntProperty(
+ name="Steps", default=10, min=0, soft_max=50,
+ description="Number of Steps")
+
+ dt : bpy.props.FloatProperty(
+ name="dt", default=0.2, min=0, soft_max=0.2,
+ description="Time Step")
+
+ diff_a : bpy.props.FloatProperty(
+ name="Diff A", default=1, min=0, soft_max=2,
+ description="Diffusion A")
+
+ diff_b : bpy.props.FloatProperty(
+ name="Diff B", default=0.5, min=0, soft_max=2,
+ description="Diffusion B")
+
+ f : bpy.props.FloatProperty(
+ name="f", default=0.055, min=0, soft_max=0.5,
+ description="Feed Rate")
+
+ k : bpy.props.FloatProperty(
+ name="k", default=0.062, min=0, soft_max=0.5,
+ description="Kill Rate")
+
+ bounds_string = ""
+
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+
+ def execute(self, context):
+ #bpy.app.handlers.frame_change_post.remove(reaction_diffusion_def)
+ reaction_diffusion_add_handler(self, context)
+ set_animatable_fix_handler(self, context)
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ a = []
+ b = []
+ for v in me.vertices:
+ try:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ except:
+ a.append(0)
+ try:
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ except:
+ b.append(0)
+
+ a = array(a)
+ b = array(b)
+ f = self.f
+ k = self.k
+ diff_a = self.diff_a
+ diff_b = self.diff_b
+ dt = self.dt
+ n_verts = len(bm.verts)
+
+ for i in range(self.steps):
+
+ lap_a = zeros((n_verts))#[0]*n_verts
+ lap_b = zeros((n_verts))#[0]*n_verts
+ for e in bm.edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap_a[id0] += a[id1] - a[id0]
+ lap_a[id1] += a[id0] - a[id1]
+ lap_b[id0] += b[id1] - b[id0]
+ lap_b[id1] += b[id0] - b[id1]
+ ab2 = a*b**2
+ a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+
+ bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
+
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+
+class edges_deformation(bpy.types.Operator):
+ bl_idname = "object.edges_deformation"
+ bl_label = "Edges Deformation"
+ bl_description = ("Compute Weight based on the deformation of edges"+
+ "according to visible modifiers.")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ bounds : bpy.props.EnumProperty(
+ items=(('MANUAL', "Manual Bounds", ""),
+ ('COMPRESSION', "Compressed Only", ""),
+ ('TENSION', "Extended Only", ""),
+ ('AUTOMATIC', "Automatic Bounds", "")),
+ default='AUTOMATIC', name="Bounds")
+
+ mode : bpy.props.EnumProperty(
+ items=(('MAX', "Max Deformation", ""),
+ ('MEAN', "Average Deformation", "")),
+ default='MEAN', name="Evaluation Mode")
+
+ min_def : bpy.props.FloatProperty(
+ name="Min", default=0, soft_min=-1, soft_max=0,
+ description="Deformations with 0 weight")
+
+ max_def : bpy.props.FloatProperty(
+ name="Max", default=0.5, soft_min=0, soft_max=5,
+ description="Deformations with 1 weight")
+
+ bounds_string = ""
+
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.modifiers) > 0
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.label(text="Evaluation Mode")
+ col.prop(self, "mode", text="")
+ col.label(text="Bounds")
+ col.prop(self, "bounds", text="")
+ if self.bounds == 'MANUAL':
+ col.label(text="Strain Rate \u03B5:")
+ col.prop(self, "min_def")
+ col.prop(self, "max_def")
+ col.label(text="\u03B5" + ": from " + self.bounds_string)
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ # check if the object is Cloth or Softbody
+ physics = False
+ for m in ob.modifiers:
+ if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
+ physics = True
+ if context.scene.frame_current == 1 and self.frame != None:
+ context.scene.frame_current = self.frame
+ break
+ if not physics: self.frame = None
+
+ if self.mode == 'MEAN': group_name = "Average Deformation"
+ elif self.mode == 'MAX': group_name = "Max Deformation"
+ ob.vertex_groups.new(name=group_name)
+ me0 = ob.data
+
+ me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
+ self.report({'ERROR'}, "The topology of the object should be" +
+ "unaltered")
+ return {'CANCELLED'}
+
+ bm0 = bmesh.new()
+ bm0.from_mesh(me0)
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ deformations = []
+ for e0, e in zip(bm0.edges, bm.edges):
+ try:
+ l0 = e0.calc_length()
+ l1 = e.calc_length()
+ epsilon = (l1 - l0)/l0
+ deformations.append(epsilon)
+ except: deformations.append(1)
+ v_deformations = []
+ for v in bm.verts:
+ vdef = []
+ for e in v.link_edges:
+ vdef.append(deformations[e.index])
+ if self.mode == 'MEAN': v_deformations.append(mean(vdef))
+ elif self.mode == 'MAX': v_deformations.append(max(vdef, key=abs))
+ #elif self.mode == 'MIN': v_deformations.append(min(vdef, key=abs))
+
+ if self.bounds == 'MANUAL':
+ min_def = self.min_def
+ max_def = self.max_def
+ elif self.bounds == 'AUTOMATIC':
+ min_def = min(v_deformations)
+ max_def = max(v_deformations)
+ self.min_def = min_def
+ self.max_def = max_def
+ elif self.bounds == 'COMPRESSION':
+ min_def = 0
+ max_def = min(v_deformations)
+ self.min_def = min_def
+ self.max_def = max_def
+ elif self.bounds == 'TENSION':
+ min_def = 0
+ max_def = max(v_deformations)
+ self.min_def = min_def
+ self.max_def = max_def
+ delta_def = max_def - min_def
+
+ # check undeformed errors
+ if delta_def == 0:
+ if self.bounds == 'MANUAL':
+ delta_def = 0.0001
+ else:
+ message = "The object doesn't have deformations."
+ if physics:
+ message = message + ("\nIf you are using Physics try to " +
+ "save it in the cache before.")
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+ else:
+ if physics:
+ self.frame = context.scene.frame_current
+
+ for i in range(len(v_deformations)):
+ weight = (v_deformations[i] - min_def)/delta_def
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
+ ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ bpy.data.meshes.remove(me)
+ return {'FINISHED'}
+
+class edges_bending(bpy.types.Operator):
+ bl_idname = "object.edges_bending"
+ bl_label = "Edges Bending"
+ bl_description = ("Compute Weight based on the bending of edges"+
+ "according to visible modifiers.")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ bounds : bpy.props.EnumProperty(
+ items=(('MANUAL', "Manual Bounds", ""),
+ ('POSITIVE', "Positive Only", ""),
+ ('NEGATIVE', "Negative Only", ""),
+ ('UNSIGNED', "Absolute Bending", ""),
+ ('AUTOMATIC', "Signed Bending", "")),
+ default='AUTOMATIC', name="Bounds")
+
+ min_def : bpy.props.FloatProperty(
+ name="Min", default=-10, soft_min=-45, soft_max=45,
+ description="Deformations with 0 weight")
+
+ max_def : bpy.props.FloatProperty(
+ name="Max", default=10, soft_min=-45, soft_max=45,
+ description="Deformations with 1 weight")
+
+ bounds_string = ""
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.modifiers) > 0
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label(text="Bounds")
+ layout.prop(self, "bounds", text="")
+ if self.bounds == 'MANUAL':
+ layout.prop(self, "min_def")
+ layout.prop(self, "max_def")
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ group_name = "Edges Bending"
+ ob.vertex_groups.new(name=group_name)
+
+ # check if the object is Cloth or Softbody
+ physics = False
+ for m in ob.modifiers:
+ if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
+ physics = True
+ if context.scene.frame_current == 1 and self.frame != None:
+ context.scene.frame_current = self.frame
+ break
+ if not physics: self.frame = None
+
+ #ob.data.update()
+ #context.scene.update()
+ me0 = ob.data
+ me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
+ self.report({'ERROR'}, "The topology of the object should be" +
+ "unaltered")
+ bm0 = bmesh.new()
+ bm0.from_mesh(me0)
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ deformations = []
+ for e0, e in zip(bm0.edges, bm.edges):
+ try:
+ ang = e.calc_face_angle_signed()
+ ang0 = e0.calc_face_angle_signed()
+ if self.bounds == 'UNSIGNED':
+ deformations.append(abs(ang-ang0))
+ else:
+ deformations.append(ang-ang0)
+ except: deformations.append(0)
+ v_deformations = []
+ for v in bm.verts:
+ vdef = []
+ for e in v.link_edges:
+ vdef.append(deformations[e.index])
+ v_deformations.append(mean(vdef))
+ if self.bounds == 'MANUAL':
+ min_def = radians(self.min_def)
+ max_def = radians(self.max_def)
+ elif self.bounds == 'AUTOMATIC':
+ min_def = min(v_deformations)
+ max_def = max(v_deformations)
+ elif self.bounds == 'POSITIVE':
+ min_def = 0
+ max_def = min(v_deformations)
+ elif self.bounds == 'NEGATIVE':
+ min_def = 0
+ max_def = max(v_deformations)
+ elif self.bounds == 'UNSIGNED':
+ min_def = 0
+ max_def = max(v_deformations)
+ delta_def = max_def - min_def
+
+ # check undeformed errors
+ if delta_def == 0:
+ if self.bounds == 'MANUAL':
+ delta_def = 0.0001
+ else:
+ message = "The object doesn't have deformations."
+ if physics:
+ message = message + ("\nIf you are using Physics try to " +
+ "save it in the cache before.")
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+ else:
+ if physics:
+ self.frame = context.scene.frame_current
+
+ for i in range(len(v_deformations)):
+ weight = (v_deformations[i] - min_def)/delta_def
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
+ ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ bpy.data.meshes.remove(me)
+ return {'FINISHED'}
+
+class weight_contour_displace(bpy.types.Operator):
+ bl_idname = "object.weight_contour_displace"
+ bl_label = "Contour Displace"
+ bl_description = ("")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_modifiers : bpy.props.BoolProperty(
+ name="Use Modifiers", default=True,
+ description="Apply all the modifiers")
+ min_iso : bpy.props.FloatProperty(
+ name="Min Iso Value", default=0.49, min=0, max=1,
+ description="Threshold value")
+ max_iso : bpy.props.FloatProperty(
+ name="Max Iso Value", default=0.51, min=0, max=1,
+ description="Threshold value")
+ n_cuts : bpy.props.IntProperty(
+ name="Cuts", default=2, min=1, soft_max=10,
+ description="Number of cuts in the selected range of values")
+ bool_displace : bpy.props.BoolProperty(
+ name="Add Displace", default=True, description="Add Displace Modifier")
+ bool_flip : bpy.props.BoolProperty(
+ name="Flip", default=False, description="Flip Output Weight")
+
+ weight_mode : bpy.props.EnumProperty(
+ items=[('Remapped', 'Remapped', 'Remap values'),
+ ('Alternate', 'Alternate', 'Alternate 0 and 1'),
+ ('Original', 'Original', 'Keep original Vertex Group')],
+ name="Weight", description="Choose how to convert vertex group",
+ default="Remapped", options={'LIBRARY_EDITABLE'})
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=350)
+
+ def execute(self, context):
+ start_time = timeit.default_timer()
+ try:
+ check = bpy.context.object.vertex_groups[0]
+ except:
+ self.report({'ERROR'}, "The object doesn't have Vertex Groups")
+ return {'CANCELLED'}
+
+ ob0 = bpy.context.object
+
+ group_id = ob0.vertex_groups.active_index
+ vertex_group_name = ob0.vertex_groups[group_id].name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if self.use_modifiers:
+ #me0 = ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ me0 = simple_to_mesh(ob0)
+ else:
+ me0 = ob0.data.copy()
+
+ # generate new bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ # store weight values
+ weight = []
+ ob = bpy.data.objects.new("temp", me0)
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ for v in me0.vertices:
+ try:
+ weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
+ except:
+ weight.append(0)
+
+ # define iso values
+ iso_values = []
+ for i_cut in range(self.n_cuts):
+ delta_iso = abs(self.max_iso - self.min_iso)
+ min_iso = min(self.min_iso, self.max_iso)
+ max_iso = max(self.min_iso, self.max_iso)
+ if delta_iso == 0: iso_val = min_iso
+ elif self.n_cuts > 1: iso_val = i_cut/(self.n_cuts-1)*delta_iso + min_iso
+ else: iso_val = (self.max_iso + self.min_iso)/2
+ iso_values.append(iso_val)
+
+ # Start Cuts Iterations
+ filtered_edges = bm.edges
+ for iso_val in iso_values:
+ delete_edges = []
+
+ faces_mask = []
+ for f in bm.faces:
+ w_min = 2
+ w_max = 2
+ for v in f.verts:
+ w = weight[v.index]
+ if w_min == 2:
+ w_max = w_min = w
+ if w > w_max: w_max = w
+ if w < w_min: w_min = w
+ if w_min < iso_val and w_max > iso_val:
+ faces_mask.append(f)
+ break
+
+ #link_faces = [[f for f in e.link_faces] for e in bm.edges]
+
+ #faces_todo = [f.select for f in bm.faces]
+ #faces_todo = [True for f in bm.faces]
+ verts = []
+ edges = []
+ edges_id = {}
+ _filtered_edges = []
+ n_verts = len(bm.verts)
+ count = n_verts
+ for e in filtered_edges:
+ #id0 = e.vertices[0]
+ #id1 = e.vertices[1]
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ w0 = weight[id0]
+ w1 = weight[id1]
+
+ if w0 == w1: continue
+ elif w0 > iso_val and w1 > iso_val:
+ _filtered_edges.append(e)
+ continue
+ elif w0 < iso_val and w1 < iso_val: continue
+ elif w0 == iso_val or w1 == iso_val:
+ _filtered_edges.append(e)
+ continue
+ else:
+ v0 = bm.verts[id0].co
+ v1 = bm.verts[id1].co
+ v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
+ if e not in delete_edges:
+ delete_edges.append(e)
+ verts.append(v)
+ edges_id[str(id0)+"_"+str(id1)] = count
+ edges_id[str(id1)+"_"+str(id0)] = count
+ count += 1
+ _filtered_edges.append(e)
+ filtered_edges = _filtered_edges
+ splitted_faces = []
+
+ switch = False
+ # splitting faces
+ for f in faces_mask:
+ # create sub-faces slots. Once a new vertex is reached it will
+ # change slot, storing the next vertices for a new face.
+ build_faces = [[],[]]
+ #switch = False
+ verts0 = [v.index for v in f.verts]
+ verts1 = list(verts0)
+ verts1.append(verts1.pop(0)) # shift list
+ for id0, id1 in zip(verts0, verts1):
+
+ # add first vertex to active slot
+ build_faces[switch].append(id0)
+
+ # try to split edge
+ try:
+ # check if the edge must be splitted
+ new_vert = edges_id[str(id0)+"_"+str(id1)]
+ # add new vertex
+ build_faces[switch].append(new_vert)
+ # if there is an open face on the other slot
+ if len(build_faces[not switch]) > 0:
+ # store actual face
+ splitted_faces.append(build_faces[switch])
+ # reset actual faces and switch
+ build_faces[switch] = []
+ # change face slot
+ switch = not switch
+ # continue previous face
+ build_faces[switch].append(new_vert)
+ except: pass
+ if len(build_faces[not switch]) == 2:
+ build_faces[not switch].append(id0)
+ if len(build_faces[not switch]) > 2:
+ splitted_faces.append(build_faces[not switch])
+ # add last face
+ splitted_faces.append(build_faces[switch])
+ #del_faces.append(f.index)
+
+ # adding new vertices
+ for v in verts: new_vert = bm.verts.new(v)
+ bm.verts.index_update()
+ bm.verts.ensure_lookup_table()
+ # adding new faces
+ missed_faces = []
+ added_faces = []
+ for f in splitted_faces:
+ try:
+ face_verts = [bm.verts[i] for i in f]
+ new_face = bm.faces.new(face_verts)
+ for e in new_face.edges:
+ filtered_edges.append(e)
+ except:
+ missed_faces.append(f)
+
+ bm.faces.ensure_lookup_table()
+ # updating weight values
+ weight = weight + [iso_val]*len(verts)
+
+ # deleting old edges/faces
+ bm.edges.ensure_lookup_table()
+ for e in delete_edges:
+ bm.edges.remove(e)
+ _filtered_edges = []
+ for e in filtered_edges:
+ if e not in delete_edges: _filtered_edges.append(e)
+ filtered_edges = _filtered_edges
+
+ name = ob0.name + '_ContourDisp'
+ me = bpy.data.meshes.new(name)
+ bm.to_mesh(me)
+ ob = bpy.data.objects.new(name, me)
+
+ # Link object to scene and make active
+ scn = bpy.context.scene
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ ob0.select_set(False)
+
+ # generate new vertex group
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ #ob.vertex_groups.new(name=vertex_group_name)
+
+ all_weight = weight + [iso_val]*len(verts)
+ #mult = 1/(1-iso_val)
+ for id in range(len(all_weight)):
+ #if False: w = (all_weight[id]-iso_val)*mult
+ w = all_weight[id]
+ if self.weight_mode == 'Alternate':
+ direction = self.bool_flip
+ for i in range(len(iso_values)-1):
+ val0, val1 = iso_values[i], iso_values[i+1]
+ if val0 < w <= val1:
+ if direction: w1 = (w-val0)/(val1-val0)
+ else: w1 = (val1-w)/(val1-val0)
+ direction = not direction
+ if w < iso_values[0]: w1 = not self.bool_flip
+ if w > iso_values[-1]: w1 = not direction
+ elif self.weight_mode == 'Remapped':
+ if w < min_iso: w1 = 0
+ elif w > max_iso: w1 = 1
+ else: w1 = (w - min_iso)/delta_iso
+ else:
+ if self.bool_flip: w1 = 1-w
+ else: w1 = w
+ ob.vertex_groups[vertex_group_name].add([id], w1, 'REPLACE')
+
+ ob.vertex_groups.active_index = group_id
+
+ # align new object
+ ob.matrix_world = ob0.matrix_world
+
+ # Displace Modifier
+ if self.bool_displace:
+ ob.modifiers.new(type='DISPLACE', name='Displace')
+ ob.modifiers["Displace"].mid_level = 0
+ ob.modifiers["Displace"].strength = 0.1
+ ob.modifiers['Displace'].vertex_group = vertex_group_name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ print("Contour Displace time: " + str(timeit.default_timer() - start_time) + " sec")
+
+ bpy.data.meshes.remove(me0)
+
+ return {'FINISHED'}
+
+class weight_contour_mask(bpy.types.Operator):
+ bl_idname = "object.weight_contour_mask"
+ bl_label = "Contour Mask"
+ bl_description = ("")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_modifiers : bpy.props.BoolProperty(
+ name="Use Modifiers", default=True,
+ description="Apply all the modifiers")
+ iso : bpy.props.FloatProperty(
+ name="Iso Value", default=0.5, soft_min=0, soft_max=1,
+ description="Threshold value")
+ bool_solidify : bpy.props.BoolProperty(
+ name="Solidify", default=True, description="Add Solidify Modifier")
+ normalize_weight : bpy.props.BoolProperty(
+ name="Normalize Weight", default=True,
+ description="Normalize weight of remaining vertices")
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def execute(self, context):
+ start_time = timeit.default_timer()
+ try:
+ check = bpy.context.object.vertex_groups[0]
+ except:
+ self.report({'ERROR'}, "The object doesn't have Vertex Groups")
+ return {'CANCELLED'}
+
+ ob0 = bpy.context.object
+
+ iso_val = self.iso
+ group_id = ob0.vertex_groups.active_index
+ vertex_group_name = ob0.vertex_groups[group_id].name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if self.use_modifiers:
+ me0 = simple_to_mesh(ob0)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ else:
+ me0 = ob0.data.copy()
+
+ # generate new bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ # store weight values
+ weight = []
+ ob = bpy.data.objects.new("temp", me0)
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ for v in me0.vertices:
+ try:
+ #weight.append(v.groups[vertex_group_name].weight)
+ weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
+ except:
+ weight.append(0)
+
+ faces_mask = []
+ for f in bm.faces:
+ w_min = 2
+ w_max = 2
+ for v in f.verts:
+ w = weight[v.index]
+ if w_min == 2:
+ w_max = w_min = w
+ if w > w_max: w_max = w
+ if w < w_min: w_min = w
+ if w_min < iso_val and w_max > iso_val:
+ faces_mask.append(f)
+ break
+
+ filtered_edges = bm.edges# me0.edges
+ faces_todo = [f.select for f in bm.faces]
+ verts = []
+ edges = []
+ delete_edges = []
+ edges_id = {}
+ _filtered_edges = []
+ n_verts = len(bm.verts)
+ count = n_verts
+ for e in filtered_edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ w0 = weight[id0]
+ w1 = weight[id1]
+
+ if w0 == w1: continue
+ elif w0 > iso_val and w1 > iso_val:
+ continue
+ elif w0 < iso_val and w1 < iso_val: continue
+ elif w0 == iso_val or w1 == iso_val: continue
+ else:
+ v0 = me0.vertices[id0].co
+ v1 = me0.vertices[id1].co
+ v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
+ delete_edges.append(e)
+ verts.append(v)
+ edges_id[str(id0)+"_"+str(id1)] = count
+ edges_id[str(id1)+"_"+str(id0)] = count
+ count += 1
+
+ splitted_faces = []
+
+ switch = False
+ # splitting faces
+ for f in faces_mask:
+ # create sub-faces slots. Once a new vertex is reached it will
+ # change slot, storing the next vertices for a new face.
+ build_faces = [[],[]]
+ #switch = False
+ verts0 = list(me0.polygons[f.index].vertices)
+ verts1 = list(verts0)
+ verts1.append(verts1.pop(0)) # shift list
+ for id0, id1 in zip(verts0, verts1):
+
+ # add first vertex to active slot
+ build_faces[switch].append(id0)
+
+ # try to split edge
+ try:
+ # check if the edge must be splitted
+ new_vert = edges_id[str(id0)+"_"+str(id1)]
+ # add new vertex
+ build_faces[switch].append(new_vert)
+ # if there is an open face on the other slot
+ if len(build_faces[not switch]) > 0:
+ # store actual face
+ splitted_faces.append(build_faces[switch])
+ # reset actual faces and switch
+ build_faces[switch] = []
+ # change face slot
+ switch = not switch
+ # continue previous face
+ build_faces[switch].append(new_vert)
+ except: pass
+ if len(build_faces[not switch]) == 2:
+ build_faces[not switch].append(id0)
+ if len(build_faces[not switch]) > 2:
+ splitted_faces.append(build_faces[not switch])
+ # add last face
+ splitted_faces.append(build_faces[switch])
+
+ # adding new vertices
+ for v in verts: bm.verts.new(v)
+ bm.verts.ensure_lookup_table()
+
+ # deleting old edges/faces
+ bm.edges.ensure_lookup_table()
+ remove_edges = []
+ for e in delete_edges: bm.edges.remove(e)
+
+ bm.verts.ensure_lookup_table()
+ # adding new faces
+ missed_faces = []
+ for f in splitted_faces:
+ try:
+ face_verts = [bm.verts[i] for i in f]
+ bm.faces.new(face_verts)
+ except:
+ missed_faces.append(f)
+
+ # Mask geometry
+ if(True):
+ all_weight = weight + [iso_val+0.0001]*len(verts)
+ weight = []
+ for w, v in zip(all_weight, bm.verts):
+ if w < iso_val: bm.verts.remove(v)
+ else: weight.append(w)
+
+ # Create mesh and object
+ name = ob0.name + '_ContourMask_{:.3f}'.format(iso_val)
+ me = bpy.data.meshes.new(name)
+ bm.to_mesh(me)
+ ob = bpy.data.objects.new(name, me)
+
+ # Link object to scene and make active
+ scn = bpy.context.scene
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ ob0.select_set(False)
+
+ # generate new vertex group
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+
+ if iso_val != 1: mult = 1/(1-iso_val)
+ else: mult = 1
+ for id in range(len(weight)):
+ if self.normalize_weight: w = (weight[id]-iso_val)*mult
+ else: w = weight[id]
+ ob.vertex_groups[vertex_group_name].add([id], w, 'REPLACE')
+ ob.vertex_groups.active_index = group_id
+
+ # align new object
+ ob.matrix_world = ob0.matrix_world
+
+ # Add Solidify
+ if self.bool_solidify and True:
+ ob.modifiers.new(type='SOLIDIFY', name='Solidify')
+ ob.modifiers['Solidify'].thickness = 0.05
+ ob.modifiers['Solidify'].offset = 0
+ ob.modifiers['Solidify'].vertex_group = vertex_group_name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ print("Contour Mask time: " + str(timeit.default_timer() - start_time) + " sec")
+
+ bpy.data.meshes.remove(me0)
+
+ return {'FINISHED'}
+
+
+class weight_contour_mask_wip(bpy.types.Operator):
+ bl_idname = "object.weight_contour_mask"
+ bl_label = "Contour Mask"
+ bl_description = ("")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_modifiers : bpy.props.BoolProperty(
+ name="Use Modifiers", default=True,
+ description="Apply all the modifiers")
+ iso : bpy.props.FloatProperty(
+ name="Iso Value", default=0.5, soft_min=0, soft_max=1,
+ description="Threshold value")
+ bool_solidify : bpy.props.BoolProperty(
+ name="Solidify", default=True, description="Add Solidify Modifier")
+ normalize_weight : bpy.props.BoolProperty(
+ name="Normalize Weight", default=True,
+ description="Normalize weight of remaining vertices")
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def execute(self, context):
+ start_time = timeit.default_timer()
+ try:
+ check = bpy.context.object.vertex_groups[0]
+ except:
+ self.report({'ERROR'}, "The object doesn't have Vertex Groups")
+ return {'CANCELLED'}
+
+ ob0 = bpy.context.object
+
+ iso_val = self.iso
+ group_id = ob0.vertex_groups.active_index
+ vertex_group_name = ob0.vertex_groups[group_id].name
+
+ #bpy.ops.object.mode_set(mode='EDIT')
+ #bpy.ops.mesh.select_all(action='SELECT')
+ #bpy.ops.object.mode_set(mode='OBJECT')
+ if self.use_modifiers:
+ me0 = simple_to_mesh(ob0)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ else:
+ me0 = ob0.data.copy()
+
+ # generate new bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+
+ # store weight values
+ weight = []
+ ob = bpy.data.objects.new("temp", me0)
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ weight = get_weight_numpy(ob.vertex_groups[vertex_group_name], len(me0.vertices))
+
+ me0, bm, weight = contour_bmesh(me0, bm, weight, iso_val)
+
+ # Mask geometry
+ mask = weight >= iso_val
+ weight = weight[mask]
+ mask = np.logical_not(mask)
+ delete_verts = np.array(bm.verts)[mask]
+ #for v in delete_verts: bm.verts.remove(v)
+
+ # Create mesh and object
+ name = ob0.name + '_ContourMask_{:.3f}'.format(iso_val)
+ me = bpy.data.meshes.new(name)
+ bm.to_mesh(me)
+ ob = bpy.data.objects.new(name, me)
+
+ # Link object to scene and make active
+ scn = bpy.context.scene
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ ob0.select_set(False)
+
+ # generate new vertex group
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+
+ if iso_val != 1: mult = 1/(1-iso_val)
+ else: mult = 1
+ for id in range(len(weight)):
+ if self.normalize_weight: w = (weight[id]-iso_val)*mult
+ else: w = weight[id]
+ ob.vertex_groups[vertex_group_name].add([id], w, 'REPLACE')
+ ob.vertex_groups.active_index = group_id
+
+ # align new object
+ ob.matrix_world = ob0.matrix_world
+
+ # Add Solidify
+ if self.bool_solidify and True:
+ ob.modifiers.new(type='SOLIDIFY', name='Solidify')
+ ob.modifiers['Solidify'].thickness = 0.05
+ ob.modifiers['Solidify'].offset = 0
+ ob.modifiers['Solidify'].vertex_group = vertex_group_name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ print("Contour Mask time: " + str(timeit.default_timer() - start_time) + " sec")
+
+ bpy.data.meshes.remove(me0)
+
+ return {'FINISHED'}
+
+
+class weight_contour_curves(bpy.types.Operator):
+ bl_idname = "object.weight_contour_curves"
+ bl_label = "Contour Curves"
+ bl_description = ("")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_modifiers : bpy.props.BoolProperty(
+ name="Use Modifiers", default=True,
+ description="Apply all the modifiers")
+
+ min_iso : bpy.props.FloatProperty(
+ name="Min Value", default=0., soft_min=0, soft_max=1,
+ description="Minimum weight value")
+ max_iso : bpy.props.FloatProperty(
+ name="Max Value", default=1, soft_min=0, soft_max=1,
+ description="Maximum weight value")
+ n_curves : bpy.props.IntProperty(
+ name="Curves", default=3, soft_min=1, soft_max=10,
+ description="Number of Contour Curves")
+
+ min_rad : bpy.props.FloatProperty(
+ name="Min Radius", default=1, soft_min=0, soft_max=1,
+ description="Change radius according to Iso Value")
+ max_rad : bpy.props.FloatProperty(
+ name="Max Radius", default=1, soft_min=0, soft_max=1,
+ description="Change radius according to Iso Value")
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ return len(ob.vertex_groups) > 0 or ob.type == 'CURVE'
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=350)
+
+ def execute(self, context):
+ start_time = timeit.default_timer()
+ try:
+ check = bpy.context.object.vertex_groups[0]
+ except:
+ self.report({'ERROR'}, "The object doesn't have Vertex Groups")
+ return {'CANCELLED'}
+ ob0 = bpy.context.object
+
+ group_id = ob0.vertex_groups.active_index
+ vertex_group_name = ob0.vertex_groups[group_id].name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if self.use_modifiers:
+ me0 = simple_to_mesh(ob0) #ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ else:
+ me0 = ob0.data.copy()
+
+ # generate new bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ # store weight values
+ weight = []
+ ob = bpy.data.objects.new("temp", me0)
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ weight = get_weight_numpy(ob.vertex_groups[vertex_group_name], len(bm.verts))
+
+ #filtered_edges = bm.edges
+ total_verts = np.zeros((0,3))
+ total_segments = []
+ radius = []
+
+ # start iterate contours levels
+ vertices = get_vertices_numpy(me0)
+ filtered_edges = get_edges_id_numpy(me0)
+
+ faces_weight = [np.array([weight[v] for v in p.vertices]) for p in me0.polygons]
+ fw_min = np.array([np.min(fw) for fw in faces_weight])
+ fw_max = np.array([np.max(fw) for fw in faces_weight])
+
+ bm_faces = np.array(bm.faces)
+
+ for c in range(self.n_curves):
+ min_iso = min(self.min_iso, self.max_iso)
+ max_iso = max(self.min_iso, self.max_iso)
+ try:
+ iso_val = c*(max_iso-min_iso)/(self.n_curves-1)+min_iso
+ if iso_val < 0: iso_val = (min_iso + max_iso)/2
+ except:
+ iso_val = (min_iso + max_iso)/2
+
+ # remove passed faces
+ bool_mask = iso_val < fw_max
+ bm_faces = bm_faces[bool_mask]
+ fw_min = fw_min[bool_mask]
+ fw_max = fw_max[bool_mask]
+
+ # mask faces
+ bool_mask = fw_min < iso_val
+ faces_mask = bm_faces[bool_mask]
+
+ n_verts = len(bm.verts)
+ count = len(total_verts)
+
+ # vertices indexes
+ id0 = filtered_edges[:,0]
+ id1 = filtered_edges[:,1]
+ # vertices weight
+ w0 = weight[id0]
+ w1 = weight[id1]
+ # weight condition
+ bool_w0 = w0 < iso_val
+ bool_w1 = w1 < iso_val
+
+ # mask all edges that have one weight value below the iso value
+ mask_new_verts = np.logical_xor(bool_w0, bool_w1)
+
+ id0 = id0[mask_new_verts]
+ id1 = id1[mask_new_verts]
+ # filter arrays
+ v0 = vertices[id0]
+ v1 = vertices[id1]
+ w0 = w0[mask_new_verts]
+ w1 = w1[mask_new_verts]
+ param = np.expand_dims((iso_val-w0)/(w1-w0),axis=1)
+ verts = v0 + (v1-v0)*param
+
+ # indexes of edges with new vertices
+ edges_index = filtered_edges[mask_new_verts][:,2]
+ edges_id = {}
+ for i, id in enumerate(edges_index): edges_id[id] = i+len(total_verts)
+
+ # remove all edges completely below the iso value
+ mask_edges = np.logical_not(np.logical_and(bool_w0, bool_w1))
+ filtered_edges = filtered_edges[mask_edges]
+ if len(verts) == 0: continue
+
+ # finding segments
+ segments = []
+ for f in faces_mask:
+ seg = []
+ for e in f.edges:
+ try:
+ seg.append(edges_id[e.index])
+ if len(seg) == 2:
+ segments.append(seg)
+ seg = []
+ except: pass
+
+
+ #curves_points_indexes = find_curves(segments)
+ total_segments = total_segments + segments
+ total_verts = np.concatenate((total_verts,verts))
+
+ if self.min_rad != self.max_rad:
+ try:
+ iso_rad = c*(self.max_rad-self.min_rad)/(self.n_curves-1)+self.min_rad
+ if iso_rad < 0: iso_rad = (self.min_rad + self.max_rad)/2
+ except:
+ iso_rad = (self.min_rad + self.max_rad)/2
+ radius = radius + [iso_rad]*len(verts)
+ print("Contour Curves, computing time: " + str(timeit.default_timer() - start_time) + " sec")
+ bm = bmesh.new()
+ # adding new vertices
+ for v in total_verts: bm.verts.new(v)
+ bm.verts.ensure_lookup_table()
+
+ # adding new edges
+ for s in total_segments:
+ try:
+ pts = [bm.verts[i] for i in s]
+ bm.edges.new(pts)
+ except: pass
+
+
+ try:
+ name = ob0.name + '_ContourCurves'
+ me = bpy.data.meshes.new(name)
+ bm.to_mesh(me)
+ ob = bpy.data.objects.new(name, me)
+ # Link object to scene and make active
+ scn = bpy.context.scene
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ ob0.select_set(False)
+
+ print("Contour Curves, bmesh time: " + str(timeit.default_timer() - start_time) + " sec")
+ bpy.ops.object.convert(target='CURVE')
+ ob = context.object
+ if not (self.min_rad == 0 and self.max_rad == 0):
+ if self.min_rad != self.max_rad:
+ count = 0
+ for s in ob.data.splines:
+ for p in s.points:
+ p.radius = radius[count]
+ count += 1
+ else:
+ for s in ob.data.splines:
+ for p in s.points:
+ p.radius = self.min_rad
+ ob.data.bevel_depth = 0.01
+ ob.data.fill_mode = 'FULL'
+ ob.data.bevel_resolution = 3
+ except:
+ self.report({'ERROR'}, "There are no values in the chosen range")
+ return {'CANCELLED'}
+
+ # align new object
+ ob.matrix_world = ob0.matrix_world
+ print("Contour Curves time: " + str(timeit.default_timer() - start_time) + " sec")
+
+ bpy.data.meshes.remove(me0)
+ bpy.data.meshes.remove(me)
+
+ return {'FINISHED'}
+
+class tissue_weight_contour_curves_pattern(bpy.types.Operator):
+ bl_idname = "object.tissue_weight_contour_curves_pattern"
+ bl_label = "Contour Curves Pattern"
+ bl_description = ("")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_modifiers : bpy.props.BoolProperty(
+ name="Use Modifiers", default=True,
+ description="Apply all the modifiers")
+
+ min_iso : bpy.props.FloatProperty(
+ name="Min Value", default=0., soft_min=0, soft_max=1,
+ description="Minimum weight value")
+ max_iso : bpy.props.FloatProperty(
+ name="Max Value", default=1, soft_min=0, soft_max=1,
+ description="Maximum weight value")
+ n_curves : bpy.props.IntProperty(
+ name="Curves", default=10, soft_min=1, soft_max=100,
+ description="Number of Contour Curves")
+ min_rad = 1
+ max_rad = 1
+
+ in_displace : bpy.props.FloatProperty(
+ name="In Displace", default=0, soft_min=0, soft_max=10,
+ description="Pattern displace strength")
+ out_displace : bpy.props.FloatProperty(
+ name="Out Displace", default=2, soft_min=0, soft_max=10,
+ description="Pattern displace strength")
+
+ in_steps : bpy.props.IntProperty(
+ name="In Steps", default=1, min=0, soft_max=10,
+ description="Number of layers to move inwards")
+ out_steps : bpy.props.IntProperty(
+ name="Out Steps", default=1, min=0, soft_max=10,
+ description="Number of layers to move outwards")
+ limit_z : bpy.props.BoolProperty(
+ name="Limit Z", default=False,
+ description="Limit Pattern in Z")
+
+ merge : bpy.props.BoolProperty(
+ name="Merge Vertices", default=True,
+ description="Merge points")
+ merge_thres : bpy.props.FloatProperty(
+ name="Merge Threshold", default=0.01, min=0, soft_max=1,
+ description="Minimum Curve Radius")
+
+ bevel_depth : bpy.props.FloatProperty(
+ name="Bevel Depth", default=0, min=0, soft_max=1,
+ description="")
+ remove_open_curves : bpy.props.BoolProperty(
+ name="Remove Open Curves", default=False,
+ description="Remove Open Curves")
+
+ vertex_group_pattern : bpy.props.StringProperty(
+ name="Pattern", default='',
+ description="Vertex Group used for pattern displace")
+
+ object_name : bpy.props.StringProperty(
+ name="Active Object", default='',
+ description="")
+
+ try: vg_name = bpy.context.object.vertex_groups.active.name
+ except: vg_name = ''
+
+ vertex_group_contour : bpy.props.StringProperty(
+ name="Contour", default=vg_name,
+ description="Vertex Group used for contouring")
+ clean_distance : bpy.props.FloatProperty(
+ name="Clean Distance", default=0, min=0, soft_max=10,
+ description="Remove short segments")
+
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ return len(ob.vertex_groups) > 0 or ob.type == 'CURVE'
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=350)
+
+ def draw(self, context):
+ if not context.object.type == 'CURVE':
+ self.object_name = context.object.name
+ ob = bpy.data.objects[self.object_name]
+ if self.vertex_group_contour not in [vg.name for vg in ob.vertex_groups]:
+ self.vertex_group_contour = ob.vertex_groups.active.name
+ layout = self.layout
+ col = layout.column(align=True)
+ col.prop(self, "use_modifiers")
+ col.prop_search(self, 'vertex_group_contour', ob, "vertex_groups", text='Contour')
+ row = col.row(align=True)
+ row.prop(self,'min_iso')
+ row.prop(self,'max_iso')
+ col.prop(self,'n_curves')
+ col.separator()
+ col.prop_search(self, 'vertex_group_pattern', ob, "vertex_groups", text='Pattern')
+ if self.vertex_group_pattern != '':
+ row = col.row(align=True)
+ row.prop(self,'in_steps')
+ row.prop(self,'out_steps')
+ row = col.row(align=True)
+ row.prop(self,'in_displace')
+ row.prop(self,'out_displace')
+ col.prop(self,'limit_z')
+ col.separator()
+ col.label(text='Curves:')
+ col.prop(self,'bevel_depth')
+ col.prop(self,'clean_distance')
+ col.prop(self,'remove_open_curves')
+
+
+ def execute(self, context):
+ start_time = timeit.default_timer()
+ try:
+ check = context.object.vertex_groups[0]
+ except:
+ self.report({'ERROR'}, "The object doesn't have Vertex Groups")
+ return {'CANCELLED'}
+ ob0 = bpy.data.objects[self.object_name]
+
+ dg = bpy.context.evaluated_depsgraph_get()
+ ob = ob0.evaluated_get(dg)
+ me0 = ob.data
+
+ # generate new bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ n_verts = len(bm.verts)
+
+ # store weight values
+ try:
+ weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_contour], len(me0.vertices))
+ except:
+ self.report({'ERROR'}, "Please select a Vertex Group for contouring")
+ return {'CANCELLED'}
+
+ try:
+ pattern_weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_pattern], len(me0.vertices))
+ except:
+ self.report({'WARNING'}, "There is no Vertex Group assigned to the pattern displace")
+ pattern_weight = np.zeros(len(me0.vertices))
+
+ #filtered_edges = bm.edges
+ total_verts = np.zeros((0,3))
+ total_segments = []# np.array([])
+ radius = []
+
+ # start iterate contours levels
+ vertices, normals = get_vertices_and_normals_numpy(me0)
+ filtered_edges = get_edges_id_numpy(me0)
+
+ faces_weight = [np.array([weight[v] for v in p.vertices]) for p in me0.polygons]
+ fw_min = np.array([np.min(fw) for fw in faces_weight])
+ fw_max = np.array([np.max(fw) for fw in faces_weight])
+
+ bm_faces = np.array(bm.faces)
+ #bm_faces = np.array([np.array([e.index for e in f.edges]) for f in bm.faces ])
+ #all_edges_by_faces = []
+ #for f in bm.faces:
+ # all_edges_by_faces += [e.index for e in f.edges]
+ #all_edges_by_faces = np.array(all_edges_by_faces)
+
+ print("Contour Curves, data loaded: " + str(timeit.default_timer() - start_time) + " sec")
+ step_time = timeit.default_timer()
+ for c in range(self.n_curves):
+ min_iso = min(self.min_iso, self.max_iso)
+ max_iso = max(self.min_iso, self.max_iso)
+ try:
+ iso_val = c*(max_iso-min_iso)/(self.n_curves-1)+min_iso
+ if iso_val < 0: iso_val = (min_iso + max_iso)/2
+ except:
+ iso_val = (min_iso + max_iso)/2
+
+ # remove passed faces
+ bool_mask = iso_val < fw_max
+ bm_faces = bm_faces[bool_mask]
+ fw_min = fw_min[bool_mask]
+ fw_max = fw_max[bool_mask]
+
+ # mask faces
+ bool_mask = fw_min < iso_val
+ faces_mask = bm_faces[bool_mask]
+
+ count = len(total_verts)
+
+ new_filtered_edges, edges_index, verts = contour_edges_pattern(self, c, len(total_verts), iso_val, vertices, normals, filtered_edges, weight, pattern_weight)
+ #new_filtered_edges, edges_index, verts = contour_edges_pattern(self.in_steps, self.out_steps, self.in_displace, self.out_displace, self.limit_z, c, iso_val, vertices, normals, filtered_edges, weight, pattern_weight)
+ if verts[0,0] == None: continue
+ else: filtered_edges = new_filtered_edges
+ edges_id = {}
+ for i, id in enumerate(edges_index): edges_id[id] = i + count
+
+ if len(verts) == 0: continue
+
+ # finding segments
+ segments = []
+ for f in faces_mask:
+ seg = []
+ for e in f.edges:
+ try:
+ #seg.append(new_ids[np.where(edges_index == e.index)[0][0]])
+ seg.append(edges_id[e.index])
+ if len(seg) == 2:
+ segments.append(seg)
+ seg = []
+ except: pass
+
+ #segments = np.array([])
+ #keys = np.array(edges_id.keys())
+ #verts_id = np.arange(len(edges_index))+len(total_verts)
+
+ #seg_mask = np.in1d(edges_index, all_edges_by_faces)
+ #segments = verts_id[seg_mask]
+
+ #try: segments = segments.reshape((len(segments)//2,2))
+ #except: continue
+ '''
+ for f in faces_mask:
+ #print(f)
+ #print(edges_index)
+ #print(verts_id)
+ seg_mask = np.in1d(edges_index,f)
+ #if not seg_mask.any(): continue
+ seg = verts_id[seg_mask]
+ seg = seg.reshape((len(seg)//2,2))
+ if len(seg)==0: continue
+ segments = seg if len(segments)==0 else np.concatenate((segments ,seg))
+ '''
+ #if len(segments)>0:
+ # total_segments = segments if len(total_segments)==0 else np.concatenate((total_segments, segments))
+
+
+ total_segments = total_segments + segments
+ total_verts = np.concatenate((total_verts,verts))
+
+ if self.min_rad != self.max_rad:
+ try:
+ iso_rad = c*(self.max_rad-self.min_rad)/(self.n_curves-1)+self.min_rad
+ if iso_rad < 0: iso_rad = (self.min_rad + self.max_rad)/2
+ except:
+ iso_rad = (self.min_rad + self.max_rad)/2
+ radius = radius + [iso_rad]*len(verts)
+ print("Contour Curves, points computing: " + str(timeit.default_timer() - step_time) + " sec")
+ step_time = timeit.default_timer()
+ if len(total_segments) > 0:
+ step_time = timeit.default_timer()
+ ordered_points = find_curves(total_segments, len(total_verts))
+ print("Contour Curves, point ordered in: " + str(timeit.default_timer() - step_time) + " sec")
+ step_time = timeit.default_timer()
+ crv = curve_from_pydata(total_verts,ordered_points,ob0.name + '_ContourCurves', self.remove_open_curves, merge_distance=self.clean_distance)
+ context.view_layer.objects.active = crv
+ crv.data.bevel_depth = self.bevel_depth
+
+ crv.select_set(True)
+ ob0.select_set(False)
+ crv.matrix_world = ob0.matrix_world
+ print("Contour Curves, curves created in: " + str(timeit.default_timer() - step_time) + " sec")
+ else:
+ self.report({'ERROR'}, "There are no values in the chosen range")
+ return {'CANCELLED'}
+ print("Contour Curves, total time: " + str(timeit.default_timer() - start_time) + " sec")
+ return {'FINISHED'}
+
+class vertex_colors_to_vertex_groups(bpy.types.Operator):
+ bl_idname = "object.vertex_colors_to_vertex_groups"
+ bl_label = "Vertex Color"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Convert the active Vertex Color into a Vertex Group.")
+
+ red : bpy.props.BoolProperty(
+ name="red channel", default=False, description="convert red channel")
+ green : bpy.props.BoolProperty(
+ name="green channel", default=False,
+ description="convert green channel")
+ blue : bpy.props.BoolProperty(
+ name="blue channel", default=False, description="convert blue channel")
+ value : bpy.props.BoolProperty(
+ name="value channel", default=True, description="convert value channel")
+ invert : bpy.props.BoolProperty(
+ name="invert", default=False, description="invert all color channels")
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.data.vertex_colors) > 0
+
+ def execute(self, context):
+ obj = bpy.context.active_object
+ id = len(obj.vertex_groups)
+ id_red = id
+ id_green = id
+ id_blue = id
+ id_value = id
+
+ boolCol = len(obj.data.vertex_colors)
+ if(boolCol): col_name = obj.data.vertex_colors.active.name
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+
+ if(self.red and boolCol):
+ bpy.ops.object.vertex_group_add()
+ bpy.ops.object.vertex_group_assign()
+ id_red = id
+ obj.vertex_groups[id_red].name = col_name + '_red'
+ id+=1
+ if(self.green and boolCol):
+ bpy.ops.object.vertex_group_add()
+ bpy.ops.object.vertex_group_assign()
+ id_green = id
+ obj.vertex_groups[id_green].name = col_name + '_green'
+ id+=1
+ if(self.blue and boolCol):
+ bpy.ops.object.vertex_group_add()
+ bpy.ops.object.vertex_group_assign()
+ id_blue = id
+ obj.vertex_groups[id_blue].name = col_name + '_blue'
+ id+=1
+ if(self.value and boolCol):
+ bpy.ops.object.vertex_group_add()
+ bpy.ops.object.vertex_group_assign()
+ id_value = id
+ obj.vertex_groups[id_value].name = col_name + '_value'
+ id+=1
+
+ mult = 1
+ if(self.invert): mult = -1
+ bpy.ops.object.mode_set(mode='OBJECT')
+ sub_red = 1 + self.value + self.blue + self.green
+ sub_green = 1 + self.value + self.blue
+ sub_blue = 1 + self.value
+ sub_value = 1
+
+ id = len(obj.vertex_groups)
+ if(id_red <= id and id_green <= id and id_blue <= id and id_value <= \
+ id and boolCol):
+ v_colors = obj.data.vertex_colors.active.data
+ i = 0
+ for f in obj.data.polygons:
+ for v in f.vertices:
+ gr = obj.data.vertices[v].groups
+ if(self.red): gr[min(len(gr)-sub_red, id_red)].weight = \
+ self.invert + mult * v_colors[i].color[0]
+ if(self.green): gr[min(len(gr)-sub_green, id_green)].weight\
+ = self.invert + mult * v_colors[i].color[1]
+ if(self.blue): gr[min(len(gr)-sub_blue, id_blue)].weight = \
+ self.invert + mult * v_colors[i].color[2]
+ if(self.value):
+ r = v_colors[i].color[0]
+ g = v_colors[i].color[1]
+ b = v_colors[i].color[2]
+ gr[min(len(gr)-sub_value, id_value)].weight\
+ = self.invert + mult * (0.2126*r + 0.7152*g + 0.0722*b)
+ i+=1
+ bpy.ops.paint.weight_paint_toggle()
+ return {'FINISHED'}
+
+class vertex_group_to_vertex_colors(bpy.types.Operator):
+ bl_idname = "object.vertex_group_to_vertex_colors"
+ bl_label = "Vertex Group"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Convert the active Vertex Group into a Vertex Color.")
+
+ channel : bpy.props.EnumProperty(
+ items=[('Blue', 'Blue Channel', 'Convert to Blue Channel'),
+ ('Green', 'Green Channel', 'Convert to Green Channel'),
+ ('Red', 'Red Channel', 'Convert to Red Channel'),
+ ('Value', 'Value Channel', 'Convert to Grayscale'),
+ ('False Colors', 'False Colors', 'Convert to False Colors')],
+ name="Convert to", description="Choose how to convert vertex group",
+ default="Value", options={'LIBRARY_EDITABLE'})
+
+ invert : bpy.props.BoolProperty(
+ name="invert", default=False, description="invert color channel")
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def execute(self, context):
+ obj = bpy.context.active_object
+ group_id = obj.vertex_groups.active_index
+ if (group_id == -1):
+ return {'FINISHED'}
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ group_name = obj.vertex_groups[group_id].name
+ bpy.ops.mesh.vertex_color_add()
+ colors_id = obj.data.vertex_colors.active_index
+
+ colors_name = group_name
+ if(self.channel == 'False Colors'): colors_name += "_false_colors"
+ elif(self.channel == 'Value'): colors_name += "_value"
+ elif(self.channel == 'Red'): colors_name += "_red"
+ elif(self.channel == 'Green'): colors_name += "_green"
+ elif(self.channel == 'Blue'): colors_name += "_blue"
+ bpy.context.object.data.vertex_colors[colors_id].name = colors_name
+
+ v_colors = obj.data.vertex_colors.active.data
+
+ mult = 1
+ if(self.invert): mult = -1
+
+ i = 0
+ for f in obj.data.polygons:
+ for v in f.vertices:
+ gr = obj.data.vertices[v].groups
+
+ if(self.channel == 'False Colors'): v_colors[i].color = (0,0,0.5,1)
+ else: v_colors[i].color = (0,0,0,1)
+
+ for g in gr:
+ if g.group == group_id:
+ w = g.weight
+ if(self.channel == 'False Colors'):
+ mult = 0.6+0.4*w
+ if w < 0.25:
+ v_colors[i].color = (0, w*4*mult, 1*mult,1)
+ elif w < 0.5:
+ v_colors[i].color = (0, 1*mult, (1-(w-0.25)*4)*mult,1)
+ elif w < 0.75:
+ v_colors[i].color = ((w-0.5)*4*mult,1*mult,0,1)
+ else:
+ v_colors[i].color = (1*mult,(1-(w-0.75)*4)*mult,0,1)
+ elif(self.channel == 'Value'):
+ v_colors[i].color = (
+ self.invert + mult * w,
+ self.invert + mult * w,
+ self.invert + mult * w,
+ 1)
+ elif(self.channel == 'Red'):
+ v_colors[i].color = (
+ self.invert + mult * w,0,0,1)
+ elif(self.channel == 'Green'):
+ v_colors[i].color = (
+ 0, self.invert + mult * w,0,1)
+ elif(self.channel == 'Blue'):
+ v_colors[i].color = (
+ 0,0, self.invert + mult * w,1)
+ i+=1
+ bpy.ops.paint.vertex_paint_toggle()
+ bpy.context.object.data.vertex_colors[colors_id].active_render = True
+ return {'FINISHED'}
+
+class curvature_to_vertex_groups(bpy.types.Operator):
+ bl_idname = "object.curvature_to_vertex_groups"
+ bl_label = "Curvature"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Generate a Vertex Group based on the curvature of the"
+ "mesh. Is based on Dirty Vertex Color.")
+
+ invert : bpy.props.BoolProperty(
+ name="invert", default=False, description="invert values")
+
+ blur_strength : bpy.props.FloatProperty(
+ name="Blur Strength", default=1, min=0.001,
+ max=1, description="Blur strength per iteration")
+
+ blur_iterations : bpy.props.IntProperty(
+ name="Blur Iterations", default=1, min=0,
+ max=40, description="Number of times to blur the values")
+
+ min_angle : bpy.props.FloatProperty(
+ name="Min Angle", default=0, min=0,
+ max=pi/2, subtype='ANGLE', description="Minimum angle")
+
+ max_angle : bpy.props.FloatProperty(
+ name="Max Angle", default=pi, min=pi/2,
+ max=pi, subtype='ANGLE', description="Maximum angle")
+
+ invert : bpy.props.BoolProperty(
+ name="Invert", default=False,
+ description="Invert the curvature map")
+
+ def execute(self, context):
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.mesh.vertex_color_add()
+ vertex_colors = bpy.context.active_object.data.vertex_colors
+ vertex_colors[-1].active = True
+ vertex_colors[-1].active_render = True
+ vertex_colors[-1].name = "Curvature"
+ for c in vertex_colors[-1].data: c.color = (1,1,1,1)
+ bpy.ops.object.mode_set(mode='VERTEX_PAINT')
+ bpy.ops.paint.vertex_color_dirt(
+ blur_strength=self.blur_strength,
+ blur_iterations=self.blur_iterations, clean_angle=self.max_angle,
+ dirt_angle=self.min_angle)
+ bpy.ops.object.vertex_colors_to_vertex_groups(invert=self.invert)
+ bpy.ops.mesh.vertex_color_remove()
+ return {'FINISHED'}
+
+
+class face_area_to_vertex_groups(bpy.types.Operator):
+ bl_idname = "object.face_area_to_vertex_groups"
+ bl_label = "Area"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Generate a Vertex Group based on the area of individual"
+ "faces.")
+
+ invert : bpy.props.BoolProperty(
+ name="invert", default=False, description="invert values")
+ bounds : bpy.props.EnumProperty(
+ items=(('MANUAL', "Manual Bounds", ""),
+ ('AUTOMATIC', "Automatic Bounds", "")),
+ default='AUTOMATIC', name="Bounds")
+
+ min_area : bpy.props.FloatProperty(
+ name="Min", default=0.01, soft_min=0, soft_max=1,
+ description="Faces with 0 weight")
+
+ max_area : bpy.props.FloatProperty(
+ name="Max", default=0.1, soft_min=0, soft_max=1,
+ description="Faces with 1 weight")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label(text="Bounds")
+ layout.prop(self, "bounds", text="")
+ if self.bounds == 'MANUAL':
+ layout.prop(self, "min_area")
+ layout.prop(self, "max_area")
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+ ob.vertex_groups.new(name="Faces Area")
+
+ areas = [[] for v in ob.data.vertices]
+
+ for p in ob.data.polygons:
+ for v in p.vertices:
+ areas[v].append(p.area)
+
+ for i in range(len(areas)):
+ areas[i] = mean(areas[i])
+ if self.bounds == 'MANUAL':
+ min_area = self.min_area
+ max_area = self.max_area
+ elif self.bounds == 'AUTOMATIC':
+ min_area = min(areas)
+ max_area = max(areas)
+ elif self.bounds == 'COMPRESSION':
+ min_area = 1
+ max_area = min(areas)
+ elif self.bounds == 'TENSION':
+ min_area = 1
+ max_area = max(areas)
+ delta_area = max_area - min_area
+ if delta_area == 0:
+ delta_area = 0.0001
+ if self.bounds == 'MANUAL':
+ delta_area = 0.0001
+ else:
+ self.report({'ERROR'}, "The faces have the same areas")
+ #return {'CANCELLED'}
+ for i in range(len(areas)):
+ weight = (areas[i] - min_area)/delta_area
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+
+class harmonic_weight(bpy.types.Operator):
+ bl_idname = "object.harmonic_weight"
+ bl_label = "Harmonic"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Create an harmonic variation of the active Vertex Group")
+
+ freq : bpy.props.FloatProperty(
+ name="Frequency", default=20, soft_min=0,
+ soft_max=100, description="Wave frequency")
+
+ amp : bpy.props.FloatProperty(
+ name="Amplitude", default=1, soft_min=0,
+ soft_max=10, description="Wave amplitude")
+
+ midlevel : bpy.props.FloatProperty(
+ name="Midlevel", default=0, min=-1,
+ max=1, description="Midlevel")
+
+ add : bpy.props.FloatProperty(
+ name="Add", default=0, min=-1,
+ max=1, description="Add to the Weight")
+
+ mult : bpy.props.FloatProperty(
+ name="Multiply", default=0, min=0,
+ max=1, description="Multiply for he Weight")
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def execute(self, context):
+ ob = bpy.context.active_object
+ if len(ob.vertex_groups) > 0:
+ group_id = ob.vertex_groups.active_index
+ ob.vertex_groups.new(name="Harmonic")
+ for i in range(len(ob.data.vertices)):
+ try: val = ob.vertex_groups[group_id].weight(i)
+ except: val = 0
+ weight = self.amp*(sin(val*self.freq) - self.midlevel)/2 + 0.5 + self.add*val*(1-(1-val)*self.mult)
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ ob.data.update()
+ else:
+ self.report({'ERROR'}, "Active object doesn't have vertex groups")
+ return {'CANCELLED'}
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+
+class tissue_weight_distance(bpy.types.Operator):
+ bl_idname = "object.tissue_weight_distance"
+ bl_label = "Weight Distance"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Create a weight map according to the distance from the "
+ "selected vertices along the mesh surface")
+
+ def fill_neighbors(self,verts,weight):
+ neigh = {}
+ for v0 in verts:
+ for f in v0.link_faces:
+ for v1 in f.verts:
+ dist = weight[v0.index] + (v0.co-v1.co).length
+ w1 = weight[v1.index]
+ if w1 == None or w1 > dist:
+ weight[v1.index] = dist
+ neigh[v1] = 0
+ if len(neigh) == 0: return weight
+ else: return self.fill_neighbors(neigh.keys(), weight)
+
+ def execute(self, context):
+ ob = context.object
+ old_mode = ob.mode
+ if old_mode != 'OBJECT':
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ # store weight values
+ weight = [None]*len(bm.verts)
+
+ selected = [v for v in bm.verts if v.select]
+ if len(selected) == 0:
+ bpy.ops.object.mode_set(mode=old_mode)
+ message = "Please, select one or more vertices"
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+ for v in selected: weight[v.index] = 0
+ weight = self.fill_neighbors(selected, weight)
+
+ weight = np.array(weight)
+ max_dist = np.max(weight)
+ if max_dist > 0:
+ weight /= max_dist
+
+ vg = ob.vertex_groups.new(name='Distance: {:.4f}'.format(max_dist))
+ for i, w in enumerate(weight):
+ if w == None: continue
+ vg.add([i], w, 'REPLACE')
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+
+
+class TISSUE_PT_color(bpy.types.Panel):
+ bl_label = "Tissue Tools"
+ bl_category = "Tissue"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ #bl_options = {'DEFAULT_CLOSED'}
+ bl_context = "vertexpaint"
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.operator("object.vertex_colors_to_vertex_groups",
+ icon="GROUP_VERTEX", text="Convert to Weight")
+
+class TISSUE_PT_weight(bpy.types.Panel):
+ bl_label = "Tissue Tools"
+ bl_category = "Tissue"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ #bl_options = {'DEFAULT_CLOSED'}
+ bl_context = "weightpaint"
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ #if context.object.type == 'MESH' and context.mode == 'OBJECT':
+ #col.label(text="Transform:")
+ #col.separator()
+ #elif bpy.context.mode == 'PAINT_WEIGHT':
+ col.label(text="Weight Generate:")
+ #col.operator(
+ # "object.vertex_colors_to_vertex_groups", icon="GROUP_VCOL")
+ col.operator("object.face_area_to_vertex_groups", icon="FACESEL")
+ col.operator("object.curvature_to_vertex_groups", icon="SMOOTHCURVE")
+ col.operator("object.tissue_weight_distance", icon="TRACKING")
+ try: col.operator("object.weight_formula", icon="CON_TRANSFORM")
+ except: col.operator("object.weight_formula")#, icon="CON_TRANSFORM")
+ #col.label(text="Weight Processing:")
+ col.separator()
+
+ # TO BE FIXED
+ #col.operator("object.weight_laplacian", icon="SMOOTHCURVE")
+
+ col.operator("object.harmonic_weight", icon="IPO_ELASTIC")
+ col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VCOL",
+ text="Convert to Colors")
+ col.separator()
+ col.label(text="Deformation Analysis:")
+ col.operator("object.edges_deformation", icon="DRIVER_DISTANCE")#FULLSCREEN_ENTER")
+ col.operator("object.edges_bending", icon="DRIVER_ROTATIONAL_DIFFERENCE")#"MOD_SIMPLEDEFORM")
+ col.separator()
+ col.label(text="Weight Contour:")
+ col.operator("object.weight_contour_curves", icon="MOD_CURVE")
+ col.operator("object.tissue_weight_contour_curves_pattern", icon="FORCE_TURBULENCE")
+ col.operator("object.weight_contour_displace", icon="MOD_DISPLACE")
+ col.operator("object.weight_contour_mask", icon="MOD_MASK")
+ col.separator()
+ col.label(text="Simulations:")
+ #col.operator("object.reaction_diffusion", icon="MOD_OCEAN")
+ col.operator("object.start_reaction_diffusion",
+ icon="EXPERIMENTAL",
+ text="Reaction-Diffusion")
+
+ #col.prop(context.object, "reaction_diffusion_run", icon="PLAY", text="Run Simulation")
+ ####col.prop(context.object, "reaction_diffusion_run")
+ #col.separator()
+ #col.label(text="Vertex Color from:")
+ #col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VERTEX")
+
+
+
+
+class start_reaction_diffusion(bpy.types.Operator):
+ bl_idname = "object.start_reaction_diffusion"
+ bl_label = "Start Reaction Diffusion"
+ bl_description = ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ run : bpy.props.BoolProperty(
+ name="Run Reaction-Diffusion", default=True, description="Compute a new iteration on frame changes")
+
+ time_steps : bpy.props.IntProperty(
+ name="Steps", default=10, min=0, soft_max=50,
+ description="Number of Steps")
+
+ dt : bpy.props.FloatProperty(
+ name="dt", default=1, min=0, soft_max=0.2,
+ description="Time Step")
+
+ diff_a : bpy.props.FloatProperty(
+ name="Diff A", default=0.18, min=0, soft_max=2,
+ description="Diffusion A")
+
+ diff_b : bpy.props.FloatProperty(
+ name="Diff B", default=0.09, min=0, soft_max=2,
+ description="Diffusion B")
+
+ f : bpy.props.FloatProperty(
+ name="f", default=0.055, min=0, soft_max=0.5, precision=4,
+ description="Feed Rate")
+
+ k : bpy.props.FloatProperty(
+ name="k", default=0.062, min=0, soft_max=0.5, precision=4,
+ description="Kill Rate")
+
+ @classmethod
+ def poll(cls, context):
+ return context.object.type == 'MESH'
+
+ def execute(self, context):
+ reaction_diffusion_add_handler(self, context)
+ set_animatable_fix_handler(self, context)
+
+ ob = context.object
+
+ ob.reaction_diffusion_settings.run = self.run
+ ob.reaction_diffusion_settings.dt = self.dt
+ ob.reaction_diffusion_settings.time_steps = self.time_steps
+ ob.reaction_diffusion_settings.f = self.f
+ ob.reaction_diffusion_settings.k = self.k
+ ob.reaction_diffusion_settings.diff_a = self.diff_a
+ ob.reaction_diffusion_settings.diff_b = self.diff_b
+
+
+ # check vertex group A
+ try:
+ vg = ob.vertex_groups['A']
+ except:
+ ob.vertex_groups.new(name='A')
+ # check vertex group B
+ try:
+ vg = ob.vertex_groups['B']
+ except:
+ ob.vertex_groups.new(name='B')
+
+ for v in ob.data.vertices:
+ ob.vertex_groups['A'].add([v.index], 1, 'REPLACE')
+ ob.vertex_groups['B'].add([v.index], 0, 'REPLACE')
+
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+
+ return {'FINISHED'}
+
+class reset_reaction_diffusion_weight(bpy.types.Operator):
+ bl_idname = "object.reset_reaction_diffusion_weight"
+ bl_label = "Reset Reaction Diffusion Weight"
+ bl_description = ("Set A and B weight to default values")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.object.type == 'MESH'
+
+ def execute(self, context):
+ reaction_diffusion_add_handler(self, context)
+ set_animatable_fix_handler(self, context)
+
+ ob = context.object
+
+ # check vertex group A
+ try:
+ vg = ob.vertex_groups['A']
+ except:
+ ob.vertex_groups.new(name='A')
+ # check vertex group B
+ try:
+ vg = ob.vertex_groups['B']
+ except:
+ ob.vertex_groups.new(name='B')
+
+ for v in ob.data.vertices:
+ ob.vertex_groups['A'].add([v.index], 1, 'REPLACE')
+ ob.vertex_groups['B'].add([v.index], 0, 'REPLACE')
+
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+
+ return {'FINISHED'}
+
+from bpy.app.handlers import persistent
+
+@persistent
+def reaction_diffusion_def_blur(scene):
+ for ob in scene.objects:
+ if ob.reaction_diffusion_settings.run:
+ #try:
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ a = []
+ b = []
+ for v in me.vertices:
+ try:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ except:
+ a.append(0)
+ try:
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ except:
+ b.append(0)
+
+ a = array(a)
+ b = array(b)
+ props = ob.reaction_diffusion_settings
+ dt = props.dt
+ time_steps = props.time_steps
+ f = props.f
+ k = props.k
+ diff_a = props.diff_a * props.diff_mult
+ diff_b = props.diff_b * props.diff_mult
+
+ n_verts = len(bm.verts)
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ #ob.data.use_paint_mask_vertex = True
+
+ for i in range(time_steps):
+ ab2 = a*b**2
+ ob.vertex_groups.active = ob.vertex_groups['A']
+ bpy.ops.object.vertex_group_smooth(group_select_mode='ACTIVE', factor=diff_a)
+ ob.vertex_groups.active = ob.vertex_groups['B']
+ bpy.ops.object.vertex_group_smooth(group_select_mode='ACTIVE', factor=diff_b)
+
+ a = []
+ b = []
+ for v in me.vertices:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ a = array(a)
+ b = array(b)
+
+ a += - (ab2 + f*(1-a))*dt
+ b += (ab2 - (k+f)*b)*dt
+
+ a = nan_to_num(a)
+ b = nan_to_num(b)
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+ #bpy.ops.object.mode_set(mode='EDIT')
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT
+ #bpy.ops.paint.weight_paint_toggle()
+ #bpy.ops.paint.weight_paint_toggle()
+
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ #except:
+ # pass
+
+def reaction_diffusion_def_(scene):
+ for ob in scene.objects:
+ if ob.reaction_diffusion_settings.run:
+ #try:
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ a = []
+ b = []
+ for v in me.vertices:
+ try:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ except:
+ a.append(0)
+ try:
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ except:
+ b.append(0)
+
+ a = array(a)
+ b = array(b)
+ props = ob.reaction_diffusion_settings
+ dt = props.dt
+ time_steps = props.time_steps
+ f = props.f
+ k = props.k
+ diff_a = props.diff_a * props.diff_mult
+ diff_b = props.diff_b * props.diff_mult
+
+ n_verts = len(bm.verts)
+ for i in range(time_steps):
+ lap_a = zeros((n_verts))#[0]*n_verts
+ lap_b = zeros((n_verts))#[0]*n_verts
+ if i == 0:
+ lap_map = [[] for i in range(n_verts)]
+ lap_mult = []
+ for e in bm.edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap_map[id0].append(id1)
+ lap_map[id1].append(id0)
+ for id in range(n_verts):
+ lap_mult.append(len(lap_map[id]))
+ lap_mult = array(lap_mult)
+ lap_map = array(lap_map)
+ for id in range(n_verts):
+ map = lap_map[id]
+ lap_a[id] = a[lap_map[id]].sum()
+ lap_b[id] = b[lap_map[id]].sum()
+ lap_a -= a*lap_mult
+ lap_b -= b*lap_mult
+ ab2 = a*b**2
+
+ a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+
+ a = nan_to_num(a)
+ b = nan_to_num(b)
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+ #bpy.ops.object.mode_set(mode='EDIT')
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ bpy.ops.paint.weight_paint_toggle()
+ bpy.ops.paint.weight_paint_toggle()
+
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ #except:
+ # pass
+
+def reaction_diffusion_def(scene):
+ for ob in scene.objects:
+ if ob.reaction_diffusion_settings.run:
+
+ start = time.time()
+
+ me = ob.data
+ n_edges = len(me.edges)
+ n_verts = len(me.vertices)
+
+ # store weight values
+ a = np.zeros(n_verts)
+ b = np.zeros(n_verts)
+ #a = thread_read_weight(a, ob.vertex_groups["A"])
+ #b = thread_read_weight(b, ob.vertex_groups["B"])
+ #a = read_weight(a, ob.vertex_groups["A"])
+ #b = read_weight(b, ob.vertex_groups["B"])
+
+ for i in range(n_verts):
+ try: a[i] = ob.vertex_groups["A"].weight(i)
+ except: pass
+ try: b[i] = ob.vertex_groups["B"].weight(i)
+ except: pass
+
+ props = ob.reaction_diffusion_settings
+ dt = props.dt
+ time_steps = props.time_steps
+ f = props.f
+ k = props.k
+ diff_a = props.diff_a * props.diff_mult
+ diff_b = props.diff_b * props.diff_mult
+
+ edge_verts = [0]*n_edges*2
+ me.edges.foreach_get("vertices", edge_verts)
+
+ timeElapsed = time.time() - start
+ print('RD - Preparation Time:',timeElapsed)
+ start = time.time()
+
+ try:
+ edge_verts = np.array(edge_verts)
+ a, b = numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, diff_a, diff_b, f, k, dt, time_steps)
+ a = nan_to_num(a)
+ b = nan_to_num(b)
+ except:
+ edge_verts = np.array(edge_verts)
+ arr = np.arange(n_edges)*2
+ id0 = edge_verts[arr] # first vertex indices for each edge
+ id1 = edge_verts[arr+1] # second vertex indices for each edge
+ for i in range(time_steps):
+ lap_a = np.zeros(n_verts)
+ lap_b = np.zeros(n_verts)
+ lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
+ lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
+
+ for i, j, la0, lb0 in np.nditer([id0,id1,lap_a0,lap_b0]):
+ lap_a[i] += la0
+ lap_b[i] += lb0
+ lap_a[j] -= la0
+ lap_b[j] -= lb0
+ ab2 = a*b**2
+ a += eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
+ b += eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
+ #a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ #b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+
+ a = nan_to_num(a)
+ b = nan_to_num(b)
+
+ timeElapsed = time.time() - start
+ print('RD - Simulation Time:',timeElapsed)
+ start = time.time()
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+
+ for ps in ob.particle_systems:
+ if ps.vertex_group_density == 'B' or ps.vertex_group_density == 'A':
+ ps.invert_vertex_group_density = not ps.invert_vertex_group_density
+ ps.invert_vertex_group_density = not ps.invert_vertex_group_density
+
+ timeElapsed = time.time() - start
+ print('RD - Closing Time:',timeElapsed)
+
+class TISSUE_PT_reaction_diffusion(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_label = "Tissue - Reaction-Diffusion"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ return 'A' and 'B' in context.object.vertex_groups
+
+ def draw(self, context):
+ reaction_diffusion_add_handler(self, context)
+
+ ob = context.object
+ props = ob.reaction_diffusion_settings
+ layout = self.layout
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ if not ("A" and "B" in ob.vertex_groups):
+ row.operator("object.start_reaction_diffusion",
+ icon="EXPERIMENTAL",
+ text="Reaction-Diffusion")
+ else:
+ row.operator("object.start_reaction_diffusion",
+ icon="EXPERIMENTAL",
+ text="Reset Reaction-Diffusion")
+ row = col.row(align=True)
+ row.prop(props, "run", text="Run Reaction-Diffusion")
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.prop(props, "time_steps")
+ row.prop(props, "dt")
+ col.separator()
+ row = col.row(align=True)
+ row.prop(props, "diff_a")
+ row.prop(props, "diff_b")
+ row = col.row(align=True)
+ row.prop(props, "diff_mult")
+ #col.separator()
+ row = col.row(align=True)
+ row.prop(props, "f")
+ row.prop(props, "k")
+
+if False:
+ @jit(["float64[:,:](int32, int32, float64, float64, boolean, int32, float64, float64[:,:], float64[:,:], int32[:,:], float64[:], float64[:])"]) #(nopython=True, parallel=True)
+ def contour_edges_pattern(in_steps, out_steps, in_displace, out_displace, limit_z, c, iso_val, vertices, normals, filtered_edges, weight, pattern_weight):
+ # vertices indexes
+ id0 = filtered_edges[:,0]
+ id1 = filtered_edges[:,1]
+ # vertices weight
+ w0 = weight[id0]
+ w1 = weight[id1]
+ # weight condition
+ bool_w0 = w0 < iso_val
+ bool_w1 = w1 < iso_val
+
+ # mask all edges that have one weight value below the iso value
+ mask_new_verts = np.logical_xor(bool_w0, bool_w1)
+ out_array = np.array([[0]]).astype(type='float64', order='A')
+ if not mask_new_verts.any(): return out_array, out_array, out_array
+
+ id0 = id0[mask_new_verts]
+ id1 = id1[mask_new_verts]
+ # filter arrays
+ v0 = vertices[id0]
+ v1 = vertices[id1]
+ n0 = normals[id0]
+ n1 = normals[id1]
+ w0 = w0[mask_new_verts]
+ w1 = w1[mask_new_verts]
+ pattern0 = pattern_weight[id0]
+ pattern1 = pattern_weight[id1]
+ param = (iso_val-w0)/(w1-w0)
+ # pattern displace
+ #mult = 1 if c%2 == 0 else -1
+ if c%(in_steps + out_steps) < in_steps:
+ mult = -in_displace
+ else:
+ mult = out_displace
+ pattern_value = pattern0 + (pattern1-pattern0)*param
+ disp = pattern_value * mult
+ param2 = np.expand_dims(param,axis=1)
+ #param = param2
+ disp2 = np.expand_dims(disp,axis=1)
+ #disp = disp2
+ verts = v0 + (v1-v0)*param2
+ norm = n0 + (n1-n0)*param2
+ if limit_z:
+ norm2 = np.expand_dims(norm[:,2], axis=1)
+ limit_mult = 1-np.absolute(norm2)
+ disp2 *= limit_mult
+ verts = verts + norm*disp2
+
+ # indexes of edges with new vertices
+ edges_index = filtered_edges[mask_new_verts][:,2]
+
+ # remove all edges completely below the iso value
+ mask_edges = np.logical_not(np.logical_and(bool_w0, bool_w1))
+
+ _filtered_edges = filtered_edges[mask_edges]
+ filtered_edges = _filtered_edges.astype(type='float64', order='A')
+
+ _edges_index = np.expand_dims(edges_index,axis=0)
+ _edges_index = _edges_index.astype(type='float64', order='A')
+
+ _verts = verts.astype(type='float64', order='A')
+ return _filtered_edges, _edges_index, _verts
+
+def contour_edges_pattern(operator, c, verts_count, iso_val, vertices, normals, filtered_edges, weight, pattern_weight):
+ # vertices indexes
+ id0 = filtered_edges[:,0]
+ id1 = filtered_edges[:,1]
+ # vertices weight
+ w0 = weight[id0]
+ w1 = weight[id1]
+ # weight condition
+ bool_w0 = w0 < iso_val
+ bool_w1 = w1 < iso_val
+
+ # mask all edges that have one weight value below the iso value
+ mask_new_verts = np.logical_xor(bool_w0, bool_w1)
+ if not mask_new_verts.any(): return np.array([[None]]), {}, np.array([[None]])
+
+ id0 = id0[mask_new_verts]
+ id1 = id1[mask_new_verts]
+ # filter arrays
+ v0 = vertices[id0]
+ v1 = vertices[id1]
+ n0 = normals[id0]
+ n1 = normals[id1]
+ w0 = w0[mask_new_verts]
+ w1 = w1[mask_new_verts]
+ pattern0 = pattern_weight[id0]
+ pattern1 = pattern_weight[id1]
+ param = (iso_val-w0)/(w1-w0)
+ # pattern displace
+ #mult = 1 if c%2 == 0 else -1
+ if c%(operator.in_steps + operator.out_steps) < operator.in_steps:
+ mult = -operator.in_displace
+ else:
+ mult = operator.out_displace
+ pattern_value = pattern0 + (pattern1-pattern0)*param
+ disp = pattern_value * mult
+ param = np.expand_dims(param,axis=1)
+ disp = np.expand_dims(disp,axis=1)
+ verts = v0 + (v1-v0)*param
+ norm = n0 + (n1-n0)*param
+ if operator.limit_z: disp *= 1-abs(np.expand_dims(norm[:,2], axis=1))
+ verts = verts + norm*disp
+
+ # indexes of edges with new vertices
+ edges_index = filtered_edges[mask_new_verts][:,2]
+
+ # remove all edges completely below the iso value
+ #mask_edges = np.logical_not(np.logical_and(bool_w0, bool_w1))
+ #filtered_edges = filtered_edges[mask_edges]
+ return filtered_edges, edges_index, verts
+
+def contour_edges_pattern_eval(operator, c, verts_count, iso_val, vertices, normals, filtered_edges, weight, pattern_weight):
+ # vertices indexes
+ id0 = eval('filtered_edges[:,0]')
+ id1 = eval('filtered_edges[:,1]')
+ # vertices weight
+ w0 = eval('weight[id0]')
+ w1 = eval('weight[id1]')
+ # weight condition
+ bool_w0 = ne.evaluate('w0 < iso_val')
+ bool_w1 = ne.evaluate('w1 < iso_val')
+
+ # mask all edges that have one weight value below the iso value
+ mask_new_verts = eval('np.logical_xor(bool_w0, bool_w1)')
+ if not mask_new_verts.any(): return np.array([[None]]), {}, np.array([[None]])
+
+ id0 = eval('id0[mask_new_verts]')
+ id1 = eval('id1[mask_new_verts]')
+ # filter arrays
+ v0 = eval('vertices[id0]')
+ v1 = eval('vertices[id1]')
+ n0 = eval('normals[id0]')
+ n1 = eval('normals[id1]')
+ w0 = eval('w0[mask_new_verts]')
+ w1 = eval('w1[mask_new_verts]')
+ pattern0 = eval('pattern_weight[id0]')
+ pattern1 = eval('pattern_weight[id1]')
+ param = ne.evaluate('(iso_val-w0)/(w1-w0)')
+ # pattern displace
+ #mult = 1 if c%2 == 0 else -1
+ if c%(operator.in_steps + operator.out_steps) < operator.in_steps:
+ mult = -operator.in_displace
+ else:
+ mult = operator.out_displace
+ pattern_value = eval('pattern0 + (pattern1-pattern0)*param')
+ disp = ne.evaluate('pattern_value * mult')
+ param = eval('np.expand_dims(param,axis=1)')
+ disp = eval('np.expand_dims(disp,axis=1)')
+ verts = ne.evaluate('v0 + (v1-v0)*param')
+ norm = ne.evaluate('n0 + (n1-n0)*param')
+ if operator.limit_z:
+ mult = eval('1-abs(np.expand_dims(norm[:,2], axis=1))')
+ disp = ne.evaluate('disp * mult')
+ verts = ne.evaluate('verts + norm*disp')
+
+ # indexes of edges with new vertices
+ edges_index = eval('filtered_edges[mask_new_verts][:,2]')
+
+ # remove all edges completely below the iso value
+ mask_edges = eval('np.logical_not(np.logical_and(bool_w0, bool_w1))')
+ filtered_edges = eval('filtered_edges[mask_edges]')
+ return filtered_edges, edges_index, verts
+
+
+def contour_bmesh(me, bm, weight, iso_val):
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ # store weight values
+
+ vertices = get_vertices_numpy(me)
+ faces_mask = np.array(bm.faces)
+ filtered_edges = get_edges_id_numpy(me)
+ n_verts = len(bm.verts)
+
+ #############################
+
+ # vertices indexes
+ id0 = filtered_edges[:,0]
+ id1 = filtered_edges[:,1]
+ # vertices weight
+ w0 = weight[id0]
+ w1 = weight[id1]
+ # weight condition
+ bool_w0 = w0 < iso_val
+ bool_w1 = w1 < iso_val
+
+ # mask all edges that have one weight value below the iso value
+ mask_new_verts = np.logical_xor(bool_w0, bool_w1)
+ if not mask_new_verts.any(): return np.array([[None]]), {}, np.array([[None]])
+
+ id0 = id0[mask_new_verts]
+ id1 = id1[mask_new_verts]
+ # filter arrays
+ v0 = vertices[id0]
+ v1 = vertices[id1]
+ w0 = w0[mask_new_verts]
+ w1 = w1[mask_new_verts]
+ param = (iso_val-w0)/(w1-w0)
+ param = np.expand_dims(param,axis=1)
+ verts = v0 + (v1-v0)*param
+
+ # indexes of edges with new vertices
+ #edges_index = filtered_edges[mask_new_verts][:,2]
+
+ edges_id = {}
+ for i, e in enumerate(filtered_edges):
+ #edges_id[id] = i + n_verts
+ edges_id['{}_{}'.format(e[0],e[1])] = i + n_verts
+ edges_id['{}_{}'.format(e[1],e[0])] = i + n_verts
+
+
+ '''
+ for e in filtered_edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ w0 = weight[id0]
+ w1 = weight[id1]
+
+ if w0 == w1: continue
+ elif w0 > iso_val and w1 > iso_val:
+ continue
+ elif w0 < iso_val and w1 < iso_val: continue
+ elif w0 == iso_val or w1 == iso_val: continue
+ else:
+ v0 = me0.vertices[id0].co
+ v1 = me0.vertices[id1].co
+ v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
+ delete_edges.append(e)
+ verts.append(v)
+ edges_id[str(id0)+"_"+str(id1)] = count
+ edges_id[str(id1)+"_"+str(id0)] = count
+ count += 1
+ '''
+
+ splitted_faces = []
+
+ switch = False
+ # splitting faces
+ for f in faces_mask:
+ # create sub-faces slots. Once a new vertex is reached it will
+ # change slot, storing the next vertices for a new face.
+ build_faces = [[],[]]
+ #switch = False
+ verts0 = list(me.polygons[f.index].vertices)
+ verts1 = list(verts0)
+ verts1.append(verts1.pop(0)) # shift list
+ for id0, id1 in zip(verts0, verts1):
+
+ # add first vertex to active slot
+ build_faces[switch].append(id0)
+
+ # try to split edge
+ try:
+ # check if the edge must be splitted
+ new_vert = edges_id['{}_{}'.format(id0,id1)]
+ # add new vertex
+ build_faces[switch].append(new_vert)
+ # if there is an open face on the other slot
+ if len(build_faces[not switch]) > 0:
+ # store actual face
+ splitted_faces.append(build_faces[switch])
+ # reset actual faces and switch
+ build_faces[switch] = []
+ # change face slot
+ switch = not switch
+ # continue previous face
+ build_faces[switch].append(new_vert)
+ except: pass
+ if len(build_faces[not switch]) == 2:
+ build_faces[not switch].append(id0)
+ if len(build_faces[not switch]) > 2:
+ splitted_faces.append(build_faces[not switch])
+ # add last face
+ splitted_faces.append(build_faces[switch])
+
+ # adding new vertices
+ for v in verts: bm.verts.new(v)
+ bm.verts.ensure_lookup_table()
+
+ # deleting old edges/faces
+ bm.edges.ensure_lookup_table()
+ remove_edges = [bm.edges[i] for i in filtered_edges[:,2]]
+ #for e in remove_edges: bm.edges.remove(e)
+ #for e in delete_edges: bm.edges.remove(e)
+
+ bm.verts.ensure_lookup_table()
+ # adding new faces
+ missed_faces = []
+ for f in splitted_faces:
+ try:
+ face_verts = [bm.verts[i] for i in f]
+ bm.faces.new(face_verts)
+ except:
+ missed_faces.append(f)
+
+ #me = bpy.data.meshes.new('_tissue_tmp_')
+ bm.to_mesh(me)
+ weight = np.concatenate((weight, np.ones(len(verts))*iso_val))
+
+ return me, bm, weight
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/DUAL_MESH.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/DUAL_MESH.PY
new file mode 100644
index 00000000..f8ab7fdc
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/DUAL_MESH.PY
@@ -0,0 +1,345 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# --------------------------------- DUAL MESH -------------------------------- #
+# -------------------------------- version 0.3 ------------------------------- #
+# #
+# Convert a generic mesh to its dual. With open meshes it can get some wired #
+# effect on the borders. #
+# #
+# (c) Alessandro Zomparelli #
+# (2017) #
+# #
+# http://www.co-de-it.com/ #
+# #
+# ############################################################################ #
+
+
+import bpy
+from bpy.types import Operator
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+ )
+import bmesh
+from .utils import *
+
+
+class dual_mesh_tessellated(Operator):
+ bl_idname = "object.dual_mesh_tessellated"
+ bl_label = "Dual Mesh"
+ bl_description = ("Generate a polygonal mesh using Tessellate. (Non-destructive)")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ apply_modifiers : BoolProperty(
+ name="Apply Modifiers",
+ default=True,
+ description="Apply object's modifiers"
+ )
+
+ source_faces : EnumProperty(
+ items=[
+ ('QUAD', 'Quad Faces', ''),
+ ('TRI', 'Triangles', '')],
+ name="Source Faces",
+ description="Source polygons",
+ default="QUAD",
+ options={'LIBRARY_EDITABLE'}
+ )
+
+ def execute(self, context):
+ auto_layer_collection()
+ ob0 = context.object
+ name1 = "DualMesh_{}_Component".format(self.source_faces)
+ # Generate component
+ if self.source_faces == 'QUAD':
+ verts = [(0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
+ (0.0, 1.0, 0.0), (0.5, 1.0, 0.0),
+ (1.0, 1.0, 0.0), (1.0, 0.5, 0.0),
+ (1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
+ (1/3, 1/3, 0.0), (2/3, 2/3, 0.0)]
+ edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
+ (7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
+ faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
+ else:
+ verts = [(0.0,0.0,0.0), (0.5,0.0,0.0), (1.0,0.0,0.0), (0.0,1.0,0.0), (0.5,1.0,0.0), (1.0,1.0,0.0)]
+ edges = [(0,1), (1,2), (2,5), (5,4), (4,3), (3,0), (1,4)]
+ faces = [(0,1,4,3), (1,2,5,4)]
+
+ # check pre-existing component
+ try:
+ _verts = [0]*len(verts)*3
+ __verts = [c for co in verts for c in co]
+ ob1 = bpy.data.objects[name1]
+ ob1.data.vertices.foreach_get("co",_verts)
+ for a, b in zip(_verts, __verts):
+ if abs(a-b) > 0.0001:
+ raise ValueError
+ except:
+ me = bpy.data.meshes.new("Dual-Mesh") # add a new mesh
+ me.from_pydata(verts, edges, faces)
+ me.update(calc_edges=True, calc_edges_loose=True)
+ if self.source_faces == 'QUAD': n_seams = 8
+ else: n_seams = 6
+ for i in range(n_seams): me.edges[i].use_seam = True
+ ob1 = bpy.data.objects.new(name1, me)
+ context.collection.objects.link(ob1)
+ # fix visualization issue
+ context.view_layer.objects.active = ob1
+ ob1.select_set(True)
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.object.editmode_toggle()
+ ob1.select_set(False)
+ # hide component
+ ob1.hide_select = True
+ ob1.hide_render = True
+ ob1.hide_viewport = True
+ ob = convert_object_to_mesh(ob0,False,False)
+ ob.name = 'DualMesh'
+ #ob = bpy.data.objects.new("DualMesh", convert_object_to_mesh(ob0,False,False))
+ #context.collection.objects.link(ob)
+ #context.view_layer.objects.active = ob
+ #ob.select_set(True)
+ ob.tissue_tessellate.component = ob1
+ ob.tissue_tessellate.generator = ob0
+ ob.tissue_tessellate.gen_modifiers = self.apply_modifiers
+ ob.tissue_tessellate.merge = True
+ ob.tissue_tessellate.bool_dissolve_seams = True
+ if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'FAN'
+ bpy.ops.object.update_tessellate()
+ ob.location = ob0.location
+ ob.matrix_world = ob0.matrix_world
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self)
+
+class dual_mesh(Operator):
+ bl_idname = "object.dual_mesh"
+ bl_label = "Convert to Dual Mesh"
+ bl_description = ("Convert a generic mesh into a polygonal mesh. (Destructive)")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ quad_method : EnumProperty(
+ items=[('BEAUTY', 'Beauty',
+ 'Split the quads in nice triangles, slower method'),
+ ('FIXED', 'Fixed',
+ 'Split the quads on the 1st and 3rd vertices'),
+ ('FIXED_ALTERNATE', 'Fixed Alternate',
+ 'Split the quads on the 2nd and 4th vertices'),
+ ('SHORTEST_DIAGONAL', 'Shortest Diagonal',
+ 'Split the quads based on the distance between the vertices')
+ ],
+ name="Quad Method",
+ description="Method for splitting the quads into triangles",
+ default="FIXED",
+ options={'LIBRARY_EDITABLE'}
+ )
+ polygon_method : EnumProperty(
+ items=[
+ ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
+ ('CLIP', 'Clip',
+ 'Split the polygons with an ear clipping algorithm')],
+ name="Polygon Method",
+ description="Method for splitting the polygons into triangles",
+ default="BEAUTY",
+ options={'LIBRARY_EDITABLE'}
+ )
+ preserve_borders : BoolProperty(
+ name="Preserve Borders",
+ default=True,
+ description="Preserve original borders"
+ )
+ apply_modifiers : BoolProperty(
+ name="Apply Modifiers",
+ default=True,
+ description="Apply object's modifiers"
+ )
+
+ def execute(self, context):
+ mode = context.mode
+ if mode == 'EDIT_MESH':
+ mode = 'EDIT'
+ act = context.active_object
+ if mode != 'OBJECT':
+ sel = [act]
+ bpy.ops.object.mode_set(mode='OBJECT')
+ else:
+ sel = context.selected_objects
+ doneMeshes = []
+
+ for ob0 in sel:
+ if ob0.type != 'MESH':
+ continue
+ if ob0.data.name in doneMeshes:
+ continue
+ ob = ob0
+ mesh_name = ob0.data.name
+
+ # store linked objects
+ clones = []
+ n_users = ob0.data.users
+ count = 0
+ for o in bpy.data.objects:
+ if o.type != 'MESH':
+ continue
+ if o.data.name == mesh_name:
+ count += 1
+ clones.append(o)
+ if count == n_users:
+ break
+
+ if self.apply_modifiers:
+ bpy.ops.object.convert(target='MESH')
+ ob.data = ob.data.copy()
+ bpy.ops.object.select_all(action='DESELECT')
+ ob.select_set(True)
+ context.view_layer.objects.active = ob0
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # prevent borders erosion
+ 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.extrude_region_move(
+ MESH_OT_extrude_region={"mirror": False},
+ TRANSFORM_OT_translate={"value": (0, 0, 0)}
+ )
+
+ bpy.ops.mesh.select_mode(
+ use_extend=False, use_expand=False, type='VERT',
+ action='TOGGLE'
+ )
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.quads_convert_to_tris(
+ quad_method=self.quad_method, ngon_method=self.polygon_method
+ )
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.modifier_add(type='SUBSURF')
+ ob.modifiers[-1].name = "dual_mesh_subsurf"
+ while True:
+ bpy.ops.object.modifier_move_up(modifier="dual_mesh_subsurf")
+ if ob.modifiers[0].name == "dual_mesh_subsurf":
+ break
+
+ bpy.ops.object.modifier_apply(
+ apply_as='DATA', modifier='dual_mesh_subsurf'
+ )
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='DESELECT')
+
+ verts = ob.data.vertices
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ verts[-1].select = True
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_more(use_face_step=False)
+
+ bpy.ops.mesh.select_similar(
+ type='EDGE', compare='EQUAL', threshold=0.01)
+ bpy.ops.mesh.select_all(action='INVERT')
+
+ bpy.ops.mesh.dissolve_verts()
+ bpy.ops.mesh.select_all(action='DESELECT')
+
+ bpy.ops.mesh.select_non_manifold(
+ extend=False, use_wire=False, use_boundary=True,
+ use_multi_face=False, use_non_contiguous=False, use_verts=False)
+ bpy.ops.mesh.select_more()
+
+ # find boundaries
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bound_v = [v.index for v in ob.data.vertices if v.select]
+ bound_e = [e.index for e in ob.data.edges if e.select]
+ bound_p = [p.index for p in ob.data.polygons if p.select]
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # select quad faces
+ context.tool_settings.mesh_select_mode = (False, False, True)
+ bpy.ops.mesh.select_face_by_sides(number=4, extend=False)
+
+ # deselect boundaries
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for i in bound_v:
+ context.active_object.data.vertices[i].select = False
+ for i in bound_e:
+ context.active_object.data.edges[i].select = False
+ for i in bound_p:
+ context.active_object.data.polygons[i].select = False
+
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ context.tool_settings.mesh_select_mode = (False, False, True)
+ bpy.ops.mesh.edge_face_add()
+ context.tool_settings.mesh_select_mode = (True, False, False)
+ bpy.ops.mesh.select_all(action='DESELECT')
+
+ # delete boundaries
+ bpy.ops.mesh.select_non_manifold(
+ extend=False, use_wire=True, use_boundary=True,
+ use_multi_face=False, use_non_contiguous=False, use_verts=True
+ )
+ bpy.ops.mesh.delete(type='VERT')
+
+ # remove middle vertices
+ bm = bmesh.from_edit_mesh(ob.data)
+ for v in bm.verts:
+ if len(v.link_edges) == 2 and len(v.link_faces) < 3:
+ v.select = True
+
+ # dissolve
+ bpy.ops.mesh.dissolve_verts()
+ bpy.ops.mesh.select_all(action='DESELECT')
+
+ # remove border faces
+ if not self.preserve_borders:
+ 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.select_more()
+ bpy.ops.mesh.delete(type='FACE')
+
+ # clean wires
+ bpy.ops.mesh.select_non_manifold(
+ extend=False, use_wire=True, use_boundary=False,
+ use_multi_face=False, use_non_contiguous=False, use_verts=False
+ )
+ bpy.ops.mesh.delete(type='EDGE')
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ ob0.data.name = mesh_name
+ doneMeshes.append(mesh_name)
+
+ for o in clones:
+ o.data = ob.data
+
+ for o in sel:
+ o.select_set(True)
+
+ context.view_layer.objects.active = act
+ bpy.ops.object.mode_set(mode=mode)
+
+ return {'FINISHED'}
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/GCODE_EXPORT.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/GCODE_EXPORT.PY
new file mode 100644
index 00000000..4e3aa578
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/GCODE_EXPORT.PY
@@ -0,0 +1,488 @@
+import bpy, os
+import numpy as np
+import mathutils
+from mathutils import Vector
+from math import pi
+from bpy.types import (
+ Operator,
+ Panel,
+ PropertyGroup,
+ )
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+ StringProperty,
+ PointerProperty
+ )
+from .utils import *
+
+def change_speed_mode(self, context):
+ props = context.scene.tissue_gcode
+ if props.previous_speed_mode != props.speed_mode:
+ if props.speed_mode == 'SPEED':
+ props.speed = props.feed/60
+ props.speed_vertical = props.feed_vertical/60
+ props.speed_horizontal = props.feed_horizontal/60
+ else:
+ props.feed = props.speed*60
+ props.feed_vertical = props.speed_vertical*60
+ props.feed_horizontal = props.speed_horizontal*60
+ props.previous_speed_mode == props.speed_mode
+ return
+
+class tissue_gcode_prop(PropertyGroup):
+ last_e : FloatProperty(name="Pull", default=5.0, min=0, soft_max=10)
+ path_length : FloatProperty(name="Pull", default=5.0, min=0, soft_max=10)
+
+ folder : StringProperty(
+ name="File", default="", subtype='FILE_PATH',
+ description = 'Destination folder.\nIf missing, the file folder will be used'
+ )
+ pull : FloatProperty(
+ name="Pull", default=5.0, min=0, soft_max=10,
+ description='Pull material before lift'
+ )
+ push : FloatProperty(
+ name="Push", default=5.0, min=0, soft_max=10,
+ description='Push material before start extruding'
+ )
+ dz : FloatProperty(
+ name="dz", default=2.0, min=0, soft_max=20,
+ description='Z movement for lifting the nozzle before travel'
+ )
+ flow_mult : FloatProperty(
+ name="Flow Mult", default=1.0, min=0, soft_max=3,
+ description = 'Flow multiplier.\nUse a single value or a list of values for changing it during the printing path'
+ )
+ feed : IntProperty(
+ name="Feed Rate (F)", default=3600, min=0, soft_max=20000,
+ description='Printing speed'
+ )
+ feed_horizontal : IntProperty(
+ name="Feed Horizontal", default=7200, min=0, soft_max=20000,
+ description='Travel speed'
+ )
+ feed_vertical : IntProperty(
+ name="Feed Vertical", default=3600, min=0, soft_max=20000,
+ description='Lift movements speed'
+ )
+
+ speed : IntProperty(
+ name="Speed", default=60, min=0, soft_max=100,
+ description='Printing speed'
+ )
+ speed_horizontal : IntProperty(
+ name="Travel", default=120, min=0, soft_max=200,
+ description='Travel speed'
+ )
+ speed_vertical : IntProperty(
+ name="Z-Lift", default=60, min=0, soft_max=200,
+ description='Lift movements speed'
+ )
+
+ esteps : FloatProperty(
+ name="E Steps/Unit", default=5, min=0, soft_max=100)
+ start_code : StringProperty(
+ name="Start", default='', description = 'Text block for starting code'
+ )
+ end_code : StringProperty(
+ name="End", default='', description = 'Text block for ending code'
+ )
+ auto_sort_layers : BoolProperty(
+ name="Auto Sort Layers", default=True,
+ description = 'Sort layers according to the Z of the median point'
+ )
+ auto_sort_points : BoolProperty(
+ name="Auto Sort Points", default=False,
+ description = 'Shift layer points trying to automatically reduce needed travel movements'
+ )
+ close_all : BoolProperty(
+ name="Close Shapes", default=False,
+ description = 'Repeat the starting point at the end of the vertices list for each layer'
+ )
+ nozzle : FloatProperty(
+ name="Nozzle", default=0.4, min=0, soft_max=10,
+ description='Nozzle diameter'
+ )
+ layer_height : FloatProperty(
+ name="Layer Height", default=0.1, min=0, soft_max=10,
+ description = 'Average layer height, needed for a correct extrusion'
+ )
+ filament : FloatProperty(
+ name="Filament (\u03A6)", default=1.75, min=0, soft_max=120,
+ description='Filament (or material container) diameter'
+ )
+
+ gcode_mode : EnumProperty(items=[
+ ("CONT", "Continuous", ""),
+ ("RETR", "Retraction", "")
+ ], default='CONT', name="Mode",
+ description = 'If retraction is used, then each separated list of vertices\nwill be considered as a different layer'
+ )
+ speed_mode : EnumProperty(items=[
+ ("SPEED", "Speed (mm/s)", ""),
+ ("FEED", "Feed (mm/min)", "")
+ ], default='SPEED', name="Speed Mode",
+ description = 'Speed control mode',
+ update = change_speed_mode
+ )
+ previous_speed_mode : StringProperty(
+ name="previous_speed_mode", default='', description = ''
+ )
+ retraction_mode : EnumProperty(items=[
+ ("FIRMWARE", "Firmware", ""),
+ ("GCODE", "Gcode", "")
+ ], default='GCODE', name="Retraction Mode",
+ description = 'If firmware retraction is used, then the retraction parameters will be controlled by the printer'
+ )
+ animate : BoolProperty(
+ name="Animate", default=False,
+ description = 'Show print progression according to current frame'
+ )
+
+
+class TISSUE_PT_gcode_exporter(Panel):
+ bl_category = "Tissue Gcode"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ #bl_space_type = 'PROPERTIES'
+ #bl_region_type = 'WINDOW'
+ #bl_context = "data"
+ bl_label = "Tissue Gcode Export"
+ #bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try: return context.object.type in ('CURVE','MESH')
+ except: return False
+
+ def draw(self, context):
+ props = context.scene.tissue_gcode
+
+ #addon = context.user_preferences.addons.get(sverchok.__name__)
+ #over_sized_buttons = addon.preferences.over_sized_buttons
+ layout = self.layout
+ col = layout.column(align=True)
+ row = col.row()
+ row.prop(props, 'folder', toggle=True, text='')
+ col = layout.column(align=True)
+ row = col.row()
+ row.prop(props, 'gcode_mode', expand=True, toggle=True)
+ #col = layout.column(align=True)
+ col = layout.column(align=True)
+ col.label(text="Extrusion:", icon='MOD_FLUIDSIM')
+ #col.prop(self, 'esteps')
+ col.prop(props, 'filament')
+ col.prop(props, 'nozzle')
+ col.prop(props, 'layer_height')
+ col.separator()
+ col.label(text="Speed (Feed Rate F):", icon='DRIVER')
+ col.prop(props, 'speed_mode', text='')
+ speed_prefix = 'feed' if props.speed_mode == 'FEED' else 'speed'
+ col.prop(props, speed_prefix, text='Print')
+ if props.gcode_mode == 'RETR':
+ col.prop(props, speed_prefix + '_vertical', text='Z Lift')
+ col.prop(props, speed_prefix + '_horizontal', text='Travel')
+ col.separator()
+ if props.gcode_mode == 'RETR':
+ col = layout.column(align=True)
+ col.label(text="Retraction Mode:", icon='NOCURVE')
+ row = col.row()
+ row.prop(props, 'retraction_mode', expand=True, toggle=True)
+ if props.retraction_mode == 'GCODE':
+ col.separator()
+ col.label(text="Retraction:", icon='PREFERENCES')
+ col.prop(props, 'pull', text='Retraction')
+ col.prop(props, 'dz', text='Z Hop')
+ col.prop(props, 'push', text='Preload')
+ col.separator()
+ #col.label(text="Layers options:", icon='ALIGN_JUSTIFY')
+ col.separator()
+ col.prop(props, 'auto_sort_layers', text="Sort Layers (Z)")
+ col.prop(props, 'auto_sort_points', text="Sort Points (XY)")
+ #col.prop(props, 'close_all')
+ col.separator()
+ col.label(text='Custom Code:', icon='TEXT')
+ col.prop_search(props, 'start_code', bpy.data, 'texts')
+ col.prop_search(props, 'end_code', bpy.data, 'texts')
+ col.separator()
+ row = col.row(align=True)
+ row.scale_y = 2.0
+ row.operator('scene.tissue_gcode_export')
+ #col.separator()
+ #col.prop(props, 'animate', icon='TIME')
+
+
+class tissue_gcode_export(Operator):
+ bl_idname = "scene.tissue_gcode_export"
+ bl_label = "Export Gcode"
+ bl_description = ("Export selected curve object as Gcode file")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ return context.object.type in ('CURVE', 'MESH')
+ except:
+ return False
+
+ def execute(self, context):
+ scene = context.scene
+ props = scene.tissue_gcode
+ # manage data
+ if props.speed_mode == 'SPEED':
+ props.feed = props.speed*60
+ props.feed_vertical = props.speed_vertical*60
+ props.feed_horizontal = props.speed_horizontal*60
+ feed = props.feed
+ feed_v = props.feed_vertical
+ feed_h = props.feed_horizontal
+ layer = props.layer_height
+ flow_mult = props.flow_mult
+ #if context.object.type != 'CURVE':
+ # self.report({'ERROR'}, 'Please select a Curve object')
+ # return {'CANCELLED'}
+ ob = context.object
+ matr = ob.matrix_world
+ if ob.type == 'MESH':
+ dg = context.evaluated_depsgraph_get()
+ mesh = ob.evaluated_get(dg).data
+ edges = [list(e.vertices) for e in mesh.edges]
+ verts = [v.co for v in mesh.vertices]
+ ordered_verts = find_curves(edges, len(mesh.vertices))
+ ob = curve_from_pydata(verts, ordered_verts, name='__temp_curve__', merge_distance=0.1, set_active=False)
+
+ vertices = [[matr @ p.co.xyz for p in s.points] for s in ob.data.splines]
+ cyclic_u = [s.use_cyclic_u for s in ob.data.splines]
+
+ if ob.name == '__temp_curve__': bpy.data.objects.remove(ob)
+
+ if len(vertices) == 1: props.gcode_mode = 'CONT'
+ export = True
+
+ # open file
+ if(export):
+ if props.folder == '':
+ folder = '//' + os.path.splitext(bpy.path.basename(bpy.context.blend_data.filepath))[0]
+ else:
+ folder = props.folder
+ if '.gcode' not in folder: folder += '.gcode'
+ path = bpy.path.abspath(folder)
+ file = open(path, 'w')
+ try:
+ for line in bpy.data.texts[props.start_code].lines:
+ file.write(line.body + '\n')
+ except:
+ pass
+
+ #if props.gcode_mode == 'RETR':
+
+ # sort layers (Z)
+ if props.auto_sort_layers:
+ sorted_verts = []
+ for curve in vertices:
+ # mean z
+ listz = [v[2] for v in curve]
+ meanz = np.mean(listz)
+ # store curve and meanz
+ sorted_verts.append((curve, meanz))
+ vertices = [data[0] for data in sorted(sorted_verts, key=lambda height: height[1])]
+
+ # sort vertices (XY)
+ if props.auto_sort_points:
+ # curves median point
+ median_points = [np.mean(verts,axis=0) for verts in vertices]
+
+ # chose starting point for each curve
+ for j, curve in enumerate(vertices):
+ # for closed curves finds the best starting point
+ if cyclic_u[j]:
+ # create kd tree
+ kd = mathutils.kdtree.KDTree(len(curve))
+ for i, v in enumerate(curve):
+ kd.insert(v, i)
+ kd.balance()
+
+ if props.gcode_mode == 'RETR':
+ if j==0:
+ # close to next two curves median point
+ co_find = np.mean(median_points[j+1:j+3],axis=0)
+ elif j < len(vertices)-1:
+ co_find = np.mean([median_points[j-1],median_points[j+1]],axis=0)
+ else:
+ co_find = np.mean(median_points[j-2:j],axis=0)
+ #flow_mult[j] = flow_mult[j][index:]+flow_mult[j][:index]
+ #layer[j] = layer[j][index:]+layer[j][:index]
+ else:
+ if j==0:
+ # close to next two curves median point
+ co_find = np.mean(median_points[j+1:j+3],axis=0)
+ else:
+ co_find = vertices[j-1][-1]
+ co, index, dist = kd.find(co_find)
+ vertices[j] = vertices[j][index:]+vertices[j][:index+1]
+ else:
+ if j > 0:
+ p0 = curve[0]
+ p1 = curve[-1]
+ last = vertices[j-1][-1]
+ d0 = (last-p0).length
+ d1 = (last-p1).length
+ if d1 < d0: vertices[j].reverse()
+
+
+
+ '''
+ # close shapes
+ if props.close_all:
+ for i in range(len(vertices)):
+ vertices[i].append(vertices[i][0])
+ #flow_mult[i].append(flow_mult[i][0])
+ #layer[i].append(layer[i][0])
+ '''
+ # calc bounding box
+ min_corner = np.min(vertices[0],axis=0)
+ max_corner = np.max(vertices[0],axis=0)
+ for i in range(1,len(vertices)):
+ eval_points = vertices[i] + [min_corner]
+ min_corner = np.min(eval_points,axis=0)
+ eval_points = vertices[i] + [max_corner]
+ max_corner = np.max(eval_points,axis=0)
+
+ # initialize variables
+ e = 0
+ last_vert = Vector((0,0,0))
+ maxz = 0
+ path_length = 0
+ travel_length = 0
+
+ printed_verts = []
+ printed_edges = []
+ travel_verts = []
+ travel_edges = []
+
+ # write movements
+ for i in range(len(vertices)):
+ curve = vertices[i]
+ first_id = len(printed_verts)
+ for j in range(len(curve)):
+ v = curve[j]
+ v_flow_mult = flow_mult#[i][j]
+ v_layer = layer#[i][j]
+
+ # record max z
+ maxz = np.max((maxz,v[2]))
+ #maxz = max(maxz,v[2])
+
+ # first point of the gcode
+ if i == j == 0:
+ printed_verts.append(v)
+ if(export):
+ file.write('G92 E0 \n')
+ params = v[:3] + (feed,)
+ to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params)
+ file.write(to_write)
+ else:
+ # start after retraction
+ if j == 0 and props.gcode_mode == 'RETR':
+ if(export):
+ params = v[:2] + (maxz+props.dz,) + (feed_h,)
+ to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params)
+ file.write(to_write)
+ params = v[:3] + (feed_v,)
+ to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params)
+ file.write(to_write)
+ to_write = 'G1 F{:.0f}\n'.format(feed)
+ file.write(to_write)
+ if props.retraction_mode == 'GCODE':
+ e += props.push
+ file.write( 'G1 E' + format(e, '.4f') + '\n')
+ else:
+ file.write('G11\n')
+ printed_verts.append((v[0], v[1], maxz+props.dz))
+ travel_edges.append((len(printed_verts)-1, len(printed_verts)-2))
+ travel_length += (Vector(printed_verts[-1])-Vector(printed_verts[-2])).length
+ printed_verts.append(v)
+ travel_edges.append((len(printed_verts)-1, len(printed_verts)-2))
+ travel_length += maxz+props.dz - v[2]
+ # regular extrusion
+ else:
+ printed_verts.append(v)
+ v1 = Vector(v)
+ v0 = Vector(curve[j-1])
+ dist = (v1-v0).length
+ area = v_layer * props.nozzle + pi*(v_layer/2)**2 # rectangle + circle
+ cylinder = pi*(props.filament/2)**2
+ flow = area / cylinder * (0 if j == 0 else 1)
+ e += dist * v_flow_mult * flow
+ params = v[:3] + (e,)
+ if(export):
+ to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} E{3:.4f}\n'.format(*params)
+ file.write(to_write)
+ path_length += dist
+ printed_edges.append([len(printed_verts)-1, len(printed_verts)-2])
+ if props.gcode_mode == 'RETR':
+ v0 = Vector(curve[-1])
+ if props.close_all and False:
+ #printed_verts.append(v0)
+ printed_edges.append([len(printed_verts)-1, first_id])
+
+ v1 = Vector(curve[0])
+ dist = (v0-v1).length
+ area = v_layer * props.nozzle + pi*(v_layer/2)**2 # rectangle + circle
+ cylinder = pi*(props.filament/2)**2
+ flow = area / cylinder
+ e += dist * v_flow_mult * flow
+ params = v1[:3] + (e,)
+ if(export):
+ to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} E{3:.4f}\n'.format(*params)
+ file.write(to_write)
+ path_length += dist
+ v0 = v1
+ if i < len(vertices)-1:
+ if(export):
+ if props.retraction_mode == 'GCODE':
+ e -= props.pull
+ file.write('G0 E' + format(e, '.4f') + '\n')
+ else:
+ file.write('G10\n')
+ params = v0[:2] + (maxz+props.dz,) + (feed_v,)
+ to_write = 'G1 X{0:.4f} Y{1:.4f} Z{2:.4f} F{3:.0f}\n'.format(*params)
+ file.write(to_write)
+ printed_verts.append(v0.to_tuple())
+ printed_verts.append((v0.x, v0.y, maxz+props.dz))
+ travel_edges.append((len(printed_verts)-1, len(printed_verts)-2))
+ travel_length += maxz+props.dz - v0.z
+ if(export):
+ # end code
+ try:
+ for line in bpy.data.texts[props.end_code].lines:
+ file.write(line.body + '\n')
+ except:
+ pass
+ file.close()
+ print("Saved gcode to " + path)
+ bb = list(min_corner) + list(max_corner)
+ info = 'Bounding Box:\n'
+ info += '\tmin\tX: {0:.1f}\tY: {1:.1f}\tZ: {2:.1f}\n'.format(*bb)
+ info += '\tmax\tX: {3:.1f}\tY: {4:.1f}\tZ: {5:.1f}\n'.format(*bb)
+ info += 'Extruded Filament: ' + format(e, '.2f') + '\n'
+ info += 'Extruded Volume: ' + format(e*pi*(props.filament/2)**2, '.2f') + '\n'
+ info += 'Printed Path Length: ' + format(path_length, '.2f') + '\n'
+ info += 'Travel Length: ' + format(travel_length, '.2f')
+ '''
+ # animate
+ if scene.animate:
+ scene = bpy.context.scene
+ try:
+ param = (scene.frame_current - scene.frame_start)/(scene.frame_end - scene.frame_start)
+ except:
+ param = 1
+ last_vert = max(int(param*len(printed_verts)),1)
+ printed_verts = printed_verts[:last_vert]
+ printed_edges = [e for e in printed_edges if e[0] < last_vert and e[1] < last_vert]
+ travel_edges = [e for e in travel_edges if e[0] < last_vert and e[1] < last_vert]
+ '''
+ return {'FINISHED'}
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/LATTICE.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/LATTICE.PY
new file mode 100644
index 00000000..4e398332
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/LATTICE.PY
@@ -0,0 +1,477 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+# --------------------------- LATTICE ALONG SURFACE -------------------------- #
+# -------------------------------- version 0.3 ------------------------------- #
+# #
+# Automatically generate and assign a lattice that follows the active surface. #
+# #
+# (c) Alessandro Zomparelli #
+# (2017) #
+# #
+# http://www.co-de-it.com/ #
+# #
+# ############################################################################ #
+
+import bpy
+import bmesh
+from bpy.types import Operator
+from bpy.props import (BoolProperty, StringProperty, FloatProperty)
+from mathutils import Vector
+
+from .utils import *
+
+
+def not_in(element, grid):
+ output = True
+ for loop in grid:
+ if element in loop:
+ output = False
+ break
+ return output
+
+
+def grid_from_mesh(mesh, swap_uv):
+ bm = bmesh.new()
+ bm.from_mesh(mesh)
+ verts_grid = []
+ edges_grid = []
+ faces_grid = []
+
+ running_grid = True
+ while running_grid:
+ verts_loop = []
+ edges_loop = []
+ faces_loop = []
+
+ # storing first point
+ verts_candidates = []
+ if len(faces_grid) == 0:
+ # for first loop check all vertices
+ verts_candidates = bm.verts
+ else:
+ # for other loops start form the vertices of the first face
+ # the last loop, skipping already used vertices
+ verts_candidates = [v for v in bm.faces[faces_grid[-1][0]].verts if not_in(v.index, verts_grid)]
+
+ # check for last loop
+ is_last = False
+ for vert in verts_candidates:
+ if len(vert.link_faces) == 1: # check if corner vertex
+ vert.select = True
+ verts_loop.append(vert.index)
+ is_last = True
+ break
+
+ if not is_last:
+ for vert in verts_candidates:
+ new_link_faces = [f for f in vert.link_faces if not_in(f.index, faces_grid)]
+ if len(new_link_faces) < 2: # check if corner vertex
+ vert.select = True
+ verts_loop.append(vert.index)
+ break
+
+ running_loop = len(verts_loop) > 0
+
+ while running_loop:
+ bm.verts.ensure_lookup_table()
+ id = verts_loop[-1]
+ link_edges = bm.verts[id].link_edges
+ # storing second point
+ if len(verts_loop) == 1: # only one vertex stored in the loop
+ if len(faces_grid) == 0: # first loop #
+ edge = link_edges[swap_uv] # chose direction
+ for vert in edge.verts:
+ if vert.index != id:
+ vert.select = True
+ verts_loop.append(vert.index) # new vertex
+ edges_loop.append(edge.index) # chosen edge
+ faces_loop.append(edge.link_faces[0].index) # only one face
+ # edge.link_faces[0].select = True
+ else: # other loops #
+ # start from the edges of the first face of the last loop
+ for edge in bm.faces[faces_grid[-1][0]].edges:
+ # chose an edge starting from the first vertex that is not returning back
+ if bm.verts[verts_loop[0]] in edge.verts and \
+ bm.verts[verts_grid[-1][0]] not in edge.verts:
+ for vert in edge.verts:
+ if vert.index != id:
+ vert.select = True
+ verts_loop.append(vert.index)
+ edges_loop.append(edge.index)
+
+ for face in edge.link_faces:
+ if not_in(face.index, faces_grid):
+ faces_loop.append(face.index)
+ # continuing the loop
+ else:
+ for edge in link_edges:
+ for vert in edge.verts:
+ store_data = False
+ if not_in(vert.index, verts_grid) and vert.index not in verts_loop:
+ if len(faces_loop) > 0:
+ bm.faces.ensure_lookup_table()
+ if vert not in bm.faces[faces_loop[-1]].verts:
+ store_data = True
+ else:
+ store_data = True
+ if store_data:
+ vert.select = True
+ verts_loop.append(vert.index)
+ edges_loop.append(edge.index)
+ for face in edge.link_faces:
+ if not_in(face.index, faces_grid):
+ faces_loop.append(face.index)
+ break
+ # ending condition
+ if verts_loop[-1] == id or verts_loop[-1] == verts_loop[0]:
+ running_loop = False
+
+ verts_grid.append(verts_loop)
+ edges_grid.append(edges_loop)
+ faces_grid.append(faces_loop)
+
+ if len(faces_loop) == 0:
+ running_grid = False
+
+ return verts_grid, edges_grid, faces_grid
+
+
+class lattice_along_surface(Operator):
+ bl_idname = "object.lattice_along_surface"
+ bl_label = "Lattice along Surface"
+ bl_description = ("Automatically add a Lattice modifier to the selected "
+ "object, adapting it to the active one.\nThe active "
+ "object must be a rectangular grid compatible with the "
+ "Lattice's topology")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ set_parent : BoolProperty(
+ name="Set Parent",
+ default=True,
+ description="Automatically set the Lattice as parent"
+ )
+ flipNormals : BoolProperty(
+ name="Flip Normals",
+ default=False,
+ description="Flip normals direction"
+ )
+ swapUV : BoolProperty(
+ name="Swap UV",
+ default=False,
+ description="Flip grid's U and V"
+ )
+ flipU : BoolProperty(
+ name="Flip U",
+ default=False,
+ description="Flip grid's U")
+
+ flipV : BoolProperty(
+ name="Flip V",
+ default=False,
+ description="Flip grid's V"
+ )
+ flipW : BoolProperty(
+ name="Flip W",
+ default=False,
+ description="Flip grid's W"
+ )
+ use_groups : BoolProperty(
+ name="Vertex Group",
+ default=False,
+ description="Use active Vertex Group for lattice's thickness"
+ )
+ high_quality_lattice : BoolProperty(
+ name="High quality",
+ default=True,
+ description="Increase the the subdivisions in normal direction for a "
+ "more correct result"
+ )
+ hide_lattice : BoolProperty(
+ name="Hide Lattice",
+ default=True,
+ description="Automatically hide the Lattice object"
+ )
+ scale_x : FloatProperty(
+ name="Scale X",
+ default=1,
+ min=0.001,
+ max=1,
+ description="Object scale"
+ )
+ scale_y : FloatProperty(
+ name="Scale Y", default=1,
+ min=0.001,
+ max=1,
+ description="Object scale"
+ )
+ scale_z : FloatProperty(
+ name="Scale Z",
+ default=1,
+ min=0.001,
+ max=1,
+ description="Object scale"
+ )
+ thickness : FloatProperty(
+ name="Thickness",
+ default=1,
+ soft_min=0,
+ soft_max=5,
+ description="Lattice thickness"
+ )
+ displace : FloatProperty(
+ name="Displace",
+ default=0,
+ soft_min=-1,
+ soft_max=1,
+ description="Lattice displace"
+ )
+ grid_object = ""
+ source_object = ""
+
+ @classmethod
+ def poll(cls, context):
+ try: return bpy.context.object.mode == 'OBJECT'
+ except: return False
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.label(text="Thickness:")
+ col.prop(
+ self, "thickness", text="Thickness", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1
+ )
+ col.prop(
+ self, "displace", text="Offset", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1
+ )
+ row = col.row()
+ row.prop(self, "use_groups")
+ col.separator()
+ col.label(text="Scale:")
+ col.prop(
+ self, "scale_x", text="U", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1
+ )
+ col.prop(
+ self, "scale_y", text="V", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1
+ )
+ col.separator()
+ col.label(text="Flip:")
+ row = col.row()
+ row.prop(self, "flipU", text="U")
+ row.prop(self, "flipV", text="V")
+ row.prop(self, "flipW", text="W")
+ col.prop(self, "swapUV")
+ col.prop(self, "flipNormals")
+ col.separator()
+ col.label(text="Lattice Options:")
+ col.prop(self, "high_quality_lattice")
+ col.prop(self, "hide_lattice")
+ col.prop(self, "set_parent")
+
+ def execute(self, context):
+ if self.source_object == self.grid_object == "" or True:
+ if len(bpy.context.selected_objects) != 2:
+ self.report({'ERROR'}, "Please, select two objects")
+ return {'CANCELLED'}
+ grid_obj = bpy.context.object
+ if grid_obj.type not in ('MESH', 'CURVE', 'SURFACE'):
+ self.report({'ERROR'}, "The surface object is not valid. Only Mesh,"
+ "Curve and Surface objects are allowed.")
+ return {'CANCELLED'}
+ obj = None
+ for o in bpy.context.selected_objects:
+ if o.name != grid_obj.name and o.type in \
+ ('MESH', 'CURVE', 'SURFACE', 'FONT'):
+ obj = o
+ o.select_set(False)
+ break
+ try:
+ obj_dim = obj.dimensions
+ obj_me = simple_to_mesh(obj)#obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
+ except:
+ self.report({'ERROR'}, "The object to deform is not valid. Only "
+ "Mesh, Curve, Surface and Font objects are allowed.")
+ return {'CANCELLED'}
+ self.grid_object = grid_obj.name
+ self.source_object = obj.name
+ else:
+ grid_obj = bpy.data.objects[self.grid_object]
+ obj = bpy.data.objects[self.source_object]
+ obj_me = simple_to_mesh(obj)# obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
+ for o in bpy.context.selected_objects: o.select_set(False)
+ grid_obj.select_set(True)
+ bpy.context.view_layer.objects.active = grid_obj
+
+ temp_grid_obj = grid_obj.copy()
+ temp_grid_obj.data = simple_to_mesh(grid_obj)
+ grid_mesh = temp_grid_obj.data
+ for v in grid_mesh.vertices:
+ v.co = grid_obj.matrix_world @ v.co
+ grid_mesh.calc_normals()
+
+ if len(grid_mesh.polygons) > 64 * 64:
+ bpy.data.objects.remove(temp_grid_obj)
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
+ self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
+ return {'CANCELLED'}
+
+ # CREATING LATTICE
+ min = Vector((0, 0, 0))
+ max = Vector((0, 0, 0))
+ first = True
+ for v in obj_me.vertices:
+ v0 = v.co.copy()
+ vert = obj.matrix_world @ v0
+ if vert[0] < min[0] or first:
+ min[0] = vert[0]
+ if vert[1] < min[1] or first:
+ min[1] = vert[1]
+ if vert[2] < min[2] or first:
+ min[2] = vert[2]
+ if vert[0] > max[0] or first:
+ max[0] = vert[0]
+ if vert[1] > max[1] or first:
+ max[1] = vert[1]
+ if vert[2] > max[2] or first:
+ max[2] = vert[2]
+ first = False
+
+ bb = max - min
+ lattice_loc = (max + min) / 2
+ bpy.ops.object.add(type='LATTICE')
+ lattice = bpy.context.active_object
+ lattice.location = lattice_loc
+ lattice.scale = Vector((bb.x / self.scale_x, bb.y / self.scale_y,
+ bb.z / self.scale_z))
+
+ if bb.x == 0:
+ lattice.scale.x = 1
+ if bb.y == 0:
+ lattice.scale.y = 1
+ if bb.z == 0:
+ lattice.scale.z = 1
+
+ bpy.context.view_layer.objects.active = obj
+ bpy.ops.object.modifier_add(type='LATTICE')
+ obj.modifiers[-1].object = lattice
+
+ # set as parent
+ if self.set_parent:
+ obj.select_set(True)
+ lattice.select_set(True)
+ bpy.context.view_layer.objects.active = lattice
+ bpy.ops.object.parent_set(type='LATTICE')
+
+ # reading grid structure
+ verts_grid, edges_grid, faces_grid = grid_from_mesh(
+ grid_mesh,
+ swap_uv=self.swapUV
+ )
+ nu = len(verts_grid)
+ nv = len(verts_grid[0])
+ nw = 2
+ scale_normal = self.thickness
+
+ try:
+ lattice.data.points_u = nu
+ lattice.data.points_v = nv
+ lattice.data.points_w = nw
+ for i in range(nu):
+ for j in range(nv):
+ for w in range(nw):
+ if self.use_groups:
+ try:
+ displace = temp_grid_obj.vertex_groups.active.weight(
+ verts_grid[i][j]) * scale_normal * bb.z
+ except:
+ displace = 0#scale_normal * bb.z
+ else:
+ displace = scale_normal * bb.z
+ target_point = (grid_mesh.vertices[verts_grid[i][j]].co +
+ grid_mesh.vertices[verts_grid[i][j]].normal *
+ (w + self.displace / 2 - 0.5) * displace) - lattice.location
+ if self.flipW:
+ w = 1 - w
+ if self.flipU:
+ i = nu - i - 1
+ if self.flipV:
+ j = nv - j - 1
+
+ lattice.data.points[i + j * nu + w * nu * nv].co_deform.x = \
+ target_point.x / bpy.data.objects[lattice.name].scale.x
+ lattice.data.points[i + j * nu + w * nu * nv].co_deform.y = \
+ target_point.y / bpy.data.objects[lattice.name].scale.y
+ lattice.data.points[i + j * nu + w * nu * nv].co_deform.z = \
+ target_point.z / bpy.data.objects[lattice.name].scale.z
+
+ except:
+ bpy.ops.object.mode_set(mode='OBJECT')
+ temp_grid_obj.select_set(True)
+ lattice.select_set(True)
+ obj.select_set(False)
+ bpy.ops.object.delete(use_global=False)
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
+ bpy.ops.object.modifier_remove(modifier=obj.modifiers[-1].name)
+ if nu > 64 or nv > 64:
+ self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
+ return {'CANCELLED'}
+ else:
+ self.report({'ERROR'}, "The grid mesh is not correct")
+ return {'CANCELLED'}
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ #grid_obj.select_set(True)
+ #lattice.select_set(False)
+ obj.select_set(False)
+ #bpy.ops.object.delete(use_global=False)
+ bpy.context.view_layer.objects.active = lattice
+ lattice.select_set(True)
+
+ if self.high_quality_lattice:
+ bpy.context.object.data.points_w = 8
+ else:
+ bpy.context.object.data.use_outside = True
+
+ if self.hide_lattice:
+ bpy.ops.object.hide_view_set(unselected=False)
+
+ bpy.context.view_layer.objects.active = obj
+ obj.select_set(True)
+ lattice.select_set(False)
+
+ if self.flipNormals:
+ try:
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.flip_normals()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ except:
+ pass
+ bpy.data.meshes.remove(grid_mesh)
+ bpy.data.meshes.remove(obj_me)
+
+ return {'FINISHED'}
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/NUMBA_FUNCTIONS.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/NUMBA_FUNCTIONS.PY
new file mode 100644
index 00000000..8a37c75d
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/NUMBA_FUNCTIONS.PY
@@ -0,0 +1,54 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import numpy as np
+try:
+ from numba import jit
+ print("Tissue: Numba module loaded succesfully")
+ @jit
+ def numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, diff_a, diff_b, f, k, dt, time_steps):
+ arr = np.arange(n_edges)*2
+ id0 = edge_verts[arr] # first vertex indices for each edge
+ id1 = edge_verts[arr+1] # second vertex indices for each edge
+ for i in range(time_steps):
+ lap_a = np.zeros(n_verts)
+ lap_b = np.zeros(n_verts)
+ lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
+ lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
+
+ for i, j, la0, lb0 in zip(id0,id1,lap_a0,lap_b0):
+ lap_a[i] += la0
+ lap_b[i] += lb0
+ lap_a[j] -= la0
+ lap_b[j] -= lb0
+ ab2 = a*b**2
+ #a += eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
+ #b += eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
+ a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+ return a, b
+
+ @jit
+ def numba_lerp2(v00, v10, v01, v11, vx, vy):
+ co0 = v00 + (v10 - v00) * vx
+ co1 = v01 + (v11 - v01) * vx
+ co2 = co0 + (co1 - co0) * vy
+ return co2
+except:
+ print("Tissue: Numba not installed")
+ pass
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/README.MD b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/README.MD
new file mode 100644
index 00000000..237c9d52
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/README.MD
@@ -0,0 +1,40 @@
+# Tissue
+![cover](http://www.co-de-it.com/wordpress/wp-content/uploads/2015/07/tissue_graphics.jpg)
+Tissue - Blender's add-on for computational design by Co-de-iT
+http://www.co-de-it.com/wordpress/code/blender-tissue
+
+Tissue is already shipped with both Blender 2.79b and Blender 2.80. However both versions can be updated manually, for more updated features and more stability.
+
+### Blender 2.80
+
+Tissue v0.3.31 for Blender 2.80 (latest stable release): https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-31
+
+Development branch (most updated version): https://github.com/alessandro-zomparelli/tissue/tree/b280-dev
+
+### Blender 2.79
+
+Tissue v0.3.4 for Blender 2.79b (latest stable release): https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-4
+
+Development branch (most updated version): https://github.com/alessandro-zomparelli/tissue/tree/dev1
+
+
+### Installation:
+
+1. Start Blender. Open User Preferences, the addons tab
+2. Search for Tissue add-on and remove existing version
+3. Click "install from file" and point Blender at the downloaded zip ("Install..." for Blender 2.80)
+4. Activate Tissue add-on from user preferences
+5. Save user preferences if you want to have it on at startup. (This could be not necessary for Blender 2.80 if "Auto-Save Preferences" id on)
+
+### Documentation
+
+Tissue documentation for Blender 2.80: https://github.com/alessandro-zomparelli/tissue/wiki
+
+
+### Contribute
+Please help me keeping Tissue stable and updated, report any issue here: https://github.com/alessandro-zomparelli/tissue/issues
+
+Tissue is free and open-source. I really think that this is the power of Blender and I wanted to give my small contribution to it.
+If you like my work and you want to help to continue the development of Tissue, please consider to make a small donation. Any small contribution is really appreciated, thanks! :-D
+
+Alessandro
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY
new file mode 100644
index 00000000..a66f7748
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY
@@ -0,0 +1,4280 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
+# ------------------------------- version 0.84 ------------------------------- #
+# #
+# Creates duplicates of selected mesh to active morphing the shape according #
+# to target faces. #
+# #
+# (c) Alessandro Zomparelli #
+# (2017) #
+# #
+# http://www.co-de-it.com/ #
+# #
+# ############################################################################ #
+
+
+import bpy
+from bpy.types import (
+ Operator,
+ Panel,
+ PropertyGroup,
+ )
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+ StringProperty,
+ PointerProperty
+ )
+from mathutils import Vector
+import numpy as np
+from math import *
+import random, time, copy
+import bmesh
+from .utils import *
+
+def anim_tessellate_active(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ if not (props.bool_lock or props.bool_hold):
+ try:
+ props.generator.name
+ props.component.name
+ bpy.ops.object.tissue_update_tessellate()
+ except: pass
+
+def anim_tessellate_object(ob):
+ try:
+ #bpy.context.view_layer.objects.active = ob
+ bpy.ops.object.tissue_update_tessellate()
+ except:
+ return None
+
+#from bpy.app.handlers import persistent
+
+def anim_tessellate(scene):
+ try:
+ active_object = bpy.context.object
+ old_mode = bpy.context.object.mode
+ selected_objects = bpy.context.selected_objects
+ except: active_object = old_mode = selected_objects = None
+ if old_mode in ('OBJECT', 'PAINT_WEIGHT'):
+ update_objects = []
+ for ob in scene.objects:
+ if ob.tissue_tessellate.bool_run and not ob.tissue_tessellate.bool_lock:
+ if ob not in update_objects: update_objects.append(ob)
+ update_objects = list(reversed(update_dependencies(ob, update_objects)))
+ for ob in update_objects:
+ override = {'object': ob}
+ '''
+ win = bpy.data.window_managers[0].windows[0]#bpy.context.window
+ scr = win.screen
+ areas3d = [area for area in scr.areas if area.type == 'VIEW_3D']
+ region = [region for region in areas3d[0].regions if region.type == 'WINDOW']
+ override = {
+ 'window':win,
+ 'screen':scr,
+ 'area' :areas3d[0],
+ 'region':region[0],
+ 'scene' :scene,
+ 'object': ob
+ }
+ '''
+ print(override)
+ bpy.ops.object.tissue_update_tessellate(override)
+ # restore selected objects
+ if old_mode != None:
+ for o in scene.objects:
+ if not o.hide_viewport: o.select_set(o in selected_objects)
+ bpy.context.view_layer.objects.active = active_object
+ bpy.ops.object.mode_set(mode=old_mode)
+ return
+
+
+def set_tessellate_handler(self, context):
+ old_handlers = []
+ blender_handlers = bpy.app.handlers.frame_change_post
+ for h in blender_handlers:
+ if "anim_tessellate" in str(h):
+ old_handlers.append(h)
+ for h in old_handlers: blender_handlers.remove(h)
+ for o in context.scene.objects:
+ if o.tissue_tessellate.bool_run:
+ blender_handlers.append(anim_tessellate)
+ break
+ return
+
+class tissue_tessellate_prop(PropertyGroup):
+ bool_lock : BoolProperty(
+ name="Lock",
+ description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.",
+ default=False
+ )
+ bool_hold : BoolProperty(
+ name="Hold",
+ description="Wait...",
+ default=False
+ )
+ bool_dependencies : BoolProperty(
+ name="Update Dependencies",
+ description="Automatically updates base and components as well, if results of other tessellations",
+ default=False
+ )
+ bool_run : BoolProperty(
+ name="Animatable Tessellation",
+ description="Automatically recompute the tessellation when the frame is changed. Currently is not working during Render Animation",
+ default = False,
+ update = set_tessellate_handler
+ )
+ zscale : FloatProperty(
+ name="Scale", default=1, soft_min=0, soft_max=10,
+ description="Scale factor for the component thickness",
+ update = anim_tessellate_active
+ )
+ scale_mode : EnumProperty(
+ items=(
+ ('CONSTANT', "Constant", "Uniform thinkness"),
+ ('ADAPTIVE', "Relative", "Preserve component's proportions")
+ ),
+ default='ADAPTIVE',
+ name="Z-Scale according to faces size",
+ update = anim_tessellate_active
+ )
+ offset : FloatProperty(
+ name="Surface Offset",
+ default=1,
+ min=-1,
+ max=1,
+ soft_min=-1,
+ soft_max=1,
+ description="Surface offset",
+ update = anim_tessellate_active
+ )
+ mode : EnumProperty(
+ items=(
+ ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
+ ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
+ ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
+ default='BOUNDS',
+ name="Component Mode",
+ update = anim_tessellate_active
+ )
+ rotation_mode : EnumProperty(
+ items=(('RANDOM', "Random", "Random faces rotation"),
+ ('UV', "Active UV", "Rotate according to UV coordinates"),
+ ('WEIGHT', "Active Weight", "Rotate according to Vertex Group gradient"),
+ ('DEFAULT', "Default", "Default rotation")),
+ default='DEFAULT',
+ name="Component Rotation",
+ update = anim_tessellate_active
+ )
+ rotation_direction : EnumProperty(
+ items=(('ORTHO', "Orthogonal", "Component main directions in XY"),
+ ('DIAG', "Diagonal", "Component main direction aligned with diagonal")),
+ default='ORTHO',
+ name="Direction",
+ update = anim_tessellate_active
+ )
+ rotation_shift : IntProperty(
+ name="Shift",
+ default=0,
+ soft_min=0,
+ soft_max=3,
+ description="Shift components rotation",
+ update = anim_tessellate_active
+ )
+ fill_mode : EnumProperty(
+ items=(
+ ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
+ ('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
+ ('PATCH', 'Patch', 'Curved tessellation according to the last ' +
+ 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
+ 'patches.\nAfter the last Subsurf (or Multires) only ' +
+ 'deformation\nmodifiers can be used'),
+ ('FRAME', 'Frame', 'Essellation along the edges of each face')),
+ default='QUAD',
+ name="Fill Mode",
+ update = anim_tessellate_active
+ )
+ combine_mode : EnumProperty(
+ items=(
+ ('LAST', 'Last', 'Show only the last iteration'),
+ ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
+ ('ALL', 'All', 'Combine the result of all iterations')),
+ default='LAST',
+ name="Combine Mode",
+ update = anim_tessellate_active
+ )
+ gen_modifiers : BoolProperty(
+ name="Generator Modifiers",
+ default=False,
+ description="Apply Modifiers and Shape Keys to the base object",
+ update = anim_tessellate_active
+ )
+ com_modifiers : BoolProperty(
+ name="Component Modifiers",
+ default=False,
+ description="Apply Modifiers and Shape Keys to the component object",
+ update = anim_tessellate_active
+ )
+ merge : BoolProperty(
+ name="Merge",
+ default=False,
+ description="Merge vertices in adjacent duplicates",
+ update = anim_tessellate_active
+ )
+ merge_thres : FloatProperty(
+ name="Distance",
+ default=0.001,
+ soft_min=0,
+ soft_max=10,
+ description="Limit below which to merge vertices",
+ update = anim_tessellate_active
+ )
+ generator : PointerProperty(
+ type=bpy.types.Object,
+ name="",
+ description="Base object for the tessellation",
+ update = anim_tessellate_active
+ )
+ component : PointerProperty(
+ type=bpy.types.Object,
+ name="",
+ description="Component object for the tessellation",
+ #default="",
+ update = anim_tessellate_active
+ )
+ bool_random : BoolProperty(
+ name="Randomize",
+ default=False,
+ description="Randomize component rotation",
+ update = anim_tessellate_active
+ )
+ random_seed : IntProperty(
+ name="Seed",
+ default=0,
+ soft_min=0,
+ soft_max=10,
+ description="Random seed",
+ update = anim_tessellate_active
+ )
+ bool_vertex_group : BoolProperty(
+ name="Map Vertex Group",
+ default=False,
+ description="Transfer all Vertex Groups from Base object",
+ update = anim_tessellate_active
+ )
+ bool_selection : BoolProperty(
+ name="On selected Faces",
+ default=False,
+ description="Create Tessellation only on selected faces",
+ update = anim_tessellate_active
+ )
+ bool_shapekeys : BoolProperty(
+ name="Use Shape Keys",
+ default=False,
+ description="Transfer Component's Shape Keys. If the name of Vertex "
+ "Groups and Shape Keys are the same, they will be "
+ "automatically combined",
+ update = anim_tessellate_active
+ )
+ bool_smooth : BoolProperty(
+ name="Smooth Shading",
+ default=False,
+ description="Output faces with smooth shading rather than flat shaded",
+ update = anim_tessellate_active
+ )
+ bool_materials : BoolProperty(
+ name="Transfer Materials",
+ default=False,
+ description="Preserve component's materials",
+ update = anim_tessellate_active
+ )
+ bool_material_id : BoolProperty(
+ name="Tessellation on Material ID",
+ default=False,
+ description="Apply the component only on the selected Material",
+ update = anim_tessellate_active
+ )
+ material_id : IntProperty(
+ name="Material ID",
+ default=0,
+ min=0,
+ description="Material ID",
+ update = anim_tessellate_active
+ )
+ bool_dissolve_seams : BoolProperty(
+ name="Dissolve Seams",
+ default=False,
+ description="Dissolve all seam edges",
+ update = anim_tessellate_active
+ )
+ iterations : IntProperty(
+ name="Iterations",
+ default=1,
+ min=1,
+ soft_max=5,
+ description="Automatically repeat the Tessellation using the "
+ + "generated geometry as new base object.\nUsefull for "
+ + "for branching systems. Dangerous!",
+ update = anim_tessellate_active
+ )
+ bool_combine : BoolProperty(
+ name="Combine unused",
+ default=False,
+ description="Combine the generated geometry with unused faces",
+ update = anim_tessellate_active
+ )
+ bool_advanced : BoolProperty(
+ name="Advanced Settings",
+ default=False,
+ description="Show more settings"
+ )
+ normals_mode : EnumProperty(
+ items=(
+ ('VERTS', 'Normals', 'Consistent direction based on vertices normal'),
+ ('FACES', 'Individual Faces', 'Based on individual faces normal'),
+ ('CUSTOM', 'Custom', "According to Base object's shape keys")),
+ default='VERTS',
+ name="Direction",
+ update = anim_tessellate_active
+ )
+ bool_multi_components : BoolProperty(
+ name="Multi Components",
+ default=False,
+ description="Combine different components according to materials name",
+ update = anim_tessellate_active
+ )
+ error_message : StringProperty(
+ name="Error Message",
+ default=""
+ )
+ warning_message : StringProperty(
+ name="Warning Message",
+ default=""
+ )
+ bounds_x : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default X coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
+ default='EXTEND',
+ name="Bounds X",
+ update = anim_tessellate_active
+ )
+ bounds_y : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default Y coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
+ default='EXTEND',
+ name="Bounds Y",
+ update = anim_tessellate_active
+ )
+ close_mesh : EnumProperty(
+ items=(
+ ('NONE', 'None', 'Keep the mesh open'),
+ ('CAP', 'Cap Holes', 'Automatically cap open loops'),
+ ('BRIDGE', 'Bridge Loops', 'Automatically bridge loop pairs')),
+ default='NONE',
+ name="Close Mesh",
+ update = anim_tessellate_active
+ )
+ cap_faces : BoolProperty(
+ name="Cap Holes",
+ default=False,
+ description="Cap open edges loops",
+ update = anim_tessellate_active
+ )
+ frame_boundary : BoolProperty(
+ name="Frame Boundary",
+ default=False,
+ description="Support face boundaries",
+ update = anim_tessellate_active
+ )
+ fill_frame : BoolProperty(
+ name="Fill Frame",
+ default=False,
+ description="Fill inner faces with Fan tessellation",
+ update = anim_tessellate_active
+ )
+ frame_boundary_mat : IntProperty(
+ name="Material Offset",
+ default=0,
+ description="Material Offset for boundaries",
+ update = anim_tessellate_active
+ )
+ fill_frame_mat : IntProperty(
+ name="Material Offset",
+ default=0,
+ description="Material Offset for inner faces",
+ update = anim_tessellate_active
+ )
+ open_edges_crease : FloatProperty(
+ name="Open Edges Crease",
+ default=0,
+ min=0,
+ max=1,
+ description="Automatically set crease for open edges",
+ update = anim_tessellate_active
+ )
+ bridge_smoothness : FloatProperty(
+ name="Smoothness",
+ default=1,
+ min=0,
+ max=1,
+ description="Bridge Smoothness",
+ update = anim_tessellate_active
+ )
+ frame_thickness : FloatProperty(
+ name="Frame Thickness",
+ default=0.2,
+ min=0,
+ soft_max=2,
+ description="Frame Thickness",
+ update = anim_tessellate_active
+ )
+ frame_mode : EnumProperty(
+ items=(
+ ('CONSTANT', 'Constant', 'Even thickness'),
+ ('RELATIVE', 'Relative', 'Frame offset depends on face areas')),
+ default='CONSTANT',
+ name="Offset",
+ update = anim_tessellate_active
+ )
+ bridge_cuts : IntProperty(
+ name="Cuts",
+ default=0,
+ min=0,
+ max=20,
+ description="Bridge Cuts",
+ update = anim_tessellate_active
+ )
+ cap_material_index : IntProperty(
+ name="Material",
+ default=0,
+ min=0,
+ description="Material index for the cap/bridge faces",
+ update = anim_tessellate_active
+ )
+ patch_subs : IntProperty(
+ name="Patch Subdivisions",
+ default=1,
+ min=0,
+ description="Subdivisions levels for Patch tessellation after the first iteration",
+ update = anim_tessellate_active
+ )
+
+def store_parameters(operator, ob):
+ ob.tissue_tessellate.bool_hold = True
+ ob.tissue_tessellate.bool_lock = operator.bool_lock
+ ob.tissue_tessellate.bool_dependencies = operator.bool_dependencies
+ ob.tissue_tessellate.generator = bpy.data.objects[operator.generator]
+ ob.tissue_tessellate.component = bpy.data.objects[operator.component]
+ ob.tissue_tessellate.zscale = operator.zscale
+ ob.tissue_tessellate.offset = operator.offset
+ ob.tissue_tessellate.gen_modifiers = operator.gen_modifiers
+ ob.tissue_tessellate.com_modifiers = operator.com_modifiers
+ ob.tissue_tessellate.mode = operator.mode
+ ob.tissue_tessellate.rotation_mode = operator.rotation_mode
+ ob.tissue_tessellate.rotation_shift = operator.rotation_shift
+ ob.tissue_tessellate.rotation_direction = operator.rotation_direction
+ ob.tissue_tessellate.merge = operator.merge
+ ob.tissue_tessellate.merge_thres = operator.merge_thres
+ ob.tissue_tessellate.scale_mode = operator.scale_mode
+ ob.tissue_tessellate.bool_random = operator.bool_random
+ ob.tissue_tessellate.random_seed = operator.random_seed
+ ob.tissue_tessellate.fill_mode = operator.fill_mode
+ ob.tissue_tessellate.bool_vertex_group = operator.bool_vertex_group
+ ob.tissue_tessellate.bool_selection = operator.bool_selection
+ ob.tissue_tessellate.bool_shapekeys = operator.bool_shapekeys
+ ob.tissue_tessellate.bool_smooth = operator.bool_smooth
+ ob.tissue_tessellate.bool_materials = operator.bool_materials
+ ob.tissue_tessellate.bool_material_id = operator.bool_material_id
+ ob.tissue_tessellate.material_id = operator.material_id
+ ob.tissue_tessellate.bool_dissolve_seams = operator.bool_dissolve_seams
+ ob.tissue_tessellate.iterations = operator.iterations
+ ob.tissue_tessellate.bool_advanced = operator.bool_advanced
+ ob.tissue_tessellate.normals_mode = operator.normals_mode
+ ob.tissue_tessellate.bool_combine = operator.bool_combine
+ ob.tissue_tessellate.bool_multi_components = operator.bool_multi_components
+ ob.tissue_tessellate.combine_mode = operator.combine_mode
+ ob.tissue_tessellate.bounds_x = operator.bounds_x
+ ob.tissue_tessellate.bounds_y = operator.bounds_y
+ ob.tissue_tessellate.cap_faces = operator.cap_faces
+ ob.tissue_tessellate.close_mesh = operator.close_mesh
+ ob.tissue_tessellate.bridge_cuts = operator.bridge_cuts
+ ob.tissue_tessellate.bridge_smoothness = operator.bridge_smoothness
+ ob.tissue_tessellate.frame_thickness = operator.frame_thickness
+ ob.tissue_tessellate.frame_mode = operator.frame_mode
+ ob.tissue_tessellate.frame_boundary = operator.frame_boundary
+ ob.tissue_tessellate.fill_frame = operator.fill_frame
+ ob.tissue_tessellate.frame_boundary_mat = operator.frame_boundary_mat
+ ob.tissue_tessellate.fill_frame_mat = operator.fill_frame_mat
+ ob.tissue_tessellate.cap_material_index = operator.cap_material_index
+ ob.tissue_tessellate.patch_subs = operator.patch_subs
+ ob.tissue_tessellate.bool_hold = False
+ return ob
+
+def load_parameters(operator, ob):
+ operator.bool_lock = ob.tissue_tessellate.bool_lock
+ operator.bool_dependencies = ob.tissue_tessellate.bool_dependencies
+ operator.generator = ob.tissue_tessellate.generator.name
+ operator.component = ob.tissue_tessellate.component.name
+ operator.zscale = ob.tissue_tessellate.zscale
+ operator.offset = ob.tissue_tessellate.offset
+ operator.gen_modifiers = ob.tissue_tessellate.gen_modifiers
+ operator.com_modifiers = ob.tissue_tessellate.com_modifiers
+ operator.mode = ob.tissue_tessellate.mode
+ operator.rotation_mode = ob.tissue_tessellate.rotation_mode
+ operator.rotation_shift = ob.tissue_tessellate.rotation_shift
+ operator.rotation_direction = ob.tissue_tessellate.rotation_direction
+ operator.merge = ob.tissue_tessellate.merge
+ operator.merge_thres = ob.tissue_tessellate.merge_thres
+ operator.scale_mode = ob.tissue_tessellate.scale_mode
+ operator.bool_random = ob.tissue_tessellate.bool_random
+ operator.random_seed = ob.tissue_tessellate.random_seed
+ operator.fill_mode = ob.tissue_tessellate.fill_mode
+ operator.bool_vertex_group = ob.tissue_tessellate.bool_vertex_group
+ operator.bool_selection = ob.tissue_tessellate.bool_selection
+ operator.bool_shapekeys = ob.tissue_tessellate.bool_shapekeys
+ operator.bool_smooth = ob.tissue_tessellate.bool_smooth
+ operator.bool_materials = ob.tissue_tessellate.bool_materials
+ operator.bool_material_id = ob.tissue_tessellate.bool_material_id
+ operator.material_id = ob.tissue_tessellate.material_id
+ operator.bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams
+ operator.iterations = ob.tissue_tessellate.iterations
+ operator.bool_advanced = ob.tissue_tessellate.bool_advanced
+ operator.normals_mode = ob.tissue_tessellate.normals_mode
+ operator.bool_combine = ob.tissue_tessellate.bool_combine
+ operator.bool_multi_components = ob.tissue_tessellate.bool_multi_components
+ operator.combine_mode = ob.tissue_tessellate.combine_mode
+ operator.bounds_x = ob.tissue_tessellate.bounds_x
+ operator.bounds_y = ob.tissue_tessellate.bounds_y
+ operator.cap_faces = ob.tissue_tessellate.cap_faces
+ operator.close_mesh = ob.tissue_tessellate.close_mesh
+ operator.bridge_cuts = ob.tissue_tessellate.bridge_cuts
+ operator.bridge_smoothness = ob.tissue_tessellate.bridge_smoothness
+ operator.cap_material_index = ob.tissue_tessellate.cap_material_index
+ operator.patch_subs = ob.tissue_tessellate.patch_subs
+ operator.frame_boundary = ob.tissue_tessellate.frame_boundary
+ operator.fill_frame = ob.tissue_tessellate.fill_frame
+ operator.frame_boundary_mat = ob.tissue_tessellate.frame_boundary_mat
+ operator.fill_frame_mat = ob.tissue_tessellate.fill_frame_mat
+ operator.frame_thickness = ob.tissue_tessellate.frame_thickness
+ operator.frame_mode = ob.tissue_tessellate.frame_mode
+ return ob
+
+def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode,
+ scale_mode, rotation_mode, rotation_shift, rand_seed, bool_vertex_group,
+ bool_selection, bool_shapekeys, bool_material_id, material_id,
+ normals_mode, bounds_x, bounds_y):
+ random.seed(rand_seed)
+
+ if normals_mode == 'CUSTOM':
+ if _ob0.data.shape_keys != None:
+ ob0_sk = convert_object_to_mesh(_ob0)
+ me0_sk = ob0_sk.data
+ key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks]
+ for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0
+ else: normals_mode = 'VERTS'
+
+ ob0 = convert_object_to_mesh(_ob0)
+ me0 = ob0.data
+
+ # base normals
+ normals0 = []
+ if normals_mode == 'CUSTOM':
+ for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val
+ for v0, v1 in zip(ob0.data.vertices, me0_sk.vertices):
+ normals0.append(v1.co - v0.co)
+ bpy.data.objects.remove(ob0_sk)
+ else:
+ ob0.data.update()
+ normals0 = [v.normal for v in ob0.data.vertices]
+
+# ob0 = convert_object_to_mesh(_ob0)
+ ob0.name = _ob0.name + "_apply_mod"
+ me0 = _ob0.data
+
+ # Check if zero faces are selected
+ if _ob0.type == 'MESH':
+ bool_cancel = True
+ for p in me0.polygons:
+ check_sel = check_mat = False
+ if not bool_selection or p.select: check_sel = True
+ if not bool_material_id or p.material_index == material_id: check_mat = True
+ if check_sel and check_mat:
+ bool_cancel = False
+ break
+ if bool_cancel:
+ bpy.data.meshes.remove(ob0.data)
+ #bpy.data.objects.remove(ob0)
+ return 0
+
+ levels = 0
+ sculpt_levels = 0
+ render_levels = 0
+ bool_multires = False
+ multires_name = ""
+ not_allowed = ['FLUID_SIMULATION', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD',
+ 'DECIMATE', 'EDGE_SPLIT', 'MASK', 'MIRROR', 'REMESH',
+ 'SCREW', 'SOLIDIFY', 'TRIANGULATE', 'WIREFRAME', 'SKIN',
+ 'EXPLODE', 'PARTICLE_INSTANCE', 'PARTICLE_SYSTEM', 'SMOKE']
+ modifiers0 = list(_ob0.modifiers)#[m for m in ob0.modifiers]
+ show_modifiers = [m.show_viewport for m in _ob0.modifiers]
+ show_modifiers.reverse()
+ modifiers0.reverse()
+ for m in modifiers0:
+ visible = m.show_viewport
+ if not visible: continue
+ #m.show_viewport = False
+ if m.type in ('SUBSURF', 'MULTIRES') and visible:
+ levels = m.levels
+ multires_name = m.name
+ if m.type == 'MULTIRES':
+ bool_multires = True
+ multires_name = m.name
+ sculpt_levels = m.sculpt_levels
+ render_levels = m.render_levels
+ else: bool_multires = False
+ break
+ elif m.type in not_allowed:
+ bpy.data.meshes.remove(ob0.data)
+ #bpy.data.meshes.remove(me0)
+ return "modifiers_error"
+
+ before = _ob0.copy()
+ before.name = _ob0.name + "_before_subs"
+ bpy.context.collection.objects.link(before)
+ #if ob0.type == 'MESH': before.data = me0
+ before_mod = list(before.modifiers)
+ before_mod.reverse()
+ for m in before_mod:
+ if m.type in ('SUBSURF', 'MULTIRES') and m.show_viewport:
+ before.modifiers.remove(m)
+ break
+ else: before.modifiers.remove(m)
+
+ before_subsurf = simple_to_mesh(before)
+
+ before_bm = bmesh.new()
+ before_bm.from_mesh(before_subsurf)
+ before_bm.faces.ensure_lookup_table()
+ before_bm.edges.ensure_lookup_table()
+ before_bm.verts.ensure_lookup_table()
+
+ error = ""
+ for f in before_bm.faces:
+ if len(f.loops) != 4:
+ error = "topology_error"
+ break
+ for e in before_bm.edges:
+ if len(e.link_faces) == 0:
+ error = "wires_error"
+ break
+ for v in before_bm.verts:
+ if len(v.link_faces) == 0:
+ error = "verts_error"
+ break
+ if error != "":
+ bpy.data.meshes.remove(ob0.data)
+ #bpy.data.meshes.remove(me0)
+ bpy.data.meshes.remove(before_subsurf)
+ bpy.data.objects.remove(before)
+ return error
+
+ me0 = ob0.data
+ verts0 = me0.vertices # Collect generator vertices
+
+ if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
+
+ # set Shape Keys to zero
+ if bool_shapekeys or not com_modifiers:
+ try:
+ original_key_values = []
+ for sk in _ob1.data.shape_keys.key_blocks:
+ original_key_values.append(sk.value)
+ sk.value = 0
+ except:
+ bool_shapekeys = False
+
+ if not com_modifiers and not bool_shapekeys:
+ mod_visibility = []
+ for m in _ob1.modifiers:
+ mod_visibility.append(m.show_viewport)
+ m.show_viewport = False
+ com_modifiers = True
+
+ ob1 = convert_object_to_mesh(_ob1, com_modifiers, False)
+ me1 = ob1.data
+
+ if mode != 'BOUNDS':
+ ob1.active_shape_key_index = 0
+ # Bound X
+ if bounds_x != 'EXTEND':
+ if mode == 'GLOBAL':
+ planes_co = ((0,0,0),(1,1,1))
+ plane_no = (1,0,0)
+ if mode == 'LOCAL':
+ planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0)))
+ plane_no = planes_co[0]-planes_co[1]
+ bpy.ops.object.mode_set(mode='EDIT')
+ for co in planes_co:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
+ bpy.ops.mesh.mark_seam()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ _faces = ob1.data.polygons
+ if mode == 'GLOBAL':
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]:
+ f.select = True
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]:
+ f.select = True
+ else:
+ for f in [f for f in _faces if f.center.x > 1]:
+ f.select = True
+ for f in [f for f in _faces if f.center.x < 0]:
+ f.select = True
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if bounds_x == 'CLIP':
+ bpy.ops.mesh.delete(type='FACE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bounds_x == 'CYCLIC':
+ bpy.ops.mesh.split()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ # Bound Y
+ if bounds_y != 'EXTEND':
+ if mode == 'GLOBAL':
+ planes_co = ((0,0,0),(1,1,1))
+ plane_no = (0,1,0)
+ if mode == 'LOCAL':
+ planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0)))
+ plane_no = planes_co[0]-planes_co[1]
+ bpy.ops.object.mode_set(mode='EDIT')
+ for co in planes_co:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
+ bpy.ops.mesh.mark_seam()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ _faces = ob1.data.polygons
+ if mode == 'GLOBAL':
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]:
+ f.select = True
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]:
+ f.select = True
+ else:
+ for f in [f for f in _faces if f.center.y > 1]:
+ f.select = True
+ for f in [f for f in _faces if f.center.y < 0]:
+ f.select = True
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if bounds_y == 'CLIP':
+ bpy.ops.mesh.delete(type='FACE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bounds_y == 'CYCLIC':
+ bpy.ops.mesh.split()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Component statistics
+ n_verts = len(me1.vertices)
+
+ # Create empty lists
+ new_verts = []
+ new_edges = []
+ new_faces = []
+ new_verts_np = np.array(())
+
+ # Component bounding box
+ min_c = Vector((0, 0, 0))
+ max_c = Vector((0, 0, 0))
+ first = True
+ for v in me1.vertices:
+ vert = v.co
+ if vert[0] < min_c[0] or first:
+ min_c[0] = vert[0]
+ if vert[1] < min_c[1] or first:
+ min_c[1] = vert[1]
+ if vert[2] < min_c[2] or first:
+ min_c[2] = vert[2]
+ if vert[0] > max_c[0] or first:
+ max_c[0] = vert[0]
+ if vert[1] > max_c[1] or first:
+ max_c[1] = vert[1]
+ if vert[2] > max_c[2] or first:
+ max_c[2] = vert[2]
+ first = False
+ bb = max_c - min_c
+
+ # adaptive XY
+ verts1 = []
+ for v in me1.vertices:
+ if mode == 'BOUNDS':
+ vert = v.co - min_c # (ob1.matrix_world * v.co) - min_c
+ vert[0] = vert[0] / bb[0] if bb[0] != 0 else 0.5
+ vert[1] = vert[1] / bb[1] if bb[1] != 0 else 0.5
+ vert[2] = vert[2] / bb[2] if bb[2] != 0 else 0
+ vert[2] = (vert[2] - 0.5 + offset * 0.5) * zscale
+ elif mode == 'LOCAL':
+ vert = v.co.xyz
+ vert[2] *= zscale
+ #vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
+ elif mode == 'GLOBAL':
+ vert = ob1.matrix_world @ v.co
+ vert[2] *= zscale
+ try:
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
+ except: pass
+ #verts1.append(vert)
+ v.co = vert
+
+ # Bounds X, Y
+ if mode != 'BOUNDS':
+ if bounds_x == 'CYCLIC':
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).x > 1]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.x -= 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.x -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).x < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.x += 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.x += 1
+ except: pass
+ if bounds_y == 'CYCLIC':
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y > 1]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y -= 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y += 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y += 1
+ except: pass
+ verts1 = [v.co for v in me1.vertices]
+ n_verts1 = len(verts1)
+
+ patch_faces = 4**levels
+ sides = int(sqrt(patch_faces))
+ step = 1/sides
+ sides0 = sides-2
+ patch_faces0 = int((sides-2)**2)
+ n_patches = int(len(me0.polygons)/patch_faces)
+ if len(me0.polygons)%patch_faces != 0:
+ #ob0.data = old_me0
+ return "topology_error"
+
+ new_verts = []
+ new_edges = []
+ new_faces = []
+
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ new_patch = None
+
+ # All vertex group
+ if bool_vertex_group:
+ try:
+ weight = []
+ for vg in ob0.vertex_groups:
+ _weight = []
+ for v in me0.vertices:
+ try:
+ _weight.append(vg.weight(v.index))
+ except:
+ _weight.append(0)
+ weight.append(_weight)
+ except:
+ bool_vertex_group = False
+
+ # Adaptive Z
+ if scale_mode == 'ADAPTIVE':
+ com_area = bb[0]*bb[1]
+ if mode != 'BOUNDS' or com_area == 0: com_area = 1
+ #mult = 1/com_area
+ verts_area = []
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ for v in bm.verts:
+ area = 0
+ faces = v.link_faces
+ for f in faces:
+ area += f.calc_area()
+ area = area/len(faces)*patch_faces/com_area
+ #area*=mult*
+ verts_area.append(sqrt(area)*bb[2])
+
+ random.seed(rand_seed)
+ bool_correct = False
+
+ _faces = [[[0] for ii in range(sides)] for jj in range(sides)]
+ _verts = [[[0] for ii in range(sides+1)] for jj in range(sides+1)]
+
+ # find relative UV component's vertices
+ verts1_uv_quads = [0]*len(verts1)
+ verts1_uv = [0]*len(verts1)
+ for i, vert in enumerate(verts1):
+ # grid coordinates
+ u = int(vert[0]//step)
+ v = int(vert[1]//step)
+ u1 = min(u+1, sides)
+ v1 = min(v+1, sides)
+ if mode != 'BOUNDS':
+ if u > sides-1:
+ u = sides-1
+ u1 = sides
+ if u < 0:
+ u = 0
+ u1 = 1
+ if v > sides-1:
+ v = sides-1
+ v1 = sides
+ if v < 0:
+ v = 0
+ v1 = 1
+ verts1_uv_quads[i] = (u,v,u1,v1)
+ # factor coordinates
+ fu = (vert[0]-u*step)/step
+ fv = (vert[1]-v*step)/step
+ fw = vert.z
+ # interpolate Z scaling factor
+ verts1_uv[i] = Vector((fu,fv,fw))
+
+ sk_uv_quads = []
+ sk_uv = []
+ if bool_shapekeys:
+ for sk in ob1.data.shape_keys.key_blocks:
+ source = sk.data
+ _sk_uv_quads = [0]*len(verts1)
+ _sk_uv = [0]*len(verts1)
+ for i, sk_v in enumerate(source):
+ if mode == 'BOUNDS':
+ sk_vert = sk_v.co - min_c
+ sk_vert[0] = (sk_vert[0] / bb[0] if bb[0] != 0 else 0.5)
+ sk_vert[1] = (sk_vert[1] / bb[1] if bb[1] != 0 else 0.5)
+ sk_vert[2] = (sk_vert[2] / bb[2] if bb[2] != 0 else sk_vert[2])
+ sk_vert[2] = (sk_vert[2] - 0.5 + offset * 0.5) * zscale
+ elif mode == 'LOCAL':
+ sk_vert = sk_v.co
+ sk_vert[2] *= zscale
+ elif mode == 'GLOBAL':
+ sk_vert = sk_v.co
+ sk_vert[2] *= zscale
+
+ # grid coordinates
+ u = int(sk_vert[0]//step)
+ v = int(sk_vert[1]//step)
+ u1 = min(u+1, sides)
+ v1 = min(v+1, sides)
+ if mode != 'BOUNDS':
+ if u > sides-1:
+ u = sides-1
+ u1 = sides
+ if u < 0:
+ u = 0
+ u1 = 1
+ if v > sides-1:
+ v = sides-1
+ v1 = sides
+ if v < 0:
+ v = 0
+ v1 = 1
+ _sk_uv_quads[i] = (u,v,u1,v1)
+ # factor coordinates
+ fu = (sk_vert[0]-u*step)/step
+ fv = (sk_vert[1]-v*step)/step
+ fw = sk_vert.z
+ _sk_uv[i] = Vector((fu,fv,fw))
+ sk_uv_quads.append(_sk_uv_quads)
+ sk_uv.append(_sk_uv)
+
+ for i in range(n_patches):
+ poly = me0.polygons[i*patch_faces]
+ if bool_selection and not poly.select: continue
+ if bool_material_id and not poly.material_index == material_id: continue
+
+ bool_correct = True
+ new_patch = bpy.data.objects.new("patch", me1.copy())
+ bpy.context.collection.objects.link(new_patch)
+
+ new_patch.select_set(True)
+ bpy.context.view_layer.objects.active = new_patch
+
+ for area in bpy.context.screen.areas:
+ for space in area.spaces:
+ try: new_patch.local_view_set(space, True)
+ except: pass
+
+ # Vertex Group
+ if bool_vertex_group:
+ for vg in ob0.vertex_groups:
+ new_patch.vertex_groups.new(name=vg.name)
+
+ # find patch faces
+ faces = _faces.copy()
+ verts = _verts.copy()
+ shift1 = sides
+ shift2 = sides*2-1
+ shift3 = sides*3-2
+ for j in range(patch_faces):
+ if j < patch_faces0:
+ if levels == 0:
+ u = j%sides0
+ v = j//sides0
+ else:
+ u = j%sides0+1
+ v = j//sides0+1
+ elif j < patch_faces0 + shift1:
+ u = j-patch_faces0
+ v = 0
+ elif j < patch_faces0 + shift2:
+ u = sides-1
+ v = j-(patch_faces0 + sides)+1
+ elif j < patch_faces0 + shift3:
+ jj = j-(patch_faces0 + shift2)
+ u = sides-jj-2
+ v = sides-1
+ else:
+ jj = j-(patch_faces0 + shift3)
+ u = 0
+ v = sides-jj-2
+ face = me0.polygons[j+i*patch_faces]
+ faces[u][v] = face
+ verts[u][v] = verts0[face.vertices[0]]
+ if u == sides-1:
+ verts[sides][v] = verts0[face.vertices[1]]
+ if v == sides-1:
+ verts[u][sides] = verts0[face.vertices[3]]
+ if u == v == sides-1:
+ verts[sides][sides] = verts0[face.vertices[2]]
+
+ # Random rotation
+ if rotation_mode == 'RANDOM' or rotation_shift != 0:
+ if rotation_mode == 'RANDOM': rot = random.randint(0, 3)
+ else: rot = rotation_shift%4
+ if rot == 1:
+ verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
+ elif rot == 2:
+ verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
+ elif rot == 3:
+ verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
+
+ # UV rotation
+ if rotation_mode == 'UV' and ob0.type == 'MESH':
+ if len(ob0.data.uv_layers) > 0:
+ uv0 = me0.uv_layers.active.data[faces[0][0].index*4].uv
+ uv1 = me0.uv_layers.active.data[faces[0][-1].index*4 + 3].uv
+ uv2 = me0.uv_layers.active.data[faces[-1][-1].index*4 + 2].uv
+ uv3 = me0.uv_layers.active.data[faces[-1][0].index*4 + 1].uv
+ v01 = (uv0 + uv1)
+ v32 = (uv3 + uv2)
+ v0132 = v32 - v01
+ v0132.normalize()
+ v12 = (uv1 + uv2)
+ v03 = (uv0 + uv3)
+ v1203 = v03 - v12
+ v1203.normalize()
+
+ vertUV = []
+ dot1203 = v1203.x
+ dot0132 = v0132.x
+ if(abs(dot1203) < abs(dot0132)):
+ if (dot0132 > 0):
+ pass
+ else:
+ verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
+ else:
+ if(dot1203 < 0):
+ verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
+ else:
+ verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
+
+ if True:
+ verts_xyz = np.array([[v.co for v in _verts] for _verts in verts])
+ #verts_norm = np.array([[v.normal for v in _verts] for _verts in verts])
+ verts_norm = np.array([[normals0[v.index] for v in _verts] for _verts in verts])
+ if normals_mode == 'FACES':
+ verts_norm = np.mean(verts_norm, axis=(0,1))
+ verts_norm = np.expand_dims(verts_norm, axis=0)
+ verts_norm = np.repeat(verts_norm,len(verts),axis=0)
+ verts_norm = np.expand_dims(verts_norm, axis=0)
+ verts_norm = np.repeat(verts_norm,len(verts),axis=0)
+ np_verts1_uv = np.array(verts1_uv)
+ verts1_uv_quads = np.array(verts1_uv_quads)
+ u = verts1_uv_quads[:,0]
+ v = verts1_uv_quads[:,1]
+ u1 = verts1_uv_quads[:,2]
+ v1 = verts1_uv_quads[:,3]
+ v00 = verts_xyz[u,v]
+ v10 = verts_xyz[u1,v]
+ v01 = verts_xyz[u,v1]
+ v11 = verts_xyz[u1,v1]
+ n00 = verts_norm[u,v]
+ n10 = verts_norm[u1,v]
+ n01 = verts_norm[u,v1]
+ n11 = verts_norm[u1,v1]
+ vx = np_verts1_uv[:,0].reshape((n_verts1,1))
+ vy = np_verts1_uv[:,1].reshape((n_verts1,1))
+ vz = np_verts1_uv[:,2].reshape((n_verts1,1))
+ co2 = np_lerp2(v00,v10,v01,v11,vx,vy)
+ n2 = np_lerp2(n00,n10,n01,n11,vx,vy)
+ if scale_mode == 'ADAPTIVE':
+ areas = np.array([[verts_area[v.index] for v in verts_v] for verts_v in verts])
+ a00 = areas[u,v].reshape((n_verts1,1))
+ a10 = areas[u1,v].reshape((n_verts1,1))
+ a01 = areas[u,v1].reshape((n_verts1,1))
+ a11 = areas[u1,v1].reshape((n_verts1,1))
+ # remapped z scale
+ a2 = np_lerp2(a00,a10,a01,a11,vx,vy)
+ co3 = co2 + n2 * vz * a2
+ else:
+ co3 = co2 + n2 * vz
+ coordinates = co3.flatten().tolist()
+ new_patch.data.vertices.foreach_set('co',coordinates)
+
+ # vertex groups
+ if bool_vertex_group:
+ for _weight, vg in zip(weight, new_patch.vertex_groups):
+ np_weight = np.array([[_weight[v.index] for v in verts_v] for verts_v in verts])
+ w00 = np_weight[u,v].reshape((n_verts1,1))
+ w10 = np_weight[u1,v].reshape((n_verts1,1))
+ w01 = np_weight[u,v1].reshape((n_verts1,1))
+ w11 = np_weight[u1,v1].reshape((n_verts1,1))
+ # remapped z scale
+ w2 = np_lerp2(w00,w10,w01,w11,vx,vy)
+ for vert_id in range(n_verts1):
+ vg.add([vert_id], w2[vert_id], "ADD")
+
+ if bool_shapekeys:
+ for i_sk, sk in enumerate(ob1.data.shape_keys.key_blocks):
+ np_verts1_uv = np.array(sk_uv[i_sk])
+ np_sk_uv_quads = np.array(sk_uv_quads[i_sk])
+ u = np_sk_uv_quads[:,0]
+ v = np_sk_uv_quads[:,1]
+ u1 = np_sk_uv_quads[:,2]
+ v1 = np_sk_uv_quads[:,3]
+ v00 = verts_xyz[u,v]
+ v10 = verts_xyz[u1,v]
+ v01 = verts_xyz[u,v1]
+ v11 = verts_xyz[u1,v1]
+ vx = np_verts1_uv[:,0].reshape((n_verts1,1))
+ vy = np_verts1_uv[:,1].reshape((n_verts1,1))
+ vz = np_verts1_uv[:,2].reshape((n_verts1,1))
+ co2 = np_lerp2(v00,v10,v01,v11,vx,vy)
+ n2 = np_lerp2(n00,n10,n01,n11,vx,vy)
+ if scale_mode == 'ADAPTIVE':
+ areas = np.array([[verts_area[v.index] for v in verts_v] for verts_v in verts])
+ a00 = areas[u,v].reshape((n_verts1,1))
+ a10 = areas[u1,v].reshape((n_verts1,1))
+ a01 = areas[u,v1].reshape((n_verts1,1))
+ a11 = areas[u1,v1].reshape((n_verts1,1))
+ # remapped z scale
+ a2 = np_lerp2(a00,a10,a01,a11,vx,vy)
+ co3 = co2 + n2 * vz * a2
+ else:
+ co3 = co2 + n2 * vz
+ coordinates = co3.flatten().tolist()
+ new_patch.data.shape_keys.key_blocks[sk.name].data.foreach_set('co', coordinates)
+ #new_patch.data.shape_keys.key_blocks[sk.name].data[i_vert].co = sk_co
+ else:
+ for _fvec, uv_quad, patch_vert in zip(verts1_uv, verts1_uv_quads, new_patch.data.vertices):
+ u = uv_quad[0]
+ v = uv_quad[1]
+ u1 = uv_quad[2]
+ v1 = uv_quad[3]
+ v00 = verts[u][v]
+ v10 = verts[u1][v]
+ v01 = verts[u][v1]
+ v11 = verts[u1][v1]
+ # interpolate Z scaling factor
+ fvec = _fvec.copy()
+ if scale_mode == 'ADAPTIVE':
+ a00 = verts_area[v00.index]
+ a10 = verts_area[v10.index]
+ a01 = verts_area[v01.index]
+ a11 = verts_area[v11.index]
+ fvec[2]*=lerp2(a00,a10,a01,a11,fvec)
+ # interpolate vertex on patch
+ patch_vert.co = lerp3(v00, v10, v01, v11, fvec)
+
+ # Vertex Group
+ if bool_vertex_group:
+ for _weight, vg in zip(weight, new_patch.vertex_groups):
+ w00 = _weight[v00.index]
+ w10 = _weight[v10.index]
+ w01 = _weight[v01.index]
+ w11 = _weight[v11.index]
+ wuv = lerp2(w00,w10,w01,w11, fvec)
+ vg.add([patch_vert.index], wuv, "ADD")
+
+ if bool_shapekeys:
+ for i_sk, sk in enumerate(ob1.data.shape_keys.key_blocks):
+ for i_vert, _fvec, _sk_uv_quad in zip(range(len(new_patch.data.vertices)), sk_uv[i_sk], sk_uv_quads[i_sk]):
+ u = _sk_uv_quad[0]
+ v = _sk_uv_quad[1]
+ u1 = _sk_uv_quad[2]
+ v1 = _sk_uv_quad[3]
+ v00 = verts[u][v]
+ v10 = verts[u1][v]
+ v01 = verts[u][v1]
+ v11 = verts[u1][v1]
+
+ fvec = _fvec.copy()
+ if scale_mode == 'ADAPTIVE':
+ a00 = verts_area[v00.index]
+ a10 = verts_area[v10.index]
+ a01 = verts_area[v01.index]
+ a11 = verts_area[v11.index]
+ fvec[2]*=lerp2(a00, a10, a01, a11, fvec)
+ sk_co = lerp3(v00, v10, v01, v11, fvec)
+
+ new_patch.data.shape_keys.key_blocks[sk.name].data[i_vert].co = sk_co
+
+ #if ob0.type == 'MESH': ob0.data = old_me0
+ if not bool_correct: return 0
+
+ bpy.ops.object.join()
+
+
+ if bool_shapekeys:
+ # set original values and combine Shape Keys and Vertex Groups
+ for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
+ sk.value = val
+ new_patch.data.shape_keys.key_blocks[sk.name].value = val
+ if bool_vertex_group:
+ for sk in new_patch.data.shape_keys.key_blocks:
+ for vg in new_patch.vertex_groups:
+ if sk.name == vg.name:
+ sk.vertex_group = vg.name
+ else:
+ try:
+ for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
+ sk.value = val
+ except: pass
+
+ new_name = ob0.name + "_" + ob1.name
+ new_patch.name = "tessellate_temp"
+
+ if bool_multires:
+ for m in ob0.modifiers:
+ if m.type == 'MULTIRES' and m.name == multires_name:
+ m.levels = levels
+ m.sculpt_levels = sculpt_levels
+ m.render_levels = render_levels
+ # restore original modifiers visibility for component object
+ try:
+ for m, vis in zip(_ob1.modifiers, mod_visibility):
+ m.show_viewport = vis
+ except: pass
+
+ bpy.data.objects.remove(before)
+ bpy.data.objects.remove(ob0)
+ bpy.data.objects.remove(ob1)
+ return new_patch
+
+def tessellate_original(_ob0, _ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
+ scale_mode, rotation_mode, rotation_shift, rotation_direction, rand_seed, fill_mode,
+ bool_vertex_group, bool_selection, bool_shapekeys,
+ bool_material_id, material_id, normals_mode, bounds_x, bounds_y):
+
+ if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
+ random.seed(rand_seed)
+
+ if bool_shapekeys:
+ try:
+ original_key_values = []
+ for sk in _ob1.data.shape_keys.key_blocks:
+ original_key_values.append(sk.value)
+ sk.value = 0
+ except:
+ bool_shapekeys = False
+
+ if normals_mode == 'CUSTOM':
+ if _ob0.data.shape_keys != None:
+ ob0_sk = convert_object_to_mesh(_ob0, True, True)
+ me0_sk = ob0_sk.data
+ key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks]
+ for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0
+ else: normals_mode == 'VERTS'
+
+ ob0 = convert_object_to_mesh(_ob0, gen_modifiers, True)
+ me0 = ob0.data
+ ob1 = convert_object_to_mesh(_ob1, com_modifiers, True)
+ me1 = ob1.data
+
+ # base normals
+ normals0 = []
+ if normals_mode == 'CUSTOM' and _ob0.data.shape_keys != None:
+ for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val
+ for v0, v1 in zip(me0.vertices, me0_sk.vertices):
+ normals0.append(v1.co - v0.co)
+ bpy.data.objects.remove(ob0_sk)
+ else:
+ me0.update()
+ normals0 = [v.normal for v in me0.vertices]
+
+ base_polygons = []
+ base_face_normals = []
+
+ n_faces0 = len(me0.polygons)
+
+ # Check if zero faces are selected
+ if (bool_selection and ob0.type == 'MESH') or bool_material_id:
+ for p in me0.polygons:
+ if (bool_selection and ob0.type == 'MESH'):
+ is_sel = p.select
+ else: is_sel = True
+ if bool_material_id:
+ is_mat = p.material_index == material_id
+ else: is_mat = True
+ if is_sel and is_mat:
+ base_polygons.append(p)
+ base_face_normals.append(p.normal)
+ else:
+ base_polygons = me0.polygons
+ base_face_normals = [p.normal for p in me0.polygons]
+
+ # numpy test: slower
+ #base_face_normals = np.zeros(n_faces0*3)
+ #me0.polygons.foreach_get("normal", base_face_normals)
+ #base_face_normals = base_face_normals.reshape((n_faces0,3))
+
+ if len(base_polygons) == 0:
+ bpy.data.objects.remove(ob0)
+ bpy.data.objects.remove(ob1)
+ bpy.data.meshes.remove(me1)
+ bpy.data.meshes.remove(me0)
+ return 0
+
+ if mode != 'BOUNDS':
+
+ bpy.ops.object.select_all(action='DESELECT')
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ bpy.context.view_layer.objects.active = ob1
+ ob1.select_set(True)
+ ob1.active_shape_key_index = 0
+ # Bound X
+ if bounds_x != 'EXTEND':
+ if mode == 'GLOBAL':
+ planes_co = ((0,0,0),(1,1,1))
+ plane_no = (1,0,0)
+ if mode == 'LOCAL':
+ planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0)))
+ plane_no = planes_co[0]-planes_co[1]
+ bpy.ops.object.mode_set(mode='EDIT')
+ for co in planes_co:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
+ bpy.ops.mesh.mark_seam()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ _faces = ob1.data.polygons
+ if mode == 'GLOBAL':
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]:
+ f.select = True
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]:
+ f.select = True
+ else:
+ for f in [f for f in _faces if f.center.x > 1]:
+ f.select = True
+ for f in [f for f in _faces if f.center.x < 0]:
+ f.select = True
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if bounds_x == 'CLIP':
+ bpy.ops.mesh.delete(type='FACE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bounds_x == 'CYCLIC':
+ bpy.ops.mesh.split()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ # Bound Y
+ if bounds_y != 'EXTEND':
+ if mode == 'GLOBAL':
+ planes_co = ((0,0,0),(1,1,1))
+ plane_no = (0,1,0)
+ if mode == 'LOCAL':
+ planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0)))
+ plane_no = planes_co[0]-planes_co[1]
+ bpy.ops.object.mode_set(mode='EDIT')
+ for co in planes_co:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
+ bpy.ops.mesh.mark_seam()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ _faces = ob1.data.polygons
+ if mode == 'GLOBAL':
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]:
+ f.select = True
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]:
+ f.select = True
+ else:
+ for f in [f for f in _faces if f.center.y > 1]:
+ f.select = True
+ for f in [f for f in _faces if f.center.y < 0]:
+ f.select = True
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if bounds_y == 'CLIP':
+ bpy.ops.mesh.delete(type='FACE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bounds_y == 'CYCLIC':
+ bpy.ops.mesh.split()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ #ob1 = new_ob1
+
+ me1 = ob1.data
+
+ verts0 = me0.vertices # Collect generator vertices
+
+ # Component statistics
+ n_verts1 = len(me1.vertices)
+ n_edges1 = len(me1.edges)
+ n_faces1 = len(me1.polygons)
+
+ # Create empty lists
+ new_verts = []
+ new_edges = []
+ new_faces = []
+ new_verts_np = np.array(())
+
+ # Component Coordinates
+ co1 = [0]*n_verts1*3
+
+ if mode == 'GLOBAL':
+ for v in me1.vertices:
+ v.co = ob1.matrix_world @ v.co
+ try:
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
+ except: pass
+ if mode != 'BOUNDS':
+ if bounds_x == 'CYCLIC':
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).x > 1]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.x -= 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.x -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).x < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.x += 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.x += 1
+ except: pass
+ if bounds_y == 'CYCLIC':
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y > 1]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y -= 1
+ try:
+ #new_ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y += 1
+ try:
+ #new_ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y += 1
+ except: pass
+ if len(me1.vertices) == 0:
+ bpy.data.objects.remove(ob0)
+ bpy.data.objects.remove(ob1)
+ return 0
+
+ me1.vertices.foreach_get("co", co1)
+ co1 = np.array(co1)
+ vx = co1[0::3].reshape((n_verts1,1))
+ vy = co1[1::3].reshape((n_verts1,1))
+ vz = co1[2::3].reshape((n_verts1,1))
+ min_c = Vector((vx.min(), vy.min(), vz.min())) # Min BB Corner
+ max_c = Vector((vx.max(), vy.max(), vz.max())) # Max BB Corner
+ bb = max_c - min_c # Bounding Box
+
+ # Component Coordinates
+ if mode == 'BOUNDS':
+ vx = (vx - min_c[0]) / bb[0] if bb[0] != 0 else 0.5
+ vy = (vy - min_c[1]) / bb[1] if bb[1] != 0 else 0.5
+ vz = (vz - min_c[2]) / bb[2] if bb[2] != 0 else 0
+ vz = (vz - 0.5 + offset * 0.5) * zscale
+ #vz = ((vz - min_c[2]) + (-0.5 + offset * 0.5) * bb[2]) * zscale
+ else:
+ vz *= zscale
+
+ # Component polygons
+ fs1 = [[i for i in p.vertices] for p in me1.polygons]
+ new_faces = fs1[:]
+
+ # Component edges
+ es1 = np.array([[i for i in e.vertices] for e in me1.edges])
+ #es1 = [[i for i in e.vertices] for e in me1.edges if e.is_loose]
+ new_edges = es1[:]
+
+ # SHAPE KEYS
+ if bool_shapekeys:
+ basis = True #com_modifiers
+ vx_key = []
+ vy_key = []
+ vz_key = []
+ sk_np = []
+ for sk in ob1.data.shape_keys.key_blocks:
+ do_shapekeys = True
+ # set all keys to 0
+ for _sk in ob1.data.shape_keys.key_blocks: _sk.value = 0
+ sk.value = 1
+
+ if basis:
+ basis = False
+ continue
+
+ # Apply component modifiers
+ if com_modifiers:
+ sk_ob = convert_object_to_mesh(_ob1)
+ sk_data = sk_ob.data
+ source = sk_data.vertices
+ else:
+ source = sk.data
+
+ shapekeys = []
+ for v in source:
+ if mode == 'BOUNDS':
+ vert = v.co - min_c
+ vert[0] = (vert[0] / bb[0] if bb[0] != 0 else 0.5)
+ vert[1] = (vert[1] / bb[1] if bb[1] != 0 else 0.5)
+ vert[2] = (vert[2] / bb[2] if bb[2] != 0 else vert[2])
+ vert[2] = (vert[2] - 0.5 + offset * 0.5) * zscale
+ elif mode == 'LOCAL':
+ vert = v.co.xyz
+ vert[2] *= zscale
+ #vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \
+ # zscale
+ elif mode == 'GLOBAL':
+ vert = v.co.xyz
+ #vert = ob1.matrix_world @ v.co
+ vert[2] *= zscale
+ shapekeys.append(vert)
+
+ # Component vertices
+ key1 = np.array([v for v in shapekeys]).reshape(len(shapekeys), 3, 1)
+ vx_key.append(key1[:, 0])
+ vy_key.append(key1[:, 1])
+ vz_key.append(key1[:, 2])
+ #sk_np.append([])
+
+ # All vertex group
+ if bool_vertex_group or rotation_mode == 'WEIGHT':
+ try:
+ weight = []
+ for vg in ob0.vertex_groups:
+ _weight = []
+ for i,v in enumerate(me0.vertices):
+ try:
+ _weight.append(vg.weight(i))
+ except:
+ _weight.append(0)
+ weight.append(_weight)
+ except:
+ bool_vertex_group = False
+
+ # Adaptive Z
+ if scale_mode == 'ADAPTIVE':
+ com_area = bb[0]*bb[1]
+ if mode != 'BOUNDS' or com_area == 0: com_area = 1
+ verts_area = []
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ for v in bm.verts:
+ area = 0
+ faces = v.link_faces
+ for f in faces:
+ area += f.calc_area()
+ try:
+ area/=len(faces) # average area
+ area/=com_area
+ verts_area.append(sqrt(area)*bb[2])
+ #verts_area.append(area)
+ except:
+ verts_area.append(1)
+
+ count = 0 # necessary for UV calculation
+
+ # TESSELLATION
+ j = 0
+ jj = -1
+ bool_correct = False
+
+ # optimization test
+ n_faces = len(base_polygons)
+ _vs0 = [0]*n_faces
+ _nvs0 = [0]*n_faces
+ _sz = [0]*n_faces
+ n_vg = len(ob0.vertex_groups)
+ _w0 = [[0]*n_faces for i in range(n_vg)]
+ np_faces = [np.array(p) for p in fs1]
+ new_faces = [0]*n_faces*n_faces1
+ face1_count = 0
+
+ for j, p in enumerate(base_polygons):
+
+ bool_correct = True
+ if rotation_mode in ['UV', 'WEIGHT'] and ob0.type != 'MESH':
+ rotation_mode = 'DEFAULT'
+
+ ordered = p.vertices
+
+ # Random rotation
+ if rotation_mode == 'RANDOM':
+ shifted_vertices = []
+ n_poly_verts = len(p.vertices)
+ rand = random.randint(0, n_poly_verts)
+ for i in range(n_poly_verts):
+ shifted_vertices.append(p.vertices[(i + rand) % n_poly_verts])
+ if scale_mode == 'ADAPTIVE':
+ verts_area0 = np.array([verts_area[i] for i in shifted_vertices])
+ ordered = shifted_vertices
+
+ # UV rotation
+ elif rotation_mode == 'UV':
+ if len(ob0.data.uv_layers) > 0:
+ i = p.index
+ if bool_material_id:
+ count = sum([len(p.vertices) for p in me0.polygons[:i]])
+ #if i == 0: count = 0
+ v01 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count + 1].uv)
+ if len(p.vertices) > 3:
+ v32 = (me0.uv_layers.active.data[count + 3].uv +
+ me0.uv_layers.active.data[count + 2].uv)
+ else:
+ v32 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count + 2].uv)
+ v0132 = v32 - v01
+ v0132.normalize()
+
+ v12 = (me0.uv_layers.active.data[count + 1].uv +
+ me0.uv_layers.active.data[count + 2].uv)
+ if len(p.vertices) > 3:
+ v03 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count + 3].uv)
+ else:
+ v03 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count].uv)
+ v1203 = v03 - v12
+ v1203.normalize()
+
+ vertUV = []
+ dot1203 = v1203.x
+ dot0132 = v0132.x
+ if(abs(dot1203) < abs(dot0132)):
+ if (dot0132 > 0):
+ vertUV = p.vertices[1:] + p.vertices[:1]
+ else:
+ vertUV = p.vertices[3:] + p.vertices[:3]
+ else:
+ if(dot1203 < 0):
+ vertUV = p.vertices[:]
+ else:
+ vertUV = p.vertices[2:] + p.vertices[:2]
+ ordered = vertUV
+ count += len(p.vertices)
+
+ # Weight Rotation
+ elif rotation_mode == 'WEIGHT':
+ if len(weight) > 0:
+ active_weight = weight[ob0.vertex_groups.active_index]
+ i = p.index
+ face_weights = [active_weight[v] for v in p.vertices]
+ face_weights*=2
+ if rotation_direction == 'DIAG':
+ differential = [face_weights[ii]-face_weights[ii+2] for ii in range(4)]
+ else:
+ differential = [face_weights[ii]+face_weights[ii+1]-face_weights[ii+2]- face_weights[ii+3] for ii in range(4)]
+ starting = differential.index(max(differential))
+
+ ordered = p.vertices[starting:] + p.vertices[:starting]
+
+ if rotation_mode != 'RANDOM':
+ ordered = np.roll(np.array(ordered),rotation_shift)
+ ordered = np.array((ordered[0], ordered[1], ordered[2], ordered[-1]))
+
+ # assign vertices and values
+ vs0 = np.array([verts0[i].co for i in ordered])
+ #nvs0 = np.array([verts0[i].normal for i in ordered])
+ nvs0 = np.array([normals0[i] for i in ordered])
+ if scale_mode == 'ADAPTIVE':
+ np_verts_area = np.array([verts_area[i] for i in ordered])
+ _sz[j] = np_verts_area
+ # Vertex weight
+ if bool_vertex_group:
+ ws0 = []
+ for w in weight:
+ _ws0 = []
+ for i in ordered:
+ try:
+ _ws0.append(w[i])
+ except:
+ _ws0.append(0)
+ ws0.append(np.array(_ws0))
+
+ # optimization test
+ _vs0[j] = (vs0[0], vs0[1], vs0[2], vs0[-1])
+ if normals_mode != 'FACES':
+ _nvs0[j] = (nvs0[0], nvs0[1], nvs0[2], nvs0[-1])
+
+ if bool_vertex_group:
+ for i_vg, ws0_face in enumerate(ws0):
+ _w0[i_vg][j] = (ws0_face[0], ws0_face[1], ws0_face[2], ws0_face[-1])
+
+ for p in fs1:
+ new_faces[face1_count] = [i + n_verts1 * j for i in p]
+ face1_count += 1
+
+ # build edges list
+ n_edges1 = new_edges.shape[0]
+ new_edges = new_edges.reshape((1, n_edges1, 2))
+ new_edges = new_edges.repeat(n_faces,axis=0)
+ new_edges = new_edges.reshape((n_edges1*n_faces, 2))
+ increment = np.arange(n_faces)*n_verts1
+ increment = increment.repeat(n_edges1, axis=0)
+ increment = increment.reshape((n_faces*n_edges1,1))
+ new_edges = new_edges + increment
+
+ # optimization test
+ _vs0 = np.array(_vs0)
+ _sz = np.array(_sz)
+
+ _vs0_0 = _vs0[:,0].reshape((n_faces,1,3))
+ _vs0_1 = _vs0[:,1].reshape((n_faces,1,3))
+ _vs0_2 = _vs0[:,2].reshape((n_faces,1,3))
+ _vs0_3 = _vs0[:,3].reshape((n_faces,1,3))
+
+ # remapped vertex coordinates
+ v2 = np_lerp2(_vs0_0, _vs0_1, _vs0_3, _vs0_2, vx, vy)
+
+ # remapped vertex normal
+ if normals_mode != 'FACES':
+ _nvs0 = np.array(_nvs0)
+ _nvs0_0 = _nvs0[:,0].reshape((n_faces,1,3))
+ _nvs0_1 = _nvs0[:,1].reshape((n_faces,1,3))
+ _nvs0_2 = _nvs0[:,2].reshape((n_faces,1,3))
+ _nvs0_3 = _nvs0[:,3].reshape((n_faces,1,3))
+ nv2 = np_lerp2(_nvs0_0, _nvs0_1, _nvs0_3, _nvs0_2, vx, vy)
+ else:
+ nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
+
+ # interpolate vertex groups
+ if bool_vertex_group:
+ w = np.array(_w0)
+ w_0 = w[:,:,0].reshape((n_vg, n_faces,1,1))
+ w_1 = w[:,:,1].reshape((n_vg, n_faces,1,1))
+ w_2 = w[:,:,2].reshape((n_vg, n_faces,1,1))
+ w_3 = w[:,:,3].reshape((n_vg, n_faces,1,1))
+ # remapped weight
+ w = np_lerp2(w_0, w_1, w_3, w_2, vx, vy)
+ w = w.reshape((n_vg, n_faces*n_verts1))
+
+ if scale_mode == 'ADAPTIVE':
+ _sz_0 = _sz[:,0].reshape((n_faces,1,1))
+ _sz_1 = _sz[:,1].reshape((n_faces,1,1))
+ _sz_2 = _sz[:,2].reshape((n_faces,1,1))
+ _sz_3 = _sz[:,3].reshape((n_faces,1,1))
+ # remapped z scale
+ sz2 = np_lerp2(_sz_0, _sz_1, _sz_3, _sz_2, vx, vy)
+ v3 = v2 + nv2 * vz * sz2
+ else:
+ v3 = v2 + nv2 * vz
+
+ new_verts_np = v3.reshape((n_faces*n_verts1,3))
+
+ if bool_shapekeys:
+ n_sk = len(vx_key)
+ sk_np = [0]*n_sk
+ for i in range(n_sk):
+ vx = np.array(vx_key[i])
+ vy = np.array(vy_key[i])
+ vz = np.array(vz_key[i])
+
+ # remapped vertex coordinates
+ v2 = np_lerp2(_vs0_0, _vs0_1, _vs0_3, _vs0_2, vx, vy)
+
+ # remapped vertex normal
+ if normals_mode != 'FACES':
+ nv2 = np_lerp2(_nvs0_0, _nvs0_1, _nvs0_3, _nvs0_2, vx, vy)
+ else:
+ nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
+
+ if scale_mode == 'ADAPTIVE':
+ # remapped z scale
+ sz2 = np_lerp2(_sz_0, _sz_1, _sz_3, _sz_2, vx, vy)
+ v3 = v2 + nv2 * vz * sz2
+ else:
+ v3 = v2 + nv2 * vz
+
+ sk_np[i] = v3.reshape((n_faces*n_verts1,3))
+
+ #if ob0.type == 'MESH': ob0.data = old_me0
+
+ if not bool_correct:
+ #bpy.data.objects.remove(ob1)
+ return 0
+
+ new_verts = new_verts_np.tolist()
+ new_name = ob0.name + "_" + ob1.name
+ new_me = bpy.data.meshes.new(new_name)
+ new_me.from_pydata(new_verts, new_edges.tolist(), new_faces)
+ new_me.update(calc_edges=True)
+ new_ob = bpy.data.objects.new("tessellate_temp", new_me)
+
+ # vertex group
+ if bool_vertex_group and False:
+ for vg in ob0.vertex_groups:
+ new_ob.vertex_groups.new(name=vg.name)
+ for i in range(len(vg_np[vg.index])):
+ new_ob.vertex_groups[vg.name].add([i], vg_np[vg.index][i],"ADD")
+ # vertex group
+ if bool_vertex_group:
+ for vg in ob0.vertex_groups:
+ new_ob.vertex_groups.new(name=vg.name)
+ for i, vertex_weight in enumerate(w[vg.index]):
+ new_ob.vertex_groups[vg.name].add([i], vertex_weight,"ADD")
+
+ if bool_shapekeys:
+ basis = com_modifiers
+ sk_count = 0
+ for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
+ sk.value = val
+ new_ob.shape_key_add(name=sk.name, from_mix=False)
+ new_ob.data.shape_keys.key_blocks[sk.name].value = val
+ # set shape keys vertices
+ sk_data = new_ob.data.shape_keys.key_blocks[sk.name].data
+ if sk_count == 0:
+ sk_count += 1
+ continue
+ for id in range(len(sk_data)):
+ sk_data[id].co = sk_np[sk_count-1][id]
+ sk_count += 1
+ if bool_vertex_group:
+ for sk in new_ob.data.shape_keys.key_blocks:
+ for vg in new_ob.vertex_groups:
+ if sk.name == vg.name:
+ sk.vertex_group = vg.name
+
+ # EDGES SEAMS
+ edge_data = [0]*n_edges1
+ me1.edges.foreach_get("use_seam",edge_data)
+ if any(edge_data):
+ edge_data = edge_data*n_faces
+ new_ob.data.edges.foreach_set("use_seam",edge_data)
+
+ # EDGES SHARP
+ edge_data = [0]*n_edges1
+ me1.edges.foreach_get("use_edge_sharp",edge_data)
+ if any(edge_data):
+ edge_data = edge_data*n_faces
+ new_ob.data.edges.foreach_set("use_edge_sharp",edge_data)
+
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.context.collection.objects.link(new_ob)
+ new_ob.select_set(True)
+ bpy.context.view_layer.objects.active = new_ob
+
+ # EDGES BEVEL
+ edge_data = [0]*n_edges1
+ me1.edges.foreach_get("bevel_weight",edge_data)
+ if any(edge_data):
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.transform.edge_bevelweight(value=1)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ edge_data = edge_data*n_faces
+ new_ob.data.edges.foreach_set("bevel_weight",edge_data)
+
+ # EDGE CREASES
+ edge_data = [0]*n_edges1
+ me1.edges.foreach_get("crease",edge_data)
+ if any(edge_data):
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.transform.edge_crease(value=1)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ edge_data = edge_data*n_faces
+ new_ob.data.edges.foreach_set('crease', edge_data)
+
+ # MATERIALS
+ for slot in ob1.material_slots: new_ob.data.materials.append(slot.material)
+
+ polygon_materials = [0]*n_faces1
+ me1.polygons.foreach_get("material_index", polygon_materials)
+ polygon_materials *= n_faces
+ new_ob.data.polygons.foreach_set("material_index", polygon_materials)
+ new_ob.data.update() ###
+
+ try:
+ bpy.data.objects.remove(new_ob1)
+ except: pass
+
+ bpy.data.objects.remove(ob0)
+ bpy.data.meshes.remove(me0)
+ bpy.data.objects.remove(ob1)
+ bpy.data.meshes.remove(me1)
+
+ return new_ob
+
+
+class tissue_tessellate(Operator):
+ bl_idname = "object.tissue_tessellate"
+ bl_label = "Tessellate"
+ bl_description = ("Create a copy of selected object on the active object's "
+ "faces, adapting the shape to the different faces")
+ bl_options = {'REGISTER', 'UNDO'}
+
+
+ bool_hold : BoolProperty(
+ name="Hold",
+ description="Wait...",
+ default=False
+ )
+ bool_lock : BoolProperty(
+ name="Lock",
+ description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.",
+ default=False
+ )
+ bool_dependencies : BoolProperty(
+ name="Update Dependencies",
+ description="Automatically updates base and components as well, if results of other tessellations",
+ default=False
+ )
+ object_name : StringProperty(
+ name="",
+ description="Name of the generated object"
+ )
+ zscale : FloatProperty(
+ name="Scale",
+ default=1,
+ soft_min=0,
+ soft_max=10,
+ description="Scale factor for the component thickness"
+ )
+ scale_mode : EnumProperty(
+ items=(
+ ('CONSTANT', "Constant", "Uniform thickness"),
+ ('ADAPTIVE', "Relative", "Preserve component's proportions")
+ ),
+ default='ADAPTIVE',
+ name="Z-Scale according to faces size"
+ )
+ offset : FloatProperty(
+ name="Surface Offset",
+ default=1,
+ min=-1, max=1,
+ soft_min=-1,
+ soft_max=1,
+ description="Surface offset"
+ )
+ mode : EnumProperty(
+ items=(
+ ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
+ ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
+ ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
+ default='BOUNDS',
+ name="Component Mode"
+ )
+ rotation_mode : EnumProperty(
+ items=(('RANDOM', "Random", "Random faces rotation"),
+ ('UV', "Active UV", "Face rotation is based on UV coordinates"),
+ ('WEIGHT', "Active Weight", "Rotate according to Vertex Group gradient"),
+ ('DEFAULT', "Default", "Default rotation")),
+ default='DEFAULT',
+ name="Component Rotation"
+ )
+ rotation_direction : EnumProperty(
+ items=(('ORTHO', "Orthogonal", "Component main directions in XY"),
+ ('DIAG', "Diagonal", "Component main direction aligned with diagonal")),
+ default='ORTHO',
+ name="Direction"
+ )
+ rotation_shift : IntProperty(
+ name="Shift",
+ default=0,
+ soft_min=0,
+ soft_max=3,
+ description="Shift components rotation"
+ )
+ fill_mode : EnumProperty(
+ items=(
+ ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
+ ('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
+ ('PATCH', 'Patch', 'Curved tessellation according to the last ' +
+ 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
+ 'patches.\nAfter the last Subsurf (or Multires) only ' +
+ 'deformation\nmodifiers can be used'),
+ ('FRAME', 'Frame', 'Essellation along the edges of each face')),
+ default='QUAD',
+ name="Fill Mode"
+ )
+ combine_mode : EnumProperty(
+ items=(
+ ('LAST', 'Last', 'Show only the last iteration'),
+ ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
+ ('ALL', 'All', 'Combine the result of all iterations')),
+ default='LAST',
+ name="Combine Mode",
+ )
+ gen_modifiers : BoolProperty(
+ name="Generator Modifiers",
+ default=False,
+ description="Apply Modifiers and Shape Keys to the base object"
+ )
+ com_modifiers : BoolProperty(
+ name="Component Modifiers",
+ default=False,
+ description="Apply Modifiers and Shape Keys to the component object"
+ )
+ merge : BoolProperty(
+ name="Merge",
+ default=False,
+ description="Merge vertices in adjacent duplicates"
+ )
+ merge_thres : FloatProperty(
+ name="Distance",
+ default=0.001,
+ soft_min=0,
+ soft_max=10,
+ description="Limit below which to merge vertices"
+ )
+ bool_random : BoolProperty(
+ name="Randomize",
+ default=False,
+ description="Randomize component rotation"
+ )
+ random_seed : IntProperty(
+ name="Seed",
+ default=0,
+ soft_min=0,
+ soft_max=10,
+ description="Random seed"
+ )
+ bool_vertex_group : BoolProperty(
+ name="Map Vertex Groups",
+ default=False,
+ description="Transfer all Vertex Groups from Base object"
+ )
+ bool_selection : BoolProperty(
+ name="On selected Faces",
+ default=False,
+ description="Create Tessellation only on selected faces"
+ )
+ bool_shapekeys : BoolProperty(
+ name="Use Shape Keys",
+ default=False,
+ description="Transfer Component's Shape Keys. If the name of Vertex "
+ "Groups and Shape Keys are the same, they will be "
+ "automatically combined"
+ )
+ bool_smooth : BoolProperty(
+ name="Smooth Shading",
+ default=False,
+ description="Output faces with smooth shading rather than flat shaded"
+ )
+ bool_materials : BoolProperty(
+ name="Transfer Materials",
+ default=True,
+ description="Preserve component's materials"
+ )
+ generator : StringProperty(
+ name="",
+ description="Base object for the tessellation",
+ default = ""
+ )
+ component : StringProperty(
+ name="",
+ description="Component object for the tessellation",
+ default = ""
+ )
+ bool_material_id : BoolProperty(
+ name="Tessellation on Material ID",
+ default=False,
+ description="Apply the component only on the selected Material"
+ )
+ bool_dissolve_seams : BoolProperty(
+ name="Dissolve Seams",
+ default=False,
+ description="Dissolve all seam edges"
+ )
+ material_id : IntProperty(
+ name="Material ID",
+ default=0,
+ min=0,
+ description="Material ID"
+ )
+ iterations : IntProperty(
+ name="Iterations",
+ default=1,
+ min=1,
+ soft_max=5,
+ description="Automatically repeat the Tessellation using the "
+ + "generated geometry as new base object.\nUsefull for "
+ + "for branching systems. Dangerous!"
+ )
+ bool_combine : BoolProperty(
+ name="Combine unused",
+ default=False,
+ description="Combine the generated geometry with unused faces"
+ )
+ bool_advanced : BoolProperty(
+ name="Advanced Settings",
+ default=False,
+ description="Show more settings"
+ )
+ normals_mode : EnumProperty(
+ items=(
+ ('VERTS', 'Normals', 'Consistent direction based on vertices normal'),
+ ('FACES', 'Individual Faces', 'Based on individual faces normal'),
+ ('CUSTOM', 'Custom', "According to Base object's shape keys")),
+ default='VERTS',
+ name="Direction"
+ )
+ bool_multi_components : BoolProperty(
+ name="Multi Components",
+ default=False,
+ description="Combine different components according to materials name"
+ )
+ bounds_x : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default X coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
+ default='EXTEND',
+ name="Bounds X",
+ )
+ bounds_y : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default Y coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
+ default='EXTEND',
+ name="Bounds Y",
+ )
+ close_mesh : EnumProperty(
+ items=(
+ ('NONE', 'None', 'Keep the mesh open'),
+ ('CAP', 'Cap Holes', 'Automatically cap open loops'),
+ ('BRIDGE', 'Bridge Loops', 'Automatically bridge loop pairs')),
+ default='NONE',
+ name="Close Mesh"
+ )
+ cap_faces : BoolProperty(
+ name="Cap Holes",
+ default=False,
+ description="Cap open edges loops"
+ )
+ frame_boundary : BoolProperty(
+ name="Frame Boundary",
+ default=False,
+ description="Support face boundaries"
+ )
+ fill_frame : BoolProperty(
+ name="Fill Frame",
+ default=False,
+ description="Fill inner faces with Fan tessellation"
+ )
+ frame_boundary_mat : IntProperty(
+ name="Material Offset",
+ default=0,
+ description="Material Offset for boundaries"
+ )
+ fill_frame_mat : IntProperty(
+ name="Material Offset",
+ default=0,
+ description="Material Offset for inner faces"
+ )
+ open_edges_crease : FloatProperty(
+ name="Open Edges Crease",
+ default=0,
+ min=0,
+ max=1,
+ description="Automatically set crease for open edges"
+ )
+ bridge_smoothness : FloatProperty(
+ name="Smoothness",
+ default=1,
+ min=0,
+ max=1,
+ description="Bridge Smoothness"
+ )
+ frame_thickness : FloatProperty(
+ name="Frame Thickness",
+ default=0.2,
+ min=0,
+ soft_max=2,
+ description="Frame Thickness"
+ )
+ frame_mode : EnumProperty(
+ items=(
+ ('CONSTANT', 'Constant', 'Even thickness'),
+ ('RELATIVE', 'Relative', 'Frame offset depends on face areas')),
+ default='CONSTANT',
+ name="Offset"
+ )
+ bridge_cuts : IntProperty(
+ name="Cuts",
+ default=0,
+ min=0,
+ max=20,
+ description="Bridge Cuts"
+ )
+ cap_material_index : IntProperty(
+ name="Material",
+ default=0,
+ min=0,
+ description="Material index for the cap/bridge faces"
+ )
+ patch_subs : IntProperty(
+ name="Patch Subdivisions",
+ default=1,
+ min=0,
+ description="Subdivisions levels for Patch tessellation after the first iteration"
+ )
+ working_on = ""
+
+ def draw(self, context):
+ allowed_obj = ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
+ '''
+ try:
+ bool_working = self.working_on == self.object_name and \
+ self.working_on != ""
+ except:
+ bool_working = False
+ '''
+
+ bool_working = False
+ bool_allowed = False
+ ob0 = None
+ ob1 = None
+
+ sel = bpy.context.selected_objects
+ if len(sel) == 1:
+ try:
+ ob0 = sel[0].tissue_tessellate.generator
+ ob1 = sel[0].tissue_tessellate.component
+ self.generator = ob0.name
+ self.component = ob1.name
+ if self.working_on == '':
+ load_parameters(self,sel[0])
+ self.working_on = sel[0].name
+ bool_working = True
+ bool_allowed = True
+ except:
+ pass
+
+ if len(sel) == 2:
+ bool_allowed = True
+ for o in sel:
+ if o.type not in allowed_obj:
+ bool_allowed = False
+
+ if len(sel) != 2 and not bool_working:
+ layout = self.layout
+ layout.label(icon='INFO')
+ layout.label(text="Please, select two different objects")
+ layout.label(text="Select first the Component object, then select")
+ layout.label(text="the Base object.")
+ elif not bool_allowed and not bool_working:
+ layout = self.layout
+ layout.label(icon='INFO')
+ layout.label(text="Only Mesh, Curve, Surface or Text objects are allowed")
+ else:
+ if ob0 == ob1 == None:
+ ob0 = bpy.context.active_object
+ self.generator = ob0.name
+ for o in sel:
+ if o != ob0:
+ ob1 = o
+ self.component = o.name
+ self.no_component = False
+ break
+
+ # new object name
+ if self.object_name == "":
+ if self.generator == "":
+ self.object_name = "Tessellation"
+ else:
+ #self.object_name = self.generator + "_Tessellation"
+ self.object_name = "Tessellation"
+
+ layout = self.layout
+ # Base and Component
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.label(text="BASE : " + self.generator)
+ row.label(text="COMPONENT : " + self.component)
+
+ # Base Modifiers
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(self, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
+ base = bpy.data.objects[self.generator]
+ try:
+ if not (base.modifiers or base.data.shape_keys):
+ col2.enabled = False
+ self.gen_modifiers = False
+ except:
+ col2.enabled = False
+ self.gen_modifiers = False
+
+ # Component Modifiers
+ row.separator()
+ col3 = row.column(align=True)
+ col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
+ component = bpy.data.objects[self.component]
+ try:
+ if not (component.modifiers or component.data.shape_keys):
+ col3.enabled = False
+ self.com_modifiers = False
+ except:
+ col3.enabled = False
+ self.com_modifiers = False
+ col.separator()
+ # Fill and Rotation
+ row = col.row(align=True)
+ row.label(text="Fill Mode:")
+ row = col.row(align=True)
+ row.prop(
+ self, "fill_mode", icon='NONE', expand=True,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ row = col.row(align=True)
+ row.prop(self, "bool_smooth")
+
+ # frame settings
+ if self.fill_mode == 'FRAME':
+ col.separator()
+ col.label(text="Frame Settings:")
+ row = col.row(align=True)
+ row.prop(self, "frame_mode", expand=True)
+ col.prop(self, "frame_thickness", text='Thickness', icon='NONE')
+ col.separator()
+ row = col.row(align=True)
+ row.prop(self, "fill_frame", icon='NONE')
+ show_frame_mat = self.bool_multi_components or self.bool_material_id
+ if self.fill_frame and show_frame_mat:
+ row.prop(self, "fill_frame_mat", icon='NONE')
+ row = col.row(align=True)
+ row.prop(self, "frame_boundary", text='Boundary', icon='NONE')
+ if self.frame_boundary and show_frame_mat:
+ row.prop(self, "frame_boundary_mat", icon='NONE')
+
+ if self.rotation_mode == 'UV':
+ uv_error = False
+
+ if ob0.type != 'MESH':
+ row = col.row(align=True)
+ row.label(
+ text="UV rotation supported only for Mesh objects",
+ icon='ERROR')
+ uv_error = True
+ else:
+ if len(ob0.data.uv_layers) == 0:
+ row = col.row(align=True)
+ check_name = self.generator
+ row.label(text="'" + check_name +
+ "' doesn't have UV Maps", icon='ERROR')
+ uv_error = True
+ if uv_error:
+ row = col.row(align=True)
+ row.label(text="Default rotation will be used instead",
+ icon='INFO')
+
+ # Component Z
+ col.separator()
+ col.label(text="Thickness:")
+ row = col.row(align=True)
+ row.prop(
+ self, "scale_mode", text="Scale Mode", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ col.prop(
+ self, "zscale", text="Scale", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ if self.mode == 'BOUNDS':
+ col.prop(
+ self, "offset", text="Offset", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ # Component XY
+ col.separator()
+ row = col.row(align=True)
+ row.label(text="Component Coordinates:")
+ row = col.row(align=True)
+ row.prop(
+ self, "mode", text="Component XY", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ if self.mode != 'BOUNDS':
+ col.separator()
+ row = col.row(align=True)
+ row.label(text="X:")
+ row.prop(
+ self, "bounds_x", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ row = col.row(align=True)
+ row.label(text="Y:")
+ row.prop(
+ self, "bounds_y", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+
+ # merge settings
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.prop(self, "merge")
+ if self.merge:
+ row.prop(self, "merge_thres")
+ col.separator()
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.label(text='Close Mesh:')
+ col2 = row.column(align=True)
+ col2.prop(self, "close_mesh",text='')
+ if self.close_mesh != 'NONE':
+ row = col.row(align=True)
+ row.prop(self, "open_edges_crease", text="Crease")
+ row.prop(self, "cap_material_index")
+ if self.close_mesh == 'BRIDGE':
+ row = col.row(align=True)
+ row.prop(self, "bridge_cuts")
+ row.prop(self, "bridge_smoothness")
+ row = col.row(align=True)
+ row.prop(self, "bool_dissolve_seams")
+
+ # Advanced Settings
+ col = layout.column(align=True)
+ col.separator()
+ col.separator()
+ row = col.row(align=True)
+ row.prop(self, "bool_advanced", icon='SETTINGS')
+ if self.bool_advanced:
+ # rotation
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+ col = layout.column(align=True)
+ col.prop(self, "rotation_mode", text='Rotation', icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ if self.rotation_mode == 'WEIGHT':
+ col.prop(self, "rotation_direction", expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ if self.rotation_mode == 'RANDOM':
+ col.prop(self, "random_seed")
+ else:
+ col.prop(self, "rotation_shift")
+
+ if self.rotation_mode == 'UV':
+ uv_error = False
+ if self.generator.type != 'MESH':
+ row = col.row(align=True)
+ row.label(
+ text="UV rotation supported only for Mesh objects",
+ icon='ERROR')
+ uv_error = True
+ else:
+ if len(self.generator.data.uv_layers) == 0:
+ row = col.row(align=True)
+ row.label(text="'" + props.generator.name +
+ " doesn't have UV Maps", icon='ERROR')
+ uv_error = True
+ if uv_error:
+ row = col.row(align=True)
+ row.label(text="Default rotation will be used instead",
+ icon='INFO')
+ layout.use_property_split = False
+
+ # Direction
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.label(text="Direction:")
+ row = col.row(align=True)
+ row.prop(
+ self, "normals_mode", text="Direction", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ #row.enabled = self.fill_mode != 'PATCH'
+
+ allow_multi = False
+ allow_shapekeys = not self.com_modifiers
+ if self.com_modifiers: self.bool_shapekeys = False
+ for m in ob0.data.materials:
+ try:
+ o = bpy.data.objects[m.name]
+ allow_multi = True
+ try:
+ if o.data.shape_keys is None: continue
+ elif len(o.data.shape_keys.key_blocks) < 2: continue
+ else: allow_shapekeys = not self.com_modifiers
+ except: pass
+ except: pass
+ # DATA #
+ col = layout.column(align=True)
+ col.label(text="Weight and Morphing:")
+ # vertex group + shape keys
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_vertex_group", icon='GROUP_VERTEX')
+ try:
+ if len(ob0.vertex_groups) == 0:
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ row.separator()
+ col2 = row.column(align=True)
+ row2 = col2.row(align=True)
+ row2.prop(self, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
+ row2.enabled = allow_shapekeys
+
+ # LIMITED TESSELLATION
+ col = layout.column(align=True)
+ col.label(text="Limited Tessellation:")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_multi_components", icon='MOD_TINT')
+ if not allow_multi:
+ col2.enabled = False
+ self.bool_multi_components = False
+ col.separator()
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
+ row.separator()
+ if ob0.type != 'MESH':
+ col2.enabled = False
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_material_id", icon='MATERIAL_DATA', text="Material ID")
+ if self.bool_material_id and not self.bool_multi_components:
+ #col2 = row.column(align=True)
+ col2.prop(self, "material_id")
+ col2.enabled = not self.bool_multi_components
+
+ col.separator()
+ row = col.row(align=True)
+ row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
+ row.prop(self, 'iterations', text='Repeat', icon='SETTINGS')
+ if self.iterations > 1 and self.fill_mode == 'PATCH':
+ col.separator()
+ row = col.row(align=True)
+ row.prop(self, 'patch_subs')
+ col.separator()
+ row = col.row(align=True)
+ row.label(text='Combine Iterations:')
+ row = col.row(align=True)
+ row.prop(
+ self, "combine_mode", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ def execute(self, context):
+ allowed_obj = ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT')
+ try:
+ ob0 = bpy.data.objects[self.generator]
+ ob1 = bpy.data.objects[self.component]
+ except:
+ return {'CANCELLED'}
+
+ self.object_name = "Tessellation"
+ # Check if existing object with same name
+ names = [o.name for o in bpy.data.objects]
+ if self.object_name in names:
+ count_name = 1
+ while True:
+ test_name = self.object_name + '.{:03d}'.format(count_name)
+ if not (test_name in names):
+ self.object_name = test_name
+ break
+ count_name += 1
+
+ if ob1.type not in allowed_obj:
+ message = "Component must be Mesh, Curve, Surface, Text or Meta object!"
+ self.report({'ERROR'}, message)
+ self.component = None
+
+ if ob0.type not in allowed_obj:
+ message = "Generator must be Mesh, Curve, Surface, Text or Meta object!"
+ self.report({'ERROR'}, message)
+ self.generator = ""
+
+ if True:#self.component not in ("",None) and self.generator not in ("",None):
+ if bpy.ops.object.select_all.poll():
+ bpy.ops.object.select_all(action='TOGGLE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ #data0 = ob0.to_mesh(False)
+ #data0 = ob0.data.copy()
+ bool_update = False
+ if bpy.context.object == ob0:
+ auto_layer_collection()
+ #new_ob = bpy.data.objects.new(self.object_name, data0)
+ new_ob = convert_object_to_mesh(ob0,False,False)
+ new_ob.data.name = self.object_name
+ #bpy.context.collection.objects.link(new_ob)
+ #bpy.context.view_layer.objects.active = new_ob
+ new_ob.name = self.object_name
+ #new_ob.select_set(True)
+ else:
+ new_ob = bpy.context.object
+ bool_update = True
+ new_ob = store_parameters(self, new_ob)
+ try: bpy.ops.object.tissue_update_tessellate()
+ except RuntimeError as e:
+ bpy.data.objects.remove(new_ob)
+ self.report({'ERROR'}, str(e))
+ return {'CANCELLED'}
+ if not bool_update:
+ self.object_name = new_ob.name
+ #self.working_on = self.object_name
+ new_ob.location = ob0.location
+ new_ob.matrix_world = ob0.matrix_world
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self)
+
+
+def update_dependencies(ob, objects):
+ ob0 = ob.tissue_tessellate.generator
+ ob1 = ob.tissue_tessellate.component
+ deps = [ob0, ob1]
+ for o in deps:
+ if o.tissue_tessellate.bool_lock: continue
+ o0 = o.tissue_tessellate.generator
+ o1 = o.tissue_tessellate.component
+ deps_deps = [o0, o1]
+ try:
+ o0.name
+ o1.name
+ if o0 not in objects and o1 not in objects:
+ objects.append(o)
+ objects = update_dependencies(o, objects)
+ except:
+ continue
+ return objects
+
+
+class tissue_refresh_tessellate(Operator):
+ bl_idname = "object.tissue_refresh_tessellate"
+ bl_label = "Refresh"
+ bl_description = ("Fast update the tessellated mesh according to base and "
+ "component changes")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ go = False
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ return context.object.tissue_tessellate.generator != None and \
+ context.object.tissue_tessellate.component != None
+ except:
+ return False
+
+ @staticmethod
+ def check_gen_comp(checking):
+ # note pass the stored name key in here to check it out
+ return checking in bpy.data.objects.keys()
+
+ def execute(self, context):
+ ob = bpy.context.object
+ ob0 = ob.tissue_tessellate.generator
+ ob1 = ob.tissue_tessellate.component
+ try:
+ ob0.name
+ ob1.name
+ except:
+ self.report({'ERROR'},
+ "Active object must be Tessellate before Update")
+ return {'CANCELLED'}
+
+ if ob.tissue_tessellate.bool_dependencies:
+ update_objects = list(reversed(update_dependencies(ob, [ob])))
+ else:
+ update_objects = [ob]
+ for o in update_objects:
+ override = {'object': o}
+ bpy.ops.object.tissue_update_tessellate(override)
+
+ return {'FINISHED'}
+
+
+class tissue_update_tessellate(Operator):
+ bl_idname = "object.tissue_update_tessellate"
+ bl_label = "Refresh"
+ bl_description = ("Fast update the tessellated mesh according to base and "
+ "component changes")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ go = False
+
+ @classmethod
+ def poll(cls, context):
+ #try:
+ try: #context.object == None: return False
+ return context.object.tissue_tessellate.generator != None and \
+ context.object.tissue_tessellate.component != None
+ except:
+ return False
+
+ @staticmethod
+ def check_gen_comp(checking):
+ # note pass the stored name key in here to check it out
+ return checking in bpy.data.objects.keys()
+
+ def execute(self, context):
+ start_time = time.time()
+
+ ob = context.object
+ if not self.go:
+ generator = ob.tissue_tessellate.generator
+ component = ob.tissue_tessellate.component
+ zscale = ob.tissue_tessellate.zscale
+ scale_mode = ob.tissue_tessellate.scale_mode
+ rotation_mode = ob.tissue_tessellate.rotation_mode
+ rotation_shift = ob.tissue_tessellate.rotation_shift
+ rotation_direction = ob.tissue_tessellate.rotation_direction
+ offset = ob.tissue_tessellate.offset
+ merge = ob.tissue_tessellate.merge
+ merge_thres = ob.tissue_tessellate.merge_thres
+ gen_modifiers = ob.tissue_tessellate.gen_modifiers
+ com_modifiers = ob.tissue_tessellate.com_modifiers
+ bool_random = ob.tissue_tessellate.bool_random
+ random_seed = ob.tissue_tessellate.random_seed
+ fill_mode = ob.tissue_tessellate.fill_mode
+ bool_vertex_group = ob.tissue_tessellate.bool_vertex_group
+ bool_selection = ob.tissue_tessellate.bool_selection
+ bool_shapekeys = ob.tissue_tessellate.bool_shapekeys
+ mode = ob.tissue_tessellate.mode
+ bool_smooth = ob.tissue_tessellate.bool_smooth
+ bool_materials = ob.tissue_tessellate.bool_materials
+ bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams
+ bool_material_id = ob.tissue_tessellate.bool_material_id
+ material_id = ob.tissue_tessellate.material_id
+ iterations = ob.tissue_tessellate.iterations
+ bool_combine = ob.tissue_tessellate.bool_combine
+ normals_mode = ob.tissue_tessellate.normals_mode
+ bool_advanced = ob.tissue_tessellate.bool_advanced
+ bool_multi_components = ob.tissue_tessellate.bool_multi_components
+ combine_mode = ob.tissue_tessellate.combine_mode
+ bounds_x = ob.tissue_tessellate.bounds_x
+ bounds_y = ob.tissue_tessellate.bounds_y
+ cap_faces = ob.tissue_tessellate.cap_faces
+ close_mesh = ob.tissue_tessellate.close_mesh
+ open_edges_crease = ob.tissue_tessellate.open_edges_crease
+ bridge_smoothness = ob.tissue_tessellate.bridge_smoothness
+ frame_thickness = ob.tissue_tessellate.frame_thickness
+ frame_mode = ob.tissue_tessellate.frame_mode
+ frame_boundary = ob.tissue_tessellate.frame_boundary
+ fill_frame = ob.tissue_tessellate.fill_frame
+ frame_boundary_mat = ob.tissue_tessellate.frame_boundary_mat
+ fill_frame_mat = ob.tissue_tessellate.fill_frame_mat
+ bridge_cuts = ob.tissue_tessellate.bridge_cuts
+ cap_material_index = ob.tissue_tessellate.cap_material_index
+ patch_subs = ob.tissue_tessellate.patch_subs
+ try:
+ generator.name
+ component.name
+ except:
+ self.report({'ERROR'},
+ "Active object must be Tessellate before Update")
+ return {'CANCELLED'}
+
+ # Solve Local View issues
+ local_spaces = []
+ local_ob0 = []
+ local_ob1 = []
+ for area in context.screen.areas:
+ for space in area.spaces:
+ try:
+ if ob.local_view_get(space):
+ local_spaces.append(space)
+ local_ob0 = ob0.local_view_get(space)
+ ob0.local_view_set(space, True)
+ local_ob1 = ob1.local_view_get(space)
+ ob1.local_view_set(space, True)
+ except:
+ pass
+
+ starting_mode = context.object.mode
+
+ #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT'
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ ob0 = generator
+ ob1 = component
+ ##### auto_layer_collection()
+
+ ob0_hide = ob0.hide_get()
+ ob0_hidev = ob0.hide_viewport
+ ob0_hider = ob0.hide_render
+ ob1_hide = ob1.hide_get()
+ ob1_hidev = ob1.hide_viewport
+ ob1_hider = ob1.hide_render
+ ob0.hide_set(False)
+ ob0.hide_viewport = False
+ ob0.hide_render = False
+ ob1.hide_set(False)
+ ob1.hide_viewport = False
+ ob1.hide_render = False
+
+ if ob0.type == 'META':
+ base_ob = convert_object_to_mesh(ob0, False, True)
+ else:
+ base_ob = ob0.copy()
+ base_ob.data = ob0.data#
+ context.collection.objects.link(base_ob)
+ base_ob.name = '_tissue_tmp_base'
+
+ # In Blender 2.80 cache of copied objects is lost, must be re-baked
+ bool_update_cloth = False
+ for m in base_ob.modifiers:
+ if m.type == 'CLOTH':
+ m.point_cache.frame_end = context.scene.frame_current
+ bool_update_cloth = True
+ if bool_update_cloth:
+ bpy.ops.ptcache.free_bake_all()
+ bpy.ops.ptcache.bake_all()
+ base_ob.modifiers.update()
+
+
+ #new_ob.location = ob.location
+ #new_ob.matrix_world = ob.matrix_world
+ #bpy.ops.object.select_all(action='DESELECT')
+ if bool_selection:
+ faces = base_ob.data.polygons
+ selections = [False]*len(faces)
+ faces.foreach_get('select',selections)
+ selections = np.array(selections)
+ if not selections.any():
+ message = "There are no faces selected."
+ context.view_layer.objects.active = ob
+ ob.select_set(True)
+ bpy.ops.object.mode_set(mode=starting_mode)
+ bpy.data.objects.remove(base_ob)
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+
+
+
+ iter_objects = [base_ob]
+ ob_location = ob.location
+ ob_matrix_world = ob.matrix_world
+ #base_ob = new_ob#.copy()
+
+ for iter in range(iterations):
+
+ if iter > 0 and len(iter_objects) == 0: break
+ if iter > 0 and normals_mode == 'CUSTOM': normals_mode = 'VERTS'
+ same_iteration = []
+ matched_materials = []
+ # iterate base object materials (needed for multi-components)
+ if bool_multi_components: mat_iter = len(base_ob.material_slots)
+ else: mat_iter = 1
+ for m_id in range(mat_iter):
+ if bool_multi_components:
+ # check if material and components match
+ try:
+ mat = base_ob.material_slots[m_id].material
+ ob1 = bpy.data.objects[mat.name]
+ if ob1.type not in ('MESH', 'CURVE','SURFACE','FONT', 'META'):
+ continue
+ material_id = m_id
+ matched_materials.append(m_id)
+ bool_material_id = True
+ except:
+ continue
+ if com_modifiers or ob1.type != 'MESH':
+ data1 = simple_to_mesh(ob1)
+ else:
+ data1 = ob1.data.copy()
+ n_edges1 = len(data1.edges)
+ bpy.data.meshes.remove(data1)
+
+ if iter != 0: gen_modifiers = True
+
+ if fill_mode == 'PATCH':
+ # patch subdivisions for additional iterations
+ if iter > 0:
+ base_ob.modifiers.new('Tissue_Subsurf', type='SUBSURF')
+ base_ob.modifiers['Tissue_Subsurf'].levels = patch_subs
+ temp_mod = base_ob.modifiers['Tissue_Subsurf']
+ # patch tessellation
+ new_ob = tessellate_patch(
+ base_ob, ob1, offset, zscale, com_modifiers, mode, scale_mode,
+ rotation_mode, rotation_shift, random_seed, bool_vertex_group,
+ bool_selection, bool_shapekeys, bool_material_id, material_id,
+ normals_mode, bounds_x, bounds_y
+ )
+ if iter > 0:
+ base_ob.modifiers.remove(temp_mod)
+ else:
+ ### FRAME and FAN ###
+ if fill_mode in ('FRAME','FAN'):
+
+ if fill_mode == 'FRAME': convert_function = convert_to_frame
+ else: convert_function = convert_to_fan
+
+ if normals_mode == 'CUSTOM' and base_ob.data.shape_keys != None:
+ ## base key
+ sk_values = [sk.value for sk in base_ob.data.shape_keys.key_blocks]
+ for sk in ob0.data.shape_keys.key_blocks: sk.value = 0
+ _base_ob = convert_function(base_ob, ob.tissue_tessellate, gen_modifiers)
+ for i, sk in enumerate(ob0.data.shape_keys.key_blocks):
+ sk.value = sk_values[i]
+ ## key 1
+ # hide modifiers
+ if not gen_modifiers and len(base_ob.modifiers) > 0:
+ mod_visibility = [m.show_viewport for m in base_ob.modifiers]
+ for m in base_ob.modifiers: m.show_viewport = False
+ base_ob.modifiers.update()
+ base_ob_sk = convert_function(ob0, ob.tissue_tessellate, True)
+ ## combine shapekeys
+ _base_ob.shape_key_add(name='Basis', from_mix=False)
+ _base_ob.shape_key_add(name='Key1', from_mix=False)
+ sk_block = _base_ob.data.shape_keys.key_blocks[1]
+ sk_block.value = 1
+ for vert, sk in zip(base_ob_sk.data.vertices, sk_block.data):
+ sk.co = vert.co
+ bpy.data.objects.remove(base_ob_sk)
+ # set original modifiers
+ if not gen_modifiers and len(base_ob.modifiers) > 0:
+ for i,m in enumerate(base_ob.modifiers):
+ m.show_viewport = mod_visibility[i]
+ base_ob.modifiers.update()
+ else:
+ _base_ob = convert_function(base_ob, ob.tissue_tessellate, gen_modifiers)
+ bpy.data.objects.remove(base_ob)
+ base_ob = _base_ob
+ # quad tessellation
+ new_ob = tessellate_original(
+ base_ob, ob1, offset, zscale, gen_modifiers,
+ com_modifiers, mode, scale_mode, rotation_mode,
+ rotation_shift, rotation_direction,
+ random_seed, fill_mode, bool_vertex_group,
+ bool_selection, bool_shapekeys, bool_material_id,
+ material_id, normals_mode, bounds_x, bounds_y
+ )
+
+ # if empty or error, continue
+ if type(new_ob) is not bpy.types.Object:
+ continue
+
+ # prepare base object
+ if iter == 0 and gen_modifiers:
+ temp_base_ob = convert_object_to_mesh(base_ob, True, True)
+ bpy.data.objects.remove(base_ob)
+ base_ob = temp_base_ob
+ iter_objects = [base_ob]
+
+ # rename, make active and change transformations
+ new_ob.name = '_tissue_tmp_{}_{}'.format(iter,m_id)
+ new_ob.select_set(True)
+ context.view_layer.objects.active = new_ob
+ new_ob.location = ob_location
+ new_ob.matrix_world = ob_matrix_world
+
+ n_components = int(len(new_ob.data.edges) / n_edges1)
+ # SELECTION
+ if bool_selection:
+ try:
+ # create selection list
+ polygon_selection = [p.select for p in ob1.data.polygons] * int(
+ len(new_ob.data.polygons) / len(ob1.data.polygons))
+ new_ob.data.polygons.foreach_set("select", polygon_selection)
+ except:
+ pass
+ if bool_multi_components: same_iteration.append(new_ob)
+
+ base_ob.location = ob_location
+ base_ob.matrix_world = ob_matrix_world
+
+ # join together multiple components iterations
+ if bool_multi_components:
+ if len(same_iteration) > 0:
+ context.view_layer.update()
+ for o in context.view_layer.objects:
+ o.select_set(o in same_iteration)
+ bpy.ops.object.join()
+ new_ob = context.view_layer.objects.active
+ new_ob.select_set(True)
+ #new_ob.data.update()
+
+ if type(new_ob) in (int,str):
+ if iter == 0:
+ try:
+ bpy.data.objects.remove(iter_objects[0])
+ iter_objects = []
+ except: continue
+ continue
+
+ # Clean last iteration, needed for combine object
+ if (bool_selection or bool_material_id) and combine_mode == 'UNUSED':
+ # remove faces from last mesh
+ bm = bmesh.new()
+ last_mesh = iter_objects[-1].data.copy()
+ bm.from_mesh(last_mesh)
+ bm.faces.ensure_lookup_table()
+ if bool_multi_components:
+ remove_materials = matched_materials
+ elif bool_material_id:
+ remove_materials = [material_id]
+ else: remove_materials = []
+ if bool_selection:
+ if bool_multi_components or bool_material_id:
+ remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select]
+ else:
+ remove_faces = [f for f in bm.faces if f.select]
+ else:
+ remove_faces = [f for f in bm.faces if f.material_index in remove_materials]
+ bmesh.ops.delete(bm, geom=remove_faces, context='FACES')
+ bm.to_mesh(last_mesh)
+ last_mesh.update()
+ last_mesh.name = '_tissue_tmp_previous_unused'
+
+ # delete previous iteration if empty or update it
+ if len(last_mesh.vertices) > 0:
+ iter_objects[-1].data = last_mesh.copy()
+ iter_objects[-1].data.update()
+ else:
+ bpy.data.objects.remove(iter_objects[-1])
+ iter_objects = iter_objects[:-1]
+ # set new base object for next iteration
+ base_ob = convert_object_to_mesh(new_ob,True,True)
+ if iter < iterations-1: new_ob.data = base_ob.data
+ # store new iteration and set transformations
+ iter_objects.append(new_ob)
+ #try:
+ # bpy.data.objects.remove(bpy.data.objects['_tissue_tmp_base'])
+ #except:
+ # pass
+ base_ob.name = '_tissue_tmp_base'
+ elif combine_mode == 'ALL':
+ base_ob = new_ob.copy()
+ iter_objects.append(new_ob)
+ else:
+ if base_ob != new_ob:
+ bpy.data.objects.remove(base_ob)
+ base_ob = new_ob
+ iter_objects = [new_ob]
+
+ # Combine
+ if combine_mode != 'LAST' and len(iter_objects)>0:
+ if base_ob not in iter_objects and type(base_ob) == bpy.types.Object:
+ bpy.data.objects.remove(base_ob)
+ for o in context.view_layer.objects:
+ o.select_set(o in iter_objects)
+ bpy.ops.object.join()
+ new_ob.data.update()
+ iter_objects = [new_ob]
+
+ if merge:
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(
+ use_extend=False, use_expand=False, type='VERT')
+ bpy.ops.mesh.select_non_manifold(
+ extend=False, use_wire=True, use_boundary=True,
+ use_multi_face=False, use_non_contiguous=False, use_verts=False)
+
+ bpy.ops.mesh.remove_doubles(
+ threshold=merge_thres, use_unselected=False)
+
+ if bool_dissolve_seams:
+ bpy.ops.mesh.select_mode(type='EDGE')
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for e in new_ob.data.edges:
+ e.select = e.use_seam
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.dissolve_edges()
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ if close_mesh != 'NONE':
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(
+ use_extend=False, use_expand=False, type='EDGE')
+ bpy.ops.mesh.select_non_manifold(
+ extend=False, use_wire=False, use_boundary=True,
+ use_multi_face=False, use_non_contiguous=False, use_verts=False)
+ if open_edges_crease != 0:
+ bpy.ops.transform.edge_crease(value=open_edges_crease)
+ if close_mesh == 'CAP':
+ bpy.ops.mesh.edge_face_add()
+ if close_mesh == 'BRIDGE':
+ try:
+ bpy.ops.mesh.bridge_edge_loops(
+ type='PAIRS',
+ number_cuts=bridge_cuts,
+ interpolation='SURFACE',
+ smoothness=bridge_smoothness)
+ except: pass
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for f in new_ob.data.polygons:
+ if f.select: f.material_index = cap_material_index
+ base_ob = context.view_layer.objects.active
+
+ # Combine iterations
+ if combine_mode != 'LAST' and len(iter_objects)>0:
+ #if base_ob not in iter_objects and type(base_ob) == bpy.types.Object:
+ # bpy.data.objects.remove(base_ob)
+ for o in context.view_layer.objects:
+ o.select_set(o in iter_objects)
+ bpy.ops.object.join()
+ new_ob = context.view_layer.objects.active
+ elif combine_mode == 'LAST' and type(new_ob) != bpy.types.Object:
+ # if last iteration gives error, then use the last correct iteration
+ try:
+ if type(iter_objects[-1]) == bpy.types.Object:
+ new_ob = iter_objects[-1]
+ except: pass
+
+ if new_ob == 0:
+ #bpy.data.objects.remove(base_ob.data)
+ try: bpy.data.objects.remove(base_ob)
+ except: pass
+ message = "The generated object is an empty geometry!"
+ context.view_layer.objects.active = ob
+ ob.select_set(True)
+ bpy.ops.object.mode_set(mode=starting_mode)
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+ errors = {}
+ errors["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \
+ "after the last Subsurf (or Multires) are not allowed."
+ errors["topology_error"] = "Make sure that the topology of the mesh before \n" \
+ "the last Subsurf (or Multires) is quads only."
+ errors["wires_error"] = "Please remove all wire edges in the base object."
+ errors["verts_error"] = "Please remove all floating vertices in the base object"
+ if new_ob in errors:
+ for o in iter_objects:
+ try: bpy.data.objects.remove(o)
+ except: pass
+ try: bpy.data.meshes.remove(data1)
+ except: pass
+ context.view_layer.objects.active = ob
+ ob.select_set(True)
+ message = errors[new_ob]
+ ob.tissue_tessellate.error_message = message
+ bpy.ops.object.mode_set(mode=starting_mode)
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+
+ #new_ob.location = ob_location
+ #new_ob.matrix_world = ob_matrix_world
+
+ # update data and preserve name
+ if ob.type != 'MESH':
+ loc, matr = ob.location, ob.matrix_world
+ ob = convert_object_to_mesh(ob,False,True)
+ ob.location, ob.matrix_world = loc, matr
+ data_name = ob.data.name
+ old_data = ob.data
+ #ob.data = bpy.data.meshes.new_from_object(new_ob)#
+ ob.data = new_ob.data.copy()
+ ob.data.name = data_name
+ bpy.data.meshes.remove(old_data)
+
+ # copy vertex group
+ if bool_vertex_group:
+ for vg in new_ob.vertex_groups:
+ if not vg.name in ob.vertex_groups.keys():
+ ob.vertex_groups.new(name=vg.name)
+ new_vg = ob.vertex_groups[vg.name]
+ for i in range(len(ob.data.vertices)):
+ try:
+ weight = vg.weight(i)
+ except:
+ weight = 0
+ new_vg.add([i], weight, 'REPLACE')
+
+ selected_objects = [o for o in context.selected_objects]
+ for o in selected_objects: o.select_set(False)
+
+ ob.select_set(True)
+ context.view_layer.objects.active = ob
+
+ if merge:
+ try:
+ bpy.ops.object.mode_set(mode='EDIT')
+ #bpy.ops.mesh.select_mode(
+ # use_extend=False, use_expand=False, type='VERT')
+ bpy.ops.mesh.select_mode(type='VERT')
+ bpy.ops.mesh.select_non_manifold(
+ extend=False, use_wire=True, use_boundary=True,
+ use_multi_face=False, use_non_contiguous=False, use_verts=False)
+
+ bpy.ops.mesh.remove_doubles(
+ threshold=merge_thres, use_unselected=False)
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bool_dissolve_seams:
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='EDGE')
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for e in ob.data.edges:
+ e.select = e.use_seam
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.dissolve_edges()
+ except: pass
+ if close_mesh != 'NONE':
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(
+ use_extend=False, use_expand=False, type='EDGE')
+ bpy.ops.mesh.select_non_manifold(
+ extend=False, use_wire=False, use_boundary=True,
+ use_multi_face=False, use_non_contiguous=False, use_verts=False)
+ if open_edges_crease != 0:
+ bpy.ops.transform.edge_crease(value=open_edges_crease)
+ if close_mesh == 'CAP':
+ bpy.ops.mesh.edge_face_add()
+ if close_mesh == 'BRIDGE':
+ try:
+ bpy.ops.mesh.bridge_edge_loops(
+ type='PAIRS',
+ number_cuts=bridge_cuts,
+ interpolation='SURFACE',
+ smoothness=bridge_smoothness)
+ except:
+ pass
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for f in ob.data.polygons:
+ if f.select: f.material_index = cap_material_index
+ #else:
+
+ try:
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ except: pass
+
+ if bool_smooth: bpy.ops.object.shade_smooth()
+
+ for mesh in bpy.data.meshes:
+ if not mesh.users: bpy.data.meshes.remove(mesh)
+
+ for o in selected_objects:
+ try: o.select_set(True)
+ except: pass
+
+ bpy.ops.object.mode_set(mode=starting_mode)
+
+ ob.tissue_tessellate.error_message = ""
+
+ # Restore Base visibility
+ ob0.hide_set(ob0_hide)
+ ob0.hide_viewport = ob0_hidev
+ ob0.hide_render = ob0_hider
+ # Restore Component visibility
+ ob1.hide_set(ob1_hide)
+ ob1.hide_viewport = ob1_hidev
+ ob1.hide_render = ob1_hider
+ # Restore Local visibility
+ for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1):
+ ob0.local_view_set(space, local0)
+ ob1.local_view_set(space, local1)
+
+ bpy.data.objects.remove(new_ob)
+
+ # clean objects
+ for o in bpy.data.objects:
+ #if o.name not in context.view_layer.objects and "_tissue_tmp" in o.name:
+ if "_tissue_tmp" in o.name:
+ bpy.data.objects.remove(o)
+
+ end_time = time.time()
+ print('Tissue: object "{}" tessellated in {:.4f} sec'.format(ob.name, end_time-start_time))
+ return {'FINISHED'}
+
+ def check(self, context):
+ return True
+
+class TISSUE_PT_tessellate(Panel):
+ bl_label = "Tissue Tools"
+ bl_category = "Tissue"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ #bl_options = {'DEFAULT_OPEN'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode in {'OBJECT', 'EDIT_MESH'}
+
+ def draw(self, context):
+ layout = self.layout
+
+ col = layout.column(align=True)
+ col.label(text="Tessellate:")
+ col.operator("object.tissue_tessellate")
+ col.operator("object.dual_mesh_tessellated")
+ col.separator()
+ col.operator("object.tissue_refresh_tessellate", icon='FILE_REFRESH')
+
+ col.separator()
+ col.label(text="Rotate Faces:")
+ row = col.row(align=True)
+ row.operator("mesh.tissue_rotate_face_left", text='Left', icon='LOOP_BACK')
+ row.operator("mesh.tissue_rotate_face_right", text='Right', icon='LOOP_FORWARDS')
+
+ col.separator()
+ col.label(text="Other:")
+ col.operator("object.dual_mesh")
+ col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
+
+ act = context.object
+ if act and act.type == 'MESH':
+ col.operator("object.uv_to_mesh", icon="UV")
+
+ if act.mode == 'EDIT':
+ col.separator()
+ col.label(text="Weight:")
+ col.operator("object.tissue_weight_distance", icon="TRACKING")
+
+class TISSUE_PT_tessellate_object(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_label = "Tessellate Settings"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try: return context.object.type == 'MESH'
+ except: return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ if not bool_tessellated:
+ layout.label(text="The selected object is not a Tessellated object",
+ icon='INFO')
+ else:
+ if props.error_message != "":
+ layout.label(text=props.error_message,
+ icon='ERROR')
+ col = layout.column(align=True)
+ row = col.row(align=True)
+
+ set_tessellate_handler(self,context)
+ set_animatable_fix_handler(self,context)
+ row.operator("object.tissue_refresh_tessellate", icon='FILE_REFRESH')
+ lock_icon = 'LOCKED' if props.bool_lock else 'UNLOCKED'
+ #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
+ deps_icon = 'LINKED' if props.bool_dependencies else 'UNLINKED'
+ row.prop(props, "bool_dependencies", text="", icon=deps_icon)
+ row.prop(props, "bool_lock", text="", icon=lock_icon)
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_run", text="",icon='TIME')
+ col2.enabled = not props.bool_lock
+ '''
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.label(text="Base :")
+ row.label(text="Component :")
+ row = col.row(align=True)
+
+ col2 = row.column(align=True)
+ col2.prop_search(props, "generator", context.scene, "objects")
+ row.separator()
+ col2 = row.column(align=True)
+ col2.prop_search(props, "component", context.scene, "objects")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
+ row.separator()
+ try:
+ if not (ob0.modifiers or ob0.data.shape_keys) or props.fill_mode == 'PATCH':
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ col2 = row.column(align=True)
+ col2.prop(props, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
+ try:
+ if not (props.component.modifiers or props.component.data.shape_keys):
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ '''
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.label(text='Base:')
+ row.prop_search(props, "generator", context.scene, "objects")
+ col2 = row.column(align=True)
+ col2.prop(props, "gen_modifiers", text='',icon='MODIFIER')
+ try:
+ if not (props.generator.modifiers or props.generator.data.shape_keys):
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ col.separator()
+ row = col.row(align=True)
+ row.label(text='Component:')
+ row.prop_search(props, "component", context.scene, "objects")
+ col2 = row.column(align=True)
+ col2.prop(props, "com_modifiers", text='',icon='MODIFIER')
+ try:
+ if not (props.component.modifiers or props.component.data.shape_keys):
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ layout.use_property_split = False
+
+ # Fill
+ col = layout.column(align=True)
+ col.label(text="Fill Mode:")
+
+ # fill
+ row = col.row(align=True)
+ row.prop(props, "fill_mode", icon='NONE', expand=True,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ #layout.use_property_split = True
+ col = layout.column(align=True)
+ col.prop(props, "bool_smooth")
+
+
+class TISSUE_PT_tessellate_frame(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Frame Settings"
+ #bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_frame = context.object.tissue_tessellate.fill_mode == 'FRAME'
+ bool_tessellated = context.object.tissue_tessellate.generator != None
+ return context.object.type == 'MESH' and bool_frame and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ if bool_tessellated:
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.prop(props, "frame_mode", expand=True)
+ row = col.row(align=True)
+ row.prop(props, "frame_thickness", icon='NONE', expand=True)
+ row = col.row(align=True)
+ row.prop(props, "fill_frame", icon='NONE')
+ show_frame_mat = props.bool_multi_components or props.bool_material_id
+ if props.fill_frame and show_frame_mat:
+ row.prop(props, "fill_frame_mat", icon='NONE')
+ row = col.row(align=True)
+ row.prop(props, "frame_boundary", text='Boundary', icon='NONE')
+ if props.frame_boundary and show_frame_mat:
+ row.prop(props, "frame_boundary_mat", icon='NONE')
+
+
+class TISSUE_PT_tessellate_coordinates(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Component Coordinates"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue_tessellate.generator != None
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ if bool_tessellated:
+ col = layout.column(align=True)
+ # component XY
+ row = col.row(align=True)
+ row.prop(props, "mode", expand=True)
+
+ if props.mode != 'BOUNDS':
+ col.separator()
+ row = col.row(align=True)
+ row.label(text="X:")
+ row.prop(
+ props, "bounds_x", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ row = col.row(align=True)
+ row.label(text="Y:")
+ row.prop(
+ props, "bounds_y", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+
+class TISSUE_PT_tessellate_rotation(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Rotation"
+ bl_options = {'DEFAULT_CLOSED'}
+
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue_tessellate.generator != None
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ if bool_tessellated:
+ # rotation
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+ col = layout.column(align=True)
+ col.prop(props, "rotation_mode", text='Rotation', icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ if props.rotation_mode == 'WEIGHT':
+ col.prop(props, "rotation_direction", expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ if props.rotation_mode == 'RANDOM':
+ col.prop(props, "random_seed")
+ else:
+ col.prop(props, "rotation_shift")
+
+ if props.rotation_mode == 'UV':
+ uv_error = False
+ if props.generator.type != 'MESH':
+ row = col.row(align=True)
+ row.label(
+ text="UV rotation supported only for Mesh objects",
+ icon='ERROR')
+ uv_error = True
+ else:
+ if len(props.generator.data.uv_layers) == 0:
+ row = col.row(align=True)
+ row.label(text="'" + props.generator.name +
+ " doesn't have UV Maps", icon='ERROR')
+ uv_error = True
+ if uv_error:
+ row = col.row(align=True)
+ row.label(text="Default rotation will be used instead",
+ icon='INFO')
+
+class TISSUE_PT_tessellate_thickness(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Thickness"
+ #bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue_tessellate.generator != None
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ #layout.use_property_split = True
+ if bool_tessellated:
+ col = layout.column(align=True)
+ # component Z
+ row = col.row(align=True)
+ row.prop(props, "scale_mode", expand=True)
+ col.prop(props, "zscale", text="Scale", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ if props.mode == 'BOUNDS':
+ col.prop(props, "offset", text="Offset", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ # Direction
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.label(text="Direction:")
+ row = col.row(align=True)
+ row.prop(
+ props, "normals_mode", text="Direction", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+
+class TISSUE_PT_tessellate_options(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = " "
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue_tessellate.generator != None
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw_header(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ self.layout.prop(props, "merge")
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+ if bool_tessellated:
+ col = layout.column(align=True)
+ if props.merge:
+ col.prop(props, "merge_thres")
+ col.prop(props, "bool_dissolve_seams")
+ col.prop(props, "close_mesh")
+ if props.close_mesh != 'NONE':
+ #row = col.row(align=True)
+ col.separator()
+ col.prop(props, "open_edges_crease", text="Crease")
+ col.prop(props, "cap_material_index", text='Material Index')
+ if props.close_mesh == 'BRIDGE':
+ col.separator()
+ col.prop(props, "bridge_cuts")
+ col.prop(props, "bridge_smoothness")
+
+
+class TISSUE_PT_tessellate_morphing(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Weight and Morphing"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue_tessellate.generator != None
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ if bool_tessellated:
+ allow_shapekeys = not props.com_modifiers
+ for m in ob0.data.materials:
+ try:
+ o = bpy.data.objects[m.name]
+ allow_multi = True
+ try:
+ if o.data.shape_keys is None: continue
+ elif len(o.data.shape_keys.key_blocks) < 2: continue
+ else: allow_shapekeys = not props.com_modifiers
+ except: pass
+ except: pass
+ col = layout.column(align=True)
+ #col.label(text="Morphing:")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_vertex_group", icon='GROUP_VERTEX')
+ #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
+ try:
+ if len(props.generator.vertex_groups) == 0:
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ row.separator()
+ col2 = row.column(align=True)
+ row2 = col2.row(align=True)
+ row2.prop(props, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
+ row2.enabled = allow_shapekeys
+ if not allow_shapekeys:
+ col2 = layout.column(align=True)
+ row2 = col2.row(align=True)
+ row2.label(text="Use Shape Keys is not compatible with Use Modifiers", icon='INFO')
+
+
+class TISSUE_PT_tessellate_selective(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Selective"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue_tessellate.generator != None
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ if bool_tessellated:
+ allow_multi = False
+ allow_shapekeys = not props.com_modifiers
+ for m in ob0.data.materials:
+ try:
+ o = bpy.data.objects[m.name]
+ allow_multi = True
+ try:
+ if o.data.shape_keys is None: continue
+ elif len(o.data.shape_keys.key_blocks) < 2: continue
+ else: allow_shapekeys = not props.com_modifiers
+ except: pass
+ except: pass
+ # LIMITED TESSELLATION
+ col = layout.column(align=True)
+ #col.label(text="Limited Tessellation:")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
+ row.separator()
+ if props.generator.type != 'MESH':
+ col2.enabled = False
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_material_id", icon='MATERIAL_DATA', text="Material ID")
+ if props.bool_material_id and not props.bool_multi_components:
+ #col2 = row.column(align=True)
+ col2.prop(props, "material_id")
+ if props.bool_multi_components:
+ col2.enabled = False
+
+ col.separator()
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_multi_components", icon='MOD_TINT')
+ if not allow_multi:
+ col2.enabled = False
+
+
+class TISSUE_PT_tessellate_iterations(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_parent_id = "TISSUE_PT_tessellate_object"
+ bl_label = "Iterations"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ bool_tessellated = context.object.tissue_tessellate.generator != None
+ return context.object.type == 'MESH' and bool_tessellated
+ except:
+ return False
+
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
+
+ try:
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+ if bool_tessellated:
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ #row.label(text='', icon='FILE_REFRESH')
+ col.prop(props, 'iterations', text='Repeat')#, icon='FILE_REFRESH')
+ if props.iterations > 1 and props.fill_mode == 'PATCH':
+ col.separator()
+ #row = col.row(align=True)
+ col.prop(props, 'patch_subs')
+ layout.use_property_split = False
+ col = layout.column(align=True)
+ #row = col.row(align=True)
+ col.label(text='Combine Iterations:')
+ row = col.row(align=True)
+ row.prop(
+ props, "combine_mode", text="Combine:",icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+class tissue_rotate_face_right(Operator):
+ bl_idname = "mesh.tissue_rotate_face_right"
+ bl_label = "Rotate Faces Right"
+ bl_description = "Rotate clockwise selected faces and update tessellated meshes"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ #bool_tessellated = context.object.tissue_tessellate.generator != None
+ ob = context.object
+ return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
+ except:
+ return False
+
+ def execute(self, context):
+ ob = context.active_object
+ me = ob.data
+
+ bm = bmesh.from_edit_mesh(me)
+ mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
+
+ for face in bm.faces:
+ if (face.select):
+ vs = face.verts[:]
+ vs2 = vs[-1:]+vs[:-1]
+ material_index = face.material_index
+ bm.faces.remove(face)
+ f2 = bm.faces.new(vs2)
+ f2.select = True
+ f2.material_index = material_index
+ bm.normal_update()
+
+ # trigger UI update
+ bmesh.update_edit_mesh(me)
+ ob.select_set(False)
+
+ # update tessellated meshes
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for o in [obj for obj in bpy.data.objects if
+ obj.tissue_tessellate.generator == ob and obj.visible_get()]:
+ context.view_layer.objects.active = o
+ bpy.ops.object.tissue_update_tessellate()
+ o.select_set(False)
+ ob.select_set(True)
+ context.view_layer.objects.active = ob
+ bpy.ops.object.mode_set(mode='EDIT')
+ context.tool_settings.mesh_select_mode = mesh_select_mode
+
+ return {'FINISHED'}
+
+class tissue_rotate_face_left(Operator):
+ bl_idname = "mesh.tissue_rotate_face_left"
+ bl_label = "Rotate Faces Left"
+ bl_description = "Rotate counterclockwise selected faces and update tessellated meshes"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ try:
+ #bool_tessellated = context.object.tissue_tessellate.generator != None
+ ob = context.object
+ return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
+ except:
+ return False
+
+ def execute(self, context):
+ ob = context.active_object
+ me = ob.data
+
+ bm = bmesh.from_edit_mesh(me)
+ mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
+
+ for face in bm.faces:
+ if (face.select):
+ vs = face.verts[:]
+ vs2 = vs[1:]+vs[:1]
+ material_index = face.material_index
+ bm.faces.remove(face)
+ f2 = bm.faces.new(vs2)
+ f2.select = True
+ f2.material_index = material_index
+ bm.normal_update()
+
+ # trigger UI update
+ bmesh.update_edit_mesh(me)
+ ob.select_set(False)
+
+ # update tessellated meshes
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for o in [obj for obj in bpy.data.objects if
+ obj.tissue_tessellate.generator == ob and obj.visible_get()]:
+ context.view_layer.objects.active = o
+ bpy.ops.object.tissue_update_tessellate()
+ o.select_set(False)
+ ob.select_set(True)
+ context.view_layer.objects.active = ob
+ bpy.ops.object.mode_set(mode='EDIT')
+ context.tool_settings.mesh_select_mode = mesh_select_mode
+
+ return {'FINISHED'}
+
+
+def convert_to_frame(ob, props, use_modifiers):
+ new_ob = convert_object_to_mesh(ob, use_modifiers, True)
+
+ # create bmesh
+ bm = bmesh.new()
+ bm.from_mesh(new_ob.data)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+ if props.bool_selection:
+ original_faces = [f for f in bm.faces if f.select]
+ else:
+ original_faces = list(bm.faces)
+ # detect edge loops
+
+ loops = []
+ boundaries_mat = []
+ neigh_face_center = []
+ face_normals = []
+ # append boundary loops
+ if props.frame_boundary:
+ #selected_edges = [e for e in bm.edges if e.select]
+ selected_edges = [e for e in bm.edges if e.is_boundary]
+ if len(selected_edges) > 0:
+ loop = []
+ count = 0
+ e0 = selected_edges[0]
+ face = e0.link_faces[0]
+ boundary_mat = [face.material_index]
+ face_center = [face.calc_center_median()]
+ loop_normals = [face.normal]
+ selected_edges = selected_edges[1:]
+ if props.bool_vertex_group:
+ n_verts = len(new_ob.data.vertices)
+ base_vg = [get_weight(vg,n_verts) for vg in new_ob.vertex_groups]
+ '''
+ base_vg = []
+ for vg in new_ob.vertex_groups:
+ vertex_group = []
+ for v in bm.verts:
+ try:
+ vertex_group.append(vg.weight(v.index))
+ except:
+ vertex_group.append(0)
+ base_vg.append(vertex_group)
+ '''
+ while True:
+ new_vert = None
+ face = None
+ for e1 in selected_edges:
+ if e1.verts[0] in e0.verts: new_vert = e1.verts[1]
+ elif e1.verts[1] in e0.verts: new_vert = e1.verts[0]
+ if new_vert != None:
+ if len(loop)==0:
+ loop = [v for v in e1.verts if v != new_vert]
+ loop.append(new_vert)
+ e0 = e1
+ face = e0.link_faces[0]
+ boundary_mat.append(face.material_index)
+ face_center.append(face.calc_center_median())
+ loop_normals.append(face.normal)
+ selected_edges.remove(e0)
+ break
+ if new_vert == None:
+ try:
+ loops.append(loop)
+ loop = []
+ e0 = selected_edges[0]
+ selected_edges = selected_edges[1:]
+ boundaries_mat.append(boundary_mat)
+ neigh_face_center.append(face_center)
+ face_normals.append(loop_normals)
+ face = e0.link_faces[0]
+ boundary_mat = [face.material_index]
+ face_center = [face.calc_center_median()]
+ loop_normals = [face.normal]
+ except: break
+ boundaries_mat.append(boundary_mat)
+ neigh_face_center.append(face_center)
+ face_normals.append(loop_normals)
+ # compute boundary frames
+ new_faces = []
+ vert_ids = []
+
+ # append regular faces
+ for f in original_faces:#bm.faces:
+ loop = list(f.verts)
+ loops.append(loop)
+ boundaries_mat.append([f.material_index for v in loop])
+ face_normals.append([f.normal for v in loop])
+
+ # calc areas for relative frame mode
+ if props.frame_mode == 'RELATIVE':
+ verts_area = []
+ for v in bm.verts:
+ linked_faces = v.link_faces
+ if len(linked_faces) > 0:
+ area = sum([sqrt(f.calc_area())/len(f.verts) for f in v.link_faces])*2
+ area /= len(linked_faces)
+ else: area = 0
+ verts_area.append(area)
+
+ for loop_index, loop in enumerate(loops):
+ is_boundary = loop_index < len(neigh_face_center)
+ materials = boundaries_mat[loop_index]
+ new_loop = []
+ loop_ext = [loop[-1]] + loop + [loop[0]]
+
+ # calc tangents
+ tangents = []
+ for i in range(len(loop)):
+ # vertices
+ vert0 = loop_ext[i]
+ vert = loop_ext[i+1]
+ vert1 = loop_ext[i+2]
+ # edge vectors
+ vec0 = (vert0.co - vert.co).normalized()
+ vec1 = (vert.co - vert1.co).normalized()
+ # tangent
+ _vec1 = -vec1
+ _vec0 = -vec0
+ ang = (pi - vec0.angle(vec1))/2
+ normal = face_normals[loop_index][i]
+ tan0 = normal.cross(vec0)
+ tan1 = normal.cross(vec1)
+ tangent = (tan0 + tan1).normalized()/sin(ang)*props.frame_thickness
+ tangents.append(tangent)
+
+ # calc correct direction for boundaries
+ mult = -1
+ if is_boundary:
+ dir_val = 0
+ for i in range(len(loop)):
+ surf_point = neigh_face_center[loop_index][i]
+ tangent = tangents[i]
+ vert = loop_ext[i+1]
+ dir_val += tangent.dot(vert.co - surf_point)
+ if dir_val > 0: mult = 1
+
+ # add vertices
+ for i in range(len(loop)):
+ vert = loop_ext[i+1]
+ if props.frame_mode == 'RELATIVE': area = verts_area[vert.index]
+ else: area = 1
+ new_co = vert.co + tangents[i] * mult * area
+ # add vertex
+ new_vert = bm.verts.new(new_co)
+ new_loop.append(new_vert)
+ vert_ids.append(vert.index)
+ new_loop.append(new_loop[0])
+
+ # add faces
+ materials += [materials[0]]
+ for i in range(len(loop)):
+ v0 = loop_ext[i+1]
+ v1 = loop_ext[i+2]
+ v2 = new_loop[i+1]
+ v3 = new_loop[i]
+ face_verts = [v1,v0,v3,v2]
+ if mult == -1: face_verts = [v0,v1,v2,v3]
+ new_face = bm.faces.new(face_verts)
+ new_face.material_index = materials[i+1] + props.frame_boundary_mat
+ new_face.select = True
+ new_faces.append(new_face)
+ # fill frame
+ if props.fill_frame and not is_boundary:
+ n_verts = len(new_loop)-1
+ loop_center = Vector((0,0,0))
+ for v in new_loop[1:]: loop_center += v.co
+ loop_center /= n_verts
+ center = bm.verts.new(loop_center)
+ for i in range(n_verts):
+ v0 = new_loop[i+1]
+ v1 = new_loop[i]
+ face_verts = [v1,v0,center]
+ new_face = bm.faces.new(face_verts)
+ new_face.material_index = materials[i] + props.frame_boundary_mat
+ new_face.select = True
+ new_faces.append(new_face)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ #for f in bm.faces: f.select_set(f not in new_faces)
+ for f in original_faces: bm.faces.remove(f)
+ bm.to_mesh(new_ob.data)
+ # propagate vertex groups
+ if props.bool_vertex_group:
+ base_vg = []
+ for vg in new_ob.vertex_groups:
+ vertex_group = []
+ for v in bm.verts:
+ try:
+ vertex_group.append(vg.weight(v.index))
+ except:
+ vertex_group.append(0)
+ base_vg.append(vertex_group)
+ new_vert_ids = range(len(bm.verts)-len(vert_ids),len(bm.verts))
+ for vg_id, vg in enumerate(new_ob.vertex_groups):
+ for ii, jj in zip(vert_ids, new_vert_ids):
+ vg.add([jj], base_vg[vg_id][ii], 'REPLACE')
+ new_ob.data.update()
+ return new_ob
+
+def convert_to_fan(ob, props, use_modifiers):
+ new_ob = convert_object_to_mesh(ob, use_modifiers, True)
+ # make base object selected and active
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ new_ob.select_set(True)
+ bpy.context.view_layer.objects.active = new_ob
+ sk_index0 = new_ob.active_shape_key_index
+ new_ob.active_shape_key_index = 0
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if not props.bool_selection:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.poke()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return new_ob
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/UTILS.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/UTILS.PY
new file mode 100644
index 00000000..e40c268b
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/UTILS.PY
@@ -0,0 +1,462 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import threading
+import numpy as np
+import multiprocessing
+from multiprocessing import Process, Pool
+from mathutils import Vector
+try: from .numba_functions import numba_lerp2
+except: pass
+
+weight = []
+n_threads = multiprocessing.cpu_count()
+
+class ThreadVertexGroup(threading.Thread):
+ def __init__ ( self, id, vertex_group, n_verts):
+ self.id = id
+ self.vertex_group = vertex_group
+ self.n_verts = n_verts
+ threading.Thread.__init__ ( self )
+
+ def run (self):
+ global weight
+ global n_threads
+ verts = np.arange(int(self.n_verts/8))*8 + self.id
+ for v in verts:
+ try:
+ weight[v] = self.vertex_group.weight(v)
+ except:
+ pass
+
+def thread_read_weight(_weight, vertex_group):
+ global weight
+ global n_threads
+ print(n_threads)
+ weight = _weight
+ n_verts = len(weight)
+ threads = [ThreadVertexGroup(i, vertex_group, n_verts) for i in range(n_threads)]
+ for t in threads: t.start()
+ for t in threads: t.join()
+ return weight
+
+def process_read_weight(id, vertex_group, n_verts):
+ global weight
+ global n_threads
+ verts = np.arange(int(self.n_verts/8))*8 + self.id
+ for v in verts:
+ try:
+ weight[v] = self.vertex_group.weight(v)
+ except:
+ pass
+
+
+def read_weight(_weight, vertex_group):
+ global weight
+ global n_threads
+ print(n_threads)
+ weight = _weight
+ n_verts = len(weight)
+ n_cores = multiprocessing.cpu_count()
+ pool = Pool(processes=n_cores)
+ multiple_results = [pool.apply_async(process_read_weight, (i, vertex_group, n_verts)) for i in range(n_cores)]
+ #processes = [Process(target=process_read_weight, args=(i, vertex_group, n_verts)) for i in range(n_threads)]
+ #for t in processes: t.start()
+ #for t in processes: t.join()
+ return weight
+
+#Recursivly transverse layer_collection for a particular name
+def recurLayerCollection(layerColl, collName):
+ found = None
+ if (layerColl.name == collName):
+ return layerColl
+ for layer in layerColl.children:
+ found = recurLayerCollection(layer, collName)
+ if found:
+ return found
+
+def auto_layer_collection():
+ # automatically change active layer collection
+ layer = bpy.context.view_layer.active_layer_collection
+ layer_collection = bpy.context.view_layer.layer_collection
+ if layer.hide_viewport or layer.collection.hide_viewport:
+ collections = bpy.context.object.users_collection
+ for c in collections:
+ lc = recurLayerCollection(layer_collection, c.name)
+ if not c.hide_viewport and not lc.hide_viewport:
+ bpy.context.view_layer.active_layer_collection = lc
+
+def lerp(a, b, t):
+ return a + (b - a) * t
+
+def _lerp2(v1, v2, v3, v4, v):
+ v12 = v1.lerp(v2,v.x) # + (v2 - v1) * v.x
+ v34 = v3.lerp(v4,v.x) # + (v4 - v3) * v.x
+ return v12.lerp(v34, v.y)# + (v34 - v12) * v.y
+
+def lerp2(v1, v2, v3, v4, v):
+ v12 = v1 + (v2 - v1) * v.x
+ v34 = v3 + (v4 - v3) * v.x
+ return v12 + (v34 - v12) * v.y
+
+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
+
+def np_lerp2(v00, v10, v01, v11, vx, vy):
+ #try:
+ # co2 = numba_lerp2(v00, v10, v01, v11, vx, vy)
+ #except:
+ co0 = v00 + (v10 - v00) * vx
+ co1 = v01 + (v11 - v01) * vx
+ co2 = co0 + (co1 - co0) * vy
+ return co2
+
+
+# Prevent Blender Crashes with handlers
+def set_animatable_fix_handler(self, context):
+ old_handlers = []
+ blender_handlers = bpy.app.handlers.render_init
+ for h in blender_handlers:
+ if "turn_off_animatable" in str(h):
+ old_handlers.append(h)
+ for h in old_handlers: blender_handlers.remove(h)
+ ################ blender_handlers.append(turn_off_animatable)
+ return
+
+def turn_off_animatable(scene):
+ for o in bpy.data.objects:
+ o.tissue_tessellate.bool_run = False
+ o.reaction_diffusion_settings.run = False
+ #except: pass
+ return
+
+### OBJECTS ###
+
+def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
+ try: ob.name
+ except: return None
+ if ob.type != 'MESH':
+ if not apply_modifiers:
+ mod_visibility = [m.show_viewport for m in ob.modifiers]
+ for m in ob.modifiers: m.show_viewport = False
+ #ob.modifiers.update()
+ #dg = bpy.context.evaluated_depsgraph_get()
+ #ob_eval = ob.evaluated_get(dg)
+ #me = bpy.data.meshes.new_from_object(ob_eval, preserve_all_data_layers=True, depsgraph=dg)
+ me = simple_to_mesh(ob)
+ new_ob = bpy.data.objects.new(ob.data.name, me)
+ new_ob.location, new_ob.matrix_world = ob.location, ob.matrix_world
+ if not apply_modifiers:
+ for m,vis in zip(ob.modifiers,mod_visibility): m.show_viewport = vis
+ else:
+ if apply_modifiers:
+ new_ob = ob.copy()
+ new_me = simple_to_mesh(ob)
+ new_ob.modifiers.clear()
+ new_ob.data = new_me
+ else:
+ new_ob = ob.copy()
+ new_ob.data = ob.data.copy()
+ new_ob.modifiers.clear()
+ bpy.context.collection.objects.link(new_ob)
+ if preserve_status:
+ new_ob.select_set(False)
+ else:
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ new_ob.select_set(True)
+ bpy.context.view_layer.objects.active = new_ob
+ return new_ob
+
+def simple_to_mesh(ob):
+ dg = bpy.context.evaluated_depsgraph_get()
+ ob_eval = ob.evaluated_get(dg)
+ me = bpy.data.meshes.new_from_object(ob_eval, preserve_all_data_layers=True, depsgraph=dg)
+ me.calc_normals()
+ return me
+
+def join_objects(objects, link_to_scene=True, make_active=False):
+ C = bpy.context
+ bm = bmesh.new()
+
+ materials = {}
+ faces_materials = []
+ dg = C.evaluated_depsgraph_get()
+ for o in objects:
+ bm.from_object(o, dg)
+ # add object's material to the dictionary
+ for m in o.data.materials:
+ if m not in materials: materials[m] = len(materials)
+ for f in o.data.polygons:
+ index = f.material_index
+ mat = o.material_slots[index].material
+ new_index = materials[mat]
+ faces_materials.append(new_index)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+ # assign new indexes
+ for index, f in zip(faces_materials, bm.faces): f.material_index = index
+ # create object
+ me = bpy.data.meshes.new('joined')
+ bm.to_mesh(me)
+ me.update()
+ ob = bpy.data.objects.new('joined', me)
+ if link_to_scene: C.collection.objects.link(ob)
+ # make active
+ if make_active:
+ for o in C.view_layer.objects: o.select_set(False)
+ ob.select_set(True)
+ C.view_layer.objects.active = ob
+ # add materials
+ for m in materials.keys(): ob.data.materials.append(m)
+ return ob
+
+### MESH FUNCTIONS
+
+def get_vertices_numpy(mesh):
+ n_verts = len(mesh.vertices)
+ verts = [0]*n_verts*3
+ mesh.vertices.foreach_get('co', verts)
+ verts = np.array(verts).reshape((n_verts,3))
+ return verts
+
+def get_vertices_and_normals_numpy(mesh):
+ n_verts = len(mesh.vertices)
+ verts = [0]*n_verts*3
+ normals = [0]*n_verts*3
+ mesh.vertices.foreach_get('co', verts)
+ mesh.vertices.foreach_get('normal', normals)
+ verts = np.array(verts).reshape((n_verts,3))
+ normals = np.array(normals).reshape((n_verts,3))
+ return verts, normals
+
+def get_edges_numpy(mesh):
+ n_edges = len(mesh.edges)
+ edges = [0]*n_edges*2
+ mesh.edges.foreach_get('vertices', edges)
+ edges = np.array(edges).reshape((n_edges,2)).astype('int')
+ return edges
+
+def get_edges_id_numpy(mesh):
+ n_edges = len(mesh.edges)
+ edges = [0]*n_edges*2
+ mesh.edges.foreach_get('vertices', edges)
+ edges = np.array(edges).reshape((n_edges,2))
+ indexes = np.arange(n_edges).reshape((n_edges,1))
+ edges = np.concatenate((edges,indexes), axis=1)
+ return edges
+
+def get_vertices(mesh):
+ n_verts = len(mesh.vertices)
+ verts = [0]*n_verts*3
+ mesh.vertices.foreach_get('co', verts)
+ verts = np.array(verts).reshape((n_verts,3))
+ verts = [Vector(v) for v in verts]
+ return verts
+
+def get_faces(mesh):
+ faces = [[v for v in f.vertices] for f in mesh.polygons]
+ return faces
+
+def get_faces_numpy(mesh):
+ faces = [[v for v in f.vertices] for f in mesh.polygons]
+ return np.array(faces)
+
+def get_faces_edges_numpy(mesh):
+ faces = [v.edge_keys for f in mesh.polygons]
+ return np.array(faces)
+
+#try:
+#from numba import jit, njit
+#from numba.typed import List
+'''
+@jit
+def find_curves(edges, n_verts):
+ #verts_dict = {key:[] for key in range(n_verts)}
+ verts_dict = {}
+ for key in range(n_verts): verts_dict[key] = []
+ for e in edges:
+ verts_dict[e[0]].append(e[1])
+ verts_dict[e[1]].append(e[0])
+ curves = []#List()
+ loop1 = True
+ while loop1:
+ if len(verts_dict) == 0:
+ loop1 = False
+ continue
+ # next starting point
+ v = list(verts_dict.keys())[0]
+ # neighbors
+ v01 = verts_dict[v]
+ if len(v01) == 0:
+ verts_dict.pop(v)
+ continue
+ curve = []#List()
+ curve.append(v) # add starting point
+ curve.append(v01[0]) # add neighbors
+ verts_dict.pop(v)
+ loop2 = True
+ while loop2:
+ last_point = curve[-1]
+ #if last_point not in verts_dict: break
+ v01 = verts_dict[last_point]
+ # curve end
+ if len(v01) == 1:
+ verts_dict.pop(last_point)
+ loop2 = False
+ continue
+ if v01[0] == curve[-2]:
+ curve.append(v01[1])
+ verts_dict.pop(last_point)
+ elif v01[1] == curve[-2]:
+ curve.append(v01[0])
+ verts_dict.pop(last_point)
+ else:
+ loop2 = False
+ continue
+ if curve[0] == curve[-1]:
+ loop2 = False
+ continue
+ curves.append(curve)
+ return curves
+'''
+def find_curves(edges, n_verts):
+ verts_dict = {key:[] for key in range(n_verts)}
+ for e in edges:
+ verts_dict[e[0]].append(e[1])
+ verts_dict[e[1]].append(e[0])
+ curves = []
+ while True:
+ if len(verts_dict) == 0: break
+ # next starting point
+ v = list(verts_dict.keys())[0]
+ # neighbors
+ v01 = verts_dict[v]
+ if len(v01) == 0:
+ verts_dict.pop(v)
+ continue
+ curve = []
+ if len(v01) > 1: curve.append(v01[1]) # add neighbors
+ curve.append(v) # add starting point
+ curve.append(v01[0]) # add neighbors
+ verts_dict.pop(v)
+ # start building curve
+ while True:
+ #last_point = curve[-1]
+ #if last_point not in verts_dict: break
+
+ # try to change direction if needed
+ if curve[-1] in verts_dict: pass
+ elif curve[0] in verts_dict: curve.reverse()
+ else: break
+
+ # neighbors points
+ last_point = curve[-1]
+ v01 = verts_dict[last_point]
+
+ # curve end
+ if len(v01) == 1:
+ verts_dict.pop(last_point)
+ if curve[0] in verts_dict: continue
+ else: break
+
+ # chose next point
+ new_point = None
+ if v01[0] == curve[-2]: new_point = v01[1]
+ elif v01[1] == curve[-2]: new_point = v01[0]
+ #else: break
+
+ #if new_point != curve[1]:
+ curve.append(new_point)
+ verts_dict.pop(last_point)
+ if curve[0] == curve[-1]:
+ verts_dict.pop(new_point)
+ break
+ curves.append(curve)
+ return curves
+
+def curve_from_points(points, name='Curve'):
+ curve = bpy.data.curves.new(name,'CURVE')
+ for c in points:
+ s = curve.splines.new('POLY')
+ s.points.add(len(c))
+ for i,p in enumerate(c): s.points[i].co = p.xyz + [1]
+ ob_curve = bpy.data.objects.new(name,curve)
+ return ob_curve
+
+def curve_from_pydata(points, indexes, name='Curve', skip_open=False, merge_distance=1, set_active=True):
+ curve = bpy.data.curves.new(name,'CURVE')
+ curve.dimensions = '3D'
+ for c in indexes:
+ # cleanup
+ pts = np.array([points[i] for i in c])
+ if merge_distance > 0:
+ pts1 = np.roll(pts,1,axis=0)
+ dist = np.linalg.norm(pts1-pts, axis=1)
+ count = 0
+ n = len(dist)
+ mask = np.ones(n).astype('bool')
+ for i in range(n):
+ count += dist[i]
+ if count > merge_distance: count = 0
+ else: mask[i] = False
+ pts = pts[mask]
+
+ bool_cyclic = c[0] == c[-1]
+ if skip_open and not bool_cyclic: continue
+ s = curve.splines.new('POLY')
+ n_pts = len(pts)
+ s.points.add(n_pts-1)
+ w = np.ones(n_pts).reshape((n_pts,1))
+ co = np.concatenate((pts,w),axis=1).reshape((n_pts*4))
+ s.points.foreach_set('co',co)
+ s.use_cyclic_u = bool_cyclic
+ ob_curve = bpy.data.objects.new(name,curve)
+ bpy.context.collection.objects.link(ob_curve)
+ if set_active:
+ bpy.context.view_layer.objects.active = ob_curve
+ return ob_curve
+
+def curve_from_vertices(indexes, verts, name='Curve'):
+ curve = bpy.data.curves.new(name,'CURVE')
+ for c in indexes:
+ s = curve.splines.new('POLY')
+ s.points.add(len(c))
+ for i,p in enumerate(c): s.points[i].co = verts[p].co.xyz + [1]
+ ob_curve = bpy.data.objects.new(name,curve)
+ return ob_curve
+
+### WEIGHT FUNCTIONS ###
+
+def get_weight(vertex_group, n_verts):
+ weight = [0]*n_verts
+ for i in range(n_verts):
+ try: weight[i] = vertex_group.weight(i)
+ except: pass
+ return weight
+
+def get_weight_numpy(vertex_group, n_verts):
+ weight = [0]*n_verts
+ for i in range(n_verts):
+ try: weight[i] = vertex_group.weight(i)
+ except: pass
+ return np.array(weight)
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/UV_TO_MESH.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/UV_TO_MESH.PY
new file mode 100644
index 00000000..8d3991a9
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/UV_TO_MESH.PY
@@ -0,0 +1,178 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# --------------------------------- UV to MESH ------------------------------- #
+# -------------------------------- version 0.1.1 ----------------------------- #
+# #
+# Create a new Mesh based on active UV #
+# #
+# (c) Alessandro Zomparelli #
+# (2017) #
+# #
+# http://www.co-de-it.com/ #
+# #
+# ############################################################################ #
+
+import bpy
+import math
+from bpy.types import Operator
+from bpy.props import BoolProperty
+from mathutils import Vector
+from .utils import *
+
+
+class uv_to_mesh(Operator):
+ bl_idname = "object.uv_to_mesh"
+ bl_label = "UV to Mesh"
+ bl_description = ("Create a new Mesh based on active UV")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ apply_modifiers : BoolProperty(
+ name="Apply Modifiers",
+ default=True,
+ description="Apply object's modifiers"
+ )
+ vertex_groups : BoolProperty(
+ name="Keep Vertex Groups",
+ default=True,
+ description="Transfer all the Vertex Groups"
+ )
+ materials : BoolProperty(
+ name="Keep Materials",
+ default=True,
+ description="Transfer all the Materials"
+ )
+ auto_scale : BoolProperty(
+ name="Resize",
+ default=True,
+ description="Scale the new object in order to preserve the average surface area"
+ )
+
+ def execute(self, context):
+ bpy.ops.object.mode_set(mode='OBJECT')
+ ob0 = bpy.context.object
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ ob0.select_set(True)
+
+ #if self.apply_modifiers:
+ # bpy.ops.object.duplicate_move()
+ # bpy.ops.object.convert(target='MESH')
+
+# me0 = ob0.to_mesh(bpy.context.depsgraph, apply_modifiers=self.apply_modifiers)
+ #if self.apply_modifiers: me0 = simple_to_mesh(ob0)
+ #else: me0 = ob0.data.copy()
+ name0 = ob0.name
+ ob0 = convert_object_to_mesh(ob0, apply_modifiers=self.apply_modifiers, preserve_status=False)
+ me0 = ob0.data
+ area = 0
+
+ verts = []
+ faces = []
+ face_materials = []
+ for face in me0.polygons:
+ area += face.area
+ uv_face = []
+ store = False
+ try:
+ for loop in face.loop_indices:
+ uv = me0.uv_layers.active.data[loop].uv
+ if uv.x != 0 and uv.y != 0:
+ store = True
+ new_vert = Vector((uv.x, uv.y, 0))
+ verts.append(new_vert)
+ uv_face.append(loop)
+ if store:
+ faces.append(uv_face)
+ face_materials.append(face.material_index)
+ except:
+ self.report({'ERROR'}, "Missing UV Map")
+
+ return {'CANCELLED'}
+
+ name = name0 + '_UV'
+ # Create mesh and object
+ me = bpy.data.meshes.new(name + 'Mesh')
+ ob = bpy.data.objects.new(name, me)
+
+ # Link object to scene and make active
+ scn = bpy.context.scene
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+
+ # Create mesh from given verts, faces.
+ me.from_pydata(verts, [], faces)
+ # Update mesh with new data
+ me.update()
+ if self.auto_scale:
+ new_area = 0
+ for p in me.polygons:
+ new_area += p.area
+ if new_area == 0:
+ self.report({'ERROR'}, "Impossible to generate mesh from UV")
+ bpy.data.objects.remove(ob0)
+
+ return {'CANCELLED'}
+
+ # VERTEX GROUPS
+ if self.vertex_groups:
+ for group in ob0.vertex_groups:
+ index = group.index
+ ob.vertex_groups.new(name=group.name)
+ for p in me0.polygons:
+ for vert, loop in zip(p.vertices, p.loop_indices):
+ try:
+ ob.vertex_groups[index].add([loop], group.weight(vert), 'REPLACE')
+ except:
+ pass
+
+ ob0.select_set(False)
+ if self.auto_scale:
+ scaleFactor = math.pow(area / new_area, 1 / 2)
+ ob.scale = Vector((scaleFactor, scaleFactor, scaleFactor))
+
+ bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+ bpy.ops.mesh.remove_doubles(threshold=1e-06)
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+ bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
+
+ # MATERIALS
+ if self.materials:
+ try:
+ # assign old material
+ uv_materials = [slot.material for slot in ob0.material_slots]
+ for i in range(len(uv_materials)):
+ bpy.ops.object.material_slot_add()
+ bpy.context.object.material_slots[i].material = uv_materials[i]
+ for i in range(len(ob.data.polygons)):
+ ob.data.polygons[i].material_index = face_materials[i]
+ except:
+ pass
+ '''
+ if self.apply_modifiers:
+ bpy.ops.object.mode_set(mode='OBJECT')
+ ob.select_set(False)
+ ob0.select_set(True)
+ bpy.ops.object.delete(use_global=False)
+ ob.select_set(True)
+ bpy.context.view_layer.objects.active = ob
+ '''
+
+ bpy.data.objects.remove(ob0)
+ bpy.data.meshes.remove(me0)
+ return {'FINISHED'}
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__INIT__.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__INIT__.PY
new file mode 100644
index 00000000..8aa8aed8
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__INIT__.PY
@@ -0,0 +1,151 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# --------------------------------- TISSUE ----------------------------------- #
+# ------------------------------- version 0.3 -------------------------------- #
+# #
+# Creates duplicates of selected mesh to active morphing the shape according #
+# to target faces. #
+# #
+# Alessandro Zomparelli #
+# (2017) #
+# #
+# http://www.co-de-it.com/ #
+# http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Mesh/Tissue #
+# #
+# ############################################################################ #
+
+bl_info = {
+ "name": "Tissue",
+ "author": "Alessandro Zomparelli (Co-de-iT)",
+ "version": (0, 3, 34),
+ "blender": (2, 80, 0),
+ "location": "",
+ "description": "Tools for Computational Design",
+ "warning": "",
+ "wiki_url": "https://github.com/alessandro-zomparelli/tissue/wiki",
+ "tracker_url": "https://github.com/alessandro-zomparelli/tissue/issues",
+ "category": "Mesh"}
+
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(tessellate_numpy)
+ importlib.reload(colors_groups_exchanger)
+ importlib.reload(dual_mesh)
+ importlib.reload(lattice)
+ importlib.reload(uv_to_mesh)
+ importlib.reload(utils)
+ importlib.reload(gcode_export)
+
+else:
+ from . import tessellate_numpy
+ from . import colors_groups_exchanger
+ from . import dual_mesh
+ from . import lattice
+ from . import uv_to_mesh
+ from . import utils
+ from . import gcode_export
+
+import bpy
+from bpy.props import PointerProperty, CollectionProperty, BoolProperty
+
+classes = (
+ tessellate_numpy.tissue_tessellate_prop,
+ tessellate_numpy.tissue_tessellate,
+ tessellate_numpy.tissue_update_tessellate,
+ tessellate_numpy.tissue_refresh_tessellate,
+ tessellate_numpy.TISSUE_PT_tessellate,
+ tessellate_numpy.tissue_rotate_face_left,
+ tessellate_numpy.tissue_rotate_face_right,
+ tessellate_numpy.TISSUE_PT_tessellate_object,
+ tessellate_numpy.TISSUE_PT_tessellate_frame,
+ tessellate_numpy.TISSUE_PT_tessellate_thickness,
+ tessellate_numpy.TISSUE_PT_tessellate_coordinates,
+ tessellate_numpy.TISSUE_PT_tessellate_rotation,
+ tessellate_numpy.TISSUE_PT_tessellate_options,
+ tessellate_numpy.TISSUE_PT_tessellate_selective,
+ tessellate_numpy.TISSUE_PT_tessellate_morphing,
+ tessellate_numpy.TISSUE_PT_tessellate_iterations,
+
+ colors_groups_exchanger.face_area_to_vertex_groups,
+ colors_groups_exchanger.vertex_colors_to_vertex_groups,
+ colors_groups_exchanger.vertex_group_to_vertex_colors,
+ colors_groups_exchanger.TISSUE_PT_weight,
+ colors_groups_exchanger.TISSUE_PT_color,
+ colors_groups_exchanger.weight_contour_curves,
+ colors_groups_exchanger.tissue_weight_contour_curves_pattern,
+ colors_groups_exchanger.weight_contour_mask,
+ colors_groups_exchanger.weight_contour_displace,
+ colors_groups_exchanger.harmonic_weight,
+ colors_groups_exchanger.edges_deformation,
+ colors_groups_exchanger.edges_bending,
+ colors_groups_exchanger.weight_laplacian,
+ colors_groups_exchanger.reaction_diffusion,
+ colors_groups_exchanger.start_reaction_diffusion,
+ colors_groups_exchanger.TISSUE_PT_reaction_diffusion,
+ colors_groups_exchanger.reset_reaction_diffusion_weight,
+ colors_groups_exchanger.formula_prop,
+ colors_groups_exchanger.reaction_diffusion_prop,
+ colors_groups_exchanger.weight_formula,
+ colors_groups_exchanger.curvature_to_vertex_groups,
+ colors_groups_exchanger.weight_formula_wiki,
+ colors_groups_exchanger.tissue_weight_distance,
+
+ dual_mesh.dual_mesh,
+ dual_mesh.dual_mesh_tessellated,
+
+ lattice.lattice_along_surface,
+
+ uv_to_mesh.uv_to_mesh,
+ gcode_export.TISSUE_PT_gcode_exporter,
+ gcode_export.tissue_gcode_prop,
+ gcode_export.tissue_gcode_export
+)
+
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ bpy.utils.register_class(cls)
+ #bpy.utils.register_module(__name__)
+ bpy.types.Object.tissue_tessellate = PointerProperty(
+ type=tessellate_numpy.tissue_tessellate_prop
+ )
+ bpy.types.Scene.tissue_gcode = PointerProperty(
+ type=gcode_export.tissue_gcode_prop
+ )
+ bpy.types.Object.formula_settings = CollectionProperty(
+ type=colors_groups_exchanger.formula_prop
+ )
+ bpy.types.Object.reaction_diffusion_settings = PointerProperty(
+ type=colors_groups_exchanger.reaction_diffusion_prop
+ )
+ # colors_groups_exchanger
+ bpy.app.handlers.frame_change_post.append(colors_groups_exchanger.reaction_diffusion_def)
+ #bpy.app.handlers.frame_change_post.append(tessellate_numpy.anim_tessellate)
+
+def unregister():
+ from bpy.utils import unregister_class
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+
+ del bpy.types.Object.tissue_tessellate
+
+
+if __name__ == "__main__":
+ register()
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/COLORS_GROUPS_EXCHANGER.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/COLORS_GROUPS_EXCHANGER.CPYTHON-37.PYC
new file mode 100644
index 00000000..7569b61d
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/COLORS_GROUPS_EXCHANGER.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/DUAL_MESH.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/DUAL_MESH.CPYTHON-37.PYC
new file mode 100644
index 00000000..eb51f42b
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/DUAL_MESH.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/GCODE_EXPORT.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/GCODE_EXPORT.CPYTHON-37.PYC
new file mode 100644
index 00000000..ac626fdb
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/GCODE_EXPORT.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/LATTICE.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/LATTICE.CPYTHON-37.PYC
new file mode 100644
index 00000000..01e4b211
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/LATTICE.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/NUMBA_FUNCTIONS.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/NUMBA_FUNCTIONS.CPYTHON-37.PYC
new file mode 100644
index 00000000..a635a906
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/NUMBA_FUNCTIONS.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/TESSELLATE_NUMPY.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/TESSELLATE_NUMPY.CPYTHON-37.PYC
new file mode 100644
index 00000000..2f316815
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/TESSELLATE_NUMPY.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/UTILS.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/UTILS.CPYTHON-37.PYC
new file mode 100644
index 00000000..77d83111
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/UTILS.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/UV_TO_MESH.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/UV_TO_MESH.CPYTHON-37.PYC
new file mode 100644
index 00000000..a835f1f3
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/UV_TO_MESH.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/__INIT__.CPYTHON-37.PYC b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/__INIT__.CPYTHON-37.PYC
new file mode 100644
index 00000000..480445c1
Binary files /dev/null and b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/__INIT__.CPYTHON-37.PYC differ
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/__INIT__.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/__INIT__.PY
new file mode 100644
index 00000000..c4aea75f
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/__INIT__.PY
@@ -0,0 +1,86 @@
+'''
+Created by Marcin Zielinski, Doug Hammond, Thomas Ludwig, Nicholas Chapman, Yves Colle
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+'''
+
+bl_info = {
+ "name": "Blendigo - Indigo Exporter",
+ "description": "This Addon will allow you to render your scenes with the Indigo render engine.",
+ "author": "Glare Technologies Ltd.",
+ "version": (4, 2, 0),
+ "blender": (2, 78, 0),
+ "location": "View3D",
+ "wiki_url": "",
+ "category": "Render" }
+
+
+import bpy
+
+# load and reload submodules
+##################################
+
+import importlib
+from . import developer_utils
+importlib.reload(developer_utils)
+modules = developer_utils.setup_addon_modules(__path__, __name__, "bpy" in locals())
+
+
+
+# register
+##################################
+
+import traceback
+
+def register():
+ try: bpy.utils.register_module(__name__)
+ except: traceback.print_exc()
+
+ from . properties.render_settings import Indigo_Engine_Properties
+ bpy.types.Scene.indigo_engine = bpy.props.PointerProperty(name="Indigo Engine Properties", type = Indigo_Engine_Properties)
+
+ from . properties.camera import Indigo_Camera_Properties
+ bpy.types.Camera.indigo_camera = bpy.props.PointerProperty(name="Indigo Camera Properties", type = Indigo_Camera_Properties)
+
+ from . properties.environment import Indigo_Lightlayers_Properties
+ bpy.types.Scene.indigo_lightlayers = bpy.props.PointerProperty(name="Indigo Lightlayers Properties", type = Indigo_Lightlayers_Properties)
+
+ from . properties.lamp import Indigo_Lamp_Sun_Properties, Indigo_Lamp_Hemi_Properties
+ bpy.types.Lamp.indigo_lamp_sun = bpy.props.PointerProperty(name="Indigo Lamp Sun Properties", type = Indigo_Lamp_Sun_Properties)
+ bpy.types.Lamp.indigo_lamp_hemi = bpy.props.PointerProperty(name="Indigo Lamp Hemi Properties", type = Indigo_Lamp_Hemi_Properties)
+
+ from . properties.material import Indigo_Material_Properties, Indigo_Texture_Properties
+ bpy.types.Material.indigo_material = bpy.props.PointerProperty(name="Indigo Material Properties", type = Indigo_Material_Properties)
+ bpy.types.Texture.indigo_texture = bpy.props.PointerProperty(name="Indigo Texture Properties", type = Indigo_Texture_Properties)
+
+ from . properties.medium import Indigo_Material_Medium_Properties
+ bpy.types.Scene.indigo_material_medium = bpy.props.PointerProperty(name="Indigo Material Medium Properties", type = Indigo_Material_Medium_Properties)
+ bpy.types.Material.indigo_material_medium = bpy.props.PointerProperty(name="Indigo Material Medium Properties", type = Indigo_Material_Medium_Properties)
+
+ from . properties.object import Indigo_Mesh_Properties
+ bpy.types.Mesh.indigo_mesh = bpy.props.PointerProperty(name="Indigo Mesh Properties", type = Indigo_Mesh_Properties)
+ bpy.types.SurfaceCurve.indigo_mesh = bpy.props.PointerProperty(name="Indigo Mesh Properties", type = Indigo_Mesh_Properties)
+ bpy.types.TextCurve.indigo_mesh = bpy.props.PointerProperty(name="Indigo Mesh Properties", type = Indigo_Mesh_Properties)
+ bpy.types.Curve.indigo_mesh = bpy.props.PointerProperty(name="Indigo Mesh Properties", type = Indigo_Mesh_Properties)
+
+ from . properties.tonemapping import Indigo_Tonemapping_Properties
+ bpy.types.Camera.indigo_tonemapping = bpy.props.PointerProperty(name="Indigo Tonemapping Properties", type = Indigo_Tonemapping_Properties)
+
+ print("Registered {} with {} modules".format(bl_info["name"], len(modules)))
+
+def unregister():
+ try: bpy.utils.unregister_module(__name__)
+ except: traceback.print_exc()
+
+ print("Unregistered {}".format(bl_info["name"]))
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/BKIT.JSON b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/BKIT.JSON
new file mode 100644
index 00000000..887b1fb9
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/BKIT.JSON
@@ -0,0 +1 @@
+{"API_key": "", "API_key_refresh": "", "global_dir": "C:\\Users\\Administrator\\blenderkit_data"}
\ No newline at end of file
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/INTERFACE_THEME/⠀.XML b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/INTERFACE_THEME/⠀.XML
new file mode 100644
index 00000000..7cca1cdf
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/INTERFACE_THEME/⠀.XML
@@ -0,0 +1,1489 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/KEYCONFIG/YP.O_PAMYEK_REDNELB_O_BLENDER_KEYMAP_O.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/KEYCONFIG/YP.O_PAMYEK_REDNELB_O_BLENDER_KEYMAP_O.PY
new file mode 100644
index 00000000..9de40381
--- /dev/null
+++ b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/KEYCONFIG/YP.O_PAMYEK_REDNELB_O_BLENDER_KEYMAP_O.PY
@@ -0,0 +1,1607 @@
+keyconfig_data = \
+[("3D View",
+ {"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
+ {"items":
+ [("view3d.cursor3d", {"type": 'RIGHTMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("transform.translate",
+ {"type": 'EVT_TWEAK_R', "value": 'ANY', "shift": True},
+ {"properties":
+ [("cursor_transform", True),
+ ("release_confirm", True),
+ ],
+ },
+ ),
+ ("view3d.localview", {"type": 'NUMPAD_SLASH', "value": 'PRESS'}, None),
+ ("view3d.localview", {"type": 'SLASH', "value": 'PRESS'}, None),
+ ("view3d.localview", {"type": 'MOUSESMARTZOOM', "value": 'ANY'}, None),
+ ("view3d.localview_remove_from", {"type": 'M', "value": 'PRESS'}, None),
+ ("view3d.rotate", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True}, None),
+ ("view3d.move", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("view3d.zoom", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("view3d.dolly", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True, "ctrl": True}, None),
+ ("view3d.view_selected",
+ {"type": 'NUMPAD_PERIOD', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("use_all_regions", True),
+ ],
+ },
+ ),
+ ("view3d.view_selected",
+ {"type": 'NUMPAD_PERIOD', "value": 'PRESS'},
+ {"properties":
+ [("use_all_regions", False),
+ ],
+ },
+ ),
+ ("view3d.smoothview", {"type": 'TIMER1', "value": 'ANY', "any": True}, None),
+ ("view3d.rotate", {"type": 'TRACKPADPAN', "value": 'ANY'}, None),
+ ("view3d.rotate", {"type": 'MOUSEROTATE', "value": 'ANY'}, None),
+ ("view3d.move", {"type": 'TRACKPADPAN', "value": 'ANY', "shift": True}, None),
+ ("view3d.zoom", {"type": 'TRACKPADZOOM', "value": 'ANY'}, None),
+ ("view3d.zoom", {"type": 'TRACKPADPAN', "value": 'ANY', "ctrl": True}, None),
+ ("view3d.zoom",
+ {"type": 'NUMPAD_PLUS', "value": 'PRESS'},
+ {"properties":
+ [("delta", 1),
+ ],
+ },
+ ),
+ ("view3d.zoom",
+ {"type": 'NUMPAD_MINUS', "value": 'PRESS'},
+ {"properties":
+ [("delta", -1),
+ ],
+ },
+ ),
+ ("view3d.zoom",
+ {"type": 'EQUAL', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("delta", 1),
+ ],
+ },
+ ),
+ ("view3d.zoom",
+ {"type": 'MINUS', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("delta", -1),
+ ],
+ },
+ ),
+ ("view3d.zoom",
+ {"type": 'WHEELINMOUSE', "value": 'PRESS'},
+ {"properties":
+ [("delta", 1),
+ ],
+ },
+ ),
+ ("view3d.zoom",
+ {"type": 'WHEELOUTMOUSE', "value": 'PRESS'},
+ {"properties":
+ [("delta", -1),
+ ],
+ },
+ ),
+ ("view3d.dolly",
+ {"type": 'NUMPAD_PLUS', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("delta", 1),
+ ],
+ },
+ ),
+ ("view3d.dolly",
+ {"type": 'NUMPAD_MINUS', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("delta", -1),
+ ],
+ },
+ ),
+ ("view3d.dolly",
+ {"type": 'EQUAL', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("delta", 1),
+ ],
+ },
+ ),
+ ("view3d.dolly",
+ {"type": 'MINUS', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("delta", -1),
+ ],
+ },
+ ),
+ ("view3d.view_center_camera", {"type": 'HOME', "value": 'PRESS'}, None),
+ ("view3d.view_center_lock", {"type": 'HOME', "value": 'PRESS'}, None),
+ ("view3d.view_all",
+ {"type": 'HOME', "value": 'PRESS'},
+ {"properties":
+ [("center", False),
+ ],
+ },
+ ),
+ ("view3d.view_all",
+ {"type": 'HOME', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("use_all_regions", True),
+ ("center", False),
+ ],
+ },
+ ),
+ ("view3d.view_all",
+ {"type": 'C', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("center", True),
+ ],
+ },
+ ),
+ ("wm.call_menu_pie",
+ {"type": 'ACCENT_GRAVE', "value": 'PRESS'},
+ {"properties":
+ [("name", 'VIEW3D_MT_view_pie'),
+ ],
+ },
+ ),
+ ("view3d.navigate", {"type": 'ACCENT_GRAVE', "value": 'PRESS', "shift": True}, None),
+ ("view3d.view_camera", {"type": 'NUMPAD_0', "value": 'PRESS'}, None),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_1', "value": 'PRESS'},
+ {"properties":
+ [("type", 'FRONT'),
+ ],
+ },
+ ),
+ ("view3d.view_orbit",
+ {"type": 'NUMPAD_2', "value": 'PRESS'},
+ {"properties":
+ [("type", 'ORBITUP'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_3', "value": 'PRESS'},
+ {"properties":
+ [("type", 'RIGHT'),
+ ],
+ },
+ ),
+ ("view3d.view_orbit",
+ {"type": 'NUMPAD_4', "value": 'PRESS'},
+ {"properties":
+ [("type", 'ORBITRIGHT'),
+ ],
+ },
+ ),
+ ("view3d.view_persportho", {"type": 'NUMPAD_5', "value": 'PRESS'}, None),
+ ("view3d.view_orbit",
+ {"type": 'NUMPAD_6', "value": 'PRESS'},
+ {"properties":
+ [("type", 'ORBITLEFT'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_7', "value": 'PRESS'},
+ {"properties":
+ [("type", 'TOP'),
+ ],
+ },
+ ),
+ ("view3d.view_orbit",
+ {"type": 'NUMPAD_8', "value": 'PRESS'},
+ {"properties":
+ [("type", 'ORBITDOWN'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_1', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'BACK'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_3', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'LEFT'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_7', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'BOTTOM'),
+ ],
+ },
+ ),
+ ("view3d.view_pan",
+ {"type": 'UP_ARROW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'PANDOWN'),
+ ],
+ },
+ ),
+ ("view3d.view_pan",
+ {"type": 'RIGHT_ARROW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'PANLEFT'),
+ ],
+ },
+ ),
+ ("view3d.view_pan",
+ {"type": 'LEFT_ARROW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'PANRIGHT'),
+ ],
+ },
+ ),
+ ("view3d.view_pan",
+ {"type": 'DOWN_ARROW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'PANUP'),
+ ],
+ },
+ ),
+ ("view3d.view_roll",
+ {"type": 'NUMPAD_4', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'LEFT'),
+ ],
+ },
+ ),
+ ("view3d.view_roll",
+ {"type": 'NUMPAD_6', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'RIGHT'),
+ ],
+ },
+ ),
+ ("view3d.view_orbit",
+ {"type": 'NUMPAD_9', "value": 'PRESS'},
+ {"properties":
+ [("angle", 3.1415927),
+ ("type", 'ORBITRIGHT'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_1', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'FRONT'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_3', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'RIGHT'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_7', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'TOP'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_1', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("type", 'BACK'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_3', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("type", 'LEFT'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NUMPAD_7', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("type", 'BOTTOM'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'EVT_TWEAK_M', "value": 'NORTH', "alt": True},
+ {"properties":
+ [("type", 'TOP'),
+ ("relative", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'EVT_TWEAK_M', "value": 'SOUTH', "alt": True},
+ {"properties":
+ [("type", 'BOTTOM'),
+ ("relative", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'EVT_TWEAK_M', "value": 'EAST', "alt": True},
+ {"properties":
+ [("type", 'RIGHT'),
+ ("relative", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'EVT_TWEAK_M', "value": 'WEST', "alt": True},
+ {"properties":
+ [("type", 'LEFT'),
+ ("relative", True),
+ ],
+ },
+ ),
+ ("view3d.view_center_pick", {"type": 'MIDDLEMOUSE', "value": 'CLICK', "alt": True}, None),
+ ("view3d.ndof_orbit_zoom", {"type": 'NDOF_MOTION', "value": 'ANY'}, None),
+ ("view3d.ndof_orbit", {"type": 'NDOF_MOTION', "value": 'ANY', "ctrl": True}, None),
+ ("view3d.ndof_pan", {"type": 'NDOF_MOTION', "value": 'ANY', "shift": True}, None),
+ ("view3d.ndof_all", {"type": 'NDOF_MOTION', "value": 'ANY', "shift": True, "ctrl": True}, None),
+ ("view3d.view_selected",
+ {"type": 'NDOF_BUTTON_FIT', "value": 'PRESS'},
+ {"properties":
+ [("use_all_regions", False),
+ ],
+ },
+ ),
+ ("view3d.view_roll",
+ {"type": 'NDOF_BUTTON_ROLL_CCW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'LEFT'),
+ ],
+ },
+ ),
+ ("view3d.view_roll",
+ {"type": 'NDOF_BUTTON_ROLL_CCW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'RIGHT'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_FRONT', "value": 'PRESS'},
+ {"properties":
+ [("type", 'FRONT'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_BACK', "value": 'PRESS'},
+ {"properties":
+ [("type", 'BACK'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_LEFT', "value": 'PRESS'},
+ {"properties":
+ [("type", 'LEFT'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_RIGHT', "value": 'PRESS'},
+ {"properties":
+ [("type", 'RIGHT'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_TOP', "value": 'PRESS'},
+ {"properties":
+ [("type", 'TOP'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_BOTTOM', "value": 'PRESS'},
+ {"properties":
+ [("type", 'BOTTOM'),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_FRONT', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'FRONT'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_RIGHT', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'RIGHT'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.view_axis",
+ {"type": 'NDOF_BUTTON_TOP', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'TOP'),
+ ("align_active", True),
+ ],
+ },
+ ),
+ ("view3d.select",
+ {"type": 'LEFTMOUSE', "value": 'CLICK'},
+ {"properties":
+ [("deselect_all", True),
+ ],
+ },
+ ),
+ ("view3d.select",
+ {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True},
+ {"properties":
+ [("toggle", True),
+ ],
+ },
+ ),
+ ("view3d.select",
+ {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True},
+ {"properties":
+ [("center", True),
+ ("object", True),
+ ],
+ },
+ ),
+ ("view3d.select",
+ {"type": 'LEFTMOUSE', "value": 'CLICK', "alt": True},
+ {"properties":
+ [("enumerate", True),
+ ],
+ },
+ ),
+ ("view3d.select",
+ {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True, "ctrl": True},
+ {"properties":
+ [("toggle", True),
+ ("center", True),
+ ],
+ },
+ ),
+ ("view3d.select",
+ {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True, "alt": True},
+ {"properties":
+ [("center", True),
+ ("enumerate", True),
+ ],
+ },
+ ),
+ ("view3d.select",
+ {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True, "alt": True},
+ {"properties":
+ [("toggle", True),
+ ("enumerate", True),
+ ],
+ },
+ ),
+ ("view3d.select",
+ {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True, "ctrl": True, "alt": True},
+ {"properties":
+ [("toggle", True),
+ ("center", True),
+ ("enumerate", True),
+ ],
+ },
+ ),
+ ("view3d.select_box", {"type": 'B', "value": 'PRESS'}, None),
+ ("view3d.select_lasso",
+ {"type": 'EVT_TWEAK_R', "value": 'ANY', "ctrl": True},
+ {"properties":
+ [("mode", 'ADD'),
+ ],
+ },
+ ),
+ ("view3d.select_lasso",
+ {"type": 'EVT_TWEAK_R', "value": 'ANY', "shift": True, "ctrl": True},
+ {"properties":
+ [("mode", 'SUB'),
+ ],
+ },
+ ),
+ ("view3d.select_circle", {"type": 'C', "value": 'PRESS'}, None),
+ ("view3d.clip_border", {"type": 'B', "value": 'PRESS', "alt": True}, None),
+ ("view3d.zoom_border", {"type": 'B', "value": 'PRESS', "shift": True}, None),
+ ("view3d.render_border", {"type": 'B', "value": 'PRESS', "ctrl": True}, None),
+ ("view3d.clear_render_border", {"type": 'B', "value": 'PRESS', "ctrl": True, "alt": True}, None),
+ ("view3d.camera_to_view", {"type": 'NUMPAD_0', "value": 'PRESS', "ctrl": True, "alt": True}, None),
+ ("view3d.object_as_camera", {"type": 'NUMPAD_0', "value": 'PRESS', "ctrl": True}, None),
+ ("view3d.copybuffer", {"type": 'C', "value": 'PRESS', "ctrl": True}, None),
+ ("view3d.pastebuffer", {"type": 'V', "value": 'PRESS', "ctrl": True}, None),
+ ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
+ ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+ ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
+ ("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
+ ("transform.bend", {"type": 'W', "value": 'PRESS', "shift": True}, None),
+ ("transform.tosphere", {"type": 'S', "value": 'PRESS', "shift": True, "alt": True}, None),
+ ("transform.shear", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
+ ("transform.mirror", {"type": 'M', "value": 'PRESS', "ctrl": True}, None),
+ ("wm.context_toggle",
+ {"type": 'TAB', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("data_path", 'tool_settings.use_snap'),
+ ],
+ },
+ ),
+ ("wm.call_panel",
+ {"type": 'TAB', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("name", 'VIEW3D_PT_snapping'),
+ ("keep_open", False),
+ ],
+ },
+ ),
+ ("object.transform_axis_target", {"type": 'T', "value": 'PRESS', "shift": True}, None),
+ ("transform.skin_resize", {"type": 'A', "value": 'PRESS', "ctrl": True}, None),
+ ("wm.call_menu_pie",
+ {"type": 'S', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("name", 'VIEW3D_MT_snap_pie'),
+ ],
+ },
+ ),
+ ("wm.context_toggle",
+ {"type": 'ACCENT_GRAVE', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("data_path", 'space_data.show_gizmo'),
+ ],
+ },
+ ),
+ ("wm.call_menu_pie",
+ {"type": 'PERIOD', "value": 'PRESS'},
+ {"properties":
+ [("name", 'VIEW3D_MT_pivot_pie'),
+ ],
+ },
+ ),
+ ("wm.call_menu_pie",
+ {"type": 'COMMA', "value": 'PRESS'},
+ {"properties":
+ [("name", 'VIEW3D_MT_orientations_pie'),
+ ],
+ },
+ ),
+ ("wm.call_menu_pie",
+ {"type": 'Z', "value": 'PRESS'},
+ {"properties":
+ [("name", 'VIEW3D_MT_shading_ex_pie'),
+ ],
+ },
+ ),
+ ("view3d.toggle_shading",
+ {"type": 'Z', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'WIREFRAME'),
+ ],
+ },
+ ),
+ ("view3d.toggle_xray", {"type": 'Z', "value": 'PRESS', "alt": True}, None),
+ ("wm.context_toggle",
+ {"type": 'Z', "value": 'PRESS', "shift": True, "alt": True},
+ {"properties":
+ [("data_path", 'space_data.overlay.show_overlays'),
+ ],
+ },
+ ),
+ ("wm.tool_set_by_id",
+ {"type": 'W', "value": 'PRESS'},
+ {"properties":
+ [("name", 'builtin.select_box'),
+ ("cycle", True),
+ ],
+ },
+ ),
+ ],
+ },
+ ),
+ ("Clip Editor",
+ {"space_type": 'CLIP_EDITOR', "region_type": 'WINDOW'},
+ {"items":
+ [("clip.view_pan", {"type": 'MIDDLEMOUSE', "value": 'PRESS'}, None),
+ ("clip.view_pan", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("clip.view_pan", {"type": 'TRACKPADPAN', "value": 'ANY'}, None),
+ ("clip.view_zoom", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("clip.view_zoom", {"type": 'TRACKPADZOOM', "value": 'ANY'}, None),
+ ("clip.view_zoom", {"type": 'MIDDLEMOUSE', "value": 'ANY', "shift": True}, None),
+ ("clip.view_zoom_in", {"type": 'WHEELINMOUSE', "value": 'PRESS'}, None),
+ ("clip.view_zoom_out", {"type": 'WHEELOUTMOUSE', "value": 'PRESS'}, None),
+ ("clip.view_zoom_in", {"type": 'NUMPAD_PLUS', "value": 'PRESS'}, None),
+ ("clip.view_zoom_out", {"type": 'NUMPAD_MINUS', "value": 'PRESS'}, None),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_8', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("ratio", 8.0),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_4', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("ratio", 4.0),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_2', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("ratio", 2.0),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_8', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("ratio", 8.0),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_4', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("ratio", 4.0),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_2', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("ratio", 2.0),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_1', "value": 'PRESS'},
+ {"properties":
+ [("ratio", 1.0),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_2', "value": 'PRESS'},
+ {"properties":
+ [("ratio", 0.5),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_4', "value": 'PRESS'},
+ {"properties":
+ [("ratio", 0.25),
+ ],
+ },
+ ),
+ ("clip.view_zoom_ratio",
+ {"type": 'NUMPAD_8', "value": 'PRESS'},
+ {"properties":
+ [("ratio", 0.125),
+ ],
+ },
+ ),
+ ("clip.view_all", {"type": 'HOME', "value": 'PRESS'}, None),
+ ("clip.view_all",
+ {"type": 'F', "value": 'PRESS'},
+ {"properties":
+ [("fit_view", True),
+ ],
+ },
+ ),
+ ("clip.view_selected", {"type": 'NUMPAD_PERIOD', "value": 'PRESS'}, None),
+ ("clip.view_all", {"type": 'NDOF_BUTTON_FIT', "value": 'PRESS'}, None),
+ ("clip.view_ndof", {"type": 'NDOF_MOTION', "value": 'ANY'}, None),
+ ("clip.frame_jump",
+ {"type": 'LEFT_ARROW', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("position", 'PATHSTART'),
+ ],
+ },
+ ),
+ ("clip.frame_jump",
+ {"type": 'RIGHT_ARROW', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("position", 'PATHEND'),
+ ],
+ },
+ ),
+ ("clip.frame_jump",
+ {"type": 'LEFT_ARROW', "value": 'PRESS', "shift": True, "alt": True},
+ {"properties":
+ [("position", 'FAILEDPREV'),
+ ],
+ },
+ ),
+ ("clip.frame_jump",
+ {"type": 'RIGHT_ARROW', "value": 'PRESS', "shift": True, "alt": True},
+ {"properties":
+ [("position", 'PATHSTART'),
+ ],
+ },
+ ),
+ ("clip.change_frame", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+ ("clip.select",
+ {"type": 'LEFTMOUSE', "value": 'PRESS'},
+ {"properties":
+ [("extend", False),
+ ("deselect_all", True),
+ ],
+ },
+ ),
+ ("clip.select",
+ {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("extend", True),
+ ],
+ },
+ ),
+ ("clip.select_all",
+ {"type": 'A', "value": 'PRESS'},
+ {"properties":
+ [("action", 'SELECT'),
+ ],
+ },
+ ),
+ ("clip.select_all",
+ {"type": 'A', "value": 'PRESS', "alt": True},
+ {"properties":
+ [("action", 'DESELECT'),
+ ],
+ },
+ ),
+ ("clip.select_all",
+ {"type": 'I', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("action", 'INVERT'),
+ ],
+ },
+ ),
+ ("clip.select_all",
+ {"type": 'A', "value": 'DOUBLE_CLICK'},
+ {"properties":
+ [("action", 'DESELECT'),
+ ],
+ },
+ ),
+ ("clip.select_box", {"type": 'B', "value": 'PRESS'}, None),
+ ("clip.select_circle", {"type": 'C', "value": 'PRESS'}, None),
+ ("wm.call_menu",
+ {"type": 'G', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("name", 'CLIP_MT_select_grouped'),
+ ],
+ },
+ ),
+ ("clip.select_lasso",
+ {"type": 'EVT_TWEAK_R', "value": 'ANY', "ctrl": True, "alt": True},
+ {"properties":
+ [("mode", 'ADD'),
+ ],
+ },
+ ),
+ ("clip.select_lasso",
+ {"type": 'EVT_TWEAK_R', "value": 'ANY', "shift": True, "ctrl": True, "alt": True},
+ {"properties":
+ [("mode", 'SUB'),
+ ],
+ },
+ ),
+ ("clip.add_marker_slide", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("clip.delete_marker", {"type": 'X', "value": 'PRESS', "shift": True}, None),
+ ("clip.delete_marker", {"type": 'DEL', "value": 'PRESS', "shift": True}, None),
+ ("clip.slide_marker", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+ ("clip.disable_markers",
+ {"type": 'D', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("action", 'TOGGLE'),
+ ],
+ },
+ ),
+ ("clip.delete_track", {"type": 'X', "value": 'PRESS'}, None),
+ ("clip.delete_track", {"type": 'DEL', "value": 'PRESS'}, None),
+ ("clip.lock_tracks",
+ {"type": 'L', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("action", 'LOCK'),
+ ],
+ },
+ ),
+ ("clip.lock_tracks",
+ {"type": 'L', "value": 'PRESS', "alt": True},
+ {"properties":
+ [("action", 'UNLOCK'),
+ ],
+ },
+ ),
+ ("clip.hide_tracks",
+ {"type": 'H', "value": 'PRESS'},
+ {"properties":
+ [("unselected", False),
+ ],
+ },
+ ),
+ ("clip.hide_tracks",
+ {"type": 'H', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("unselected", True),
+ ],
+ },
+ ),
+ ("clip.hide_tracks_clear", {"type": 'H', "value": 'PRESS', "alt": True}, None),
+ ("clip.slide_plane_marker", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, None),
+ ("clip.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None),
+ ("clip.keyframe_delete", {"type": 'I', "value": 'PRESS', "alt": True}, None),
+ ("clip.join_tracks", {"type": 'J', "value": 'PRESS', "ctrl": True}, None),
+ ("clip.lock_selection_toggle", {"type": 'L', "value": 'PRESS'}, None),
+ ("wm.context_toggle",
+ {"type": 'D', "value": 'PRESS', "alt": True},
+ {"properties":
+ [("data_path", 'space_data.show_disabled'),
+ ],
+ },
+ ),
+ ("wm.context_toggle",
+ {"type": 'S', "value": 'PRESS', "alt": True},
+ {"properties":
+ [("data_path", 'space_data.show_marker_search'),
+ ],
+ },
+ ),
+ ("wm.context_toggle",
+ {"type": 'M', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.use_mute_footage'),
+ ],
+ },
+ ),
+ ("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
+ ("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+ ("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
+ ("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
+ ("clip.clear_track_path",
+ {"type": 'T', "value": 'PRESS', "alt": True},
+ {"properties":
+ [("action", 'REMAINED'),
+ ("clear_active", False),
+ ],
+ },
+ ),
+ ("clip.clear_track_path",
+ {"type": 'T', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("action", 'UPTO'),
+ ("clear_active", False),
+ ],
+ },
+ ),
+ ("clip.clear_track_path",
+ {"type": 'T', "value": 'PRESS', "shift": True, "alt": True},
+ {"properties":
+ [("action", 'ALL'),
+ ("clear_active", False),
+ ],
+ },
+ ),
+ ("clip.cursor_set", {"type": 'RIGHTMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("wm.call_menu_pie",
+ {"type": 'PERIOD', "value": 'PRESS'},
+ {"properties":
+ [("name", 'CLIP_MT_pivot_pie'),
+ ],
+ },
+ ),
+ ("clip.copy_tracks", {"type": 'C', "value": 'PRESS', "ctrl": True}, None),
+ ("clip.paste_tracks", {"type": 'V', "value": 'PRESS', "ctrl": True}, None),
+ ("wm.call_menu",
+ {"type": 'RIGHTMOUSE', "value": 'PRESS'},
+ {"properties":
+ [("name", 'CLIP_MT_tracking_context_menu'),
+ ],
+ },
+ ),
+ ("wm.call_menu",
+ {"type": 'APP', "value": 'PRESS'},
+ {"properties":
+ [("name", 'CLIP_MT_tracking_context_menu'),
+ ],
+ },
+ ),
+ ],
+ },
+ ),
+ ("Frames",
+ {"space_type": 'EMPTY', "region_type": 'WINDOW'},
+ {"items":
+ [("screen.frame_jump",
+ {"type": 'RIGHT_ARROW', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("end", True),
+ ],
+ },
+ ),
+ ("screen.frame_jump",
+ {"type": 'LEFT_ARROW', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("end", False),
+ ],
+ },
+ ),
+ ("screen.keyframe_jump",
+ {"type": 'MEDIA_LAST', "value": 'PRESS'},
+ {"properties":
+ [("next", True),
+ ],
+ },
+ ),
+ ("screen.keyframe_jump",
+ {"type": 'MEDIA_FIRST', "value": 'PRESS'},
+ {"properties":
+ [("next", False),
+ ],
+ },
+ ),
+ ("screen.frame_offset",
+ {"type": 'WHEELDOWNMOUSE', "value": 'PRESS', "alt": True},
+ {"properties":
+ [("delta", 1),
+ ],
+ },
+ ),
+ ("screen.frame_offset",
+ {"type": 'WHEELUPMOUSE', "value": 'PRESS', "alt": True},
+ {"properties":
+ [("delta", -1),
+ ],
+ },
+ ),
+ ("screen.animation_play", {"type": 'SPACE', "value": 'PRESS'}, None),
+ ("screen.animation_play",
+ {"type": 'SPACE', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("reverse", True),
+ ],
+ },
+ ),
+ ("screen.animation_cancel", {"type": 'ESC', "value": 'PRESS'}, None),
+ ("screen.animation_play", {"type": 'MEDIA_PLAY', "value": 'PRESS'}, None),
+ ("screen.animation_cancel", {"type": 'MEDIA_STOP', "value": 'PRESS'}, None),
+ ],
+ },
+ ),
+ ("Image",
+ {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'},
+ {"items":
+ [("image.view_all", {"type": 'HOME', "value": 'PRESS'}, None),
+ ("image.view_all",
+ {"type": 'HOME', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("fit_view", True),
+ ],
+ },
+ ),
+ ("image.view_selected", {"type": 'NUMPAD_PERIOD', "value": 'PRESS'}, None),
+ ("image.view_pan", {"type": 'MIDDLEMOUSE', "value": 'PRESS'}, None),
+ ("image.view_pan", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("image.view_pan", {"type": 'TRACKPADPAN', "value": 'ANY'}, None),
+ ("image.view_all", {"type": 'NDOF_BUTTON_FIT', "value": 'PRESS'}, None),
+ ("image.view_ndof", {"type": 'NDOF_MOTION', "value": 'ANY'}, None),
+ ("image.view_zoom_in", {"type": 'WHEELINMOUSE', "value": 'PRESS'}, None),
+ ("image.view_zoom_out", {"type": 'WHEELOUTMOUSE', "value": 'PRESS'}, None),
+ ("image.view_zoom_in", {"type": 'NUMPAD_PLUS', "value": 'PRESS'}, None),
+ ("image.view_zoom_out", {"type": 'NUMPAD_MINUS', "value": 'PRESS'}, None),
+ ("image.view_zoom", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("image.view_zoom", {"type": 'TRACKPADZOOM', "value": 'ANY'}, None),
+ ("image.view_zoom", {"type": 'MIDDLEMOUSE', "value": 'ANY', "shift": True}, None),
+ ("image.view_zoom_border", {"type": 'B', "value": 'PRESS', "shift": True}, None),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_8', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("ratio", 8.0),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_4', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("ratio", 4.0),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_2', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("ratio", 2.0),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_8', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("ratio", 8.0),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_4', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("ratio", 4.0),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_2', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("ratio", 2.0),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_1', "value": 'PRESS'},
+ {"properties":
+ [("ratio", 1.0),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_2', "value": 'PRESS'},
+ {"properties":
+ [("ratio", 0.5),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_4', "value": 'PRESS'},
+ {"properties":
+ [("ratio", 0.25),
+ ],
+ },
+ ),
+ ("image.view_zoom_ratio",
+ {"type": 'NUMPAD_8', "value": 'PRESS'},
+ {"properties":
+ [("ratio", 0.125),
+ ],
+ },
+ ),
+ ("image.change_frame", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+ ("image.sample", {"type": 'RIGHTMOUSE', "value": 'PRESS'}, None),
+ ("image.curves_point_set",
+ {"type": 'RIGHTMOUSE', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("point", 'BLACK_POINT'),
+ ],
+ },
+ ),
+ ("image.curves_point_set",
+ {"type": 'RIGHTMOUSE', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("point", 'WHITE_POINT'),
+ ],
+ },
+ ),
+ ("object.mode_set",
+ {"type": 'TAB', "value": 'PRESS'},
+ {"properties":
+ [("mode", 'EDIT'),
+ ("toggle", True),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'ONE', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 0),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'TWO', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 1),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'THREE', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 2),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'FOUR', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 3),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'FIVE', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 4),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'SIX', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 5),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'SEVEN', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 6),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'EIGHT', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 7),
+ ],
+ },
+ ),
+ ("wm.context_set_int",
+ {"type": 'NINE', "value": 'PRESS'},
+ {"properties":
+ [("data_path", 'space_data.image.render_slots.active_index'),
+ ("value", 8),
+ ],
+ },
+ ),
+ ("wm.call_menu_pie",
+ {"type": 'PERIOD', "value": 'PRESS'},
+ {"properties":
+ [("name", 'IMAGE_MT_pivot_pie'),
+ ],
+ },
+ ),
+ ("image.render_border", {"type": 'B', "value": 'PRESS', "ctrl": True}, None),
+ ("image.clear_render_border", {"type": 'B', "value": 'PRESS', "ctrl": True, "alt": True}, None),
+ ("wm.call_menu",
+ {"type": 'RIGHTMOUSE', "value": 'PRESS'},
+ {"properties":
+ [("name", 'IMAGE_MT_mask_context_menu'),
+ ],
+ },
+ ),
+ ("wm.call_menu",
+ {"type": 'APP', "value": 'PRESS'},
+ {"properties":
+ [("name", 'IMAGE_MT_mask_context_menu'),
+ ],
+ },
+ ),
+ ],
+ },
+ ),
+ ("Object Non-modal",
+ {"space_type": 'EMPTY', "region_type": 'WINDOW'},
+ {"items":
+ [("object.mode_set",
+ {"type": 'TAB', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("mode", 'EDIT'),
+ ("toggle", True),
+ ],
+ },
+ ),
+ ("wm.call_menu_pie",
+ {"type": 'TAB', "value": 'PRESS'},
+ {"properties":
+ [("name", 'VIEW3D_MT_object_mode_pie'),
+ ],
+ "active":False,
+ },
+ ),
+ ],
+ },
+ ),
+ ("Text",
+ {"space_type": 'TEXT_EDITOR', "region_type": 'WINDOW'},
+ {"items":
+ [("wm.context_cycle_int",
+ {"type": 'WHEELUPMOUSE', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("data_path", 'space_data.font_size'),
+ ("reverse", False),
+ ],
+ },
+ ),
+ ("wm.context_cycle_int",
+ {"type": 'WHEELDOWNMOUSE', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("data_path", 'space_data.font_size'),
+ ("reverse", True),
+ ],
+ },
+ ),
+ ("wm.context_cycle_int",
+ {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("data_path", 'space_data.font_size'),
+ ("reverse", False),
+ ],
+ },
+ ),
+ ("wm.context_cycle_int",
+ {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("data_path", 'space_data.font_size'),
+ ("reverse", True),
+ ],
+ },
+ ),
+ ("text.new", {"type": 'N', "value": 'PRESS', "alt": True}, None),
+ ("text.open", {"type": 'O', "value": 'PRESS', "alt": True}, None),
+ ("text.reload", {"type": 'R', "value": 'PRESS', "alt": True}, None),
+ ("text.save", {"type": 'S', "value": 'PRESS', "alt": True}, None),
+ ("text.save_as", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
+ ("text.run_script", {"type": 'P', "value": 'PRESS', "alt": True}, None),
+ ("text.cut", {"type": 'X', "value": 'PRESS', "ctrl": True}, None),
+ ("text.copy", {"type": 'C', "value": 'PRESS', "ctrl": True}, None),
+ ("text.paste", {"type": 'V', "value": 'PRESS', "ctrl": True}, None),
+ ("text.cut", {"type": 'DEL', "value": 'PRESS', "shift": True}, None),
+ ("text.copy", {"type": 'INSERT', "value": 'PRESS', "ctrl": True}, None),
+ ("text.paste", {"type": 'INSERT', "value": 'PRESS', "shift": True}, None),
+ ("text.duplicate_line", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
+ ("text.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True}, None),
+ ("text.select_line", {"type": 'A', "value": 'PRESS', "shift": True, "ctrl": True}, None),
+ ("text.select_word", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, None),
+ ("text.move_lines",
+ {"type": 'UP_ARROW', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("direction", 'UP'),
+ ],
+ },
+ ),
+ ("text.move_lines",
+ {"type": 'DOWN_ARROW', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("direction", 'DOWN'),
+ ],
+ },
+ ),
+ ("text.indent_or_autocomplete", {"type": 'TAB', "value": 'PRESS'}, None),
+ ("text.unindent", {"type": 'TAB', "value": 'PRESS', "shift": True}, None),
+ ("text.comment_toggle", {"type": 'SLASH', "value": 'PRESS', "ctrl": True}, None),
+ ("text.move",
+ {"type": 'HOME', "value": 'PRESS'},
+ {"properties":
+ [("type", 'LINE_BEGIN'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'END', "value": 'PRESS'},
+ {"properties":
+ [("type", 'LINE_END'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'E', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'LINE_END'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'E', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("type", 'LINE_END'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'LEFT_ARROW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'PREVIOUS_CHARACTER'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'RIGHT_ARROW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'NEXT_CHARACTER'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'LEFT_ARROW', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'PREVIOUS_WORD'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'RIGHT_ARROW', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'NEXT_WORD'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'UP_ARROW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'PREVIOUS_LINE'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'DOWN_ARROW', "value": 'PRESS'},
+ {"properties":
+ [("type", 'NEXT_LINE'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'PAGE_UP', "value": 'PRESS'},
+ {"properties":
+ [("type", 'PREVIOUS_PAGE'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'PAGE_DOWN', "value": 'PRESS'},
+ {"properties":
+ [("type", 'NEXT_PAGE'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'HOME', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'FILE_TOP'),
+ ],
+ },
+ ),
+ ("text.move",
+ {"type": 'END', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'FILE_BOTTOM'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'HOME', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'LINE_BEGIN'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'END', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'LINE_END'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'LEFT_ARROW', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'PREVIOUS_CHARACTER'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'RIGHT_ARROW', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'NEXT_CHARACTER'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'LEFT_ARROW', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("type", 'PREVIOUS_WORD'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'RIGHT_ARROW', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("type", 'NEXT_WORD'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'UP_ARROW', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'PREVIOUS_LINE'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'DOWN_ARROW', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'NEXT_LINE'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'PAGE_UP', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'PREVIOUS_PAGE'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'PAGE_DOWN', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'NEXT_PAGE'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'HOME', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("type", 'FILE_TOP'),
+ ],
+ },
+ ),
+ ("text.move_select",
+ {"type": 'END', "value": 'PRESS', "shift": True, "ctrl": True},
+ {"properties":
+ [("type", 'FILE_BOTTOM'),
+ ],
+ },
+ ),
+ ("text.delete",
+ {"type": 'DEL', "value": 'PRESS'},
+ {"properties":
+ [("type", 'NEXT_CHARACTER'),
+ ],
+ },
+ ),
+ ("text.delete",
+ {"type": 'BACK_SPACE', "value": 'PRESS'},
+ {"properties":
+ [("type", 'PREVIOUS_CHARACTER'),
+ ],
+ },
+ ),
+ ("text.delete",
+ {"type": 'BACK_SPACE', "value": 'PRESS', "shift": True},
+ {"properties":
+ [("type", 'PREVIOUS_CHARACTER'),
+ ],
+ },
+ ),
+ ("text.delete",
+ {"type": 'DEL', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'NEXT_WORD'),
+ ],
+ },
+ ),
+ ("text.delete",
+ {"type": 'BACK_SPACE', "value": 'PRESS', "ctrl": True},
+ {"properties":
+ [("type", 'PREVIOUS_WORD'),
+ ],
+ },
+ ),
+ ("text.overwrite_toggle", {"type": 'INSERT', "value": 'PRESS'}, None),
+ ("text.scroll_bar", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+ ("text.scroll_bar", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("text.scroll", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("text.scroll", {"type": 'TRACKPADPAN', "value": 'ANY'}, None),
+ ("text.selection_set", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
+ ("text.cursor_set", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+ ("text.selection_set", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("text.scroll",
+ {"type": 'WHEELUPMOUSE', "value": 'PRESS'},
+ {"properties":
+ [("lines", -1),
+ ],
+ },
+ ),
+ ("text.scroll",
+ {"type": 'WHEELDOWNMOUSE', "value": 'PRESS'},
+ {"properties":
+ [("lines", 1),
+ ],
+ },
+ ),
+ ("text.line_break", {"type": 'RET', "value": 'PRESS'}, None),
+ ("text.line_break", {"type": 'NUMPAD_ENTER', "value": 'PRESS'}, None),
+ ("text.line_number", {"type": 'TEXTINPUT', "value": 'ANY', "any": True}, None),
+ ("wm.call_menu",
+ {"type": 'RIGHTMOUSE', "value": 'PRESS'},
+ {"properties":
+ [("name", 'TEXT_MT_context_menu'),
+ ],
+ },
+ ),
+ ("text.insert", {"type": 'TEXTINPUT', "value": 'ANY', "any": True}, None),
+ ],
+ },
+ ),
+ ("View2D",
+ {"space_type": 'EMPTY', "region_type": 'WINDOW'},
+ {"items":
+ [("view2d.scroller_activate", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+ ("view2d.scroller_activate", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("view2d.pan", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("view2d.pan", {"type": 'TRACKPADPAN', "value": 'ANY'}, None),
+ ("view2d.scroll_right", {"type": 'RIGHT_ARROW', "value": 'PRESS'}, None),
+ ("view2d.scroll_left", {"type": 'LEFT_ARROW', "value": 'PRESS'}, None),
+ ("view2d.scroll_down", {"type": 'DOWN_ARROW', "value": 'PRESS'}, None),
+ ("view2d.scroll_up", {"type": 'UP_ARROW', "value": 'PRESS'}, None),
+ ("view2d.ndof", {"type": 'NDOF_MOTION', "value": 'ANY'}, None),
+ ("view2d.zoom_out", {"type": 'WHEELOUTMOUSE', "value": 'PRESS'}, None),
+ ("view2d.zoom_in", {"type": 'WHEELINMOUSE', "value": 'PRESS'}, None),
+ ("view2d.zoom_out", {"type": 'NUMPAD_MINUS', "value": 'PRESS'}, None),
+ ("view2d.zoom_in", {"type": 'NUMPAD_PLUS', "value": 'PRESS'}, None),
+ ("view2d.zoom", {"type": 'LEFTMOUSE', "value": 'ANY', "shift": True}, None),
+ ("view2d.smoothview", {"type": 'TIMER1', "value": 'ANY', "any": True}, None),
+ ("view2d.scroll_down", {"type": 'NUMPAD_2', "value": 'PRESS'}, None),
+ ("view2d.scroll_up", {"type": 'NUMPAD_8', "value": 'PRESS'}, None),
+ ("view2d.scroll_right", {"type": 'NUMPAD_6', "value": 'PRESS'}, None),
+ ("view2d.scroll_left", {"type": 'NUMPAD_4', "value": 'PRESS'}, None),
+ ("view2d.zoom", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("view2d.zoom", {"type": 'TRACKPADZOOM', "value": 'ANY'}, None),
+ ("view2d.zoom_border", {"type": 'B', "value": 'PRESS', "shift": True}, None),
+ ],
+ },
+ ),
+ ("View2D Buttons List",
+ {"space_type": 'EMPTY', "region_type": 'WINDOW'},
+ {"items":
+ [("view2d.scroller_activate", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+ ("view2d.scroller_activate", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("view2d.pan", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
+ ("view2d.pan", {"type": 'TRACKPADPAN', "value": 'ANY'}, None),
+ ("view2d.scroll_down", {"type": 'WHEELDOWNMOUSE', "value": 'PRESS'}, None),
+ ("view2d.scroll_up", {"type": 'WHEELUPMOUSE', "value": 'PRESS'}, None),
+ ("view2d.scroll_down",
+ {"type": 'PAGE_DOWN', "value": 'PRESS'},
+ {"properties":
+ [("page", True),
+ ],
+ },
+ ),
+ ("view2d.scroll_up",
+ {"type": 'PAGE_UP', "value": 'PRESS'},
+ {"properties":
+ [("page", True),
+ ],
+ },
+ ),
+ ("view2d.zoom", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, None),
+ ("view2d.zoom", {"type": 'TRACKPADZOOM', "value": 'ANY'}, None),
+ ("view2d.zoom", {"type": 'LEFTMOUSE', "value": 'ANY', "shift": True}, None),
+ ("view2d.zoom_out", {"type": 'NUMPAD_MINUS', "value": 'PRESS'}, None),
+ ("view2d.zoom_in", {"type": 'NUMPAD_PLUS', "value": 'PRESS'}, None),
+ ("view2d.reset", {"type": 'HOME', "value": 'PRESS'}, None),
+ ],
+ },
+ ),
+ ]
+
+
+if __name__ == "__main__":
+ import os
+ from bl_keymap_utils.io import keyconfig_import_from_data
+ keyconfig_import_from_data(os.path.splitext(os.path.basename(__file__))[0], keyconfig_data)