1# -*- coding:utf-8 -*-
2
3# ##### BEGIN GPL LICENSE BLOCK #####
4#
5#  This program is free software; you can redistribute it and/or
6#  modify it under the terms of the GNU General Public License
7#  as published by the Free Software Foundation; either version 2
8#  of the License, or (at your option) any later version.
9#
10#  This program is distributed in the hope that it will be useful,
11#  but WITHOUT ANY WARRANTY; without even the implied warranty of
12#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#  GNU General Public License for more details.
14#
15#  You should have received a copy of the GNU General Public License
16#  along with this program; if not, write to the Free Software Foundation,
17#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
18#
19# ##### END GPL LICENSE BLOCK #####
20
21# <pep8 compliant>
22
23# ----------------------------------------------------------
24# Author: Stephen Leger (s-leger)
25#
26# ----------------------------------------------------------
27import bpy
28import bmesh
29
30
31class BmeshEdit():
32    @staticmethod
33    def _start(context, o):
34        """
35            private, start bmesh editing of active object
36        """
37        o.select_set(state=True)
38        context.view_layer.objects.active = o
39        bpy.ops.object.mode_set(mode='EDIT')
40        bm = bmesh.from_edit_mesh(o.data)
41        bm.verts.ensure_lookup_table()
42        bm.faces.ensure_lookup_table()
43        return bm
44
45    @staticmethod
46    def bmesh_join(context, o, list_of_bmeshes, normal_update=False):
47        """
48            takes as input a list of bm references and outputs a single merged bmesh
49            allows an additional 'normal_update=True' to force _normal_ calculations.
50        """
51        bm = BmeshEdit._start(context, o)
52
53        add_vert = bm.verts.new
54        add_face = bm.faces.new
55        add_edge = bm.edges.new
56
57        for bm_to_add in list_of_bmeshes:
58            offset = len(bm.verts)
59
60            for v in bm_to_add.verts:
61                add_vert(v.co)
62
63            bm.verts.index_update()
64            bm.verts.ensure_lookup_table()
65
66            if bm_to_add.faces:
67                layer = bm_to_add.loops.layers.uv.verify()
68                dest = bm.loops.layers.uv.verify()
69                for face in bm_to_add.faces:
70                    f = add_face(tuple(bm.verts[i.index + offset] for i in face.verts))
71                    f.material_index = face.material_index
72                    for j, loop in enumerate(face.loops):
73                        f.loops[j][dest].uv = loop[layer].uv
74                bm.faces.index_update()
75
76            if bm_to_add.edges:
77                for edge in bm_to_add.edges:
78                    edge_seq = tuple(bm.verts[i.index + offset] for i in edge.verts)
79                    try:
80                        add_edge(edge_seq)
81                    except ValueError:
82                        # edge exists!
83                        pass
84                bm.edges.index_update()
85
86        # cleanup
87        for old_bm in list_of_bmeshes:
88            old_bm.free()
89
90        if normal_update:
91            bm.normal_update()
92
93        BmeshEdit._end(bm, o)
94
95    @staticmethod
96    def _end(bm, o):
97        """
98            private, end bmesh editing of active object
99        """
100        bm.normal_update()
101        bmesh.update_edit_mesh(o.data, True)
102        bpy.ops.object.mode_set(mode='OBJECT')
103        bm.free()
104
105    @staticmethod
106    def _matids(bm, matids):
107        for i, matid in enumerate(matids):
108            bm.faces[i].material_index = matid
109
110    @staticmethod
111    def _uvs(bm, uvs):
112        layer = bm.loops.layers.uv.verify()
113        l_i = len(uvs)
114        for i, face in enumerate(bm.faces):
115            if i > l_i:
116                raise RuntimeError("Missing uvs for face {}".format(i))
117            l_j = len(uvs[i])
118            for j, loop in enumerate(face.loops):
119                if j > l_j:
120                    raise RuntimeError("Missing uv {} for face {}".format(j, i))
121                loop[layer].uv = uvs[i][j]
122
123    @staticmethod
124    def _verts(bm, verts):
125        for i, v in enumerate(verts):
126            bm.verts[i].co = v
127
128    @staticmethod
129    def buildmesh(context, o, verts, faces,
130            matids=None, uvs=None, weld=False,
131            clean=False, auto_smooth=True, temporary=False):
132
133        if temporary:
134            bm = bmesh.new()
135        else:
136            bm = BmeshEdit._start(context, o)
137            bm.clear()
138
139        for v in verts:
140            bm.verts.new(v)
141        bm.verts.index_update()
142        bm.verts.ensure_lookup_table()
143
144        for f in faces:
145            bm.faces.new([bm.verts[i] for i in f])
146        bm.faces.index_update()
147        bm.faces.ensure_lookup_table()
148
149        if matids is not None:
150            BmeshEdit._matids(bm, matids)
151
152        if uvs is not None:
153            BmeshEdit._uvs(bm, uvs)
154
155        if temporary:
156            return bm
157
158        if weld:
159            bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
160        BmeshEdit._end(bm, o)
161        bpy.ops.object.mode_set(mode='EDIT')
162        bpy.ops.mesh.select_all(action='SELECT')
163        if auto_smooth:
164            bpy.ops.mesh.faces_shade_smooth()
165            o.data.use_auto_smooth = True
166        else:
167            bpy.ops.mesh.faces_shade_flat()
168        if clean:
169            bpy.ops.mesh.delete_loose()
170        bpy.ops.object.mode_set(mode='OBJECT')
171
172    @staticmethod
173    def addmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=False, auto_smooth=True):
174        bm = BmeshEdit._start(context, o)
175        nv = len(bm.verts)
176        nf = len(bm.faces)
177
178        for v in verts:
179            bm.verts.new(v)
180
181        bm.verts.ensure_lookup_table()
182
183        for f in faces:
184            bm.faces.new([bm.verts[nv + i] for i in f])
185
186        bm.faces.ensure_lookup_table()
187
188        if matids is not None:
189            for i, matid in enumerate(matids):
190                bm.faces[nf + i].material_index = matid
191
192        if uvs is not None:
193            layer = bm.loops.layers.uv.verify()
194            for i, face in enumerate(bm.faces[nf:]):
195                for j, loop in enumerate(face.loops):
196                    loop[layer].uv = uvs[i][j]
197
198        if weld:
199            bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
200        BmeshEdit._end(bm, o)
201        bpy.ops.object.mode_set(mode='EDIT')
202        bpy.ops.mesh.select_all(action='SELECT')
203        if auto_smooth:
204            bpy.ops.mesh.faces_shade_smooth()
205            o.data.use_auto_smooth = True
206        else:
207            bpy.ops.mesh.faces_shade_flat()
208        if clean:
209            bpy.ops.mesh.delete_loose()
210        bpy.ops.object.mode_set(mode='OBJECT')
211
212    @staticmethod
213    def bevel(context, o,
214            offset,
215            offset_type='OFFSET',
216            segments=1,
217            profile=0.5,
218            # vertex_only=False,
219            clamp_overlap=True,
220            material=-1,
221            use_selection=True):
222        """
223        /* Bevel offset_type slot values */
224        enum {
225          BEVEL_AMT_OFFSET,
226          BEVEL_AMT_WIDTH,
227          BEVEL_AMT_DEPTH,
228          BEVEL_AMT_PERCENT
229        };
230        """
231        bm = bmesh.new()
232        bm.from_mesh(o.data)
233        bm.verts.ensure_lookup_table()
234        if use_selection:
235            geom = [v for v in bm.verts if v.select]
236            geom.extend([ed for ed in bm.edges if ed.select])
237        else:
238            geom = bm.verts[:]
239            geom.extend(bm.edges[:])
240
241        bmesh.ops.bevel(bm,
242            geom=geom,
243            offset=offset,
244            offset_type=offset_type,
245            segments=segments,
246            profile=profile,
247            # vertex_only=vertex_only,
248            clamp_overlap=clamp_overlap,
249            material=material)
250
251        bm.to_mesh(o.data)
252        bm.free()
253
254    @staticmethod
255    def bissect(context, o,
256            plane_co,
257            plane_no,
258            dist=0.001,
259            use_snap_center=False,
260            clear_outer=True,
261            clear_inner=False
262            ):
263
264        bm = bmesh.new()
265        bm.from_mesh(o.data)
266        bm.verts.ensure_lookup_table()
267        geom = bm.verts[:]
268        geom.extend(bm.edges[:])
269        geom.extend(bm.faces[:])
270
271        bmesh.ops.bisect_plane(bm,
272            geom=geom,
273            dist=dist,
274            plane_co=plane_co,
275            plane_no=plane_no,
276            use_snap_center=False,
277            clear_outer=clear_outer,
278            clear_inner=clear_inner
279            )
280
281        bm.to_mesh(o.data)
282        bm.free()
283
284    @staticmethod
285    def solidify(context, o, amt, floor_bottom=False, altitude=0):
286        bm = bmesh.new()
287        bm.from_mesh(o.data)
288        bm.verts.ensure_lookup_table()
289        geom = bm.faces[:]
290        bmesh.ops.solidify(bm, geom=geom, thickness=amt)
291        if floor_bottom:
292            for v in bm.verts:
293                if not v.select:
294                    v.co.z = altitude
295        bm.to_mesh(o.data)
296        bm.free()
297
298    @staticmethod
299    def verts(context, o, verts):
300        """
301            update vertex position of active object
302        """
303        bm = BmeshEdit._start(context, o)
304        BmeshEdit._verts(bm, verts)
305        BmeshEdit._end(bm, o)
306
307    @staticmethod
308    def aspect(context, o, matids, uvs):
309        """
310            update material id and uvmap of active object
311        """
312        bm = BmeshEdit._start(context, o)
313        BmeshEdit._matids(bm, matids)
314        BmeshEdit._uvs(bm, uvs)
315        BmeshEdit._end(bm, o)
316