Newer
Older
# -*- coding: utf-8 -*-
# ##### 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, see <http://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Export Paper Model",
"author": "Addam Dominec",
"version": (0, 9),
"location": "File > Export > Paper Model",
"warning": "",
"description": "Export printable net of the active mesh",
"category": "Import-Export",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Import-Export/Paper_Model",
"tracker_url": "https://developer.blender.org/T38441"
}
# TODO:
# sanitize the constructors Edge, Face, UVFace so that they don't edit their parent object
# The Exporter classes should take parameters as a whole pack, and parse it themselves
# remember objects selected before baking (except selected to active)
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# add 'estimated number of pages' to the export UI
# profile QuickSweepline vs. BruteSweepline with/without blist: for which nets is it faster?
# rotate islands to minimize area -- and change that only if necessary to fill the page size
# Sticker.vertices should be of type Vector
# check conflicts in island naming and either:
# * append a number to the conflicting names or
# * enumerate faces uniquely within all islands of the same name (requires a check that both label and abbr. equals)
"""
Additional links:
e-mail: adominec {at} gmail {dot} com
"""
import bpy
import bl_operators
import bgl
import mathutils as M
from re import compile as re_compile
from itertools import chain, repeat
from math import pi, ceil
try:
import os.path as os_path
except ImportError:
os_path = None
try:
from blist import blist
except ImportError:
blist = list
default_priority_effect = {
'CONVEX': 0.5,
'CONCAVE': 1,
'LENGTH': -0.05
}
global_paper_sizes = [
('USER', "User defined", "User defined paper size"),
('A4', "A4", "International standard paper size"),
('A3', "A3", "International standard paper size"),
('US_LETTER', "Letter", "North American paper size"),
('US_LEGAL', "Legal", "North American paper size")
]
def first_letters(text):
"""Iterator over the first letter of each word"""
for match in first_letters.pattern.finditer(text):
yield text[match.start()]
first_letters.pattern = re_compile("((?<!\w)\w)|\d")
def is_upsidedown_wrong(name):
"""Tell if the string would get a different meaning if written upside down"""
chars = set(name)
mistakable = set("69NZMWpbqd")
rotatable = set("80oOxXIl").union(mistakable)
return chars.issubset(rotatable) and not chars.isdisjoint(mistakable)
def pairs(sequence):
"""Generate consecutive pairs throughout the given sequence; at last, it gives elements last, first."""
i = iter(sequence)
previous = first = next(i)
for this in i:
yield previous, this
previous = this
yield this, first
def argmax_pair(array, key):
"""Find an (unordered) pair of indices that maximize the given function"""
mi, mj, m = None, None, None
for i in range(n):
for j in range(i+1, n):
k = key(array[i], array[j])
if not m or k > m:
mi, mj, m = i, j, k
return mi, mj
def fitting_matrix(v1, v2):
"""Get a matrix that rotates v1 to the same direction as v2"""
return (1 / v1.length_squared) * M.Matrix((
(v1.x*v2.x + v1.y*v2.y, v1.y*v2.x - v1.x*v2.y),
(v1.x*v2.y - v1.y*v2.x, v1.x*v2.x + v1.y*v2.y)))
def z_up_matrix(n):
"""Get a rotation matrix that aligns given vector upwards."""
b = n.xy.length
if b > 0:
return M.Matrix((
(n.x*n.z/(b*s), n.y*n.z/(b*s), -b/s),
(-n.y/b, n.x/b, 0),
(0, 0, 0)
))
else:
# no need for rotation
return M.Matrix((
(1, 0, 0),
(0, (-1 if n.z < 0 else 1), 0),
(0, 0, 0)
))
def create_blank_image(image_name, dimensions, alpha=1):
"""Create a new image and assign white color to all its pixels"""
image_name = image_name[:64]
width, height = int(dimensions.x), int(dimensions.y)
image = bpy.data.images.new(image_name, width, height, alpha=True)
if image.users > 0:
raise UnfoldError(
"There is something wrong with the material of the model. "
"Please report this on the BlenderArtists forum. Export failed.")
image.pixels = [1, 1, 1, alpha] * (width * height)
image.file_format = 'PNG'
return image
def bake(face_indices, uvmap, image):
import bpy
is_cycles = (bpy.context.scene.render.engine == 'CYCLES')
if is_cycles:
# please excuse the following mess. Cycles baking API does not seem to allow better.
ob = bpy.context.active_object
me = ob.data
# add a disconnected image node that defines the bake target
temp_nodes = dict()
for mat in me.materials:
mat.use_nodes = True
img = mat.node_tree.nodes.new('ShaderNodeTexImage')
img.image = image
temp_nodes[mat] = img
mat.node_tree.nodes.active = img
uvmap.active = True
# move all excess faces to negative numbers (that is the only way to disable them)
loop = me.uv_layers[me.uv_layers.active_index].data
face_indices = set(face_indices)
ignored_uvs = [
face.loop_start + i
for face in me.polygons if face.index not in face_indices
for i, v in enumerate(face.vertices)]
for vid in ignored_uvs:
bake_type = bpy.context.scene.cycles.bake_type
sta = bpy.context.scene.render.bake.use_selected_to_active
try:
bpy.ops.object.bake(type=bake_type, margin=0, use_selected_to_active=sta, cage_extrusion=100, use_clear=False)
except RuntimeError as e:
raise UnfoldError(*e.args)
finally:
for mat, node in temp_nodes.items():
mat.node_tree.nodes.remove(node)
for vid in ignored_uvs:
else:
texfaces = uvmap.data
Loading
Loading full blame...