[Bf-extensions-cvs] [2ba08390] master: Paper model exporter: update for Bl 2.8, upstream 414b27f

Adam Dominec noreply at git.blender.org
Wed Jun 26 22:16:59 CEST 2019


Commit: 2ba08390b59e9874d505d8f2815467b7b726d7ed
Author: Adam Dominec
Date:   Wed Jun 26 22:14:52 2019 +0200
Branches: master
https://developer.blender.org/rBA2ba08390b59e9874d505d8f2815467b7b726d7ed

Paper model exporter: update for Bl 2.8, upstream 414b27f

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

M	io_export_paper_model.py

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

diff --git a/io_export_paper_model.py b/io_export_paper_model.py
index a5818339..39d6b508 100644
--- a/io_export_paper_model.py
+++ b/io_export_paper_model.py
@@ -1,41 +1,37 @@
 # -*- 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 #####
+# This script is Free software. Please share and reuse.
+# ♡2010-2019 Adam Dominec <adominec at gmail.com>
+
+## Code structure
+# This file consists of several components, in this order:
+# * Unfolding and baking
+# * Export (SVG or PDF)
+# * User interface
+# During the unfold process, the mesh is mirrored into a 2D structure: UVFace, UVEdge, UVVertex.
 
 bl_info = {
     "name": "Export Paper Model",
     "author": "Addam Dominec",
-    "version": (0, 9),
-    "blender": (2, 73, 0),
+    "version": (1, 0),
+    "blender": (2, 80, 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"
+                "Scripts/Import-Export/Paper_Model"
 }
 
+# Task: split into four files (SVG and PDF separately)
+    # does any portion of baking belong into the export module?
+    # sketch out the code for GCODE and two-sided export
+
 # 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?
+# QuickSweepline is very much broken -- it throws GeometryError for all nets > ~15 faces
 # rotate islands to minimize area -- and change that only if necessary to fill the page size
 # Sticker.vertices should be of type Vector
 
@@ -43,30 +39,14 @@ bl_info = {
 #  * 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 bmesh
 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
+from itertools import chain, repeat, product, combinations
+from math import pi, ceil, asin, atan2
+import os.path as os_path
 
 default_priority_effect = {
     'CONVEX': 0.5,
@@ -108,18 +88,6 @@ def pairs(sequence):
     yield this, first
 
 
-def argmax_pair(array, key):
-    """Find an (unordered) pair of indices that maximize the given function"""
-    n = len(array)
-    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((
@@ -146,6 +114,45 @@ def z_up_matrix(n):
         ))
 
 
+def cage_fit(points, aspect):
+    """Find rotation for a minimum bounding box with a given aspect ratio
+    returns a tuple: rotation angle, box height"""
+    def guesses(polygon):
+        """Yield all tentative extrema of the bounding box height wrt. polygon rotation"""
+        for a, b in pairs(polygon):
+            if a == b:
+                continue
+            direction = (b - a).normalized()
+            sinx, cosx = -direction.y, direction.x
+            rot = M.Matrix(((cosx, -sinx), (sinx, cosx)))
+            rot_polygon = [rot @ p for p in polygon]
+            left, right = [fn(rot_polygon, key=lambda p: p.to_tuple()) for fn in (min, max)]
+            bottom, top = [fn(rot_polygon, key=lambda p: p.yx.to_tuple()) for fn in (min, max)]
+            #print(f"{rot_polygon.index(left)}-{rot_polygon.index(right)}, {rot_polygon.index(bottom)}-{rot_polygon.index(top)}")
+            horz, vert = right - left, top - bottom
+            # solve (rot * a).y == (rot * b).y
+            yield max(aspect * horz.x, vert.y), sinx, cosx
+            # solve (rot * a).x == (rot * b).x
+            yield max(horz.x, aspect * vert.y), -cosx, sinx
+            # solve aspect * (rot * (right - left)).x == (rot * (top - bottom)).y
+            # using substitution t = tan(rot / 2)
+            q = aspect * horz.x - vert.y
+            r = vert.x + aspect * horz.y
+            t = ((r**2 + q**2)**0.5 - r) / q if q != 0 else 0
+            t = -1 / t if abs(t) > 1 else t  # pick the positive solution
+            siny, cosy = 2 * t / (1 + t**2), (1 - t**2) / (1 + t**2)
+            rot = M.Matrix(((cosy, -siny), (siny, cosy)))
+            for p in rot_polygon:
+                p[:] = rot @ p  # note: this also modifies left, right, bottom, top
+            #print(f"solve {aspect * (right - left).x} == {(top - bottom).y} with aspect = {aspect}")
+            if left.x < right.x and bottom.y < top.y and all(left.x <= p.x <= right.x and bottom.y <= p.y <= top.y for p in rot_polygon):
+                #print(f"yield {max(aspect * (right - left).x, (top - bottom).y)}")
+                yield max(aspect * (right - left).x, (top - bottom).y), sinx*cosy + cosx*siny, cosx*cosy - sinx*siny
+    polygon = [points[i] for i in M.geometry.convex_hull_2d(points)]
+    height, sinx, cosx = min(guesses(polygon))
+    return atan2(sinx, cosx), height
+
+
 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]
@@ -160,79 +167,43 @@ def create_blank_image(image_name, dimensions, alpha=1):
     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:
-            loop[vid].uv *= -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:
-            for mat, node in temp_nodes.items():
-                mat.node_tree.nodes.remove(node)
-        for vid in ignored_uvs:
-            loop[vid].uv *= -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
+    def mesh_select(self):
+        if len(self.args) > 1:
+            elems, bm = self.args[1:3]
+            bpy.context.tool_settings.mesh_select_mode = [bool(elems[key]) for key in ("verts", "edges", "faces")]
+            for elem in chain(bm.verts, bm.edges, bm.faces):
+                elem.select = False
+            for elem in chain(*elems.values()):
+                elem.select_set(True)
+            bmesh.update_edit_mesh(bpy.context.object.data, False, False)
 
 
 class Unfolder:
     def __init__(self, ob):
-        self.ob = ob
-        self.mesh = Mesh(ob.data, ob.matrix_world)
+        self.do_create_uvmap = False
+        bm = bmesh.from_edit_mesh(ob.data)
+        self.mesh = Mesh(bm, ob.matrix_world)
+        self.mesh.copy_freestyle_marks(ob.data)
         self.mesh.check_correct()
-        self.tex = None
+    
+    def __del__(self):
+        if not self.do_create_uvmap:
+            self.mesh.delete_uvmap()
 
-    def prepare(self, cage_size=None, create_uvmap=False, mark_seams=False, priority_effect=default_priority_effect, scale=1):
+    def prepare(self, cage_size=None, priority_effect=default_priority_effect, scale=1, limit_by_page=False):
         """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.generate_cuts(cage_size / scale if limit_by_page and cage_size else None, priority_effect)
+        self.

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list