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)