#!BPY

""" Registration info for Blender menus:
Name: 'Subdivide'
Blender: 241
Group: 'Mesh'
Tooltip: 'Apply subdivision while preserving metadata'
"""

__author__ = "Bruce Merry"
__version__ = "2007/05/17"
__bpydoc__ = """\
Applies one step of Catmull-Clark subdivision. Unlike using a subdivision
modifiers and applying (within Blender), this script will preserve
vertex groups (applying subdivision to the weights).

The current version does not take into account
- creased edges
- boundaries (it shouldn't crash, but the kernel is wrong)

UV coordinates use simple (not smoothed) subdivision
"""

from Blender import *
from Blender.Mathutils import *

class MyVert:
        def __init__(self, mesh, vert):
                self.index = vert.index
                self.co = Vector(vert.co)

def combine(mesh, VGW, target, sources, weights, update):
        pos = Vector(0.0, 0.0, 0.0)
        for s, w in zip(sources, weights):
                pos += s.co * w
        pos = pos * (1.0 / sum(weights))
        mesh.verts[target].co = pos

        for g, vg in VGW.items():
                weight = 0.0
                for s, w in zip(sources, weights):
                        if vg.has_key(s.index):
                                weight += vg[s.index] * w
                weight /= sum(weights)
                if weight > 0.0:
                        mesh.assignVertsToGroup(g, [target], weight, Mesh.AssignModes['REPLACE'])
                        if update:
                                vg[target] = weight

def combine_colours(target, colours):
        ans = Vector(0.0, 0.0, 0.0, 0.0)
        for c in colours:
                ans += Vector(c.r, c.g, c.b, c.a)
        ans *= 1.0 / len(colours)
        target.r = int(ans[0])
        target.g = int(ans[1])
        target.b = int(ans[2])
        target.a = int(ans[3])

in_editmode = Window.EditMode()
if in_editmode: Window.EditMode(0)
for obj in Object.GetSelected():
        if obj.getType() == 'Mesh':
                mesh = obj.getData(False, True)

                nOV = len(mesh.verts)
                nOE = len(mesh.edges)
                nOF = len(mesh.faces)
                OV = [MyVert(mesh, v) for v in mesh.verts]
                VGW = {}
                adjvv = [[] for v in OV]
                adjvf = [[] for v in OV]
                adjve = [[] for v in OV]
                adjef = [[] for e in mesh.edges]
                edge_map = {}

                Window.DrawProgressBar(0.0, 'Obtaining vertex groups')
                for g in mesh.getVertGroupNames():
                        VGW[g] = {}
                        for v, w in mesh.getVertsFromGroup(g, 1):
                                VGW[g][v] = w

                Window.DrawProgressBar(0.0, 'Building edge map')
                for e in mesh.edges:
                        edge_map[(e.v1.index, e.v2.index)] = e.index
                        edge_map[(e.v2.index, e.v1.index)] = e.index

                Window.DrawProgressBar(0.0, 'Setting face vertices')
                for f in mesh.faces:
                        index = len(mesh.verts)
                        mesh.verts.extend(Vector(0.0, 0.0, 0.0))
                        combine(mesh, VGW, index, f.verts, [1.0 for v in f.verts], True)
                        for v in f:
                                adjvf[v.index].append(mesh.verts[index])
                        for i in range(len(f.verts)):
                                key = (f.verts[i - 1].index, f.verts[i].index)
                                if not edge_map.has_key(key):
                                        raise AttributeError("Missing edge")
                                adjef[edge_map[key]].append(mesh.verts[index])

                Window.DrawProgressBar(0.0, 'Setting edge vertices')
                for e in mesh.edges:
                        index = len(mesh.verts)
                        mesh.verts.extend(Vector(0.0, 0.0, 0.0))
                        sources = [e.v1, e.v2]
                        sources.extend(adjef[e.index])
                        weights = [1.0 for x in sources]
                        combine(mesh, VGW, index, sources, weights, True)
                        adjvv[e.v1.index].append(OV[e.v2.index])
                        adjvv[e.v2.index].append(OV[e.v1.index])
                        adjve[e.v1.index].append(mesh.verts[index])
                        adjve[e.v2.index].append(mesh.verts[index])

                Window.DrawProgressBar(0.0, 'Computing new vertex positions')
                for v in OV:
                        n = len(adjve[v.index])
                        sources = [v]
                        sources.extend(adjvv[v.index])
                        sources.extend(adjve[v.index])
                        weights = [1.0 for s in sources]
                        weights[0] = (n - 2) * n
                        combine(mesh, VGW, v.index, sources, weights, False)

                # Work around a Blender 2.41 bug: the sticky coordinates
                # are not extended when the vertices are
                if mesh.vertexUV:
                        sticky = [mesh.verts[x].uvco for x in range(nOV)]
                        mesh.vertexUV = 0
                        mesh.vertexUV = 1
                        for i, uv in enumerate(sticky):
                                mesh.verts[i].uvco = uv
                        sticky = []

                Window.DrawProgressBar(0.0, 'Creating new edges')
                new_edges = []
                for e in mesh.edges:
                        ve = mesh.verts[e.index + nOV + nOF]
                        new_edges.append([e.v1, ve])
                        new_edges.append([ve, e.v2])
                        if mesh.vertexUV:
                                ve.uvco = 0.5 * (e.v1.uvco + e.v2.uvco)
                mesh.edges.extend(new_edges)
                new_edges = []

                Window.DrawProgressBar(0.0, 'Creating new faces')
                new_faces = []
                for f in mesh.faces:
                        vf = mesh.verts[f.index + nOV]
                        for i in range(len(f.verts)):
                                v1 = f.verts[i - 2]
                                v2 = f.verts[i - 1]
                                v3 = f.verts[i]
                                v12 = mesh.verts[edge_map[(v1.index, v2.index)] + nOV + nOF]
                                v23 = mesh.verts[edge_map[(v2.index, v3.index)] + nOV + nOF]
                                new_faces.append([v12, v2, v23, vf])
                        if mesh.vertexUV:
                                vf.uvco = (0.0, 0.0)
                                for v in f.verts: vf.uvco += v.uvco
                                vf.uvco *= 1.0 / len(f.verts)
                mesh.faces.extend(new_faces)
                new_faces = []

                Window.DrawProgressBar(0.0, 'Setting edge attributes')
                for e in range(0, nOE):
                        mesh.edges[nOE + 2 * e].flag = mesh.edges[e].flag
                        mesh.edges[nOE + 2 * e + 1].flag = mesh.edges[e].flag
                        crease = mesh.edges[e].crease
                        if crease >= 1: crease = crease - 1
                        else: crease = 0
                        mesh.edges[nOE + 2 * e].crease = crease
                        mesh.edges[nOE + 2 * e + 1].crease = crease

                Window.DrawProgressBar(0.0, 'Setting face attributes')
                index = nOF
                for f in range(0, nOF):
                        if mesh.faceUV:
                                uvf = Vector(0.0, 0.0)
                                for uv in mesh.faces[f].uv: uvf += uv
                                uvf *= 1.0 / len(mesh.faces[f].uv)
                        for i in range(index, index + len(mesh.faces[f].verts)):
                                mesh.faces[i].sel = mesh.faces[f].sel
                                mesh.faces[i].hide = mesh.faces[f].hide
                                mesh.faces[i].smooth = mesh.faces[f].smooth
                                mesh.faces[i].mat = mesh.faces[f].mat
                                if mesh.faceUV:
                                        mesh.faces[i].image = mesh.faces[f].image
                                        mesh.faces[i].mode = mesh.faces[f].mode
                                        mesh.faces[i].flag = mesh.faces[f].flag
                                        mesh.faces[i].transp = mesh.faces[f].transp
                                        uv1 = mesh.faces[f].uv[i - index - 2]
                                        uv2 = mesh.faces[f].uv[i - index - 1]
                                        uv3 = mesh.faces[f].uv[i - index - 0]
                                        mesh.faces[i].uv = [0.5 * (uv1 + uv2), uv2, 0.5 * (uv2 + uv3), uvf]
                                        # mesh.faces[i].uvSel = mesh.faces[f].uvSel
                                if mesh.faceUV and mesh.vertexColors:
                                        col1 = mesh.faces[f].col[i - index - 2]
                                        col2 = mesh.faces[f].col[i - index - 1]
                                        col3 = mesh.faces[f].col[i - index - 0]
                                        combine_colours(mesh.faces[i].col[0], [col1, col2])
                                        combine_colours(mesh.faces[i].col[1], [col2])
                                        combine_colours(mesh.faces[i].col[2], [col2, col3])
                                        combine_colours(mesh.faces[i].col[3], mesh.faces[f].col)

                        index += len(mesh.faces[f].verts)

                Window.DrawProgressBar(0.0, 'Removing old edges and faces')
                mesh.edges.delete(range(nOE))
                Window.DrawProgressBar(0.0, 'Updating normals')
                mesh.calcNormals()
                Window.DrawProgressBar(0.0, '')

if in_editmode: Window.EditMode(1)
