Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Dominec <a.dominec@gmail.com>2017-08-06 11:12:08 +0300
committerAdam Dominec <a.dominec@gmail.com>2017-08-06 11:12:59 +0300
commitc9605694fa2abb23f51dedafaa03eadd34f3aab5 (patch)
tree46ca140843a96b3ee9782641e3684179fb054244
parente3fa74306fc5dbe877d84e930bde2fa140c89899 (diff)
io_export_paper_model: update to upstream 932e32
-rw-r--r--io_export_paper_model.py654
1 files changed, 366 insertions, 288 deletions
diff --git a/io_export_paper_model.py b/io_export_paper_model.py
index 508b953f..1203d18f 100644
--- a/io_export_paper_model.py
+++ b/io_export_paper_model.py
@@ -20,7 +20,7 @@ bl_info = {
"name": "Export Paper Model",
"author": "Addam Dominec",
"version": (0, 9),
- "blender": (2, 70, 0),
+ "blender": (2, 73, 0),
"location": "File > Export > Paper Model",
"warning": "",
"description": "Export printable net of the active mesh",
@@ -30,12 +30,10 @@ bl_info = {
"tracker_url": "https://developer.blender.org/T38441"
}
-#### TODO:
-# sanitize the constructors so that they don't edit their parent object
-# rename verts -> vertices, edge.vect -> edge.vector
-# SVG object doesn't need a 'pure_net' argument in constructor
-# remember selected objects before baking, except selected to active
-# islands with default names should be excluded while matching
+# 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)
# 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
@@ -76,6 +74,14 @@ default_priority_effect = {
'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"""
@@ -104,10 +110,10 @@ def pairs(sequence):
def argmax_pair(array, key):
"""Find an (unordered) pair of indices that maximize the given function"""
- l = len(array)
+ n = len(array)
mi, mj, m = None, None, None
- for i in range(l):
- for j in range(i+1, l):
+ 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
@@ -124,10 +130,10 @@ def fitting_matrix(v1, v2):
def z_up_matrix(n):
"""Get a rotation matrix that aligns given vector upwards."""
b = n.xy.length
- l = n.length
+ s = n.length
if b > 0:
return M.Matrix((
- (n.x*n.z/(b*l), n.y*n.z/(b*l), -b/l),
+ (n.x*n.z/(b*s), n.y*n.z/(b*s), -b/s),
(-n.y/b, n.x/b, 0),
(0, 0, 0)
))
@@ -146,7 +152,8 @@ def create_blank_image(image_name, dimensions, alpha=1):
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. "
+ 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'
@@ -175,7 +182,10 @@ def bake(face_indices, uvmap, image):
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)]
+ 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
@@ -284,7 +294,7 @@ class Unfolder:
bk = rd.bake
if rd.engine == 'CYCLES':
recall = sce.cycles.bake_type, bk.use_selected_to_active, bk.margin, bk.cage_extrusion, bk.use_cage, bk.use_clear
- lookup = {'TEXTURE': 'DIFFUSE_COLOR', 'AMBIENT_OCCLUSION': 'AO', 'RENDER': 'COMBINED', 'SELECTED_TO_ACTIVE': 'COMBINED'}
+ lookup = {'TEXTURE': 'DIFFUSE', 'AMBIENT_OCCLUSION': 'AO', 'RENDER': 'COMBINED', 'SELECTED_TO_ACTIVE': 'COMBINED'}
sce.cycles.bake_type = lookup[properties.output_type]
bk.use_selected_to_active = (properties.output_type == 'SELECTED_TO_ACTIVE')
bk.margin, bk.cage_extrusion, bk.use_cage, bk.use_clear = 0, 10, False, False
@@ -299,7 +309,8 @@ class Unfolder:
if image_packing == 'PAGE_LINK':
self.mesh.save_image(tex, printable_size * ppm, filepath)
elif image_packing == 'ISLAND_LINK':
- self.mesh.save_separate_images(tex, ppm, filepath)
+ image_dir = filepath[:filepath.rfind(".")]
+ self.mesh.save_separate_images(tex, ppm, image_dir)
elif image_packing == 'ISLAND_EMBED':
self.mesh.save_separate_images(tex, ppm, filepath, embed=Exporter.encode_image)
@@ -322,7 +333,7 @@ class Mesh:
"""Wrapper for Bpy Mesh"""
def __init__(self, mesh, matrix):
- self.verts = dict()
+ self.vertices = dict()
self.edges = dict()
self.edges_by_verts_indices = dict()
self.faces = dict()
@@ -330,7 +341,7 @@ class Mesh:
self.data = mesh
self.pages = list()
for bpy_vertex in mesh.vertices:
- self.verts[bpy_vertex.index] = Vertex(bpy_vertex, matrix)
+ self.vertices[bpy_vertex.index] = Vertex(bpy_vertex, matrix)
for bpy_edge in mesh.edges:
edge = Edge(bpy_edge, self, matrix)
self.edges[bpy_edge.index] = edge
@@ -346,7 +357,7 @@ class Mesh:
def check_correct(self, epsilon=1e-6):
"""Check for invalid geometry"""
- null_edges = {i for i, e in self.edges.items() if e.length < epsilon and e.faces}
+ null_edges = {i for i, e in self.edges.items() if e.vector.length < epsilon and e.faces}
null_faces = {i for i, f in self.faces.items() if f.normal.length_squared < epsilon}
twisted_faces = {i for i, f in self.faces.items() if f.is_twisted()}
if not (null_edges or null_faces or twisted_faces):
@@ -358,8 +369,11 @@ class Mesh:
edge.select = (edge.index in null_edges)
for face in self.data.polygons:
face.select = (face.index in null_faces or face.index in twisted_faces)
- cure = "Remove Doubles and Triangulate" if (null_edges or null_faces) and twisted_faces else "Triangulate" if twisted_faces else "Remove Doubles"
- raise UnfoldError("The model contains:\n" +
+ cure = ("Remove Doubles and Triangulate" if (null_edges or null_faces) and twisted_faces
+ else "Triangulate" if twisted_faces
+ else"Remove Doubles")
+ raise UnfoldError(
+ "The model contains:\n" +
(" {} zero-length edge(s)\n".format(len(null_edges)) if null_edges else "") +
(" {} zero-area face(s)\n".format(len(null_faces)) if null_faces else "") +
(" {} twisted polygon(s)\n".format(len(twisted_faces)) if twisted_faces else "") +
@@ -373,12 +387,12 @@ class Mesh:
edges = [edge for edge in self.edges.values() if not edge.force_cut and len(edge.faces) > 1]
if edges:
- average_length = sum(edge.length for edge in edges) / len(edges)
+ average_length = sum(edge.vector.length for edge in edges) / len(edges)
for edge in edges:
edge.generate_priority(priority_effect, average_length)
edges.sort(reverse=False, key=lambda edge: edge.priority)
for edge in edges:
- if edge.length == 0:
+ if edge.vector.length_squared == 0:
continue
face_a, face_b = edge.main_faces
island_a, island_b = face_a.uvface.island, face_b.uvface.island
@@ -449,7 +463,9 @@ class Mesh:
return direction_to_float(uvedge.vb.co - uvedge.va.co)
uvedges.sort(key=uvedge_sortkey)
- for right, left in zip(uvedges[:-1:2], uvedges[1::2]) if is_inwards(uvedges[0]) else zip([uvedges[-1]] + uvedges[1::2], uvedges[:-1:2]):
+ for right, left in (
+ zip(uvedges[:-1:2], uvedges[1::2]) if is_inwards(uvedges[0])
+ else zip([uvedges[-1]] + uvedges[1::2], uvedges[:-1:2])):
left.neighbor_right = right
right.neighbor_left = left
return True
@@ -465,14 +481,14 @@ class Mesh:
def uvedge_priority(uvedge):
"""Retuns whether it is a good idea to stick something on this edge's face"""
# TODO: it should take into account overlaps with faces and with other stickers
- return uvedge.uvface.face.area / sum((vb.co - va.co).length for (va, vb) in pairs(uvedge.uvface.verts))
+ return uvedge.uvface.face.area / sum((vb.co - va.co).length for (va, vb) in pairs(uvedge.uvface.vertices))
def add_sticker(uvedge, index, target_island):
uvedge.sticker = Sticker(uvedge, default_width, index, target_island)
uvedge.island.add_marker(uvedge.sticker)
for edge in self.edges.values():
- if edge.is_main_cut and len(edge.uvedges) >= 2 and edge.vect.length_squared > 0:
+ if edge.is_main_cut and len(edge.uvedges) >= 2 and edge.vector.length_squared > 0:
uvedge_a, uvedge_b = edge.uvedges[:2]
if uvedge_priority(uvedge_a) < uvedge_priority(uvedge_b):
uvedge_a, uvedge_b = uvedge_b, uvedge_a
@@ -521,14 +537,14 @@ class Mesh:
def scale_islands(self, scale):
for island in self.islands:
- for point in chain((vertex.co for vertex in island.verts), island.fake_verts):
+ for point in chain((vertex.co for vertex in island.vertices), island.fake_vertices):
point *= scale
def finalize_islands(self, is_landscape=False, title_height=0):
for island in self.islands:
if title_height:
island.title = "[{}] {}".format(island.abbreviation, island.label)
- points = list(vertex.co for vertex in island.verts) + island.fake_verts
+ points = list(vertex.co for vertex in island.vertices) + island.fake_vertices
angle = M.geometry.box_fit_2d(points)
rot = M.Matrix.Rotation(angle, 2)
# ensure that the island matches page orientation (portrait/landscape)
@@ -592,7 +608,8 @@ class Mesh:
return [stop for stop, distance in zip(stops, chain([quantile], distances)) if distance >= quantile]
if any(island.bounding_box.x > cage_size.x or island.bounding_box.y > cage_size.y for island in self.islands):
- raise UnfoldError("An island is too big to fit onto page of the given size. "
+ raise UnfoldError(
+ "An island is too big to fit onto page of the given size. "
"Either downscale the model or find and split that island manually.\n"
"Export failed, sorry.")
# sort islands by their diagonal... just a guess
@@ -658,7 +675,7 @@ class Mesh:
image_path = os_path.join(image_dir, "island{}.png".format(i))
image.filepath_raw = image_path
image.save()
- island.image_path = image.path
+ island.image_path = image_path
image.user_clear()
bpy.data.images.remove(image)
@@ -683,14 +700,13 @@ class Vertex:
class Edge:
"""Wrapper for BPy Edge"""
__slots__ = ('va', 'vb', 'faces', 'main_faces', 'uvedges',
- 'vect', 'length', 'angle',
+ 'vector', 'angle',
'is_main_cut', 'force_cut', 'priority', 'freestyle')
def __init__(self, edge, mesh, matrix=1):
- self.va = mesh.verts[edge.vertices[0]]
- self.vb = mesh.verts[edge.vertices[1]]
- self.vect = self.vb.co - self.va.co
- self.length = self.vect.length
+ self.va = mesh.vertices[edge.vertices[0]]
+ self.vb = mesh.vertices[edge.vertices[1]]
+ self.vector = self.vb.co - self.va.co
self.faces = list()
# if self.main_faces is set, then self.uvedges[:2] must correspond to self.main_faces, in their order
# this constraint is assured at the time of finishing mesh.generate_cuts
@@ -703,9 +719,9 @@ class Edge:
self.is_main_cut = True
self.priority = None
self.angle = None
- self.freestyle = getattr(edge, "use_freestyle_mark", False) # freestyle edges will be highlighted
- self.va.edges.append(self) #FIXME: editing foreign attribute
- self.vb.edges.append(self) #FIXME: editing foreign attribute
+ self.freestyle = getattr(edge, "use_freestyle_mark", False) # freestyle edges will be highlighted
+ self.va.edges.append(self) # FIXME: editing foreign attribute
+ self.vb.edges.append(self) # FIXME: editing foreign attribute
def choose_main_faces(self):
"""Choose two main faces that might get connected in an island"""
@@ -720,11 +736,11 @@ class Edge:
"""Calculate the angle between the main faces"""
face_a, face_b = self.main_faces
if face_a.normal.length_squared == 0 or face_b.normal.length_squared == 0:
- self.angle = -3 # just a very sharp angle
+ self.angle = -3 # just a very sharp angle
return
# correction if normals are flipped
- a_is_clockwise = ((face_a.verts.index(self.va) - face_a.verts.index(self.vb)) % len(face_a.verts) == 1)
- b_is_clockwise = ((face_b.verts.index(self.va) - face_b.verts.index(self.vb)) % len(face_b.verts) == 1)
+ a_is_clockwise = ((face_a.vertices.index(self.va) - face_a.vertices.index(self.vb)) % len(face_a.vertices) == 1)
+ b_is_clockwise = ((face_b.vertices.index(self.va) - face_b.vertices.index(self.vb)) % len(face_b.vertices) == 1)
is_equal_flip = True
if face_a.uvface and face_b.uvface:
a_is_clockwise ^= face_a.uvface.flipped
@@ -732,7 +748,7 @@ class Edge:
is_equal_flip = (face_a.uvface.flipped == face_b.uvface.flipped)
# TODO: maybe this need not be true in _really_ ugly cases: assert(a_is_clockwise != b_is_clockwise)
if a_is_clockwise != b_is_clockwise:
- if (a_is_clockwise == (face_b.normal.cross(face_a.normal).dot(self.vect) > 0)) == is_equal_flip:
+ if (a_is_clockwise == (face_b.normal.cross(face_a.normal).dot(self.vector) > 0)) == is_equal_flip:
# the angle is convex
self.angle = face_a.normal.angle(face_b.normal)
else:
@@ -750,7 +766,7 @@ class Edge:
self.priority = priority_effect['CONVEX'] * angle / pi
else:
self.priority = priority_effect['CONCAVE'] * (-angle) / pi
- self.priority += (self.length / average_length) * priority_effect['LENGTH']
+ self.priority += (self.vector.length / average_length) * priority_effect['LENGTH']
def is_cut(self, face):
"""Return False if this edge will the given face to another one in the resulting net
@@ -770,28 +786,28 @@ class Edge:
class Face:
"""Wrapper for BPy Face"""
- __slots__ = ('index', 'edges', 'verts', 'uvface',
+ __slots__ = ('index', 'edges', 'vertices', 'uvface',
'loop_start', 'area', 'normal')
def __init__(self, bpy_face, mesh):
self.index = bpy_face.index
self.edges = list()
- self.verts = [mesh.verts[i] for i in bpy_face.vertices]
+ self.vertices = [mesh.vertices[i] for i in bpy_face.vertices]
self.loop_start = bpy_face.loop_start
self.area = bpy_face.area
self.uvface = None
- self.normal = M.geometry.normal(v.co for v in self.verts)
+ self.normal = M.geometry.normal(v.co for v in self.vertices)
for verts_indices in bpy_face.edge_keys:
edge = mesh.edges_by_verts_indices[verts_indices]
self.edges.append(edge)
- edge.faces.append(self) #FIXME: editing foreign attribute
+ edge.faces.append(self) # FIXME: editing foreign attribute
def is_twisted(self):
- if len(self.verts) > 3:
- center = sum((vertex.co for vertex in self.verts), M.Vector((0, 0, 0))) / len(self.verts)
+ if len(self.vertices) > 3:
+ center = sum((vertex.co for vertex in self.vertices), M.Vector((0, 0, 0))) / len(self.vertices)
plane_d = center.dot(self.normal)
- diameter = max((center - vertex.co).length for vertex in self.verts)
- for vertex in self.verts:
+ diameter = max((center - vertex.co).length for vertex in self.vertices)
+ for vertex in self.vertices:
# check coplanarity
if abs(vertex.co.dot(self.normal) - plane_d) > diameter * 0.01:
return True
@@ -803,19 +819,19 @@ class Face:
class Island:
"""Part of the net to be exported"""
- __slots__ = ('faces', 'edges', 'verts', 'fake_verts', 'uvverts_by_id', 'boundary', 'markers',
+ __slots__ = ('faces', 'edges', 'vertices', 'fake_vertices', 'uvverts_by_id', 'boundary', 'markers',
'pos', 'bounding_box',
'image_path', 'embedded_image',
'number', 'label', 'abbreviation', 'title',
'has_safe_geometry', 'is_inside_out',
'sticker_numbering')
- def __init__(self, face=None):
+ def __init__(self, face):
"""Create an Island from a single Face"""
self.faces = list()
self.edges = set()
- self.verts = set()
- self.fake_verts = list()
+ self.vertices = set()
+ self.fake_vertices = list()
self.markers = list()
self.label = None
self.abbreviation = None
@@ -826,14 +842,12 @@ class Island:
self.is_inside_out = False # swaps concave <-> convex edges
self.has_safe_geometry = True
self.sticker_numbering = 0
-
- if face:
- uvface = UVFace(face, self)
- self.verts.update(uvface.verts)
- self.edges.update(uvface.edges)
- self.faces.append(uvface)
+ uvface = UVFace(face, self)
+ self.vertices.update(uvface.vertices)
+ self.edges.update(uvface.edges)
+ self.faces.append(uvface)
# speedup for Island.join
- self.uvverts_by_id = {uvvertex.vertex.index: [uvvertex] for uvvertex in self.verts}
+ self.uvverts_by_id = {uvvertex.vertex.index: [uvvertex] for uvvertex in self.vertices}
# UVEdges on the boundary
self.boundary = list(self.edges)
@@ -978,13 +992,17 @@ class Island:
rot = fitting_matrix(flip * (first_b.co - second_b.co), uvedge_a.vb.co - uvedge_a.va.co) * flip
trans = uvedge_a.vb.co - rot * first_b.co
# extract and transform island_b's boundary
- phantoms = {uvvertex: UVVertex(rot*uvvertex.co + trans, uvvertex.vertex) for uvvertex in other.verts}
+ phantoms = {uvvertex: UVVertex(rot*uvvertex.co + trans, uvvertex.vertex) for uvvertex in other.vertices}
# check the size of the resulting island
if size_limit:
# first check: bounding box
- bbox_width = max(max(seg.max.co.x for seg in self.boundary), max(vertex.co.x for vertex in phantoms)) - min(min(seg.min.co.x for seg in self.boundary), min(vertex.co.x for vertex in phantoms))
- bbox_height = max(max(seg.top for seg in self.boundary), max(vertex.co.y for vertex in phantoms)) - min(min(seg.bottom for seg in self.boundary), min(vertex.co.y for vertex in phantoms))
+ left = min(min(seg.min.co.x for seg in self.boundary), min(vertex.co.x for vertex in phantoms))
+ right = max(max(seg.max.co.x for seg in self.boundary), max(vertex.co.x for vertex in phantoms))
+ bottom = min(min(seg.bottom for seg in self.boundary), min(vertex.co.y for vertex in phantoms))
+ top = max(max(seg.top for seg in self.boundary), max(vertex.co.y for vertex in phantoms))
+ bbox_width = right - left
+ bbox_height = top - bottom
if min(bbox_width, bbox_height)**2 > size_limit.x**2 + size_limit.y**2:
return False
if (bbox_width > size_limit.x or bbox_height > size_limit.y) and (bbox_height > size_limit.x or bbox_width > size_limit.y):
@@ -992,7 +1010,7 @@ class Island:
# for the time being, just throw this piece away
return False
- distance_limit = edge.vect.length_squared * epsilon
+ distance_limit = edge.vector.length_squared * epsilon
# try and merge UVVertices closer than sqrt(distance_limit)
merged_uvedges = set()
merged_uvedge_pairs = list()
@@ -1036,11 +1054,12 @@ class Island:
if uvedge_b not in merged_uvedges:
raise UnfoldError("Export failed. Please report this error, including the model if you can.")
- boundary_other = [PhantomUVEdge(phantoms[uvedge.va], phantoms[uvedge.vb], flipped ^ uvedge.uvface.flipped)
+ boundary_other = [
+ PhantomUVEdge(phantoms[uvedge.va], phantoms[uvedge.vb], flipped ^ uvedge.uvface.flipped)
for uvedge in other.boundary if uvedge not in merged_uvedges]
# TODO: if is_merged_mine, it might make sense to create a similar list from self.boundary as well
- incidence = {vertex.tup for vertex in phantoms.values()}.intersection(vertex.tup for vertex in self.verts)
+ incidence = {vertex.tup for vertex in phantoms.values()}.intersection(vertex.tup for vertex in self.vertices)
incidence = {position: list() for position in incidence} # from now on, 'incidence' is a dict
for uvedge in chain(boundary_other, self.boundary):
if uvedge.va.co == uvedge.vb.co:
@@ -1078,7 +1097,7 @@ class Island:
uvedge.edge.is_main_cut = False
# include all trasformed vertices as mine
- self.verts.update(phantoms.values())
+ self.vertices.update(phantoms.values())
# update the uvverts_by_id dictionary
for source, target in phantoms.items():
@@ -1106,21 +1125,24 @@ class Island:
for uvface in other.faces:
uvface.island = self
- uvface.verts = [phantoms[uvvertex] for uvvertex in uvface.verts]
- uvface.uvvertex_by_id = {index: phantoms[uvvertex]
+ uvface.vertices = [phantoms[uvvertex] for uvvertex in uvface.vertices]
+ uvface.uvvertex_by_id = {
+ index: phantoms[uvvertex]
for index, uvvertex in uvface.uvvertex_by_id.items()}
uvface.flipped ^= flipped
if is_merged_mine:
# there may be own uvvertices that need to be replaced by phantoms
for uvface in self.faces:
- if any(uvvertex in phantoms for uvvertex in uvface.verts):
- uvface.verts = [phantoms.get(uvvertex, uvvertex) for uvvertex in uvface.verts]
- uvface.uvvertex_by_id = {index: phantoms.get(uvvertex, uvvertex)
+ if any(uvvertex in phantoms for uvvertex in uvface.vertices):
+ uvface.vertices = [phantoms.get(uvvertex, uvvertex) for uvvertex in uvface.vertices]
+ uvface.uvvertex_by_id = {
+ index: phantoms.get(uvvertex, uvvertex)
for index, uvvertex in uvface.uvvertex_by_id.items()}
self.faces.extend(other.faces)
- self.boundary = [uvedge for uvedge in
- chain(self.boundary, other.boundary) if uvedge not in merged_uvedges]
+ self.boundary = [
+ uvedge for uvedge in chain(self.boundary, other.boundary)
+ if uvedge not in merged_uvedges]
for uvedge, partner in merged_uvedge_pairs:
# make sure that main faces are the ones actually merged (this changes nothing in most cases)
@@ -1130,7 +1152,7 @@ class Island:
return True
def add_marker(self, marker):
- self.fake_verts.extend(marker.bounds)
+ self.fake_vertices.extend(marker.bounds)
self.markers.append(marker)
def generate_label(self, label=None, abbreviation=None):
@@ -1148,7 +1170,7 @@ class Island:
page_size: size of the page in pixels (vector)"""
texface = tex.data
for uvface in self.faces:
- for i, uvvertex in enumerate(uvface.verts):
+ for i, uvvertex in enumerate(uvface.vertices):
uv = uvvertex.co + self.pos
texface[uvface.face.loop_start + i].uv[0] = uv.x / cage_size.x
texface[uvface.face.loop_start + i].uv[1] = uv.y / cage_size.y
@@ -1160,7 +1182,7 @@ class Island:
texface = tex.data
scale_x, scale_y = 1 / self.bounding_box.x, 1 / self.bounding_box.y
for uvface in self.faces:
- for i, uvvertex in enumerate(uvface.verts):
+ for i, uvvertex in enumerate(uvface.vertices):
texface[uvface.face.loop_start + i].uv[0] = uvvertex.co.x * scale_x
texface[uvface.face.loop_start + i].uv[1] = uvvertex.co.y * scale_y
@@ -1240,32 +1262,35 @@ class PhantomUVEdge:
class UVFace:
"""Face in 2D"""
- __slots__ = ('verts', 'edges', 'face', 'island', 'flipped', 'uvvertex_by_id')
+ __slots__ = ('vertices', 'edges', 'face', 'island', 'flipped', 'uvvertex_by_id')
def __init__(self, face: Face, island: Island):
"""Creace an UVFace from a Face and a fixed edge.
face: Face to take coordinates from
island: Island to register itself in
fixed_edge: Edge to connect to (that already has UV coordinates)"""
- self.verts = list()
+ self.vertices = list()
self.face = face
face.uvface = self
self.island = island
self.flipped = False # a flipped UVFace has edges clockwise
rot = z_up_matrix(face.normal)
- self.uvvertex_by_id = {vertex.index: UVVertex(rot * vertex.co, vertex) for vertex in face.verts}
- self.verts = [self.uvvertex_by_id[vertex.index] for vertex in face.verts]
+ self.uvvertex_by_id = dict() # link vertex id -> UVVertex
+ for vertex in face.vertices:
+ uvvertex = UVVertex(rot * vertex.co, vertex)
+ self.vertices.append(uvvertex)
+ self.uvvertex_by_id[vertex.index] = uvvertex
self.edges = list()
edge_by_verts = dict()
for edge in face.edges:
edge_by_verts[(edge.va.index, edge.vb.index)] = edge
edge_by_verts[(edge.vb.index, edge.va.index)] = edge
- for va, vb in pairs(self.verts):
+ for va, vb in pairs(self.vertices):
edge = edge_by_verts[(va.vertex.index, vb.vertex.index)]
uvedge = UVEdge(va, vb, island, self, edge)
self.edges.append(uvedge)
- edge.uvedges.append(uvedge) #FIXME: editing foreign attribute
+ edge.uvedges.append(uvedge) # FIXME: editing foreign attribute
class Arrow:
@@ -1277,10 +1302,10 @@ class Arrow:
edge = (uvedge.vb.co - uvedge.va.co) if not uvedge.uvface.flipped else (uvedge.va.co - uvedge.vb.co)
self.center = (uvedge.va.co + uvedge.vb.co) / 2
self.size = size
- sin, cos = edge.y / edge.length, edge.x / edge.length
- self.rot = M.Matrix(((cos, -sin), (sin, cos)))
tangent = edge.normalized()
- normal = M.Vector((tangent.y, -tangent.x))
+ cos, sin = tangent
+ self.rot = M.Matrix(((cos, -sin), (sin, cos)))
+ normal = M.Vector((sin, -cos))
self.bounds = [self.center, self.center + (1.2*normal + tangent)*size, self.center + (1.2*normal - tangent)*size]
@@ -1324,14 +1349,14 @@ class Sticker:
# Calculate the lengths of the glue tab edges using the possibly smaller angles
sin_a = abs(1 - cos_a**2)**0.5
- len_b = min(len_a, (edge.length*sin_a) / (sin_a*cos_b + sin_b*cos_a))
+ len_b = min(len_a, (edge.length * sin_a) / (sin_a * cos_b + sin_b * cos_a))
len_a = 0 if sin_a == 0 else min(sticker_width / sin_a, (edge.length - len_b*cos_b) / cos_a)
sin_b = abs(1 - cos_b**2)**0.5
- len_a = min(len_a, (edge.length*sin_b) / (sin_a*cos_b + sin_b*cos_a))
- len_b = 0 if sin_b == 0 else min(sticker_width / sin_b, (edge.length - len_a*cos_a) / cos_b)
+ len_a = min(len_a, (edge.length * sin_b) / (sin_a * cos_b + sin_b * cos_a))
+ len_b = 0 if sin_b == 0 else min(sticker_width / sin_b, (edge.length - len_a * cos_a) / cos_b)
- v3 = UVVertex(second_vertex.co + M.Matrix(((cos_b, -sin_b), (sin_b, cos_b))) * edge *len_b / edge.length)
+ v3 = UVVertex(second_vertex.co + M.Matrix(((cos_b, -sin_b), (sin_b, cos_b))) * edge * len_b / edge.length)
v4 = UVVertex(first_vertex.co + M.Matrix(((-cos_a, -sin_a), (sin_a, -cos_a))) * edge * len_a / edge.length)
if v3.co != v4.co:
self.vertices = [second_vertex, v3, v4, first_vertex]
@@ -1400,7 +1425,9 @@ class SVG:
rows = "\n".join
dl = ["{:.2f}".format(length * self.style.line_width * 1000) for length in (2, 5, 10)]
- format_style = {'SOLID': "none", 'DOT': "{0},{1}".format(*dl), 'DASH': "{1},{2}".format(*dl), 'LONGDASH': "{2},{1}".format(*dl), 'DASHDOT': "{2},{1},{0},{1}".format(*dl)}
+ format_style = {
+ 'SOLID': "none", 'DOT': "{0},{1}".format(*dl), 'DASH': "{1},{2}".format(*dl),
+ 'LONGDASH': "{2},{1}".format(*dl), 'DASHDOT': "{2},{1},{0},{1}".format(*dl)}
def format_color(vec):
return "#{:02x}{:02x}{:02x}".format(round(vec[0] * 255), round(vec[1] * 255), round(vec[2] * 255))
@@ -1415,18 +1442,22 @@ class SVG:
string = string.replace(os_path.sep, '/')
return string
- styleargs = {name: format_color(getattr(self.style, name)) for name in
- ("outer_color", "outbg_color", "convex_color", "concave_color", "freestyle_color",
- "inbg_color", "sticker_fill", "text_color")}
- styleargs.update({name: format_style[getattr(self.style, name)] for name in
+ styleargs = {
+ name: format_color(getattr(self.style, name)) for name in (
+ "outer_color", "outbg_color", "convex_color", "concave_color", "freestyle_color",
+ "inbg_color", "sticker_fill", "text_color")}
+ styleargs.update({
+ name: format_style[getattr(self.style, name)] for name in
("outer_style", "convex_style", "concave_style", "freestyle_style")})
- styleargs.update({name: getattr(self.style, attr)[3] for name, attr in
- (("outer_alpha", "outer_color"), ("outbg_alpha", "outbg_color"),
- ("convex_alpha", "convex_color"), ("concave_alpha", "concave_color"),
- ("freestyle_alpha", "freestyle_color"),
- ("inbg_alpha", "inbg_color"), ("sticker_alpha", "sticker_fill"),
- ("text_alpha", "text_color"))})
- styleargs.update({name: getattr(self.style, name) * self.style.line_width * 1000 for name in
+ styleargs.update({
+ name: getattr(self.style, attr)[3] for name, attr in (
+ ("outer_alpha", "outer_color"), ("outbg_alpha", "outbg_color"),
+ ("convex_alpha", "convex_color"), ("concave_alpha", "concave_color"),
+ ("freestyle_alpha", "freestyle_color"),
+ ("inbg_alpha", "inbg_color"), ("sticker_alpha", "sticker_fill"),
+ ("text_alpha", "text_color"))})
+ styleargs.update({
+ name: getattr(self.style, name) * self.style.line_width * 1000 for name in
("outer_width", "convex_width", "concave_width", "freestyle_width", "outbg_width", "inbg_width")})
for num, page in enumerate(mesh.pages):
page_filename = "{}_{}.svg".format(filename[:filename.rfind(".svg")], page.name) if len(mesh.pages) > 1 else filename
@@ -1434,11 +1465,12 @@ class SVG:
print(self.svg_base.format(width=self.page_size.x*1000, height=self.page_size.y*1000), file=f)
print(self.css_base.format(**styleargs), file=f)
if page.image_path:
- print(self.image_linked_tag.format(
- pos="{0:.6f} {0:.6f}".format(self.margin*1000),
- width=(self.page_size.x - 2 * self.margin)*1000,
- height=(self.page_size.y - 2 * self.margin)*1000,
- path=path_convert(page.image_path)),
+ print(
+ self.image_linked_tag.format(
+ pos="{0:.6f} {0:.6f}".format(self.margin*1000),
+ width=(self.page_size.x - 2 * self.margin)*1000,
+ height=(self.page_size.y - 2 * self.margin)*1000,
+ path=path_convert(page.image_path)),
file=f)
if len(page.islands) > 1:
print("<g>", file=f)
@@ -1446,14 +1478,16 @@ class SVG:
for island in page.islands:
print("<g>", file=f)
if island.image_path:
- print(self.image_linked_tag.format(
- pos=self.format_vertex(island.pos + M.Vector((0, island.bounding_box.y))),
- width=island.bounding_box.x*1000,
- height=island.bounding_box.y*1000,
- path=path_convert(island.image_path)),
+ print(
+ self.image_linked_tag.format(
+ pos=self.format_vertex(island.pos + M.Vector((0, island.bounding_box.y))),
+ width=island.bounding_box.x*1000,
+ height=island.bounding_box.y*1000,
+ path=path_convert(island.image_path)),
file=f)
elif island.embedded_image:
- print(self.image_embedded_tag.format(
+ print(
+ self.image_embedded_tag.format(
pos=self.format_vertex(island.pos + M.Vector((0, island.bounding_box.y))),
width=island.bounding_box.x*1000,
height=island.bounding_box.y*1000,
@@ -1461,11 +1495,13 @@ class SVG:
island.embedded_image, "'/>",
file=f, sep="")
if island.title:
- print(self.text_tag.format(
- size=1000 * self.text_size,
- x=1000 * (island.bounding_box.x*0.5 + island.pos.x + self.margin),
- y=1000 * (self.page_size.y - island.pos.y - self.margin - 0.2 * self.text_size),
- label=island.title), file=f)
+ print(
+ self.text_tag.format(
+ size=1000 * self.text_size,
+ x=1000 * (island.bounding_box.x*0.5 + island.pos.x + self.margin),
+ y=1000 * (self.page_size.y - island.pos.y - self.margin - 0.2 * self.text_size),
+ label=island.title),
+ file=f)
data_markers, data_stickerfill, data_outer, data_convex, data_concave, data_freestyle = (list() for i in range(6))
for marker in island.markers:
@@ -1630,6 +1666,12 @@ class PDF:
"""Simple PDF exporter"""
mm_to_pt = 72 / 25.4
+ character_width_packed = {
+ 191: "'", 222: 'ijl\x82\x91\x92', 278: '|¦\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !,./:;I[\\]ft\xa0·ÌÍÎÏìíîï',
+ 333: '()-`r\x84\x88\x8b\x93\x94\x98\x9b¡¨\xad¯²³´¸¹{}', 350: '\x7f\x81\x8d\x8f\x90\x95\x9d', 365: '"ºª*°', 469: '^', 500: 'Jcksvxyz\x9a\x9eçýÿ', 584: '¶+<=>~¬±×÷', 611: 'FTZ\x8e¿ßø',
+ 667: '&ABEKPSVXY\x8a\x9fÀÁÂÃÄÅÈÉÊËÝÞ', 722: 'CDHNRUwÇÐÑÙÚÛÜ', 737: '©®', 778: 'GOQÒÓÔÕÖØ', 833: 'Mm¼½¾', 889: '%æ', 944: 'W\x9c', 1000: '\x85\x89\x8c\x97\x99Æ', 1015: '@', }
+ character_width = {c: value for (value, chars) in character_width_packed.items() for c in chars}
+
def __init__(self, page_size: M.Vector, style, margin, pure_net=True, angle_epsilon=0.01):
self.page_size = page_size
self.style = style
@@ -1637,18 +1679,18 @@ class PDF:
self.pure_net = pure_net
self.angle_epsilon = angle_epsilon
- character_width_packed = {833: 'mM', 834: '¼½¾', 260: '¦|', 389: '*', 584: '>~+¬±<×÷=', 778: 'ÒGÖÕQÔØÓO', 333: '¹\xad\x98\x84²¨\x94\x9b¯¡´()\x8b\x93¸³-\x88`r', 334: '{}', 400: '°', 722: 'DÛÚUÑwRÐÜCÇNÙH', 611: '¿øTßZF\x8e', 469: '^', 278: 'ì\x05\x06 ;\x01/\x08I\x07,\x13\x11\x04\\.![\x15\r\x10:\x18]\x0c\x00\x1bÍf\xa0\x14\x1c\n\t\x1e\x1dïí\x12·\x16\x0bî\x0e\x03tÏ\x17\x1fÎ\x19\x0f\x02Ì\x1a', 537: '¶', 667: 'ÄË\x8aÃÀBÊVX&AKSÈÞPÁYÉ\x9fÝEÅÂ', 222: 'jl\x92\x91i\x82', 737: '©®', 355: '"', 1000: '\x89\x97\x8c\x99\x85Æ', 556: 'éhòúd»§ùþ5\x803õ¢åëûa64_ã\x83ñ¤8n?g2e#9«oqL$âö1päuð\x86¥µ\x967üóê\x87bá0àèô£', 365: 'º', 944: '\x9cW', 370: 'ª', 500: 'Js\x9eçyÿ\x9aývckzx', 350: '\x90\x8d\x81\x8f\x95\x7f\x9d', 1015: '@', 889: 'æ%', 191: "'"}
- character_width = {c: value for (value, chars) in character_width_packed.items() for c in chars}
def text_width(self, text, scale=None):
return (scale or self.text_size) * sum(self.character_width.get(c, 556) for c in text) / 1000
@classmethod
def encode_image(cls, bpy_image):
data = bytes(int(255 * px) for (i, px) in enumerate(bpy_image.pixels) if i % 4 != 3)
- image = {"Type": "XObject", "Subtype": "Image", "Width": bpy_image.size[0], "Height": bpy_image.size[1], "ColorSpace": "DeviceRGB", "BitsPerComponent": 8, "Interpolate": True, "Filter": ["ASCII85Decode", "FlateDecode"], "stream": data}
+ image = {
+ "Type": "XObject", "Subtype": "Image", "Width": bpy_image.size[0], "Height": bpy_image.size[1],
+ "ColorSpace": "DeviceRGB", "BitsPerComponent": 8, "Interpolate": True,
+ "Filter": ["ASCII85Decode", "FlateDecode"], "stream": data}
return image
-
def write(self, mesh, filename):
def format_dict(obj, refs=tuple()):
return "<< " + "".join("/{} {}\n".format(key, format_value(value, refs)) for (key, value) in obj.items()) + ">>"
@@ -1700,10 +1742,14 @@ class PDF:
page_size_pt = 1000 * self.mm_to_pt * self.page_size
root = {"Type": "Pages", "MediaBox": [0, 0, page_size_pt.x, page_size_pt.y], "Kids": list()}
catalog = {"Type": "Catalog", "Pages": root}
- font = {"Type": "Font", "Subtype": "Type1", "Name": "F1", "BaseFont": "Helvetica", "Encoding": "MacRomanEncoding"}
+ font = {
+ "Type": "Font", "Subtype": "Type1", "Name": "F1",
+ "BaseFont": "Helvetica", "Encoding": "MacRomanEncoding"}
dl = [length * self.style.line_width * 1000 for length in (1, 4, 9)]
- format_style = {'SOLID': list(), 'DOT': [dl[0], dl[1]], 'DASH': [dl[1], dl[2]], 'LONGDASH': [dl[2], dl[1]], 'DASHDOT': [dl[2], dl[1], dl[0], dl[1]]}
+ format_style = {
+ 'SOLID': list(), 'DOT': [dl[0], dl[1]], 'DASH': [dl[1], dl[2]],
+ 'LONGDASH': [dl[2], dl[1]], 'DASHDOT': [dl[2], dl[1], dl[0], dl[1]]}
styles = {
"Gtext": {"ca": self.style.text_color[3], "Font": [font, 1000 * self.text_size]},
"Gsticker": {"ca": self.style.sticker_fill[3]}}
@@ -1730,12 +1776,12 @@ class PDF:
commands.append("q 1 0 0 1 {0.x:.6f} {0.y:.6f} cm".format(1000*(self.margin + island.pos)))
if island.embedded_image:
identifier = "Im{}".format(len(resources["XObject"]) + 1)
- commands.append("q {0.x:.6f} 0 0 {0.y:.6f} 0 0 cm 1 0 0 -1 0 1 cm /{1} Do Q".format(1000 * island.bounding_box, identifier))
+ commands.append(self.command_image.format(1000 * island.bounding_box, identifier))
objects.append(island.embedded_image)
resources["XObject"][identifier] = island.embedded_image
if island.title:
- commands.append("/Gtext gs BT {x:.6f} {y:.6f} Td ({label}) Tj ET".format(
+ commands.append(self.command_label.format(
size=1000*self.text_size,
x=500 * (island.bounding_box.x - self.text_width(island.title)),
y=1000 * 0.2 * self.text_size,
@@ -1746,7 +1792,7 @@ class PDF:
if isinstance(marker, Sticker):
data_stickerfill.append(line_through(marker.vertices) + "f")
if marker.text:
- data_markers.append("q {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {pos.x:.6f} {pos.y:.6f} cm BT {align:.6f} 0 Td /F1 {size:.6f} Tf ({label}) Tj ET Q".format(
+ data_markers.append(self.command_sticker.format(
label=marker.text,
pos=1000*marker.center,
mat=marker.rot,
@@ -1755,17 +1801,17 @@ class PDF:
elif isinstance(marker, Arrow):
size = 1000 * marker.size
position = 1000 * (marker.center + marker.rot*marker.size*M.Vector((0, -0.9)))
- data_markers.append("q BT {pos.x:.6f} {pos.y:.6f} Td /F1 {size:.6f} Tf ({index}) Tj ET {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {arrow_pos.x:.6f} {arrow_pos.y:.6f} cm 0 0 m 1 -1 l 0 -0.25 l -1 -1 l f Q".format(
+ data_markers.append(self.command_arrow.format(
index=marker.text,
arrow_pos=1000 * marker.center,
pos=position - 1000 * M.Vector((0.5 * self.text_width(marker.text), 0.4 * self.text_size)),
mat=size * marker.rot,
size=size))
elif isinstance(marker, NumberAlone):
- data_markers.append("q {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {pos.x:.6f} {pos.y:.6f} cm BT /F1 {size:.6f} Tf ({label}) Tj ET Q".format(
+ data_markers.append(self.command_number.format(
label=marker.text,
pos=1000*marker.center,
- mat = marker.rot,
+ mat=marker.rot,
size=1000*marker.size))
outer_edges = set(island.boundary)
@@ -1846,6 +1892,12 @@ class PDF:
f.write(format_dict({"Size": len(xref_table), "Root": catalog}, objects))
f.write("\nstartxref\n{}\n%%EOF\n".format(xref_pos))
+ command_label = "/Gtext gs BT {x:.6f} {y:.6f} Td ({label}) Tj ET"
+ command_image = "q {0.x:.6f} 0 0 {0.y:.6f} 0 0 cm 1 0 0 -1 0 1 cm /{1} Do Q"
+ command_sticker = "q {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {pos.x:.6f} {pos.y:.6f} cm BT {align:.6f} 0 Td /F1 {size:.6f} Tf ({label}) Tj ET Q"
+ command_arrow = "q BT {pos.x:.6f} {pos.y:.6f} Td /F1 {size:.6f} Tf ({index}) Tj ET {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {arrow_pos.x:.6f} {arrow_pos.y:.6f} cm 0 0 m 1 -1 l 0 -0.25 l -1 -1 l f Q"
+ command_number = "q {mat[0][0]:.6f} {mat[1][0]:.6f} {mat[0][1]:.6f} {mat[1][1]:.6f} {pos.x:.6f} {pos.y:.6f} cm BT /F1 {size:.6f} Tf ({label}) Tj ET Q"
+
class Unfold(bpy.types.Operator):
"""Blender Operator: unfold the selected object."""
@@ -1854,18 +1906,18 @@ class Unfold(bpy.types.Operator):
bl_label = "Unfold"
bl_description = "Mark seams so that the mesh can be exported as a paper model"
bl_options = {'REGISTER', 'UNDO'}
- edit = bpy.props.BoolProperty(name="", description="", default=False, options={'HIDDEN'})
- priority_effect_convex = bpy.props.FloatProperty(name="Priority Convex",
- description="Priority effect for edges in convex angles",
+ edit = bpy.props.BoolProperty(default=False, options={'HIDDEN'})
+ priority_effect_convex = bpy.props.FloatProperty(
+ name="Priority Convex", description="Priority effect for edges in convex angles",
default=default_priority_effect['CONVEX'], soft_min=-1, soft_max=10, subtype='FACTOR')
- priority_effect_concave = bpy.props.FloatProperty(name="Priority Concave",
- description="Priority effect for edges in concave angles",
+ priority_effect_concave = bpy.props.FloatProperty(
+ name="Priority Concave", description="Priority effect for edges in concave angles",
default=default_priority_effect['CONCAVE'], soft_min=-1, soft_max=10, subtype='FACTOR')
- priority_effect_length = bpy.props.FloatProperty(name="Priority Length",
- description="Priority effect of edge length",
+ priority_effect_length = bpy.props.FloatProperty(
+ name="Priority Length", description="Priority effect of edge length",
default=default_priority_effect['LENGTH'], soft_min=-10, soft_max=1, subtype='FACTOR')
- do_create_uvmap = bpy.props.BoolProperty(name="Create UVMap",
- description="Create a new UV Map showing the islands and page layout", default=False)
+ do_create_uvmap = bpy.props.BoolProperty(
+ name="Create UVMap", description="Create a new UV Map showing the islands and page layout", default=False)
object = None
@classmethod
@@ -1895,10 +1947,15 @@ class Unfold(bpy.types.Operator):
mesh = self.object.data
cage_size = M.Vector((settings.output_size_x, settings.output_size_y)) if settings.limit_by_page else None
- priority_effect = {'CONVEX': self.priority_effect_convex, 'CONCAVE': self.priority_effect_concave, 'LENGTH': self.priority_effect_length}
+ priority_effect = {
+ 'CONVEX': self.priority_effect_convex,
+ 'CONCAVE': self.priority_effect_concave,
+ 'LENGTH': self.priority_effect_length}
try:
unfolder = Unfolder(self.object)
- unfolder.prepare(cage_size, self.do_create_uvmap, mark_seams=True, priority_effect=priority_effect, scale=sce.unit_settings.scale_length/settings.scale)
+ unfolder.prepare(
+ cage_size, self.do_create_uvmap, mark_seams=True,
+ priority_effect=priority_effect, scale=sce.unit_settings.scale_length/settings.scale)
except UnfoldError as error:
self.report(type={'ERROR_INVALID_INPUT'}, message=error.args[0])
bpy.ops.object.mode_set(mode=recall_mode)
@@ -1919,7 +1976,9 @@ class Unfold(bpy.types.Operator):
lface.id = uvface.face.index
list_item["label"] = island.label
- list_item["abbreviation"], list_item["auto_label"], list_item["auto_abbrev"] = attributes.get(island.label, (island.abbreviation, True, True))
+ list_item["abbreviation"], list_item["auto_label"], list_item["auto_abbrev"] = attributes.get(
+ island.label,
+ (island.abbreviation, True, True))
island_item_changed(list_item, context)
mesh.paper_island_index = -1
@@ -1954,6 +2013,8 @@ class ClearAllSeams(bpy.types.Operator):
def page_size_preset_changed(self, context):
"""Update the actual document size to correct values"""
+ if hasattr(self, "limit_by_page") and not self.limit_by_page:
+ return
if self.page_size_preset == 'A4':
self.output_size_x = 0.210
self.output_size_y = 0.297
@@ -1976,70 +2037,70 @@ class PaperModelStyle(bpy.types.PropertyGroup):
('LONGDASH', "Long Dashes (-- --)", "Solid line"),
('DASHDOT', "Dash-dotted (-- .)", "Solid line")
]
- outer_color = bpy.props.FloatVectorProperty(name="Outer Lines",
- description="Color of net outline",
+ outer_color = bpy.props.FloatVectorProperty(
+ name="Outer Lines", description="Color of net outline",
default=(0.0, 0.0, 0.0, 1.0), min=0, max=1, subtype='COLOR', size=4)
- outer_style = bpy.props.EnumProperty(name="Outer Lines Drawing Style",
- description="Drawing style of net outline",
+ outer_style = bpy.props.EnumProperty(
+ name="Outer Lines Drawing Style", description="Drawing style of net outline",
default='SOLID', items=line_styles)
- line_width = bpy.props.FloatProperty(name="Base Lines Thickness",
- description="Base thickness of net lines, each actual value is a multiple of this length",
+ line_width = bpy.props.FloatProperty(
+ name="Base Lines Thickness", description="Base thickness of net lines, each actual value is a multiple of this length",
default=1e-4, min=0, soft_max=5e-3, precision=5, step=1e-2, subtype="UNSIGNED", unit="LENGTH")
- outer_width = bpy.props.FloatProperty(name="Outer Lines Thickness",
- description="Relative thickness of net outline",
+ outer_width = bpy.props.FloatProperty(
+ name="Outer Lines Thickness", description="Relative thickness of net outline",
default=3, min=0, soft_max=10, precision=1, step=10, subtype='FACTOR')
- use_outbg = bpy.props.BoolProperty(name="Highlight Outer Lines",
- description="Add another line below every line to improve contrast",
+ use_outbg = bpy.props.BoolProperty(
+ name="Highlight Outer Lines", description="Add another line below every line to improve contrast",
default=True)
- outbg_color = bpy.props.FloatVectorProperty(name="Outer Highlight",
- description="Color of the highlight for outer lines",
+ outbg_color = bpy.props.FloatVectorProperty(
+ name="Outer Highlight", description="Color of the highlight for outer lines",
default=(1.0, 1.0, 1.0, 1.0), min=0, max=1, subtype='COLOR', size=4)
- outbg_width = bpy.props.FloatProperty(name="Outer Highlight Thickness",
- description="Relative thickness of the highlighting lines",
+ outbg_width = bpy.props.FloatProperty(
+ name="Outer Highlight Thickness", description="Relative thickness of the highlighting lines",
default=5, min=0, soft_max=10, precision=1, step=10, subtype='FACTOR')
- convex_color = bpy.props.FloatVectorProperty(name="Inner Convex Lines",
- description="Color of lines to be folded to a convex angle",
+ convex_color = bpy.props.FloatVectorProperty(
+ name="Inner Convex Lines", description="Color of lines to be folded to a convex angle",
default=(0.0, 0.0, 0.0, 1.0), min=0, max=1, subtype='COLOR', size=4)
- convex_style = bpy.props.EnumProperty(name="Convex Lines Drawing Style",
- description="Drawing style of lines to be folded to a convex angle",
+ convex_style = bpy.props.EnumProperty(
+ name="Convex Lines Drawing Style", description="Drawing style of lines to be folded to a convex angle",
default='DASH', items=line_styles)
- convex_width = bpy.props.FloatProperty(name="Convex Lines Thickness",
- description="Relative thickness of concave lines",
+ convex_width = bpy.props.FloatProperty(
+ name="Convex Lines Thickness", description="Relative thickness of concave lines",
default=2, min=0, soft_max=10, precision=1, step=10, subtype='FACTOR')
- concave_color = bpy.props.FloatVectorProperty(name="Inner Concave Lines",
- description="Color of lines to be folded to a concave angle",
+ concave_color = bpy.props.FloatVectorProperty(
+ name="Inner Concave Lines", description="Color of lines to be folded to a concave angle",
default=(0.0, 0.0, 0.0, 1.0), min=0, max=1, subtype='COLOR', size=4)
- concave_style = bpy.props.EnumProperty(name="Concave Lines Drawing Style",
- description="Drawing style of lines to be folded to a concave angle",
+ concave_style = bpy.props.EnumProperty(
+ name="Concave Lines Drawing Style", description="Drawing style of lines to be folded to a concave angle",
default='DASHDOT', items=line_styles)
- concave_width = bpy.props.FloatProperty(name="Concave Lines Thickness",
- description="Relative thickness of concave lines",
+ concave_width = bpy.props.FloatProperty(
+ name="Concave Lines Thickness", description="Relative thickness of concave lines",
default=2, min=0, soft_max=10, precision=1, step=10, subtype='FACTOR')
- freestyle_color = bpy.props.FloatVectorProperty(name="Freestyle Edges",
- description="Color of lines marked as Freestyle Edge",
+ freestyle_color = bpy.props.FloatVectorProperty(
+ name="Freestyle Edges", description="Color of lines marked as Freestyle Edge",
default=(0.0, 0.0, 0.0, 1.0), min=0, max=1, subtype='COLOR', size=4)
- freestyle_style = bpy.props.EnumProperty(name="Freestyle Edges Drawing Style",
- description="Drawing style of Freestyle Edges",
+ freestyle_style = bpy.props.EnumProperty(
+ name="Freestyle Edges Drawing Style", description="Drawing style of Freestyle Edges",
default='SOLID', items=line_styles)
- freestyle_width = bpy.props.FloatProperty(name="Freestyle Edges Thickness",
- description="Relative thickness of Freestyle edges",
+ freestyle_width = bpy.props.FloatProperty(
+ name="Freestyle Edges Thickness", description="Relative thickness of Freestyle edges",
default=2, min=0, soft_max=10, precision=1, step=10, subtype='FACTOR')
- use_inbg = bpy.props.BoolProperty(name="Highlight Inner Lines",
- description="Add another line below every line to improve contrast",
+ use_inbg = bpy.props.BoolProperty(
+ name="Highlight Inner Lines", description="Add another line below every line to improve contrast",
default=True)
- inbg_color = bpy.props.FloatVectorProperty(name="Inner Highlight",
- description="Color of the highlight for inner lines",
+ inbg_color = bpy.props.FloatVectorProperty(
+ name="Inner Highlight", description="Color of the highlight for inner lines",
default=(1.0, 1.0, 1.0, 1.0), min=0, max=1, subtype='COLOR', size=4)
- inbg_width = bpy.props.FloatProperty(name="Inner Highlight Thickness",
- description="Relative thickness of the highlighting lines",
+ inbg_width = bpy.props.FloatProperty(
+ name="Inner Highlight Thickness", description="Relative thickness of the highlighting lines",
default=2, min=0, soft_max=10, precision=1, step=10, subtype='FACTOR')
- sticker_fill = bpy.props.FloatVectorProperty(name="Tabs Fill",
- description="Fill color of sticking tabs",
+ sticker_fill = bpy.props.FloatVectorProperty(
+ name="Tabs Fill", description="Fill color of sticking tabs",
default=(0.9, 0.9, 0.9, 1.0), min=0, max=1, subtype='COLOR', size=4)
- text_color = bpy.props.FloatVectorProperty(name="Text Color",
- description="Color of all text used in the document",
+ text_color = bpy.props.FloatVectorProperty(
+ name="Text Color", description="Color of all text used in the document",
default=(0.0, 0.0, 0.0, 1.0), min=0, max=1, subtype='COLOR', size=4)
bpy.utils.register_class(PaperModelStyle)
@@ -2047,43 +2108,29 @@ bpy.utils.register_class(PaperModelStyle)
class ExportPaperModel(bpy.types.Operator):
"""Blender Operator: save the selected object's net and optionally bake its texture"""
- def scaled_getter(name):
- return lambda self: self[name] / bpy.context.scene.unit_settings.scale_length
-
- def scaled_setter(name):
- def setter(self, value):
- self[name] = value * bpy.context.scene.unit_settings.scale_length
- return setter
-
bl_idname = "export_mesh.paper_model"
bl_label = "Export Paper Model"
bl_description = "Export the selected object's net and optionally bake its texture"
- filepath = bpy.props.StringProperty(name="File Path",
- description="Target file to save the SVG", options={'SKIP_SAVE'})
- filename = bpy.props.StringProperty(name="File Name",
- description="Name of the file", options={'SKIP_SAVE'})
- directory = bpy.props.StringProperty(name="Directory",
- description="Directory of the file", options={'SKIP_SAVE'})
- page_size_preset = bpy.props.EnumProperty(name="Page Size",
- description="Size of the exported document",
- default='A4', update=page_size_preset_changed, items=[
- ('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")
- ])
- output_size_x = bpy.props.FloatProperty(name="Page Width",
- description="Width of the exported document",
+ filepath = bpy.props.StringProperty(
+ name="File Path", description="Target file to save the SVG", options={'SKIP_SAVE'})
+ filename = bpy.props.StringProperty(
+ name="File Name", description="Name of the file", options={'SKIP_SAVE'})
+ directory = bpy.props.StringProperty(
+ name="Directory", description="Directory of the file", options={'SKIP_SAVE'})
+ page_size_preset = bpy.props.EnumProperty(
+ name="Page Size", description="Size of the exported document",
+ default='A4', update=page_size_preset_changed, items=global_paper_sizes)
+ output_size_x = bpy.props.FloatProperty(
+ name="Page Width", description="Width of the exported document",
default=0.210, soft_min=0.105, soft_max=0.841, subtype="UNSIGNED", unit="LENGTH")
- output_size_y = bpy.props.FloatProperty(name="Page Height",
- description="Height of the exported document",
+ output_size_y = bpy.props.FloatProperty(
+ name="Page Height", description="Height of the exported document",
default=0.297, soft_min=0.148, soft_max=1.189, subtype="UNSIGNED", unit="LENGTH")
- output_margin = bpy.props.FloatProperty(name="Page Margin",
- description="Distance from page borders to the printable area",
- default=0.005, min=0, soft_max=0.1, step=0.1, subtype="DISTANCE", unit="LENGTH", get=scaled_getter("output_margin"), set=scaled_setter("output_margin"))
- output_type = bpy.props.EnumProperty(name="Textures",
- description="Source of a texture for the model",
+ output_margin = bpy.props.FloatProperty(
+ name="Page Margin", description="Distance from page borders to the printable area",
+ default=0.005, min=0, soft_max=0.1, step=0.1, subtype="UNSIGNED", unit="LENGTH")
+ output_type = bpy.props.EnumProperty(
+ name="Textures", description="Source of a texture for the model",
default='NONE', items=[
('NONE', "No Texture", "Export the net only"),
('TEXTURE', "From Materials", "Render the diffuse color and all painted textures"),
@@ -2091,45 +2138,45 @@ class ExportPaperModel(bpy.types.Operator):
('RENDER', "Full Render", "Render the material in actual scene illumination"),
('SELECTED_TO_ACTIVE', "Selected to Active", "Render all selected surrounding objects as a texture")
])
- do_create_stickers = bpy.props.BoolProperty(name="Create Tabs",
- description="Create gluing tabs around the net (useful for paper)",
+ do_create_stickers = bpy.props.BoolProperty(
+ name="Create Tabs", description="Create gluing tabs around the net (useful for paper)",
default=True)
- do_create_numbers = bpy.props.BoolProperty(name="Create Numbers",
- description="Enumerate edges to make it clear which edges should be sticked together",
+ do_create_numbers = bpy.props.BoolProperty(
+ name="Create Numbers", description="Enumerate edges to make it clear which edges should be sticked together",
default=True)
- sticker_width = bpy.props.FloatProperty(name="Tabs and Text Size",
- description="Width of gluing tabs and their numbers",
+ sticker_width = bpy.props.FloatProperty(
+ name="Tabs and Text Size", description="Width of gluing tabs and their numbers",
default=0.005, soft_min=0, soft_max=0.05, step=0.1, subtype="UNSIGNED", unit="LENGTH")
- angle_epsilon = bpy.props.FloatProperty(name="Hidden Edge Angle",
- description="Folds with angle below this limit will not be drawn",
+ angle_epsilon = bpy.props.FloatProperty(
+ name="Hidden Edge Angle", description="Folds with angle below this limit will not be drawn",
default=pi/360, min=0, soft_max=pi/4, step=0.01, subtype="ANGLE", unit="ROTATION")
- output_dpi = bpy.props.FloatProperty(name="Resolution (DPI)",
- description="Resolution of images in pixels per inch",
+ output_dpi = bpy.props.FloatProperty(
+ name="Resolution (DPI)", description="Resolution of images in pixels per inch",
default=90, min=1, soft_min=30, soft_max=600, subtype="UNSIGNED")
- file_format = bpy.props.EnumProperty(name="Document Format",
- description="File format of the exported net",
+ file_format = bpy.props.EnumProperty(
+ name="Document Format", description="File format of the exported net",
default='PDF', items=[
('PDF', "PDF", "Adobe Portable Document Format 1.4"),
('SVG', "SVG", "W3C Scalable Vector Graphics"),
])
- image_packing = bpy.props.EnumProperty(name="Image Packing Method",
- description="Method of attaching baked image(s) to the SVG",
+ image_packing = bpy.props.EnumProperty(
+ name="Image Packing Method", description="Method of attaching baked image(s) to the SVG",
default='ISLAND_EMBED', items=[
('PAGE_LINK', "Single Linked", "Bake one image per page of output and save it separately"),
('ISLAND_LINK', "Linked", "Bake images separately for each island and save them in a directory"),
('ISLAND_EMBED', "Embedded", "Bake images separately for each island and embed them into the SVG")
])
- scale = bpy.props.FloatProperty(name="Scale",
- description="Divisor of all dimensions when exporting",
+ scale = bpy.props.FloatProperty(
+ name="Scale", description="Divisor of all dimensions when exporting",
default=1, soft_min=1.0, soft_max=10000.0, step=100, subtype='UNSIGNED', precision=1)
- do_create_uvmap = bpy.props.BoolProperty(name="Create UVMap",
- description="Create a new UV Map showing the islands and page layout",
+ do_create_uvmap = bpy.props.BoolProperty(
+ name="Create UVMap", description="Create a new UV Map showing the islands and page layout",
default=False, options={'SKIP_SAVE'})
- ui_expanded_document = bpy.props.BoolProperty(name="Show Document Settings Expanded",
- description="Shows the box 'Document Settings' expanded in user interface",
+ ui_expanded_document = bpy.props.BoolProperty(
+ name="Show Document Settings Expanded", description="Shows the box 'Document Settings' expanded in user interface",
default=True, options={'SKIP_SAVE'})
- ui_expanded_style = bpy.props.BoolProperty(name="Show Style Settings Expanded",
- description="Shows the box 'Colors and Style' expanded in user interface",
+ ui_expanded_style = bpy.props.BoolProperty(
+ name="Show Style Settings Expanded", description="Shows the box 'Colors and Style' expanded in user interface",
default=False, options={'SKIP_SAVE'})
style = bpy.props.PointerProperty(type=PaperModelStyle)
@@ -2150,8 +2197,6 @@ class ExportPaperModel(bpy.types.Operator):
except UnfoldError as error:
self.report(type={'ERROR_INVALID_INPUT'}, message=error.args[0])
return {'CANCELLED'}
- except:
- raise
def get_scale_ratio(self, sce):
margin = self.output_margin + self.sticker_width + 1e-5
@@ -2171,7 +2216,9 @@ class ExportPaperModel(bpy.types.Operator):
cage_size = M.Vector((sce.paper_model.output_size_x, sce.paper_model.output_size_y)) if sce.paper_model.limit_by_page else None
try:
self.unfolder = Unfolder(self.object)
- self.unfolder.prepare(cage_size, create_uvmap=self.do_create_uvmap, scale=sce.unit_settings.scale_length/self.scale)
+ self.unfolder.prepare(
+ cage_size, create_uvmap=self.do_create_uvmap,
+ scale=sce.unit_settings.scale_length/self.scale)
except UnfoldError as error:
self.report(type={'ERROR_INVALID_INPUT'}, message=error.args[0])
bpy.ops.object.mode_set(mode=recall_mode)
@@ -2199,13 +2246,20 @@ class ExportPaperModel(bpy.types.Operator):
layout.prop(self.properties, "scale", text="Scale: 1")
scale_ratio = self.get_scale_ratio(context.scene)
if scale_ratio > 1:
- layout.label(text="An island is roughly {:.1f}x bigger than page".format(scale_ratio), icon="ERROR")
+ layout.label(
+ text="An island is roughly {:.1f}x bigger than page".format(scale_ratio),
+ icon="ERROR")
elif scale_ratio > 0:
layout.label(text="Largest island is roughly 1/{:.1f} of page".format(1 / scale_ratio))
+ if context.scene.unit_settings.scale_length != 1:
+ layout.label(
+ text="Unit scale {:.1f} makes page size etc. not display correctly".format(
+ context.scene.unit_settings.scale_length), icon="ERROR")
box = layout.box()
row = box.row(align=True)
- row.prop(self.properties, "ui_expanded_document", text="",
+ row.prop(
+ self.properties, "ui_expanded_document", text="",
icon=('TRIA_DOWN' if self.ui_expanded_document else 'TRIA_RIGHT'), emboss=False)
row.label(text="Document Settings")
@@ -2239,7 +2293,8 @@ class ExportPaperModel(bpy.types.Operator):
box = layout.box()
row = box.row(align=True)
- row.prop(self.properties, "ui_expanded_style", text="",
+ row.prop(
+ self.properties, "ui_expanded_style", text="",
icon=('TRIA_DOWN' if self.ui_expanded_style else 'TRIA_RIGHT'), emboss=False)
row.label(text="Colors and Style")
@@ -2305,7 +2360,8 @@ class AddPresetPaperModel(bl_operators.presets.AddPresetBase, bpy.types.Operator
op = bpy.ops.export_mesh.paper_model
properties = op.get_rna().bl_rna.properties.items()
blacklist = bpy.types.Operator.bl_rna.properties.keys()
- return ["op.{}".format(prop_id) for (prop_id, prop) in properties
+ return [
+ "op.{}".format(prop_id) for (prop_id, prop) in properties
if not (prop.is_hidden or prop.is_skip_save or prop_id in blacklist)]
@@ -2334,14 +2390,17 @@ class VIEW3D_PT_paper_model_tools(bpy.types.Panel):
else:
layout.operator("mesh.clear_all_seams")
- layout.prop(sce.paper_model, "scale", text="Model Scale: 1")
+ props = sce.paper_model
+ layout.prop(props, "scale", text="Model Scale: 1")
- col = layout.column(align=True)
- col.prop(sce.paper_model, "limit_by_page")
+ layout.prop(props, "limit_by_page")
+ col = layout.column()
+ col.active = props.limit_by_page
+ col.prop(props, "page_size_preset")
sub = col.column(align=True)
- sub.active = sce.paper_model.limit_by_page
- sub.prop(sce.paper_model, "output_size_x")
- sub.prop(sce.paper_model, "output_size_y")
+ sub.active = props.page_size_preset == 'USER'
+ sub.prop(props, "output_size_x")
+ sub.prop(props, "output_size_y")
class VIEW3D_PT_paper_model_islands(bpy.types.Panel):
@@ -2357,9 +2416,11 @@ class VIEW3D_PT_paper_model_islands(bpy.types.Panel):
mesh = obj.data if obj and obj.type == 'MESH' else None
if mesh and mesh.paper_island_list:
- layout.label(text="1 island:" if len(mesh.paper_island_list) == 1 else
+ layout.label(
+ text="1 island:" if len(mesh.paper_island_list) == 1 else
"{} islands:".format(len(mesh.paper_island_list)))
- layout.template_list('UI_UL_list', 'paper_model_island_list', mesh,
+ layout.template_list(
+ 'UI_UL_list', 'paper_model_island_list', mesh,
'paper_island_list', mesh, 'paper_island_index', rows=1, maxrows=5)
if mesh.paper_island_index >= 0:
list_item = mesh.paper_island_list[mesh.paper_island_index]
@@ -2421,7 +2482,8 @@ def display_islands_changed(self, context):
"""Switch highlighting islands on/off"""
if self.display_islands:
if not display_islands.handle:
- display_islands.handle = bpy.types.SpaceView3D.draw_handler_add(display_islands, (self, context), 'WINDOW', 'POST_VIEW')
+ display_islands.handle = bpy.types.SpaceView3D.draw_handler_add(
+ display_islands, (self, context), 'WINDOW', 'POST_VIEW')
else:
if display_islands.handle:
bpy.types.SpaceView3D.draw_handler_remove(display_islands.handle, 'WINDOW')
@@ -2437,6 +2499,13 @@ def label_changed(self, context):
def island_item_changed(self, context):
"""The labelling of an island was changed"""
+ def increment(abbrev, collisions):
+ letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+ while abbrev in collisions:
+ abbrev = abbrev.rstrip(letters[-1])
+ abbrev = abbrev[:2] + letters[letters.find(abbrev[-1]) + 1 if len(abbrev) == 3 else 0]
+ return abbrev
+
# accessing properties via [..] to avoid a recursive call after the update
island_list = context.active_object.data.paper_island_list
if self.auto_label:
@@ -2446,51 +2515,59 @@ def island_item_changed(self, context):
number += 1
self["label"] = "Island {}".format(number)
if self.auto_abbrev:
- self["abbreviation"] = "".join(first_letters(self.label))[:3].upper()
+ self["abbreviation"] = "" # avoid self-conflict
+ abbrev = "".join(first_letters(self.label))[:3].upper()
+ self["abbreviation"] = increment(abbrev, {item.abbreviation for item in island_list})
elif len(self.abbreviation) > 3:
self["abbreviation"] = self.abbreviation[:3]
- self.name = "[{}] {} ({} {})".format(self.abbreviation, self.label, len(self.faces), "faces" if len(self.faces) > 1 else "face")
+ self.name = "[{}] {} ({} {})".format(
+ self.abbreviation, self.label, len(self.faces), "faces" if len(self.faces) > 1 else "face")
class FaceList(bpy.types.PropertyGroup):
id = bpy.props.IntProperty(name="Face ID")
+bpy.utils.register_class(FaceList)
class IslandList(bpy.types.PropertyGroup):
- faces = bpy.props.CollectionProperty(type=FaceList, name="Faces",
- description="Faces belonging to this island")
- label = bpy.props.StringProperty(name="Label",
- description="Label on this island",
+ faces = bpy.props.CollectionProperty(
+ name="Faces", description="Faces belonging to this island", type=FaceList)
+ label = bpy.props.StringProperty(
+ name="Label", description="Label on this island",
default="", update=label_changed)
- abbreviation = bpy.props.StringProperty(name="Abbreviation",
- description="Three-letter label to use when there is not enough space",
+ abbreviation = bpy.props.StringProperty(
+ name="Abbreviation", description="Three-letter label to use when there is not enough space",
default="", update=island_item_changed)
- auto_label = bpy.props.BoolProperty(name="Auto Label",
- description="Generate the label automatically",
+ auto_label = bpy.props.BoolProperty(
+ name="Auto Label", description="Generate the label automatically",
default=True, update=island_item_changed)
- auto_abbrev = bpy.props.BoolProperty(name="Auto Abbreviation",
- description="Generate the abbreviation automatically",
+ auto_abbrev = bpy.props.BoolProperty(
+ name="Auto Abbreviation", description="Generate the abbreviation automatically",
default=True, update=island_item_changed)
-bpy.utils.register_class(FaceList)
bpy.utils.register_class(IslandList)
class PaperModelSettings(bpy.types.PropertyGroup):
- display_islands = bpy.props.BoolProperty(name="Highlight selected island",
- description="Highlight faces corresponding to the selected island in the 3D View",
+ display_islands = bpy.props.BoolProperty(
+ name="Highlight selected island", description="Highlight faces corresponding to the selected island in the 3D View",
options={'SKIP_SAVE'}, update=display_islands_changed)
- islands_alpha = bpy.props.FloatProperty(name="Opacity",
- description="Opacity of island highlighting", min=0.0, max=1.0, default=0.3)
- limit_by_page = bpy.props.BoolProperty(name="Limit Island Size",
- description="Do not create islands larger than given dimensions", default=False)
- output_size_x = bpy.props.FloatProperty(name="Width",
- description="Maximal width of an island",
+ islands_alpha = bpy.props.FloatProperty(
+ name="Opacity", description="Opacity of island highlighting",
+ min=0.0, max=1.0, default=0.3)
+ limit_by_page = bpy.props.BoolProperty(
+ name="Limit Island Size", description="Do not create islands larger than given dimensions",
+ default=False, update=page_size_preset_changed)
+ page_size_preset = bpy.props.EnumProperty(
+ name="Page Size", description="Maximal size of an island",
+ default='A4', update=page_size_preset_changed, items=global_paper_sizes)
+ output_size_x = bpy.props.FloatProperty(
+ name="Width", description="Maximal width of an island",
default=0.2, soft_min=0.105, soft_max=0.841, subtype="UNSIGNED", unit="LENGTH")
- output_size_y = bpy.props.FloatProperty(name="Height",
- description="Maximal height of an island",
+ output_size_y = bpy.props.FloatProperty(
+ name="Height", description="Maximal height of an island",
default=0.29, soft_min=0.148, soft_max=1.189, subtype="UNSIGNED", unit="LENGTH")
- scale = bpy.props.FloatProperty(name="Scale",
- description="Divisor of all dimensions when exporting",
+ scale = bpy.props.FloatProperty(
+ name="Scale", description="Divisor of all dimensions when exporting",
default=1, soft_min=1.0, soft_max=10000.0, step=100, subtype='UNSIGNED', precision=1)
bpy.utils.register_class(PaperModelSettings)
@@ -2498,13 +2575,13 @@ bpy.utils.register_class(PaperModelSettings)
def register():
bpy.utils.register_module(__name__)
- bpy.types.Scene.paper_model = bpy.props.PointerProperty(type=PaperModelSettings,
- name="Paper Model",
- description="Settings of the Export Paper Model script",
- options={'SKIP_SAVE'})
- bpy.types.Mesh.paper_island_list = bpy.props.CollectionProperty(type=IslandList,
- name="Island List", description="")
- bpy.types.Mesh.paper_island_index = bpy.props.IntProperty(name="Island List Index",
+ bpy.types.Scene.paper_model = bpy.props.PointerProperty(
+ name="Paper Model", description="Settings of the Export Paper Model script",
+ type=PaperModelSettings, options={'SKIP_SAVE'})
+ bpy.types.Mesh.paper_island_list = bpy.props.CollectionProperty(
+ name="Island List", type=IslandList)
+ bpy.types.Mesh.paper_island_index = bpy.props.IntProperty(
+ name="Island List Index",
default=-1, min=-1, max=100, options={'SKIP_SAVE'})
bpy.types.INFO_MT_file_export.append(menu_func)
@@ -2516,5 +2593,6 @@ def unregister():
bpy.types.SpaceView3D.draw_handler_remove(display_islands.handle, 'WINDOW')
display_islands.handle = None
+
if __name__ == "__main__":
register()