mirror of http://CODE.RHODECODE.COM/u/O/O/O
0000OOOO0000
4 years ago
committed by
GitHub
52 changed files with 19571 additions and 0 deletions
@ -0,0 +1,2 @@
|
||||
[Bookmarks] |
||||
[Recent] |
@ -0,0 +1 @@
|
||||
{NVIDIA Corporation/GeForce GTX 460/PCIe/SSE2/4.5.0 NVIDIA 391.35}=SUPPORTED |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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 |
File diff suppressed because it is too large
Load Diff
@ -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 |
File diff suppressed because it is too large
Load Diff
@ -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() |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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() |
||||
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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 |
||||
![cover](http://www.co-de-it.com/wordpress/wp-content/uploads/2015/07/tissue_graphics.jpg) |
||||
Tissue - Blender's add-on for computational design by Co-de-iT |
||||
http://www.co-de-it.com/wordpress/code/blender-tissue |
||||
|
||||
Tissue is already shipped with both Blender 2.79b and Blender 2.80. However both versions can be updated manually, for more updated features and more stability. |
||||
|
||||
### Blender 2.80 |
||||
|
||||
Tissue v0.3.31 for Blender 2.80 (latest stable release): https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-31 |
||||
|
||||
Development branch (most updated version): https://github.com/alessandro-zomparelli/tissue/tree/b280-dev |
||||
|
||||
### Blender 2.79 |
||||
|
||||
Tissue v0.3.4 for Blender 2.79b (latest stable release): https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-4 |
||||
|
||||
Development branch (most updated version): https://github.com/alessandro-zomparelli/tissue/tree/dev1 |
||||
|
||||
|
||||
### Installation: |
||||
|
||||
1. Start Blender. Open User Preferences, the addons tab |
||||
2. Search for Tissue add-on and remove existing version |
||||
3. Click "install from file" and point Blender at the downloaded zip ("Install..." for Blender 2.80) |
||||
4. Activate Tissue add-on from user preferences |
||||
5. Save user preferences if you want to have it on at startup. (This could be not necessary for Blender 2.80 if "Auto-Save Preferences" id on) |
||||
|
||||
### Documentation |
||||
|
||||
Tissue documentation for Blender 2.80: https://github.com/alessandro-zomparelli/tissue/wiki |
||||
|
||||
|
||||
### Contribute |
||||
Please help me keeping Tissue stable and updated, report any issue here: https://github.com/alessandro-zomparelli/tissue/issues |
||||
|
||||
Tissue is free and open-source. I really think that this is the power of Blender and I wanted to give my small contribution to it. |
||||
If you like my work and you want to help to continue the development of Tissue, please consider to make a small donation. Any small contribution is really appreciated, thanks! :-D |
||||
|
||||
Alessandro |
File diff suppressed because it is too large
Load Diff
@ -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() |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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"} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue