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

face_inset_fillet.py « mesh_tools - git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: a236f26879263cf619db975f31b0f41d26e66ba9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# SPDX-License-Identifier: GPL-2.0-or-later

# based completely on addon by zmj100
# added some distance limits to prevent overlap - max12345


import bpy
import bmesh
from bpy.types import Operator
from bpy.props import (
        FloatProperty,
        IntProperty,
        BoolProperty,
        EnumProperty,
        )
from math import (
        sin, cos, tan,
        degrees, radians,
        )
from mathutils import Matrix


def edit_mode_out():
    bpy.ops.object.mode_set(mode='OBJECT')


def edit_mode_in():
    bpy.ops.object.mode_set(mode='EDIT')


def angle_rotation(rp, q, axis, angle):
    # returns the vector made by the rotation of the vector q
    # rp by angle around axis and then adds rp

    return (Matrix.Rotation(angle, 3, axis) * (q - rp)) + rp


def face_inset_fillet(bme, face_index_list, inset_amount, distance,
                      number_of_sides, out, radius, type_enum, kp):
    list_del = []

    for faceindex in face_index_list:

        bme.faces.ensure_lookup_table()
        # loops through the faces...
        f = bme.faces[faceindex]
        f.select_set(False)
        list_del.append(f)
        f.normal_update()
        vertex_index_list = [v.index for v in f.verts]
        dict_0 = {}
        orientation_vertex_list = []
        n = len(vertex_index_list)
        for i in range(n):
            # loops through the vertices
            dict_0[i] = []
            bme.verts.ensure_lookup_table()
            p = (bme.verts[vertex_index_list[i]].co).copy()
            p1 = (bme.verts[vertex_index_list[(i - 1) % n]].co).copy()
            p2 = (bme.verts[vertex_index_list[(i + 1) % n]].co).copy()
            # copies some vert coordinates, always the 3 around i
            dict_0[i].append(bme.verts[vertex_index_list[i]])
            # appends the bmesh vert of the appropriate index to the dict
            vec1 = p - p1
            vec2 = p - p2
            # vectors for the other corner points to the cornerpoint
            # corresponding to i / p
            angle = vec1.angle(vec2)

            adj = inset_amount / tan(angle * 0.5)
            h = (adj ** 2 + inset_amount ** 2) ** 0.5
            if round(degrees(angle)) == 180 or round(degrees(angle)) == 0.0:
                # if the corner is a straight line...
                # I think this creates some new points...
                if out is True:
                    val = ((f.normal).normalized() * inset_amount)
                else:
                    val = -((f.normal).normalized() * inset_amount)
                p6 = angle_rotation(p, p + val, vec1, radians(90))
            else:
                # if the corner is an actual corner
                val = ((f.normal).normalized() * h)
                if out is True:
                    # this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
                    p6 = angle_rotation(
                                p, p + val,
                                -(p - (vec2.normalized() * adj)),
                                -radians(90)
                                )
                else:
                    p6 = angle_rotation(
                                p, p - val,
                                ((p - (vec1.normalized() * adj)) - (p - (vec2.normalized() * adj))),
                                -radians(90)
                                )

                orientation_vertex_list.append(p6)

        new_inner_face = []
        orientation_vertex_list_length = len(orientation_vertex_list)
        ovll = orientation_vertex_list_length

        for j in range(ovll):
            q = orientation_vertex_list[j]
            q1 = orientation_vertex_list[(j - 1) % ovll]
            q2 = orientation_vertex_list[(j + 1) % ovll]
            # again, these are just vectors between somewhat displaced corner vertices
            vec1_ = q - q1
            vec2_ = q - q2
            ang_ = vec1_.angle(vec2_)

            # the angle between them
            if round(degrees(ang_)) == 180 or round(degrees(ang_)) == 0.0:
                # again... if it's really a line...
                v = bme.verts.new(q)
                new_inner_face.append(v)
                dict_0[j].append(v)
            else:
                # s.a.
                if radius is False:
                    h_ = distance * (1 / cos(ang_ * 0.5))
                    d = distance
                elif radius is True:
                    h_ = distance / sin(ang_ * 0.5)
                    d = distance / tan(ang_ * 0.5)
                # max(d) is vec1_.magnitude * 0.5
                # or vec2_.magnitude * 0.5 respectively

                # only functional difference v
                if d > vec1_.magnitude * 0.5:
                    d = vec1_.magnitude * 0.5

                if d > vec2_.magnitude * 0.5:
                    d = vec2_.magnitude * 0.5
                # only functional difference ^

                q3 = q - (vec1_.normalized() * d)
                q4 = q - (vec2_.normalized() * d)
                # these are new verts somewhat offset from the corners
                rp_ = q - ((q - ((q3 + q4) * 0.5)).normalized() * h_)
                # reference point inside the curvature
                axis_ = vec1_.cross(vec2_)
                # this should really be just the face normal
                vec3_ = rp_ - q3
                vec4_ = rp_ - q4
                rot_ang = vec3_.angle(vec4_)
                cornerverts = []

                for o in range(number_of_sides + 1):
                    # this calculates the actual new vertices
                    q5 = angle_rotation(rp_, q4, axis_, rot_ang * o / number_of_sides)
                    v = bme.verts.new(q5)

                    # creates new bmesh vertices from it
                    bme.verts.index_update()

                    dict_0[j].append(v)
                    cornerverts.append(v)

                cornerverts.reverse()
                new_inner_face.extend(cornerverts)

        if out is False:
            f = bme.faces.new(new_inner_face)
            f.select_set(True)
        elif out is True and kp is True:
            f = bme.faces.new(new_inner_face)
            f.select_set(True)

        n2_ = len(dict_0)
        # these are the new side faces, those that don't depend on cornertype
        for o in range(n2_):
            list_a = dict_0[o]
            list_b = dict_0[(o + 1) % n2_]
            bme.faces.new([list_a[0], list_b[0], list_b[-1], list_a[1]])
            bme.faces.index_update()
        # cornertype 1 - ngon faces
        if type_enum == 'opt0':
            for k in dict_0:
                if len(dict_0[k]) > 2:
                    bme.faces.new(dict_0[k])
                    bme.faces.index_update()
        # cornertype 2 - triangulated faces
        if type_enum == 'opt1':
            for k_ in dict_0:
                q_ = dict_0[k_][0]
                dict_0[k_].pop(0)
                n3_ = len(dict_0[k_])
                for kk in range(n3_ - 1):
                    bme.faces.new([dict_0[k_][kk], dict_0[k_][(kk + 1) % n3_], q_])
                    bme.faces.index_update()

    del_ = [bme.faces.remove(f) for f in list_del]

    if del_:
        del del_


# Operator

class MESH_OT_face_inset_fillet(Operator):
    bl_idname = "mesh.face_inset_fillet"
    bl_label = "Face Inset Fillet"
    bl_description = ("Inset selected and Fillet (make round) the corners \n"
                     "of the newly created Faces")
    bl_options = {"REGISTER", "UNDO"}

    # inset amount
    inset_amount: FloatProperty(
            name="Inset amount",
            description="Define the size of the Inset relative to the selection",
            default=0.04,
            min=0, max=100.0,
            step=1,
            precision=3
            )
    # number of sides
    number_of_sides: IntProperty(
            name="Number of sides",
            description="Define the roundness of the corners by specifying\n"
                        "the subdivision count",
            default=4,
            min=1, max=100,
            step=1
            )
    distance: FloatProperty(
            name="",
            description="Use distance or radius for corners' size calculation",
            default=0.04,
            min=0.00001, max=100.0,
            step=1,
            precision=3
            )
    out: BoolProperty(
            name="Outside",
            description="Inset the Faces outwards in relation to the selection\n"
                        "Note: depending on the geometry, can give unsatisfactory results",
            default=False
            )
    radius: BoolProperty(
            name="Radius",
            description="Use radius for corners' size calculation",
            default=False
            )
    type_enum: EnumProperty(
            items=(('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
                   ('opt1', "Triangle", "Triangulate corners")),
            name="Corner Type",
            default="opt0"
            )
    kp: BoolProperty(
            name="Keep faces",
            description="Do not delete the inside Faces\n"
                        "Only available if the Out option is checked",
            default=False
            )

    def draw(self, context):
        layout = self.layout

        layout.label(text="Corner Type:")

        row = layout.row()
        row.prop(self, "type_enum", text="")

        row = layout.row(align=True)
        row.prop(self, "out")

        if self.out is True:
            row.prop(self, "kp")

        row = layout.row()
        row.prop(self, "inset_amount")

        row = layout.row()
        row.prop(self, "number_of_sides")

        row = layout.row()
        row.prop(self, "radius")

        row = layout.row()
        dist_rad = "Radius" if self.radius else "Distance"
        row.prop(self, "distance", text=dist_rad)

    def execute(self, context):
        # this really just prepares everything for the main function
        inset_amount = self.inset_amount
        number_of_sides = self.number_of_sides
        distance = self.distance
        out = self.out
        radius = self.radius
        type_enum = self.type_enum
        kp = self.kp

        edit_mode_out()
        ob_act = context.active_object
        bme = bmesh.new()
        bme.from_mesh(ob_act.data)
        # this
        face_index_list = [f.index for f in bme.faces if f.select and f.is_valid]

        if len(face_index_list) == 0:
            self.report({'WARNING'},
                        "No suitable Face selection found. Operation cancelled")
            edit_mode_in()

            return {'CANCELLED'}

        elif len(face_index_list) != 0:
            face_inset_fillet(bme, face_index_list,
                              inset_amount, distance, number_of_sides,
                              out, radius, type_enum, kp)

        bme.to_mesh(ob_act.data)
        edit_mode_in()

        return {'FINISHED'}