1107 lines · 46 KB
Raw Download
1
# <pep8 compliant>
2
#  This program is free software; you can redistribute it and/or
3
#  modify it under the terms of the GNU General Public License
4
#  as published by the Free Software Foundation; either version 2
5
#  of the License, or (at your option) any later version.
6
#
7
#  This program is distributed in the hope that it will be useful,
8
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
9
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
#  GNU General Public License for more details.
11
#
12
#  You should have received a copy of the GNU General Public License
13
#  along with this program; if not, write to the Free Software Foundation,
14
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
16
import re
17
import os
18
from bpy.types import (Panel, Operator, PropertyGroup)
19
from bpy.props import (BoolProperty, EnumProperty, PointerProperty)
20
import bpy.utils.previews
21
22
CMPlist = [
23
    'Ceiling-Crown01', 'Ceiling-Crown02', 'Ceiling-Crown03',
24
    'Ceiling-Crown04', 'Ceiling-Crown05', 'Ceiling-Crown06', 'Ceiling-Crown07',
25
    'Ceiling-Crown08', 'Ceiling-Crown09', 'Ceiling-Crown10', 'Ceiling-Crown11',
26
    'Ceiling-Crown12', 'Ceiling-Crown13', 'Ceiling-Crown14', 'Ceiling-Crown15',
27
    'Ceiling-Crown16', 'Ceiling-Crown17', 'Ceiling-Crown18', 'Ceiling-Crown19',
28
    'Ceiling-Crown20', 'Ceiling-Crown21', 'Ceiling-Crown22', 'Ceiling-Crown23',
29
    'Ceiling-Crown24', 'Ceiling-Crown25', 'Ceiling-Crown26', 'Ceiling-Crown27',
30
    'Ceiling-Crown28', 'Ceiling-Crown29', 'Ceiling-Crown30', 'Ceiling-Crown31',
31
    'Ceiling-Crown32', 'Ceiling-Crown33', 'Ceiling-Crown34',
32
    'Corner-Cornerguard01', 'Corner-Cornerguard02', 'Corner-Plycap01',
33
    'Corner-Plycap02', 'Corner-Plycap03', 'Floor-Base01', 'Floor-Base02',
34
    'Floor-Base03', 'Floor-Base04', 'Floor-Base05', 'Floor-Base06',
35
    'Floor-Base07', 'Floor-Base08', 'Floor-Base09', 'Floor-Base10',
36
    'Floor-Base11', 'Floor-Basecap01', 'Floor-Basecap02', 'Floor-Basecap03',
37
    'Floor-Basecap04', 'Floor-Basecap05', 'Floor-Basecap06', 'Floor-Lip01',
38
    'Floor-Lip02', 'Floor-Quarterround01', 'Floor-Shoe01', 'Floor-Shoe02',
39
    'Floor-Shoe03', 'Floor-Stair01', 'Floor-Stair02', 'Floor-Wallbase01',
40
    'Panel-Mullion01', 'Panel-Mullion02', 'Panel-Mullion03', 'Panel-Panel01',
41
    'Panel-Panel02', 'Panel-Panel03', 'Panel-Panel04', 'Panel-Panel05',
42
    'Panel-Panel06', 'Panel-Trimmid01', 'Panel-Trimmid02', 'Panel-Trimmid03',
43
    'Panel-Trimmid04', 'Panel-Trimmid05', 'Picture-Frame01', 'Picture-Frame02',
44
    'Picture-Frame03', 'Picture-Frame04', 'Picture-Frame05', 'Picture-Frame06',
45
    'Picture-Frame07', 'Picture-Frame08', 'Picture-Frame09', 'Picture-Frame10',
46
    'Picture-Frame11', 'Picture-Frame12', 'Picture-Frame13', 'Picture-Frame14',
47
    'Picture-Frame15', 'Picture-Frame16', 'Picture-Frame17', 'Picture-Frame18',
48
    'Picture-Frame19', 'Picture-Frame20', 'Rail-Round01', 'Rail-Top01',
49
    'Rail-Top02', 'Rail-Top03', 'Wall-Chairrail01', 'Wall-Chairrail02',
50
    'Wall-Chairrail03', 'Wall-Chairrail04', 'Wall-Chairrail05',
51
    'Wall-Chairrail06', 'Wall-Chairrail07', 'Wall-Chairrail08',
52
    'Wall-Chairrail09', 'Wall-Chairrail10', 'Window-Trimbottom01',
53
    'Window-Trimbottom02', 'Window-Trimbottom03', 'Window-Trimbottom04',
54
    'Window-Trimbottom05', 'Window-Trimbottom06', 'Window-Trimbottom07',
55
    'Window-Trimbottom08', 'Window-Trimbottom09', 'Window-Trimbottom10',
56
    'Window-Trimbottom11', 'Window-Trimbottom12', 'Window-Trimbottom13',
57
    'Window-Trimbottom14', 'Window-Trimbottom15', 'Window-Trimbottom16',
58
    'Window-Trimbottom17', 'Window-Trimbottom18', 'Window-Trimbottom19',
59
    'Window-Trimbottom20', 'Window-Trimbottom21', 'Window-Trimbottom22',
60
    'Window-Trimbottom23', 'Window-Trimbottom24', 'Window-Trimbottom25',
61
    'Window-Trimtop01', 'Window-Trimtop02', 'Window-Trimtop03',
62
    'Window-Trimtop04', 'Window-Trimtop05', 'Window-Trimtop06',
63
    'Window-Trimtop07', 'Window-Trimtop08', 'Window-Trimtop09',
64
    'Window-Trimtop10', 'Window-Trimtop11', 'Window-Trimtop12',
65
    'Window-Trimtop13', 'Window-Trimtop14', 'Window-Trimtop15',
66
    'Window-Trimtop16', 'Window-Trimtop17', 'Window-Trimtop18',
67
    'Window-Trimtop19', 'Window-Trimtop20', 'Window-Trimtop21',
68
    'Window-Trimtop22', 'Window-Trimtop23', 'Window-Trimtop24',
69
    'Window-Trimtop25'
70
]
71
72
73
# ------------------------------------------------------------------------
74
#    Operators
75
# ------------------------------------------------------------------------
76
77
78
class WM_OT_SelectAllCMP(Operator):
79
    """Select All CMP Paths"""
80
    bl_label = "Select CMP Paths"
81
    bl_idname = "wm.selallcmp"
82
83
    def execute(self, context):
84
        bpy.ops.object.select_by_type(type='CURVE')
85
        selectedobjects = bpy.context.selected_objects
86
        if selectedobjects:
87
            for i in selectedobjects:
88
                try:
89
                    print(i.data.bevel_object.name)
90
                    # is cmp, do nothing really, don't delete the line above
91
                except AttributeError:
92
                    i.select_set(False)
93
        return {'FINISHED'}
94
95
96
class WM_OT_TiltCurvePositive(Operator):
97
    """Tilts Curve +90 Degrees"""
98
    bl_label = "Tilt +"
99
    bl_idname = "wm.tiltcurvepositive"
100
101
    def execute(self, context):
102
        selectedobjects = bpy.context.selected_objects
103
        if selectedobjects:
104
            for i in selectedobjects:
105
                if (i.type == 'CURVE'):
106
                    if i.data.dimensions == '3D':
107
                        # select object
108
                        bpy.ops.object.select_all(action='DESELECT')
109
                        bcvo = bpy.context.view_layer.objects
110
                        bcvo.active.select_set(state=False)
111
                        bpy.data.objects[i.name].select_set(True)
112
                        bpy.context.view_layer.objects.active = i
113
                        # rotate it
114
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
115
                        bpy.ops.curve.select_all(action='SELECT')
116
                        bpy.ops.transform.tilt(value=1.570796)
117
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
118
                    else:
119
                        self.report(
120
                            {'ERROR'}, "Can't tilt " + i.name
121
                            + ". Convert curve to 3D.")
122
                    # reselect objects
123
                    for m in selectedobjects:
124
                        bpy.data.objects[m.name].select_set(True)
125
        return {'FINISHED'}
126
127
128
class WM_OT_TiltCurveNegative(Operator):
129
    """Tilts Curve -90 Degrees"""
130
    bl_label = "Tilt -"
131
    bl_idname = "wm.tiltcurvenegative"
132
133
    def execute(self, context):
134
        selectedobjects = bpy.context.selected_objects
135
        if selectedobjects:
136
            for i in selectedobjects:
137
                if (i.type == 'CURVE'):
138
                    if i.data.dimensions == '3D':
139
                        # select object
140
                        bpy.ops.object.select_all(action='DESELECT')
141
                        bcvo = bpy.context.view_layer.objects
142
                        bcvo.active.select_set(state=False)
143
                        bpy.data.objects[i.name].select_set(True)
144
                        bpy.context.view_layer.objects.active = i
145
                        # rotate it
146
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
147
                        bpy.ops.curve.select_all(action='SELECT')
148
                        bpy.ops.transform.tilt(value=-1.570796)
149
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
150
                    else:
151
                        self.report(
152
                            {'ERROR'}, "Can't tilt " + i.name
153
                            + ". Convert curve to 3D.")
154
                    # reselect objects
155
                    for m in selectedobjects:
156
                        bpy.data.objects[m.name].select_set(True)
157
        return {'FINISHED'}
158
159
160
class WM_OT_TiltCurveFlip(Operator):
161
    """Tilts Curve 180 Degrees"""
162
    bl_label = "Tilt <>"
163
    bl_idname = "wm.tiltcurveflip"
164
165
    def execute(self, context):
166
        selectedobjects = bpy.context.selected_objects
167
        if selectedobjects:
168
            for i in selectedobjects:
169
                if (i.type == 'CURVE'):
170
                    if i.data.dimensions == '3D':
171
                        # select object
172
                        bpy.ops.object.select_all(action='DESELECT')
173
                        bcvo = bpy.context.view_layer.objects
174
                        bcvo.active.select_set(state=False)
175
                        bpy.data.objects[i.name].select_set(True)
176
                        bpy.context.view_layer.objects.active = i
177
                        # rotate it
178
                        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
179
                        bpy.ops.curve.select_all(action='SELECT')
180
                        bpy.ops.transform.tilt(value=3.141592)
181
                        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
182
                    else:
183
                        self.report(
184
                            {'ERROR'}, "Can't tilt " + i.name
185
                            + ". Convert curve to 3D.")
186
                    # reselect objects
187
                    for m in selectedobjects:
188
                        bpy.data.objects[m.name].select_set(True)
189
        return {'FINISHED'}
190
191
192
class WM_OT_MirrorCurveX(Operator):
193
    """Mirror Object on X Axis"""
194
    bl_label = "Mirror X"
195
    bl_idname = "wm.mirrorcurvex"
196
197
    def execute(self, context):
198
        previouspivot = bpy.context.scene.tool_settings.transform_pivot_point
199
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
200
        bpy.ops.transform.mirror(
201
            orient_type='GLOBAL', orient_matrix=(
202
                (1, 0, 0), (0, 1, 0), (0, 0, 1)
203
            ), orient_matrix_type='GLOBAL',
204
            constraint_axis=(True, False, False))
205
        bpy.context.scene.tool_settings.transform_pivot_point = previouspivot
206
        return {'FINISHED'}
207
208
209
class WM_OT_MirrorCurveY(Operator):
210
    """Mirror Object on Y Axis"""
211
    bl_label = "Mirror Y"
212
    bl_idname = "wm.mirrorcurvey"
213
214
    def execute(self, context):
215
        previouspivot = bpy.context.scene.tool_settings.transform_pivot_point
216
        bcst = bpy.context.scene.tool_settings
217
        bcst.transform_pivot_point = 'INDIVIDUAL_ORIGINS'
218
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
219
        bpy.ops.transform.mirror(
220
            orient_type='GLOBAL', orient_matrix=(
221
                (1, 0, 0), (0, 1, 0), (0, 0, 1)
222
            ), orient_matrix_type='GLOBAL',
223
            constraint_axis=(False, True, False))
224
        bpy.context.scene.tool_settings.transform_pivot_point = previouspivot
225
226
        return {'FINISHED'}
227
228
229
class WM_OT_MirrorCurveZ(Operator):
230
    """Mirror Object on Z Axis"""
231
    bl_label = "Mirror Z"
232
    bl_idname = "wm.mirrorcurvez"
233
234
    def execute(self, context):
235
        previouspivot = bpy.context.scene.tool_settings.transform_pivot_point
236
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
237
        bpy.ops.transform.mirror(
238
            orient_type='GLOBAL', orient_matrix=(
239
                (1, 0, 0), (0, 1, 0), (0, 0, 1)
240
            ), orient_matrix_type='GLOBAL',
241
            constraint_axis=(False, False, True))
242
        bpy.context.scene.tool_settings.transform_pivot_point = previouspivot
243
        return {'FINISHED'}
244
245
246
class WM_OT_ScaleCurvePositive(Operator):
247
    """Increase the Scale of the Curve Bevel"""
248
    bl_label = "Scale +"
249
    bl_idname = "wm.scalecurvepositive"
250
251
    def execute(self, context):
252
        selectedobjects = bpy.context.selected_objects
253
        if selectedobjects:
254
            for i in selectedobjects:
255
                if (i.type == 'CURVE'):
256
                    # select object
257
                    bpy.ops.object.select_all(action='DESELECT')
258
                    bcvo = bpy.context.view_layer.objects
259
                    bcvo.active.select_set(state=False)
260
                    bpy.data.objects[i.name].select_set(True)
261
                    bpy.context.view_layer.objects.active = i
262
                    # scale it
263
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
264
                    bpy.ops.curve.select_all(action='SELECT')
265
                    bpy.ops.transform.transform(
266
                        mode='CURVE_SHRINKFATTEN', value=(1.1, 0, 0, 0))
267
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
268
                else:
269
                    self.report(
270
                        {'ERROR'}, "Can't scale " + i.name
271
                        + ". Not a curve.")
272
                # reselect objects
273
                for m in selectedobjects:
274
                    bpy.data.objects[m.name].select_set(True)
275
        return {'FINISHED'}
276
277
278
class WM_OT_ScaleCurveNegative(Operator):
279
    """Decrease the Scale of the Curve Bevel"""
280
    bl_label = "Scale -"
281
    bl_idname = "wm.scalecurvenegative"
282
283
    def execute(self, context):
284
        selectedobjects = bpy.context.selected_objects
285
        if selectedobjects:
286
            for i in selectedobjects:
287
                if (i.type == 'CURVE'):
288
                    # select object
289
                    bpy.ops.object.select_all(action='DESELECT')
290
                    bcvo = bpy.context.view_layer.objects
291
                    bcvo.active.select_set(state=False)
292
                    bpy.data.objects[i.name].select_set(True)
293
                    bpy.context.view_layer.objects.active = i
294
                    # scale it
295
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
296
                    bpy.ops.curve.select_all(action='SELECT')
297
                    bpy.ops.transform.transform(
298
                        mode='CURVE_SHRINKFATTEN', value=(0.9, 0, 0, 0))
299
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
300
                else:
301
                    self.report(
302
                        {'ERROR'}, "Can't scale " + i.name
303
                        + ". Not a curve.")
304
                # reselect objects
305
                for m in selectedobjects:
306
                    bpy.data.objects[m.name].select_set(True)
307
        return {'FINISHED'}
308
309
310
class WM_OT_Curve3DToggle2D(Operator):
311
    """Toggle between 2D and 3D Curve Dimensions"""
312
    bl_label = "2D/3D"
313
    bl_idname = "wm.curve3dtoggle2d"
314
315
    def execute(self, context):
316
        selectedobjects = bpy.context.selected_objects
317
        if selectedobjects:
318
            for i in selectedobjects:
319
                if (i.type == 'CURVE'):
320
                    if i.data.dimensions == '2D':
321
                        i.data.dimensions = '3D'
322
                    elif i.data.dimensions == '3D':
323
                        i.data.dimensions = '2D'
324
                else:
325
                    self.report(
326
                        {'ERROR'}, "Can't convert " + i.name
327
                        + ". Not a curve.")
328
        return {'FINISHED'}
329
330
331
class WM_OT_AllCurves3D(Operator):
332
    """Set all Curves to 3D Curve Dimension"""
333
    bl_label = "Curve 3D"
334
    bl_idname = "wm.curve3d"
335
336
    def execute(self, context):
337
        selectedobjects = bpy.context.selected_objects
338
        if selectedobjects:
339
            for i in selectedobjects:
340
                if (i.type == 'CURVE'):
341
                    i.data.dimensions = '3D'
342
                else:
343
                    self.report(
344
                        {'ERROR'}, "Can't convert " + i.name
345
                        + ". Not a curve.")
346
        return {'FINISHED'}
347
348
349
class WM_OT_AllCurves2D(Operator):
350
    """Set all Curves to 2D Curve Dimension"""
351
    bl_label = "Curve 2D"
352
    bl_idname = "wm.curve2d"
353
354
    def execute(self, context):
355
        selectedobjects = bpy.context.selected_objects
356
        if selectedobjects:
357
            for i in selectedobjects:
358
                if (i.type == 'CURVE'):
359
                    i.data.dimensions = '2D'
360
                else:
361
                    self.report(
362
                        {'ERROR'}, "Can't convert " + i.name
363
                        + ". Not a curve.")
364
        return {'FINISHED'}
365
366
367
class WM_OT_SwitchDirection(Operator):
368
    """Toggle Direction for Curves between 2D and 3D"""
369
    bl_label = "<> Direction"
370
    bl_idname = "wm.switchdirection"
371
372
    def execute(self, context):
373
        selectedobjects = bpy.context.selected_objects
374
        if selectedobjects:
375
            for i in selectedobjects:
376
                if (i.type == 'CURVE'):
377
                    # select object
378
                    bpy.ops.object.select_all(action='DESELECT')
379
                    bpy.context.view_layer.objects.active.select_set(
380
                        state=False)
381
                    bpy.data.objects[i.name].select_set(True)
382
                    bpy.context.view_layer.objects.active = i
383
                    # switch curve direction
384
                    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
385
                    bpy.ops.curve.switch_direction()
386
                    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
387
                else:
388
                    self.report(
389
                        {'ERROR'}, "Can't edit " + i.name
390
                        + ". Not a curve.")
391
                # reselect objects
392
                for m in selectedobjects:
393
                    bpy.data.objects[m.name].select_set(True)
394
        return {'FINISHED'}
395
396
397
class WM_OT_ConvertToCMP(Operator):
398
    """Converts a selected face, edge loop,""" \
399
    """ or path into a crown molding path"""
400
    bl_label = "Convert to CMP Path"
401
    bl_idname = "wm.converttocmp"
402
    bl_options = {'REGISTER', 'UNDO'}
403
404
    def execute(self, context):
405
        scene = context.scene
406
        cmptool = scene.cmptool
407
        # gather current state
408
        previouspivot = bpy.context.scene.tool_settings.transform_pivot_point
409
        if bpy.context.active_object.mode == 'EDIT':
410
            bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
411
            bpy.ops.object.transform_apply(
412
                location=False, rotation=False, scale=True)
413
            bpy.ops.object.mode_set(mode='EDIT', toggle=False)
414
            bpy.ops.mesh.duplicate_move(
415
                MESH_OT_duplicate={"mode": 1},
416
                TRANSFORM_OT_translate={"value": (0, 0, 0)})
417
            bpy.ops.mesh.separate(type='SELECTED')
418
            bcvo = bpy.context.view_layer.objects.active
419
            bcvo.name = bpy.context.view_layer.objects.active.name + \
420
                "---ORIGINAL---"
421
            bpy.context.view_layer.objects.active.select_set(state=False)
422
            newActiveObjects = []
423
            for j in bpy.context.selected_objects:
424
                newActiveObjects.append(j.name)
425
            for k in newActiveObjects:
426
                nobj = bpy.data.objects.get(k)
427
                if nobj:
428
                    bpy.context.view_layer.objects.active = nobj
429
            bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
430
            bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
431
            bpy.ops.object.mode_set(mode='EDIT', toggle=False)
432
            bpy.ops.mesh.select_all(action='SELECT')
433
            bpy.ops.mesh.separate(type='LOOSE')
434
            bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
435
            bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
436
            bpy.ops.object.convert(target='CURVE')
437
            bpy.ops.object.mode_set(mode='EDIT', toggle=False)
438
            bpy.ops.curve.select_all(action='SELECT')
439
            bpy.ops.curve.spline_type_set(type='BEZIER')
440
            bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
441
            bpy.ops.object.transform_apply(
442
                location=False, rotation=True, scale=True)
443
            for b in bpy.context.visible_objects:
444
                if '---ORIGINAL---' in b.name:
445
                    b.name = re.sub('---ORIGINAL---', '', b.name)
446
            selectedobjects = bpy.context.selected_objects
447
            bpy.ops.object.select_all(action='DESELECT')
448
            bpy.context.view_layer.objects.active.select_set(state=False)
449
            if selectedobjects:
450
                # loop through new objects
451
                for i in selectedobjects:
452
                    bpy.ops.object.select_all(action='DESELECT')
453
                    bcvo = bpy.context.view_layer.objects
454
                    bcvo.active.select_set(state=False)
455
                    bpy.data.objects[i.name].select_set(True)
456
                    # This was the last line of code for this v0.0.1 function
457
                    # a celebration commenced after figuring this out
458
                    bpy.context.view_layer.objects.active = i
459
                    bpy.ops.object.origin_set(
460
                        type='ORIGIN_GEOMETRY', center='MEDIAN')
461
                    # rotate to fix 2D path issues
462
                    if cmptool.advanced_rotate_curve:
463
                        curve = i.data
464
                        spline = curve.splines[0]
465
                        xGlobalPathPoints = []
466
                        yGlobalPathPoints = []
467
                        zGlobalPathPoints = []
468
                        for p in range(0, len(spline.bezier_points)):
469
                            if float(
470
                                "{:.3f}".format(float(i.location.x))
471
                            ) + 0 != 0:
472
                                xGlobalPathPoint = i.location.x * \
473
                                    spline.bezier_points[p].co.x
474
                            else:
475
                                xGlobalPathPoint = spline.bezier_points[p].co.x
476
                            xGlobalPathPoint = "{:.3f}".format(
477
                                float(xGlobalPathPoint))
478
                            xGlobalPathPoints.append(
479
                                float(xGlobalPathPoint) + 0)
480
                            if float(
481
                                "{:.3f}".format(float(i.location.y))
482
                            ) + 0 != 0:
483
                                yGlobalPathPoint = i.location.y * \
484
                                    spline.bezier_points[p].co.y
485
                            else:
486
                                yGlobalPathPoint = spline.bezier_points[p].co.y
487
                            yGlobalPathPoint = "{:.3f}".format(
488
                                float(yGlobalPathPoint))
489
                            yGlobalPathPoints.append(
490
                                float(yGlobalPathPoint) + 0)
491
                            if float(
492
                                "{:.3f}".format(float(i.location.z))
493
                            ) + 0 != 0:
494
                                zGlobalPathPoint = i.location.z * \
495
                                    spline.bezier_points[p].co.z
496
                            else:
497
                                zGlobalPathPoint = spline.bezier_points[p].co.z
498
                            zGlobalPathPoint = "{:.3f}".format(
499
                                float(zGlobalPathPoint))
500
                            zGlobalPathPoints.append(
501
                                float(zGlobalPathPoint) + 0)
502
                        if len(set(xGlobalPathPoints)) == 1:
503
                            # faces X
504
                            bcst = bpy.context.scene.tool_settings
505
                            bcst.transform_pivot_point = 'MEDIAN_POINT'
506
                            bpy.ops.object.mode_set(mode='EDIT', toggle=False)
507
                            bpy.ops.transform.rotate(
508
                                value=1.570796, orient_axis='Y',
509
                                orient_type='GLOBAL', orient_matrix=(
510
                                    (1, 0, 0), (0, 1, 0), (0, 0, 1)
511
                                ), orient_matrix_type='GLOBAL',
512
                                constraint_axis=(
513
                                    False, True, False), mirror=True)
514
                            bpy.ops.object.mode_set(
515
                                mode='OBJECT', toggle=False)
516
                            bpy.ops.transform.rotate(
517
                                value=-1.570796, orient_axis='Y',
518
                                orient_type='GLOBAL', orient_matrix=(
519
                                    (1, 0, 0), (0, 1, 0), (0, 0, 1)
520
                                ), orient_matrix_type='GLOBAL',
521
                                constraint_axis=(
522
                                    False, True, False), mirror=True)
523
                        if len(set(yGlobalPathPoints)) == 1:
524
                            # faces Y
525
                            bcst = bpy.context.scene.tool_settings
526
                            bcst.transform_pivot_point = 'MEDIAN_POINT'
527
                            bpy.ops.object.mode_set(mode='EDIT', toggle=False)
528
                            bpy.ops.transform.rotate(
529
                                value=1.570796, orient_axis='X',
530
                                orient_type='GLOBAL', orient_matrix=(
531
                                    (1, 0, 0), (0, 1, 0), (0, 0, 1)
532
                                ), orient_matrix_type='GLOBAL',
533
                                constraint_axis=(
534
                                    True, False, False), mirror=True)
535
                            bpy.ops.object.mode_set(
536
                                mode='OBJECT', toggle=False)
537
                            bpy.ops.transform.rotate(
538
                                value=-1.570796, orient_axis='X',
539
                                orient_type='GLOBAL', orient_matrix=(
540
                                    (1, 0, 0), (0, 1, 0), (0, 0, 1)
541
                                ), orient_matrix_type='GLOBAL',
542
                                constraint_axis=(
543
                                    True, False, False), mirror=True)
544
                        if len(set(zGlobalPathPoints)) == 1:
545
                            print("faces Z")
546
                            # Leave print or else you get error
547
                    # set bevel object
548
                    # new in 2.91, if current blender version is above
549
                    # 2.91.0, set bevel mode to object (previously default)
550
                    if (2, 91, 0) <= bpy.app.version:
551
                        i.data.bevel_mode = 'OBJECT'
552
                    # set bevel profile to crown molding
553
                    selection = bpy.data.window_managers["WinMan"]. \
554
                        cmpy_previews.replace(".png", "")
555
                    if bpy.data.objects.get(selection) is not None:
556
                        i.data.bevel_object = \
557
                            bpy.data.objects[selection]
558
                    else:
559
                        self.report(
560
                            {'ERROR'}, "Unable to auto add bevel object. "
561
                            + "Append the CrownMoldingPack .blend file to "
562
                            + "fix this in the future. Or, manually set "
563
                            + "in Path > Object Data > Geometry > Bevel "
564
                            + "> Object"
565
                        )
566
                    # make path look nice
567
                    i.data.use_fill_caps = True
568
                    bpy.ops.object.shade_smooth()
569
                    bpy.ops.object.modifier_add(type='EDGE_SPLIT')
570
                    if cmptool.advanced_rotate_curve:
571
                        i.data.dimensions = '2D'
572
                    else:
573
                        i.data.dimensions = '3D'
574
                    # rename object
575
                    needle = re.compile(r"\.\d{2,}")
576
                    if needle.search(bpy.context.object.name):
577
                        i.name = re.sub(r"(\.\d{2,})", r".CMP\1", i.name)
578
                    else:
579
                        i.name = i.name + ".CMP"
580
                    bpy.ops.object.select_all(action='DESELECT')
581
                # reselect new objects
582
                bcst = bpy.context.scene.tool_settings
583
                bcst.transform_pivot_point = previouspivot
584
                for m in selectedobjects:
585
                    bpy.data.objects[m.name].select_set(True)
586
        return {'FINISHED'}
587
588
589
class WM_OT_CycleBack(Operator):
590
    """Change the Curve Profile to the Previous Option"""
591
    bl_label = "< Previous"
592
    bl_idname = "wm.cycleback"
593
594
    def execute(self, context):
595
        selectedobjects = bpy.context.selected_objects
596
        if selectedobjects:
597
            for i in selectedobjects:
598
                if (i.type == 'CURVE'):
599
                    if i.data.bevel_object.name in CMPlist:
600
                        # figure out the index of our current
601
                        currentindex = CMPlist.index(i.data.bevel_object.name)
602
                        previndex = currentindex - 1
603
                        if previndex < 0:
604
                            previndex = len(CMPlist) - 1
605
                        prevname = CMPlist[previndex]
606
                        # set bevel object to previous in list
607
                        i.data.bevel_object = bpy.data.objects[prevname]
608
                    else:
609
                        self.report(
610
                            {'ERROR'}, "Current Bevel Object not in " +
611
                            "defined list. Can't choose previous option." +
612
                            " Edit CMPlist in add-on python file.")
613
                else:
614
                    self.report(
615
                        {'ERROR'}, "Can't edit " + i.name +
616
                        ". Not a curve.")
617
                # reselect objects
618
                for m in selectedobjects:
619
                    bpy.data.objects[m.name].select_set(True)
620
        return {'FINISHED'}
621
622
623
class WM_OT_CycleForward(Operator):
624
    """Change the Curve Profile to the Next Option"""
625
    bl_label = "Next >"
626
    bl_idname = "wm.cycleforward"
627
628
    def execute(self, context):
629
        selectedobjects = bpy.context.selected_objects
630
        if selectedobjects:
631
            for i in selectedobjects:
632
                if (i.type == 'CURVE'):
633
                    if i.data.bevel_object.name in CMPlist:
634
                        # figure out the index of our current
635
                        currentindex = CMPlist.index(i.data.bevel_object.name)
636
                        nextindex = currentindex + 1
637
                        if nextindex >= len(CMPlist):
638
                            nextindex = 0
639
                        nextname = CMPlist[nextindex]
640
                        # set bevel object to next in list
641
                        i.data.bevel_object = bpy.data.objects[nextname]
642
                    else:
643
                        self.report(
644
                            {'ERROR'}, "Current Bevel Object not in "
645
                            + "defined list. Can't choose next option. "
646
                            + "Edit CMPlist in add-on python file.")
647
                else:
648
                    self.report(
649
                        {'ERROR'}, "Can't edit " + i.name
650
                        + ". Not a curve.")
651
                # reselect objects
652
                for m in selectedobjects:
653
                    bpy.data.objects[m.name].select_set(True)
654
        return {'FINISHED'}
655
656
657
class WM_OT_ChangeSelected(Operator):
658
    """Change the curve profile to the picked option"""
659
    bl_label = "Change Path"
660
    bl_idname = "wm.changeselected"
661
662
    def execute(self, context):
663
        selectedobjects = bpy.context.selected_objects
664
        if selectedobjects:
665
            for i in selectedobjects:
666
                if (i.type == 'CURVE'):
667
                    selection = bpy.data.window_managers["WinMan"]. \
668
                        cmpy_previews.replace(".png", "")
669
                    if bpy.data.objects.get(selection) is not None:
670
                        i.data.bevel_object = \
671
                            bpy.data.objects[selection]
672
                    else:
673
                        self.report(
674
                            {'ERROR'}, "Unable to auto add bevel object. "
675
                            + "You should let Jeff know if you see this."
676
                        )
677
                else:
678
                    self.report(
679
                        {'ERROR'}, "Can't edit " + i.name
680
                        + ". Not a curve.")
681
                # reselect objects
682
                for m in selectedobjects:
683
                    bpy.data.objects[m.name].select_set(True)
684
        return {'FINISHED'}
685
686
687
class WM_OT_SetOptions(Operator):
688
    # you can use this function to make a list of paths for the CMPlist
689
    """Append .blend"""
690
    bl_label = "Set Options"
691
    bl_idname = "wm.cycleset"
692
693
    def execute(self, context):
694
        selectedobjects = bpy.context.selected_objects
695
        cycleset = []
696
        if selectedobjects:
697
            for i in selectedobjects:
698
                if (i.type == 'CURVE'):
699
                    # build a list
700
                    cycleset.append(i.name)
701
                else:
702
                    self.report(
703
                        {'ERROR'}, "Cycleset Can't edit " + i.name
704
                        + ". Not a curve.")
705
                # reselect objects
706
                for m in selectedobjects:
707
                    bpy.data.objects[m.name].select_set(True)
708
            print(sorted(cycleset))
709
            # leave above print since that's the purpose of this class
710
        return {'FINISHED'}
711
712
713
class WM_OT_AppendCMP(Operator):
714
    # append .blend file, set collection to hidden / excluded so it doesn't
715
    # show up or render
716
    # https://blender.stackexchange.com/questions/264622/
717
    # https://blender.stackexchange.com/questions/34540/
718
    """Set Options to Pick From by Selecting All Desired Choices"""
719
    bl_label = "Append .blend File"
720
    bl_idname = "wm.appendcmp"
721
722
    def execute(self, context):
723
        cmpfilepath = os.path.join(os.path.dirname(__file__),
724
                                   "CrownMoldingPack-v3.1.blend")
725
        link = False
726
        # link all collections starting with 'MyCollection'
727
        with bpy.data.libraries. \
728
        load(cmpfilepath, link=link) as (data_from, data_to):
729
            data_to.collections = [c for c in data_from.collections]
730
731
        # link collection to scene collection
732
        for coll in data_to.collections:
733
            if coll is not None:
734
                bpy.context.scene.collection.children.link(coll)
735
736
        included_collection_list_names = [
737
            "CrownMoldingPack-v3.0", "CrownMoldingPack-v3.1",
738
            "CrownMoldingPack-v3.2"]
739
        layer_collections = list(
740
            bpy.context.view_layer.layer_collection.children)
741
742
        while layer_collections:
743
            layer_collection = layer_collections.pop(0)
744
            layer_collections.extend(layer_collection.children)
745
            layer_collection.exclude = layer_collection.name \
746
                in included_collection_list_names
747
        return {'FINISHED'}
748
749
750
# custom icons for dropdown
751
preview_collections = {}
752
CMPicons = bpy.utils.previews.new()
753
# the path is calculated relative to this py file inside the addon folder
754
# '..' goes up a level, seems to be needed when in dev?
755
cmpy_icons_dir = os.path.join(os.path.dirname(__file__), "cmp-icons")
756
ceil_dir = os.path.join(os.path.dirname(__file__),
757
                        "cmp-path-thumbnails/ceil")
758
flor_dir = os.path.join(os.path.dirname(__file__),
759
                        "cmp-path-thumbnails/flor")
760
corn_dir = os.path.join(os.path.dirname(__file__),
761
                        "cmp-path-thumbnails/corn")
762
panl_dir = os.path.join(os.path.dirname(__file__),
763
                        "cmp-path-thumbnails/panl")
764
picf_dir = os.path.join(os.path.dirname(__file__),
765
                        "cmp-path-thumbnails/picf")
766
rail_dir = os.path.join(os.path.dirname(__file__),
767
                        "cmp-path-thumbnails/rail")
768
wall_dir = os.path.join(os.path.dirname(__file__),
769
                        "cmp-path-thumbnails/wall")
770
wind_dir = os.path.join(os.path.dirname(__file__),
771
                        "cmp-path-thumbnails/wind")
772
773
# load a preview thumbnail of a file and store in the previews collection
774
CMPicons.load("icon_ceil",
775
              os.path.join(cmpy_icons_dir, "ceiling.png"), 'IMAGE')
776
CMPicons.load("icon_wind",
777
              os.path.join(cmpy_icons_dir, "window.png"), 'IMAGE')
778
CMPicons.load("icon_panl",
779
              os.path.join(cmpy_icons_dir, "panel.png"), 'IMAGE')
780
CMPicons.load("icon_wall",
781
              os.path.join(cmpy_icons_dir, "wall.png"), 'IMAGE')
782
CMPicons.load("icon_flor",
783
              os.path.join(cmpy_icons_dir, "floor.png"), 'IMAGE')
784
CMPicons.load("icon_rail",
785
              os.path.join(cmpy_icons_dir, "rail.png"), 'IMAGE')
786
CMPicons.load("icon_corn",
787
              os.path.join(cmpy_icons_dir, "corner.png"), 'IMAGE')
788
CMPicons.load("icon_picf",
789
              os.path.join(cmpy_icons_dir, "pictureframe.png"), 'IMAGE')
790
preview_collections["main"] = CMPicons
791
# create aliases for icon names
792
customicons = preview_collections["main"]
793
ic1 = customicons["icon_ceil"]
794
ic2 = customicons["icon_wind"]
795
ic3 = customicons["icon_panl"]
796
ic4 = customicons["icon_wall"]
797
ic5 = customicons["icon_flor"]
798
ic6 = customicons["icon_rail"]
799
ic7 = customicons["icon_corn"]
800
ic8 = customicons["icon_picf"]
801
# end custom icons for dropdown
802
803
804
class CrownMoldingProperties(PropertyGroup):
805
806
    advanced_rotate_curve: BoolProperty(
807
        name="Auto-rotate Curve",
808
        description="Disable to prevent add-on from rotating"
809
        + " the curve automatically. Helpful for arbitrarily"
810
        + " rotated faces. Manual Rotation with CTRL + T required"
811
        + " if disabled",
812
        default=True
813
    )
814
815
    curvecategory: EnumProperty(
816
        name="Curve Category:",
817
        description="Type of profiles to display",
818
        items=[('ceiling', "Ceiling",
819
                "Crown Molding and others aligned to top",
820
                ic1.icon_id, 1),
821
               ('window', "Window",
822
                "Window Trim in both top and bottom alignment",
823
                ic2.icon_id, 2),
824
               ('panel', "Panel",
825
                "panel edges for accents aligned to middle",
826
                ic3.icon_id, 3),
827
               ('wall', "Wall",
828
                "Chair rail for walls aligned to middle",
829
                ic4.icon_id, 4),
830
               ('floor', "Floor",
831
                "Base trim, stair lips mostly aligned to bottom",
832
                ic5.icon_id, 5),
833
               ('rail', "Rail",
834
                "Hand rails aligned to middle",
835
                ic6.icon_id, 6),
836
               ('corner', "Corner",
837
                "Corner gaurds and caps aligned to inset edge",
838
                ic7.icon_id, 7),
839
               ('pictureframe', "Picture Frame",
840
                "Frames for photos or art aligned to wall",
841
                ic8.icon_id, 8)
842
              ],
843
        default='ceiling'
844
    )
845
846
847
# ------------------------------------------------------------------------
848
#    Panel in Object Mode
849
# ------------------------------------------------------------------------
850
851
852
def enum_previews_from_directory_items(self, context):
853
    """EnumProperty callback"""
854
    enum_items = []
855
856
    if context is None:
857
        return enum_items
858
859
    scene = context.scene
860
    cmptool = scene.cmptool
861
    if cmptool.curvecategory == 'ceiling':
862
        directory = ceil_dir
863
    elif cmptool.curvecategory == 'window':
864
        directory = wind_dir
865
    elif cmptool.curvecategory == 'panel':
866
        directory = panl_dir
867
    elif cmptool.curvecategory == 'wall':
868
        directory = wall_dir
869
    elif cmptool.curvecategory == 'floor':
870
        directory = flor_dir
871
    elif cmptool.curvecategory == 'corner':
872
        directory = corn_dir
873
    elif cmptool.curvecategory == 'pictureframe':
874
        directory = picf_dir
875
    elif cmptool.curvecategory == 'rail':
876
        directory = rail_dir
877
878
    # Get the preview collection (defined in register func).
879
    pcoll = preview_collections["main"]
880
881
    if directory == pcoll.cmpy_previews_dir:
882
        return pcoll.cmpy_previews
883
884
    if directory and os.path.exists(directory):
885
        # Scan the directory for png files
886
        image_paths = []
887
        for fn in os.listdir(directory):
888
            if fn.lower().endswith(".png"):
889
                image_paths.append(fn)
890
891
        for i, name in enumerate(image_paths):
892
            # generates a thumbnail preview for a file.
893
            filepath = os.path.join(directory, name)
894
            icon = pcoll.get(name)
895
            if not icon:
896
                thumb = pcoll.load(name, filepath, 'IMAGE')
897
            else:
898
                thumb = pcoll[name]
899
            enum_items.append((name, name,
900
                               "Path Selection", thumb.icon_id, i))
901
902
    sorted_data = sorted(enum_items, key=lambda x: x[0])
903
    pcoll.cmpy_previews = sorted_data
904
    pcoll.cmpy_previews_dir = directory
905
    return pcoll.cmpy_previews
906
907
908
class OBJECT_PT_CrownMoldingTools(Panel):
909
    bl_label = "Crown Molding Tools v3.1"
910
    bl_idname = "OBJECT_PT_crown_molding_tools"
911
    bl_space_type = "VIEW_3D"
912
    bl_region_type = "UI"
913
    bl_category = "Tool"  # label in tab if different than defaults
914
915
    @classmethod
916
    def poll(self, context):
917
        return context.object is not None
918
919
    def draw(self, context):
920
        layout = self.layout
921
        scene = context.scene
922
        cmptool = scene.cmptool
923
        wm = context.window_manager
924
925
        if bpy.data.objects.get('Ceiling-Crown01') is not None:
926
            # ====== BEGIN IMAGE SECTION ======
927
            row = layout.row()
928
            row.prop(cmptool, "curvecategory", text="Category")
929
930
            row = layout.row()
931
            row.template_icon_view(wm, "cmpy_previews")
932
933
            row = layout.row()
934
            row.prop(wm, "cmpy_previews", text="Path Selection")
935
            # ======  END IMAGE SECTION ======
936
            row = layout.row()
937
            row.operator("wm.converttocmp")
938
939
            row = layout.row()
940
            row.operator("wm.changeselected")
941
            row.operator("wm.selallcmp")
942
943
            row = layout.row()
944
            row.operator("wm.cycleback")
945
            # developer / custom use only
946
            # row.operator("wm.cycleset")
947
            row.operator("wm.cycleforward")
948
949
            row = layout.row()
950
            row.operator("wm.tiltcurvenegative")
951
            row.operator("wm.tiltcurveflip")
952
            row.operator("wm.tiltcurvepositive")
953
954
            row = layout.row()
955
            row.operator("wm.mirrorcurvex")
956
            row.operator("wm.mirrorcurvey")
957
            row.operator("wm.mirrorcurvez")
958
959
            row = layout.row()
960
            row.operator("wm.scalecurvenegative")
961
            row.operator("wm.switchdirection")
962
            row.operator("wm.scalecurvepositive")
963
964
            row = layout.row()
965
            row.operator("wm.curve2d")
966
            row.operator("wm.curve3dtoggle2d")
967
            row.operator("wm.curve3d")
968
969
            row = layout.row()
970
            row.prop(cmptool, "advanced_rotate_curve")
971
        else:
972
            row = layout.row()
973
            row.label(icon="ERROR", text="Paths Not Appended")
974
            row = layout.row()
975
            row.operator("wm.appendcmp")
976
977
        box = layout.box()
978
        box.label(text="Support")
979
        column = box.column()
980
981
        row = column.row()
982
        row.scale_y = 1.2
983
        row.operator("wm.url_open", text="Documentation",
984
                     icon='WORLD').url = "https://jefftml.com/cmp3doc"
985
986
987
# ------------------------------------------------------------------------
988
#    Registration
989
# ------------------------------------------------------------------------
990
991
classes = (
992
    CrownMoldingProperties,
993
    OBJECT_PT_CrownMoldingTools,
994
    WM_OT_AllCurves2D,
995
    WM_OT_AllCurves3D,
996
    WM_OT_AppendCMP,
997
    WM_OT_ChangeSelected,
998
    WM_OT_ConvertToCMP,
999
    WM_OT_Curve3DToggle2D,
1000
    WM_OT_CycleBack,
1001
    WM_OT_CycleForward,
1002
    WM_OT_MirrorCurveX,
1003
    WM_OT_MirrorCurveY,
1004
    WM_OT_MirrorCurveZ,
1005
    WM_OT_ScaleCurveNegative,
1006
    WM_OT_ScaleCurvePositive,
1007
    WM_OT_SelectAllCMP,
1008
    WM_OT_SetOptions,
1009
    WM_OT_SwitchDirection,
1010
    WM_OT_TiltCurveFlip,
1011
    WM_OT_TiltCurveNegative,
1012
    WM_OT_TiltCurvePositive
1013
)
1014
1015
1016
def register():
1017
    from bpy.utils import register_class
1018
    from bpy.types import WindowManager
1019
    from bpy.props import (StringProperty, EnumProperty)
1020
1021
    # ====== BEGIN IMAGE SECTION ======
1022
    WindowManager.cmpy_previews_dir = StringProperty(
1023
        name="Folder Path",
1024
        subtype='DIR_PATH',
1025
        default=""
1026
    )
1027
    WindowManager.ceil_dir = StringProperty(
1028
        name="Ceiling Folder Path",
1029
        subtype='DIR_PATH',
1030
        default=""
1031
    )
1032
    WindowManager.flor_dir = StringProperty(
1033
        name="Floor Folder Path",
1034
        subtype='DIR_PATH',
1035
        default=""
1036
    )
1037
    WindowManager.corn_dir = StringProperty(
1038
        name="Corner Folder Path",
1039
        subtype='DIR_PATH',
1040
        default=""
1041
    )
1042
    WindowManager.panl_dir = StringProperty(
1043
        name="Panel Folder Path",
1044
        subtype='DIR_PATH',
1045
        default=""
1046
    )
1047
    WindowManager.picf_dir = StringProperty(
1048
        name="Picture Frame Folder Path",
1049
        subtype='DIR_PATH',
1050
        default=""
1051
    )
1052
    WindowManager.rail_dir = StringProperty(
1053
        name="Rail  Folder Path",
1054
        subtype='DIR_PATH',
1055
        default=""
1056
    )
1057
    WindowManager.wall_dir = StringProperty(
1058
        name="Wall Folder Path",
1059
        subtype='DIR_PATH',
1060
        default=""
1061
    )
1062
    WindowManager.wind_dir = StringProperty(
1063
        name="Window Folder Path",
1064
        subtype='DIR_PATH',
1065
        default=""
1066
    )
1067
1068
    WindowManager.cmpy_previews = EnumProperty(
1069
        items=enum_previews_from_directory_items
1070
    )
1071
    import bpy.utils.previews
1072
    pcoll = bpy.utils.previews.new()
1073
    pcoll.cmpy_previews_dir = ""
1074
    pcoll.cmpy_previews = ()
1075
1076
    preview_collections["main"] = pcoll
1077
    # ======  END  IMAGE SECTION ======
1078
1079
    # registers list from above
1080
    for cls in classes:
1081
        register_class(cls)
1082
1083
    bpy.types.Scene.cmptool = PointerProperty(type=CrownMoldingProperties)
1084
1085
1086
def unregister():
1087
    from bpy.utils import unregister_class
1088
    from bpy.types import WindowManager
1089
1090
    # ====== BEGIN IMAGE SECTION ======
1091
    del WindowManager.cmpy_previews
1092
    for pcoll in preview_collections.values():
1093
        bpy.utils.previews.remove(pcoll)
1094
    preview_collections.clear()
1095
    # ====== END IMAGE SECTION ======
1096
1097
    # unregisters list from above
1098
    for cls in reversed(classes):
1099
        unregister_class(cls)
1100
    del bpy.types.Scene.cmptool
1101
1102
1103
if __name__ == "__main__":
1104
    # The path of this text (if saved)
1105
    __file__ = bpy.context.space_data.text.filepath
1106
    register()
1107