[Bf-extensions-cvs] [725dbe4] master: Initial commit Export Paper Model by emu: T51199 T50357

meta-androcto noreply at git.blender.org
Sat Apr 15 06:04:23 CEST 2017


Commit: 725dbe4d410d5afbd903b1d6464854fd25166913
Author: meta-androcto
Date:   Sat Apr 15 14:03:55 2017 +1000
Branches: master
https://developer.blender.org/rBA725dbe4d410d5afbd903b1d6464854fd25166913

Initial commit Export Paper Model by emu: T51199 T50357

===================================================================

A	io_export_paper_model.py

===================================================================

diff --git a/io_export_paper_model.py b/io_export_paper_model.py
new file mode 100644
index 0000000..508b953
--- /dev/null
+++ b/io_export_paper_model.py
@@ -0,0 +1,2520 @@
+# -*- 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),
+    "blender": (2, 70, 0),
+    "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 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
+# 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
+}
+
+
+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"""
+    l = len(array)
+    mi, mj, m = None, None, None
+    for i in range(l):
+        for j in range(i+1, l):
+            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
+    l = n.length
+    if b > 0:
+        return M.Matrix((
+            (n.x*n.z/(b*l), n.y*n.z/(b*l), -b/l),
+            (-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
+        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):
+                slot.material = recall
+            for i, recall in enumerate(recall_mesh_slots):
+                me.materials[i] = recall
+            bpy.data.materials.remove(mat)
+        for vid in ignored_uvs:
+            loop[vid].uv[0] *= -1
+            loop[vid].uv[1] *= -1
+    else:
+        texfaces = uvmap.data
+        for fid in face_indices:
+            texfaces[fid].image = image
+        bpy.ops.object.bake_image()
+        for fid in face_indices:
+            texfaces[fid].image = None
+
+
+class UnfoldError(ValueError):
+    pass
+
+
+class Unfolder:
+    def __init__(self, ob):
+        self.ob = ob
+        self.mesh = Mesh(ob.data, ob.matrix_world)
+        self.mesh.check_correct()
+        self.tex = None
+
+    def prepare(self, cage_size=None, create_uvmap=False, mark_seams=False, priority_effect=default_priority_effect, scale=1):
+        """Create the islands of the net"""
+        self.mesh.generate_cuts(cage_size / scale if cage_size else None, priority_effect)
+        is_landscape = cage_size and cage_size.x > cage_size.y
+        self.mesh.finalize_islands(is_landscape)
+        self.mesh.enumerate_islands()
+        if create_uvmap:
+            self.tex = self.mesh.save_uv()
+        if mark_seams:
+            self.mesh.mark_cuts()
+
+    def copy_island_names(self, island_list):
+        """Copy island label and abbreviation from the best matching island in the list"""
+        orig_islands = [{face.id for face in item.faces} for item in island_list]
+        matching = list()
+        for i, island in enumerate(self.mesh.islands):
+            islfaces = {uvface.face.index for uvface in island.faces}
+            matching.extend((len(islfaces.intersection(item)), i, j) for j, item in enumerate(orig_islands))
+        matching.sort(reverse=True)
+        available_new = [True for island in self.mesh.islands]
+        available_orig = [True for item in island_list]
+        for face_count, i, j in matching:
+            if available_new[i] and available_orig[j]:
+                available_new[i] = available_orig[j] = False
+                self.mesh.islands[i].label = island_list[j].label
+                self.mesh.islands[i].abbreviation = island_list[j].abbreviation
+
+    def save(self, properties):
+        """Export the document"""
+        # Note about scale: input is direcly in blender length
+        # Mesh.scale_islands multiplies everything by a user-defined ratio
+        # exporters (SVG or PDF) multiply everything by 1000 (output in millimeters)
+        Exporter = SVG if properties.file_format == 'SVG' else PDF
+        filepath = properties.filepath
+        extension = properties.file_format.lower()
+        filepath = bpy.path.ensure_ext(filepath, "." + extension)
+        # page size in meters
+        page_size = M.Vector((properties.output_size_x, properties.output_size_y))
+        # printable area size in meters
+        printable_size = page_size - 2 * properties.output_margin * M.Vector((1, 1))
+        unit_scale = bpy.context.scene.unit_settings.scale_length
+        ppm = properties.output_dpi * 100 / 2.54  # pixels per meter
+
+        # after this call, all dimensions will be 

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list