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
52 changed files with 19571 additions and 0 deletions

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