
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