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. "
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"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
mat = bpy.data.materials.new("unfolder dummy")
mat.use_nodes = True
img = mat.node_tree.nodes.new('ShaderNodeTexImage')
img.image = image
mat.node_tree.nodes.active = img
uv = mat.node_tree.nodes.new('ShaderNodeUVMap')
uv.uv_map = uvmap.name
mat.node_tree.links.new(uv.outputs['UV'], img.inputs['Vector'])
uvmap.active = True
recall_object_slots, recall_mesh_slots = [slot.material for slot in ob.material_slots], me.materials[:]
for i, slot in enumerate(ob.material_slots):
slot.material = me.materials[i] = mat
me.materials.append(mat)
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:
loop[vid].uv[0] *= -1
loop[vid].uv[1] *= -1
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:
me.materials.pop()
for slot, recall in zip(ob.material_slots, recall_object_slots):
Loading
Loading full blame...