Add files via upload

This commit is contained in:
0000OOOO0000 2020-11-20 17:33:46 +02:00 committed by GitHub
parent 08b88e29fc
commit 774f049e12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 19571 additions and 0 deletions

View File

@ -0,0 +1,2 @@
[Bookmarks]
[Recent]

View File

@ -0,0 +1 @@
{NVIDIA Corporation/GeForce GTX 460/PCIe/SSE2/4.5.0 NVIDIA 391.35}=SUPPORTED

View File

@ -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

View File

@ -0,0 +1,164 @@
# -*- coding: utf-8 -*-
import bpy
import math
import random
from mathutils import Matrix
from mathutils import Vector
from . import cfg
def at_random_fill(min, max):
first = random.uniform(min, max)
second = random.uniform(min, max)
if first <= second:
return(first, second)
else:
return(second, first)
def at_random(seed, totalc, totalr, mint, maxt, mins, maxs, minr, maxr, btr, bsc, brot, uniform,
tr1, tr2, sc1, sc2, r1, r2, pivot, varia, valign):
"""Random function for translation, scale and rotation,
seed : seed for random
totalc : number of elements in column
totalr : number of elements in row
mint : minimum for translation
maxt : maximum for translation
mins : minimum for scale
maxs : maximum for scale
minr : minimum for rotation
maxr : maximun for rotation
btr : (boolean) use translation or not
bsc : (boolean) use scale or not
brot : (boolean) use rotation or not
uniform : (boolean) use uniform scale or not
tr1 : translation offset of the column
tr2 : translation offset of the row
sc1 : scale offset of the column
sc2 : scale offset of the row
r1 : rotation offset of the column
r2 : rotation offset of the row
pivot : pivot
varia : variation of rows
valign : Vector of align of rows
"""
random.seed(seed)
tr, sc, rot = [0, 0, 0], [0, 0, 0], [0, 0, 0]
xyz_vec = (x_axis(), y_axis(), z_axis())
ref_name = cfg.atools_objs[0][0]
for j in range(totalr):
for k in range(totalc + j*varia):
elem_name = cfg.atools_objs[j][k]
if elem_name == ref_name:
continue
elem = bpy.data.objects[elem_name]
for i in range(3):
tr[i] = random.uniform(mint[i], maxt[i])
sc[i] = random.uniform(mins[i]/100, maxs[i]/100)
rot[i] = random.uniform(minr[i], maxr[i])
if uniform:
sc[0] = sc[1] = sc[2]
mt = Matrix.Translation(tr)
ms = Matrix.Scale(sc[0], 4, (1, 0, 0)) @ Matrix.Scale(sc[1], 4, (0, 1, 0)) @ Matrix.Scale(sc[2], 4, (0, 0, 1))
mr = Matrix.Rotation(rot[0], 4, (1, 0, 0)) @ Matrix.Rotation(rot[1], 4, (0, 1, 0)) @ Matrix.Rotation(rot[2], 4, (0, 0, 1))
# recalculate the position...
vt, vs, vr = tsr(cfg.ref_mtx, k, j, tr1, tr2, sc1, sc2, Vector(r1), Vector(r2), valign)
if pivot is not None:
emat = at_all_in_one(cfg.ref_mtx, vr, xyz_vec, vt, vs, pivot.location)
else:
emat = at_all_in_one(cfg.ref_mtx, vr, xyz_vec, vt, vs, cfg.ref_mtx.translation)
elem.matrix_world = emat
if btr:
elem.matrix_world @= mt
if bsc:
elem.matrix_world @= ms
if brot:
elem.matrix_world @= mr
def x_axis():
"""Get the x axis"""
return Vector((1.0, 0.0, 0.0))
def y_axis():
"""Get the y axis"""
return Vector((0.0, 1.0, 0.0))
def z_axis():
"""Get the z axis"""
return Vector((0.0, 0.0, 1.0))
def xyz_axis():
"""Get the xyz axis"""
return Vector((1.0, 1.0, 1.0))
def at_all_in_one(ref, angle, vecxyz, vec_tr, vec_sc, pivot):
"""Return the matrix of transformations"""
# Matrix is composed by location @ rotation @ scale
loc_ref, rot_ref, sc_ref = ref.decompose()
# ref_location = bpy.data.objects[cfg.atools_objs[0][0]].location
loc_ma = Matrix.Translation(loc_ref)
rot_ma = rot_ref.to_matrix().to_4x4()
sc_ma = Matrix.Scale(sc_ref[0], 4, (1, 0, 0)) @ Matrix.Scale(sc_ref[1], 4, (0, 1, 0)) @ Matrix.Scale(sc_ref[2], 4, (0, 0, 1))
mt = Matrix.Translation(pivot - loc_ref)
mr = Matrix.Rotation(angle[0], 4, vecxyz[0]) @ Matrix.Rotation(angle[1], 4, vecxyz[1]) @ Matrix.Rotation(angle[2], 4, vecxyz[2])
mra = mt @ mr @ mt.inverted()
trm = Matrix.Translation(vec_tr)
scm = Matrix.Scale(vec_sc[0], 4, (1, 0, 0)) @ Matrix.Scale(vec_sc[1], 4, (0, 1, 0)) @ Matrix.Scale(vec_sc[2], 4, (0, 0, 1))
if pivot == loc_ref:
mw = loc_ma @ rot_ma @ trm @ scm @ sc_ma @ mr
else:
mw = loc_ma @ mra @ rot_ma @ trm @ scm @ sc_ma
return mw
def fill_rotation(context):
prop = context.scene.arraytools_prop
offset = prop.rot_offset
for i in range(3):
if offset[i] == 0.0:
prop.rot_min[i], prop.rot_max[i] = at_random_fill(-math.pi, math.pi)
else:
prop.rot_min[i], prop.rot_max[i] = at_random_fill(-offset[i]*2, offset[i]*2)
def sum_serie(n, factor):
"""Return the sum of the serie 1+2+3+4+...+n
with a factor
"""
return ((n * (n - 1)) / 2) * factor
# (T)ranslate (S)cale (R)otation vector
def tsr(mat, col, row, tcol, trow, scol, srow, rcol, rrow, ralign):
"""Retrieve the translation, scale and rotation vector according
to the position in the array
mat : matrix of the reference object
col : position in column
row : position in row
tcol : translate offset in column
trow : translate offset in row
scol : scale offset in column
srow : scale offset in row
rcol : rotation offset in column
rrow : rotation offset in row
ralign : row align
"""
translate = col * tcol + row * trow + row * ralign
rotate = col * Vector(rcol) + row * Vector(rrow)
s1 = col * (mat.to_scale() - (scol/100))
s2 = row * (mat.to_scale() - (srow/100))
scale = xyz_axis() - s1 - s2
return translate, scale, rotate

View File

@ -0,0 +1,219 @@
# -*- coding: utf-8 -*-
# ---------------------------- Operators ------------------------
import bpy
import math
from mathutils import Vector
from . import cfg
from . import at_interface
from . at_calc_func import at_random_fill, fill_rotation
class OBJECT_OT_at_start(bpy.types.Operator):
"""Start and init the addon"""
bl_idname = 'scene.at_op'
bl_label = "Start array"
@classmethod
def poll(cls, context):
return not context.scene.arraytools_prop.already_start
def execute(self, context):
cfg.init_array_tool(context)
return {'FINISHED'}
class OBJECT_OT_at_done(bpy.types.Operator):
"""Apply the settings"""
bl_idname = 'scene.at_done'
bl_label = "Done !"
def execute(self, context):
cfg.atools_objs.clear()
#cfg.at_mtx_list.clear()
array_col = bpy.data.collections.get(cfg.col_name)
cfg.col_name = "Array_collection"
context.scene.arraytools_prop.up_ui_reset()
context.scene.arraytools_prop.already_start = False
return {'FINISHED'}
class OBJECT_OT_at_cancel(bpy.types.Operator):
"""Cancel the settings"""
bl_idname = 'scene.at_cancel'
bl_label = "Cancel"
def execute(self, context):
scn = context.scene
scn.arraytools_prop.at_del_all(True)
scn.arraytools_prop.up_ui_reset()
scn.arraytools_prop.already_start = False
cfg.col_name = "Array_collection"
return {'FINISHED'}
class OBJECT_OT_fill_tr(bpy.types.Operator):
"""Fill the random translation fields"""
bl_idname = 'scene.fill_tr'
bl_label = "Fill"
def execute(self, context):
prop = context.scene.arraytools_prop
offset = prop.tr_offset
for i in range(3):
if offset[i] == 0.0:
prop.tr_min[i], prop.tr_max[i] = at_random_fill(-3.0, 3.0)
else:
prop.tr_min[i], prop.tr_max[i] = at_random_fill(-offset[i]/2, offset[i]/2)
return{'FINISHED'}
class OBJECT_OT_fill_sc(bpy.types.Operator):
"""Fill the random scale fields"""
bl_idname = 'scene.fill_sc'
bl_label = "Fill"
def execute(self, context):
prop = context.scene.arraytools_prop
offset = prop.sc_offset
if 100 in [offset[0], offset[1], offset[2]]:
prop.sc_min_x, prop.sc_max_x = at_random_fill(40.0, 120.0)
prop.sc_min_y, prop.sc_max_y = at_random_fill(40.0, 120.0)
prop.sc_min_z, prop.sc_max_z = at_random_fill(40.0, 120.0)
else:
rand = [(100 - offset[i]) / 2 for i in range(3)]
print(rand)
prop.sc_min_x, prop.sc_max_x = at_random_fill(offset[0]-rand[0], offset[0]+rand[0])
prop.sc_min_y, prop.sc_max_y = at_random_fill(offset[1]-rand[1], offset[1]+rand[1])
prop.sc_min_z, prop.sc_max_z = at_random_fill(offset[2]-rand[2], offset[2]+rand[2])
if prop.sc_all:
prop.sc_min_x = prop.sc_min_y = prop.sc_min_z
prop.sc_max_x = prop.sc_max_y = prop.sc_max_z
return {'FINISHED'}
class OBJECT_OT_fill_rot(bpy.types.Operator):
"""Fill the random rotation fields"""
bl_idname = 'scene.fill_rot'
bl_label = "Fill"
def execute(self, context):
fill_rotation(context)
return {'FINISHED'}
class OBJECT_OT_x360(bpy.types.Operator):
"""Quick 360 degrees on X axis"""
bl_idname = 'scene.x360'
bl_label = "360"
def execute(self, context):
prop = context.scene.arraytools_prop
prop.tr_offset = Vector((0.0, 0.0, 0.0))
prop.rot_global = Vector((math.pi/180*360, 0.0, 0.0))
return{'FINISHED'}
class OBJECT_OT_y360(bpy.types.Operator):
"""Quick 360 degrees on Y axis"""
bl_idname = 'scene.y360'
bl_label = "360"
def execute(self, context):
prop = context.scene.arraytools_prop
prop.tr_offset = Vector((0.0, 0.0, 0.0))
prop.rot_global = Vector((0.0, math.pi/180*360, 0.0))
return{'FINISHED'}
class OBJECT_OT_z360(bpy.types.Operator):
"""Quick 360 degrees on Z axis"""
bl_idname = 'scene.z360'
bl_label = "360"
def execute(self, context):
prop = context.scene.arraytools_prop
prop.tr_offset = Vector((0.0, 0.0, 0.0))
prop.rot_global = Vector((0.0, 0.0, math.pi/180*360))
return{'FINISHED'}
class OBJECT_OT_reset_tr(bpy.types.Operator):
"""Reset the settings of random translation"""
bl_idname = 'scene.at_reset_tr'
bl_label = 'Reset'
def execute(self, context):
prop = context.scene.arraytools_prop
prop.tr_min[0], prop.tr_min[1], prop.tr_min[2] = 0.0, 0.0, 0.0
prop.tr_max[0], prop.tr_max[1], prop.tr_max[2] = 0.0, 0.0, 0.0
# if operator is used many times
# get weird result != 0 with vector
# prop.tr_max = Vector((0.0, 0.0, 0.0))
return {'FINISHED'}
class OBJECT_OT_reset_sc(bpy.types.Operator):
"""Reset the settings of random scale"""
bl_idname = 'scene.at_reset_sc'
bl_label = 'Reset'
def execute(self, context):
prop = context.scene.arraytools_prop
prop.sc_min_x, prop.sc_min_y, prop.sc_min_z = 100, 100, 100
prop.sc_max_x, prop.sc_max_y, prop.sc_max_z = 100, 100, 100
return{'FINISHED'}
class OBJECT_OT_reset_rot(bpy.types.Operator):
"""Reset the settings of random rotation"""
bl_idname = 'scene.at_reset_rot'
bl_label = 'Reset'
def execute(self, context):
prop = context.scene.arraytools_prop
prop.rot_min[0], prop.rot_min[1], prop.rot_min[2] = 0.0, 0.0, 0.0
prop.rot_max[0], prop.rot_max[1], prop.rot_max[2] = 0.0, 0.0, 0.0
return{'FINISHED'}
class OBJECT_OT_reset_second(bpy.types.Operator):
"""Reset the settings of row options"""
bl_idname = 'scene.at_reset_second'
bl_label = 'Reset'
def execute(self, context):
prop = context.scene.arraytools_prop
prop.tr_second = (0,0,0)
prop.sc_second = (100,100,100)
prop.rot_second = (0,0,0)
return {'FINISHED'}
class OBJECT_OT_error(bpy.types.Operator):
"""Draw a message box to display error"""
bl_idname = "info.at_error"
bl_label = "Message info"
info: bpy.props.StringProperty(
name = "Message",
description = "Display a message",
default = ''
)
def execute(self, context):
self.report({'INFO'}, self.info)
print(self.info)
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.label(text=self.info)
layout.label(text="")

View File

@ -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')

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -0,0 +1,345 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# --------------------------------- DUAL MESH -------------------------------- #
# -------------------------------- version 0.3 ------------------------------- #
# #
# Convert a generic mesh to its dual. With open meshes it can get some wired #
# effect on the borders. #
# #
# (c) Alessandro Zomparelli #
# (2017) #
# #
# http://www.co-de-it.com/ #
# #
# ############################################################################ #
import bpy
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
)
import bmesh
from .utils import *
class dual_mesh_tessellated(Operator):
bl_idname = "object.dual_mesh_tessellated"
bl_label = "Dual Mesh"
bl_description = ("Generate a polygonal mesh using Tessellate. (Non-destructive)")
bl_options = {'REGISTER', 'UNDO'}
apply_modifiers : BoolProperty(
name="Apply Modifiers",
default=True,
description="Apply object's modifiers"
)
source_faces : EnumProperty(
items=[
('QUAD', 'Quad Faces', ''),
('TRI', 'Triangles', '')],
name="Source Faces",
description="Source polygons",
default="QUAD",
options={'LIBRARY_EDITABLE'}
)
def execute(self, context):
auto_layer_collection()
ob0 = context.object
name1 = "DualMesh_{}_Component".format(self.source_faces)
# Generate component
if self.source_faces == 'QUAD':
verts = [(0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
(0.0, 1.0, 0.0), (0.5, 1.0, 0.0),
(1.0, 1.0, 0.0), (1.0, 0.5, 0.0),
(1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
(1/3, 1/3, 0.0), (2/3, 2/3, 0.0)]
edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
(7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
else:
verts = [(0.0,0.0,0.0), (0.5,0.0,0.0), (1.0,0.0,0.0), (0.0,1.0,0.0), (0.5,1.0,0.0), (1.0,1.0,0.0)]
edges = [(0,1), (1,2), (2,5), (5,4), (4,3), (3,0), (1,4)]
faces = [(0,1,4,3), (1,2,5,4)]
# check pre-existing component
try:
_verts = [0]*len(verts)*3
__verts = [c for co in verts for c in co]
ob1 = bpy.data.objects[name1]
ob1.data.vertices.foreach_get("co",_verts)
for a, b in zip(_verts, __verts):
if abs(a-b) > 0.0001:
raise ValueError
except:
me = bpy.data.meshes.new("Dual-Mesh") # add a new mesh
me.from_pydata(verts, edges, faces)
me.update(calc_edges=True, calc_edges_loose=True)
if self.source_faces == 'QUAD': n_seams = 8
else: n_seams = 6
for i in range(n_seams): me.edges[i].use_seam = True
ob1 = bpy.data.objects.new(name1, me)
context.collection.objects.link(ob1)
# fix visualization issue
context.view_layer.objects.active = ob1
ob1.select_set(True)
bpy.ops.object.editmode_toggle()
bpy.ops.object.editmode_toggle()
ob1.select_set(False)
# hide component
ob1.hide_select = True
ob1.hide_render = True
ob1.hide_viewport = True
ob = convert_object_to_mesh(ob0,False,False)
ob.name = 'DualMesh'
#ob = bpy.data.objects.new("DualMesh", convert_object_to_mesh(ob0,False,False))
#context.collection.objects.link(ob)
#context.view_layer.objects.active = ob
#ob.select_set(True)
ob.tissue_tessellate.component = ob1
ob.tissue_tessellate.generator = ob0
ob.tissue_tessellate.gen_modifiers = self.apply_modifiers
ob.tissue_tessellate.merge = True
ob.tissue_tessellate.bool_dissolve_seams = True
if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'FAN'
bpy.ops.object.update_tessellate()
ob.location = ob0.location
ob.matrix_world = ob0.matrix_world
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
class dual_mesh(Operator):
bl_idname = "object.dual_mesh"
bl_label = "Convert to Dual Mesh"
bl_description = ("Convert a generic mesh into a polygonal mesh. (Destructive)")
bl_options = {'REGISTER', 'UNDO'}
quad_method : EnumProperty(
items=[('BEAUTY', 'Beauty',
'Split the quads in nice triangles, slower method'),
('FIXED', 'Fixed',
'Split the quads on the 1st and 3rd vertices'),
('FIXED_ALTERNATE', 'Fixed Alternate',
'Split the quads on the 2nd and 4th vertices'),
('SHORTEST_DIAGONAL', 'Shortest Diagonal',
'Split the quads based on the distance between the vertices')
],
name="Quad Method",
description="Method for splitting the quads into triangles",
default="FIXED",
options={'LIBRARY_EDITABLE'}
)
polygon_method : EnumProperty(
items=[
('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
('CLIP', 'Clip',
'Split the polygons with an ear clipping algorithm')],
name="Polygon Method",
description="Method for splitting the polygons into triangles",
default="BEAUTY",
options={'LIBRARY_EDITABLE'}
)
preserve_borders : BoolProperty(
name="Preserve Borders",
default=True,
description="Preserve original borders"
)
apply_modifiers : BoolProperty(
name="Apply Modifiers",
default=True,
description="Apply object's modifiers"
)
def execute(self, context):
mode = context.mode
if mode == 'EDIT_MESH':
mode = 'EDIT'
act = context.active_object
if mode != 'OBJECT':
sel = [act]
bpy.ops.object.mode_set(mode='OBJECT')
else:
sel = context.selected_objects
doneMeshes = []
for ob0 in sel:
if ob0.type != 'MESH':
continue
if ob0.data.name in doneMeshes:
continue
ob = ob0
mesh_name = ob0.data.name
# store linked objects
clones = []
n_users = ob0.data.users
count = 0
for o in bpy.data.objects:
if o.type != 'MESH':
continue
if o.data.name == mesh_name:
count += 1
clones.append(o)
if count == n_users:
break
if self.apply_modifiers:
bpy.ops.object.convert(target='MESH')
ob.data = ob.data.copy()
bpy.ops.object.select_all(action='DESELECT')
ob.select_set(True)
context.view_layer.objects.active = ob0
bpy.ops.object.mode_set(mode='EDIT')
# prevent borders erosion
bpy.ops.mesh.select_mode(
use_extend=False, use_expand=False, type='EDGE'
)
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=False, use_boundary=True,
use_multi_face=False, use_non_contiguous=False,
use_verts=False
)
bpy.ops.mesh.extrude_region_move(
MESH_OT_extrude_region={"mirror": False},
TRANSFORM_OT_translate={"value": (0, 0, 0)}
)
bpy.ops.mesh.select_mode(
use_extend=False, use_expand=False, type='VERT',
action='TOGGLE'
)
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.quads_convert_to_tris(
quad_method=self.quad_method, ngon_method=self.polygon_method
)
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.modifier_add(type='SUBSURF')
ob.modifiers[-1].name = "dual_mesh_subsurf"
while True:
bpy.ops.object.modifier_move_up(modifier="dual_mesh_subsurf")
if ob.modifiers[0].name == "dual_mesh_subsurf":
break
bpy.ops.object.modifier_apply(
apply_as='DATA', modifier='dual_mesh_subsurf'
)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
verts = ob.data.vertices
bpy.ops.object.mode_set(mode='OBJECT')
verts[-1].select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_more(use_face_step=False)
bpy.ops.mesh.select_similar(
type='EDGE', compare='EQUAL', threshold=0.01)
bpy.ops.mesh.select_all(action='INVERT')
bpy.ops.mesh.dissolve_verts()
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=False, use_boundary=True,
use_multi_face=False, use_non_contiguous=False, use_verts=False)
bpy.ops.mesh.select_more()
# find boundaries
bpy.ops.object.mode_set(mode='OBJECT')
bound_v = [v.index for v in ob.data.vertices if v.select]
bound_e = [e.index for e in ob.data.edges if e.select]
bound_p = [p.index for p in ob.data.polygons if p.select]
bpy.ops.object.mode_set(mode='EDIT')
# select quad faces
context.tool_settings.mesh_select_mode = (False, False, True)
bpy.ops.mesh.select_face_by_sides(number=4, extend=False)
# deselect boundaries
bpy.ops.object.mode_set(mode='OBJECT')
for i in bound_v:
context.active_object.data.vertices[i].select = False
for i in bound_e:
context.active_object.data.edges[i].select = False
for i in bound_p:
context.active_object.data.polygons[i].select = False
bpy.ops.object.mode_set(mode='EDIT')
context.tool_settings.mesh_select_mode = (False, False, True)
bpy.ops.mesh.edge_face_add()
context.tool_settings.mesh_select_mode = (True, False, False)
bpy.ops.mesh.select_all(action='DESELECT')
# delete boundaries
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=True, use_boundary=True,
use_multi_face=False, use_non_contiguous=False, use_verts=True
)
bpy.ops.mesh.delete(type='VERT')
# remove middle vertices
bm = bmesh.from_edit_mesh(ob.data)
for v in bm.verts:
if len(v.link_edges) == 2 and len(v.link_faces) < 3:
v.select = True
# dissolve
bpy.ops.mesh.dissolve_verts()
bpy.ops.mesh.select_all(action='DESELECT')
# remove border faces
if not self.preserve_borders:
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=False, use_boundary=True,
use_multi_face=False, use_non_contiguous=False, use_verts=False
)
bpy.ops.mesh.select_more()
bpy.ops.mesh.delete(type='FACE')
# clean wires
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=True, use_boundary=False,
use_multi_face=False, use_non_contiguous=False, use_verts=False
)
bpy.ops.mesh.delete(type='EDGE')
bpy.ops.object.mode_set(mode='OBJECT')
ob0.data.name = mesh_name
doneMeshes.append(mesh_name)
for o in clones:
o.data = ob.data
for o in sel:
o.select_set(True)
context.view_layer.objects.active = act
bpy.ops.object.mode_set(mode=mode)
return {'FINISHED'}

View File

@ -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'}

View File

@ -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'}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,462 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
import threading
import numpy as np
import multiprocessing
from multiprocessing import Process, Pool
from mathutils import Vector
try: from .numba_functions import numba_lerp2
except: pass
weight = []
n_threads = multiprocessing.cpu_count()
class ThreadVertexGroup(threading.Thread):
def __init__ ( self, id, vertex_group, n_verts):
self.id = id
self.vertex_group = vertex_group
self.n_verts = n_verts
threading.Thread.__init__ ( self )
def run (self):
global weight
global n_threads
verts = np.arange(int(self.n_verts/8))*8 + self.id
for v in verts:
try:
weight[v] = self.vertex_group.weight(v)
except:
pass
def thread_read_weight(_weight, vertex_group):
global weight
global n_threads
print(n_threads)
weight = _weight
n_verts = len(weight)
threads = [ThreadVertexGroup(i, vertex_group, n_verts) for i in range(n_threads)]
for t in threads: t.start()
for t in threads: t.join()
return weight
def process_read_weight(id, vertex_group, n_verts):
global weight
global n_threads
verts = np.arange(int(self.n_verts/8))*8 + self.id
for v in verts:
try:
weight[v] = self.vertex_group.weight(v)
except:
pass
def read_weight(_weight, vertex_group):
global weight
global n_threads
print(n_threads)
weight = _weight
n_verts = len(weight)
n_cores = multiprocessing.cpu_count()
pool = Pool(processes=n_cores)
multiple_results = [pool.apply_async(process_read_weight, (i, vertex_group, n_verts)) for i in range(n_cores)]
#processes = [Process(target=process_read_weight, args=(i, vertex_group, n_verts)) for i in range(n_threads)]
#for t in processes: t.start()
#for t in processes: t.join()
return weight
#Recursivly transverse layer_collection for a particular name
def recurLayerCollection(layerColl, collName):
found = None
if (layerColl.name == collName):
return layerColl
for layer in layerColl.children:
found = recurLayerCollection(layer, collName)
if found:
return found
def auto_layer_collection():
# automatically change active layer collection
layer = bpy.context.view_layer.active_layer_collection
layer_collection = bpy.context.view_layer.layer_collection
if layer.hide_viewport or layer.collection.hide_viewport:
collections = bpy.context.object.users_collection
for c in collections:
lc = recurLayerCollection(layer_collection, c.name)
if not c.hide_viewport and not lc.hide_viewport:
bpy.context.view_layer.active_layer_collection = lc
def lerp(a, b, t):
return a + (b - a) * t
def _lerp2(v1, v2, v3, v4, v):
v12 = v1.lerp(v2,v.x) # + (v2 - v1) * v.x
v34 = v3.lerp(v4,v.x) # + (v4 - v3) * v.x
return v12.lerp(v34, v.y)# + (v34 - v12) * v.y
def lerp2(v1, v2, v3, v4, v):
v12 = v1 + (v2 - v1) * v.x
v34 = v3 + (v4 - v3) * v.x
return v12 + (v34 - v12) * v.y
def lerp3(v1, v2, v3, v4, v):
loc = lerp2(v1.co, v2.co, v3.co, v4.co, v)
nor = lerp2(v1.normal, v2.normal, v3.normal, v4.normal, v)
nor.normalize()
return loc + nor * v.z
def np_lerp2(v00, v10, v01, v11, vx, vy):
#try:
# co2 = numba_lerp2(v00, v10, v01, v11, vx, vy)
#except:
co0 = v00 + (v10 - v00) * vx
co1 = v01 + (v11 - v01) * vx
co2 = co0 + (co1 - co0) * vy
return co2
# Prevent Blender Crashes with handlers
def set_animatable_fix_handler(self, context):
old_handlers = []
blender_handlers = bpy.app.handlers.render_init
for h in blender_handlers:
if "turn_off_animatable" in str(h):
old_handlers.append(h)
for h in old_handlers: blender_handlers.remove(h)
################ blender_handlers.append(turn_off_animatable)
return
def turn_off_animatable(scene):
for o in bpy.data.objects:
o.tissue_tessellate.bool_run = False
o.reaction_diffusion_settings.run = False
#except: pass
return
### OBJECTS ###
def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
try: ob.name
except: return None
if ob.type != 'MESH':
if not apply_modifiers:
mod_visibility = [m.show_viewport for m in ob.modifiers]
for m in ob.modifiers: m.show_viewport = False
#ob.modifiers.update()
#dg = bpy.context.evaluated_depsgraph_get()
#ob_eval = ob.evaluated_get(dg)
#me = bpy.data.meshes.new_from_object(ob_eval, preserve_all_data_layers=True, depsgraph=dg)
me = simple_to_mesh(ob)
new_ob = bpy.data.objects.new(ob.data.name, me)
new_ob.location, new_ob.matrix_world = ob.location, ob.matrix_world
if not apply_modifiers:
for m,vis in zip(ob.modifiers,mod_visibility): m.show_viewport = vis
else:
if apply_modifiers:
new_ob = ob.copy()
new_me = simple_to_mesh(ob)
new_ob.modifiers.clear()
new_ob.data = new_me
else:
new_ob = ob.copy()
new_ob.data = ob.data.copy()
new_ob.modifiers.clear()
bpy.context.collection.objects.link(new_ob)
if preserve_status:
new_ob.select_set(False)
else:
for o in bpy.context.view_layer.objects: o.select_set(False)
new_ob.select_set(True)
bpy.context.view_layer.objects.active = new_ob
return new_ob
def simple_to_mesh(ob):
dg = bpy.context.evaluated_depsgraph_get()
ob_eval = ob.evaluated_get(dg)
me = bpy.data.meshes.new_from_object(ob_eval, preserve_all_data_layers=True, depsgraph=dg)
me.calc_normals()
return me
def join_objects(objects, link_to_scene=True, make_active=False):
C = bpy.context
bm = bmesh.new()
materials = {}
faces_materials = []
dg = C.evaluated_depsgraph_get()
for o in objects:
bm.from_object(o, dg)
# add object's material to the dictionary
for m in o.data.materials:
if m not in materials: materials[m] = len(materials)
for f in o.data.polygons:
index = f.material_index
mat = o.material_slots[index].material
new_index = materials[mat]
faces_materials.append(new_index)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
# assign new indexes
for index, f in zip(faces_materials, bm.faces): f.material_index = index
# create object
me = bpy.data.meshes.new('joined')
bm.to_mesh(me)
me.update()
ob = bpy.data.objects.new('joined', me)
if link_to_scene: C.collection.objects.link(ob)
# make active
if make_active:
for o in C.view_layer.objects: o.select_set(False)
ob.select_set(True)
C.view_layer.objects.active = ob
# add materials
for m in materials.keys(): ob.data.materials.append(m)
return ob
### MESH FUNCTIONS
def get_vertices_numpy(mesh):
n_verts = len(mesh.vertices)
verts = [0]*n_verts*3
mesh.vertices.foreach_get('co', verts)
verts = np.array(verts).reshape((n_verts,3))
return verts
def get_vertices_and_normals_numpy(mesh):
n_verts = len(mesh.vertices)
verts = [0]*n_verts*3
normals = [0]*n_verts*3
mesh.vertices.foreach_get('co', verts)
mesh.vertices.foreach_get('normal', normals)
verts = np.array(verts).reshape((n_verts,3))
normals = np.array(normals).reshape((n_verts,3))
return verts, normals
def get_edges_numpy(mesh):
n_edges = len(mesh.edges)
edges = [0]*n_edges*2
mesh.edges.foreach_get('vertices', edges)
edges = np.array(edges).reshape((n_edges,2)).astype('int')
return edges
def get_edges_id_numpy(mesh):
n_edges = len(mesh.edges)
edges = [0]*n_edges*2
mesh.edges.foreach_get('vertices', edges)
edges = np.array(edges).reshape((n_edges,2))
indexes = np.arange(n_edges).reshape((n_edges,1))
edges = np.concatenate((edges,indexes), axis=1)
return edges
def get_vertices(mesh):
n_verts = len(mesh.vertices)
verts = [0]*n_verts*3
mesh.vertices.foreach_get('co', verts)
verts = np.array(verts).reshape((n_verts,3))
verts = [Vector(v) for v in verts]
return verts
def get_faces(mesh):
faces = [[v for v in f.vertices] for f in mesh.polygons]
return faces
def get_faces_numpy(mesh):
faces = [[v for v in f.vertices] for f in mesh.polygons]
return np.array(faces)
def get_faces_edges_numpy(mesh):
faces = [v.edge_keys for f in mesh.polygons]
return np.array(faces)
#try:
#from numba import jit, njit
#from numba.typed import List
'''
@jit
def find_curves(edges, n_verts):
#verts_dict = {key:[] for key in range(n_verts)}
verts_dict = {}
for key in range(n_verts): verts_dict[key] = []
for e in edges:
verts_dict[e[0]].append(e[1])
verts_dict[e[1]].append(e[0])
curves = []#List()
loop1 = True
while loop1:
if len(verts_dict) == 0:
loop1 = False
continue
# next starting point
v = list(verts_dict.keys())[0]
# neighbors
v01 = verts_dict[v]
if len(v01) == 0:
verts_dict.pop(v)
continue
curve = []#List()
curve.append(v) # add starting point
curve.append(v01[0]) # add neighbors
verts_dict.pop(v)
loop2 = True
while loop2:
last_point = curve[-1]
#if last_point not in verts_dict: break
v01 = verts_dict[last_point]
# curve end
if len(v01) == 1:
verts_dict.pop(last_point)
loop2 = False
continue
if v01[0] == curve[-2]:
curve.append(v01[1])
verts_dict.pop(last_point)
elif v01[1] == curve[-2]:
curve.append(v01[0])
verts_dict.pop(last_point)
else:
loop2 = False
continue
if curve[0] == curve[-1]:
loop2 = False
continue
curves.append(curve)
return curves
'''
def find_curves(edges, n_verts):
verts_dict = {key:[] for key in range(n_verts)}
for e in edges:
verts_dict[e[0]].append(e[1])
verts_dict[e[1]].append(e[0])
curves = []
while True:
if len(verts_dict) == 0: break
# next starting point
v = list(verts_dict.keys())[0]
# neighbors
v01 = verts_dict[v]
if len(v01) == 0:
verts_dict.pop(v)
continue
curve = []
if len(v01) > 1: curve.append(v01[1]) # add neighbors
curve.append(v) # add starting point
curve.append(v01[0]) # add neighbors
verts_dict.pop(v)
# start building curve
while True:
#last_point = curve[-1]
#if last_point not in verts_dict: break
# try to change direction if needed
if curve[-1] in verts_dict: pass
elif curve[0] in verts_dict: curve.reverse()
else: break
# neighbors points
last_point = curve[-1]
v01 = verts_dict[last_point]
# curve end
if len(v01) == 1:
verts_dict.pop(last_point)
if curve[0] in verts_dict: continue
else: break
# chose next point
new_point = None
if v01[0] == curve[-2]: new_point = v01[1]
elif v01[1] == curve[-2]: new_point = v01[0]
#else: break
#if new_point != curve[1]:
curve.append(new_point)
verts_dict.pop(last_point)
if curve[0] == curve[-1]:
verts_dict.pop(new_point)
break
curves.append(curve)
return curves
def curve_from_points(points, name='Curve'):
curve = bpy.data.curves.new(name,'CURVE')
for c in points:
s = curve.splines.new('POLY')
s.points.add(len(c))
for i,p in enumerate(c): s.points[i].co = p.xyz + [1]
ob_curve = bpy.data.objects.new(name,curve)
return ob_curve
def curve_from_pydata(points, indexes, name='Curve', skip_open=False, merge_distance=1, set_active=True):
curve = bpy.data.curves.new(name,'CURVE')
curve.dimensions = '3D'
for c in indexes:
# cleanup
pts = np.array([points[i] for i in c])
if merge_distance > 0:
pts1 = np.roll(pts,1,axis=0)
dist = np.linalg.norm(pts1-pts, axis=1)
count = 0
n = len(dist)
mask = np.ones(n).astype('bool')
for i in range(n):
count += dist[i]
if count > merge_distance: count = 0
else: mask[i] = False
pts = pts[mask]
bool_cyclic = c[0] == c[-1]
if skip_open and not bool_cyclic: continue
s = curve.splines.new('POLY')
n_pts = len(pts)
s.points.add(n_pts-1)
w = np.ones(n_pts).reshape((n_pts,1))
co = np.concatenate((pts,w),axis=1).reshape((n_pts*4))
s.points.foreach_set('co',co)
s.use_cyclic_u = bool_cyclic
ob_curve = bpy.data.objects.new(name,curve)
bpy.context.collection.objects.link(ob_curve)
if set_active:
bpy.context.view_layer.objects.active = ob_curve
return ob_curve
def curve_from_vertices(indexes, verts, name='Curve'):
curve = bpy.data.curves.new(name,'CURVE')
for c in indexes:
s = curve.splines.new('POLY')
s.points.add(len(c))
for i,p in enumerate(c): s.points[i].co = verts[p].co.xyz + [1]
ob_curve = bpy.data.objects.new(name,curve)
return ob_curve
### WEIGHT FUNCTIONS ###
def get_weight(vertex_group, n_verts):
weight = [0]*n_verts
for i in range(n_verts):
try: weight[i] = vertex_group.weight(i)
except: pass
return weight
def get_weight_numpy(vertex_group, n_verts):
weight = [0]*n_verts
for i in range(n_verts):
try: weight[i] = vertex_group.weight(i)
except: pass
return np.array(weight)

View File

@ -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'}

View File

@ -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()

View File

@ -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"]))

View File

@ -0,0 +1 @@
{"API_key": "", "API_key_refresh": "", "global_dir": "C:\\Users\\Administrator\\blenderkit_data"}