
committed by
GitHub

No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 19571 additions and 0 deletions
-
2◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/BOOKMARKS.TXT
-
1◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/PLATFORM_SUPPORT.TXT
-
1◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/RECENT-FILES.TXT
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/STARTUP.BLEND
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/CONFIG/USERPREF.BLEND
-
BIN◯ᴥᗱᗴᗝИ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
-
48◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/DATAFILES/STUDIOLIGHTS/STUDIO/LƧ.SL
-
1304◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ADD_MESH_CLUSTER.PY
-
164◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_CALC_FUNC.PY
-
1223◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_INTERFACE.PY
-
219◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_OPERATORS.PY
-
210◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_PANEL.PY
-
103◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/CFG.PY
-
68◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__INIT__.PY
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_CALC_FUNC.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_INTERFACE.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_OPERATORS.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/AT_PANEL.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/CFG.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/__PYCACHE__/__INIT__.CPYTHON-37.PYC
-
298◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/MESH_SOLIDIFY_WIREFRAME.PY
-
421◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/OBJECT_RENDER_WIRE.PY
-
158◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/PRECISE_RENDER_BORDER_ADJUST_V1-3.PY
-
307◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/RENDER-BORDER.PY
-
125◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/RA_DRAW_UI.PY
-
666◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__INIT__.PY
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__PYCACHE__/RA_DRAW_UI.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/R_ARRAY-MASTER/__PYCACHE__/__INIT__.CPYTHON-37.PYC
-
1360◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/SPACE_VIEW_3D_DISPLAY_TOOLS.PY
-
3235◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/COLORS_GROUPS_EXCHANGER.PY
-
345◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/DUAL_MESH.PY
-
488◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/GCODE_EXPORT.PY
-
477◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/LATTICE.PY
-
54◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/NUMBA_FUNCTIONS.PY
-
40◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/README.MD
-
4280◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY
-
462◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/UTILS.PY
-
178◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/UV_TO_MESH.PY
-
151◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__INIT__.PY
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/COLORS_GROUPS_EXCHANGER.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/DUAL_MESH.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/GCODE_EXPORT.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/LATTICE.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/NUMBA_FUNCTIONS.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/TESSELLATE_NUMPY.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/UTILS.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/UV_TO_MESH.CPYTHON-37.PYC
-
BIN◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/__PYCACHE__/__INIT__.CPYTHON-37.PYC
-
86◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/__INIT__.PY
-
1◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/BKIT.JSON
-
1489◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/INTERFACE_THEME/⠀.XML
-
1607◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/KEYCONFIG/YP.O_PAMYEK_REDNELB_O_BLENDER_KEYMAP_O.PY
@ -0,0 +1,2 @@ |
|||||
|
[Bookmarks] |
||||
|
[Recent] |
@ -0,0 +1 @@ |
|||||
|
{NVIDIA Corporation/GeForce GTX 460/PCIe/SSE2/4.5.0 NVIDIA 391.35}=SUPPORTED |
@ -0,0 +1 @@ |
|||||
|
|
@ -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 |
1304
◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ADD_MESH_CLUSTER.PY
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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 |
1223
◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/ARRAY_TOOLS_1-2-1/AT_INTERFACE.PY
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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="") |
@ -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') |
@ -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) |
||||
|
|
@ -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 <http://www.gnu.org/licenses/>. |
||||
|
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() |
@ -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() |
@ -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() |
@ -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() |
@ -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() |
@ -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) |
@ -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() |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
1360
◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/SPACE_VIEW_3D_DISPLAY_TOOLS.PY
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
3235
◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/COLORS_GROUPS_EXCHANGER.PY
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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'} |
@ -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'} |
@ -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'} |
@ -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 |
@ -0,0 +1,40 @@ |
|||||
|
# Tissue |
||||
|
 |
||||
|
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 |
4280
◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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) |
@ -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'} |
@ -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() |
@ -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 <http://www.gnu.org/licenses/>. |
||||
|
''' |
||||
|
|
||||
|
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"])) |
@ -0,0 +1 @@ |
|||||
|
{"API_key": "", "API_key_refresh": "", "global_dir": "C:\\Users\\Administrator\\blenderkit_data"} |
1489
◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/INTERFACE_THEME/⠀.XML
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1607
◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/PRESETS/KEYCONFIG/YP.O_PAMYEK_REDNELB_O_BLENDER_KEYMAP_O.PY
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue