Merge branch 'master' into blender2.8
[blender.git] / release / scripts / startup / bl_operators / freestyle.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22
23 from bpy.props import (
24     BoolProperty,
25     EnumProperty,
26     StringProperty,
27 )
28
29
30 class SCENE_OT_freestyle_fill_range_by_selection(bpy.types.Operator):
31     """Fill the Range Min/Max entries by the min/max distance between selected mesh objects and the source object """
32     """(either a user-specified object or the active camera)"""
33     bl_idname = "scene.freestyle_fill_range_by_selection"
34     bl_label = "Fill Range by Selection"
35     bl_options = {'INTERNAL'}
36
37     type = EnumProperty(
38             name="Type", description="Type of the modifier to work on",
39             items=(("COLOR", "Color", "Color modifier type"),
40                    ("ALPHA", "Alpha", "Alpha modifier type"),
41                    ("THICKNESS", "Thickness", "Thickness modifier type")),
42             )
43     name = StringProperty(
44             name="Name",
45             description="Name of the modifier to work on",
46             )
47
48     @classmethod
49     def poll(cls, context):
50         rl = context.scene.render_layers.active
51         return rl and rl.freestyle_settings.linesets.active
52
53     def execute(self, context):
54         import sys
55
56         scene = context.scene
57         rl = scene.render_layers.active
58         lineset = rl.freestyle_settings.linesets.active
59         linestyle = lineset.linestyle
60         # Find the modifier to work on
61         if self.type == 'COLOR':
62             m = linestyle.color_modifiers[self.name]
63         elif self.type == 'ALPHA':
64             m = linestyle.alpha_modifiers[self.name]
65         else:
66             m = linestyle.thickness_modifiers[self.name]
67         # Find the reference object
68         if m.type == 'DISTANCE_FROM_CAMERA':
69             ref = scene.camera
70             matrix_to_camera = ref.matrix_world.inverted()
71         elif m.type == 'DISTANCE_FROM_OBJECT':
72             if m.target is None:
73                 self.report({'ERROR'}, "Target object not specified")
74                 return {'CANCELLED'}
75             ref = m.target
76             target_location = ref.location
77         else:
78             self.report({'ERROR'}, "Unexpected modifier type: " + m.type)
79             return {'CANCELLED'}
80         # Find selected vertices in editmesh
81         ob = bpy.context.active_object
82         if ob.type == 'MESH' and ob.mode == 'EDIT' and ob.name != ref.name:
83             bpy.ops.object.mode_set(mode='OBJECT')
84             selected_verts = [v for v in bpy.context.active_object.data.vertices if v.select]
85             bpy.ops.object.mode_set(mode='EDIT')
86             # Compute the min/max distance from the reference to mesh vertices
87             min_dist = sys.float_info.max
88             max_dist = -min_dist
89             if m.type == 'DISTANCE_FROM_CAMERA':
90                 ob_to_cam = matrix_to_camera * ob.matrix_world
91                 for vert in selected_verts:
92                     # dist in the camera space
93                     dist = (ob_to_cam * vert.co).length
94                     min_dist = min(dist, min_dist)
95                     max_dist = max(dist, max_dist)
96             elif m.type == 'DISTANCE_FROM_OBJECT':
97                 for vert in selected_verts:
98                     # dist in the world space
99                     dist = (ob.matrix_world * vert.co - target_location).length
100                     min_dist = min(dist, min_dist)
101                     max_dist = max(dist, max_dist)
102             # Fill the Range Min/Max entries with the computed distances
103             m.range_min = min_dist
104             m.range_max = max_dist
105             return {'FINISHED'}
106         # Find selected mesh objects
107         selection = [ob for ob in scene.objects if ob.select_get() and ob.type == 'MESH' and ob.name != source.name]
108         if selection:
109             # Compute the min/max distance from the reference to mesh vertices
110             min_dist = sys.float_info.max
111             max_dist = -min_dist
112             if m.type == 'DISTANCE_FROM_CAMERA':
113                 for ob in selection:
114                     ob_to_cam = matrix_to_camera * ob.matrix_world
115                     for vert in ob.data.vertices:
116                         # dist in the camera space
117                         dist = (ob_to_cam * vert.co).length
118                         min_dist = min(dist, min_dist)
119                         max_dist = max(dist, max_dist)
120             elif m.type == 'DISTANCE_FROM_OBJECT':
121                 for ob in selection:
122                     for vert in ob.data.vertices:
123                         # dist in the world space
124                         dist = (ob.matrix_world * vert.co - target_location).length
125                         min_dist = min(dist, min_dist)
126                         max_dist = max(dist, max_dist)
127             # Fill the Range Min/Max entries with the computed distances
128             m.range_min = min_dist
129             m.range_max = max_dist
130         return {'FINISHED'}
131
132
133 class SCENE_OT_freestyle_add_edge_marks_to_keying_set(bpy.types.Operator):
134     '''Add the data paths to the Freestyle Edge Mark property of selected edges to the active keying set'''
135     bl_idname = "scene.freestyle_add_edge_marks_to_keying_set"
136     bl_label = "Add Edge Marks to Keying Set"
137     bl_options = {'UNDO'}
138
139     @classmethod
140     def poll(cls, context):
141         ob = context.active_object
142         return (ob and ob.type == 'MESH')
143
144     def execute(self, context):
145         # active keying set
146         scene = context.scene
147         ks = scene.keying_sets.active
148         if ks is None:
149             ks = scene.keying_sets.new(idname="FreestyleEdgeMarkKeyingSet", name="Freestyle Edge Mark Keying Set")
150             ks.bl_description = ""
151         # add data paths to the keying set
152         ob = context.active_object
153         ob_mode = ob.mode
154         mesh = ob.data
155         bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
156         for i, edge in enumerate(mesh.edges):
157             if not edge.hide and edge.select:
158                 path = 'edges[%d].use_freestyle_mark' % i
159                 ks.paths.add(mesh, path, index=0)
160         bpy.ops.object.mode_set(mode=ob_mode, toggle=False)
161         return {'FINISHED'}
162
163
164 class SCENE_OT_freestyle_add_face_marks_to_keying_set(bpy.types.Operator):
165     '''Add the data paths to the Freestyle Face Mark property of selected polygons to the active keying set'''
166     bl_idname = "scene.freestyle_add_face_marks_to_keying_set"
167     bl_label = "Add Face Marks to Keying Set"
168     bl_options = {'UNDO'}
169
170     @classmethod
171     def poll(cls, context):
172         ob = context.active_object
173         return (ob and ob.type == 'MESH')
174
175     def execute(self, context):
176         # active keying set
177         scene = context.scene
178         ks = scene.keying_sets.active
179         if ks is None:
180             ks = scene.keying_sets.new(idname="FreestyleFaceMarkKeyingSet", name="Freestyle Face Mark Keying Set")
181             ks.bl_description = ""
182         # add data paths to the keying set
183         ob = context.active_object
184         ob_mode = ob.mode
185         mesh = ob.data
186         bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
187         for i, polygon in enumerate(mesh.polygons):
188             if not polygon.hide and polygon.select:
189                 path = 'polygons[%d].use_freestyle_mark' % i
190                 ks.paths.add(mesh, path, index=0)
191         bpy.ops.object.mode_set(mode=ob_mode, toggle=False)
192         return {'FINISHED'}
193
194
195 class SCENE_OT_freestyle_module_open(bpy.types.Operator):
196     """Open a style module file"""
197     bl_idname = "scene.freestyle_module_open"
198     bl_label = "Open Style Module File"
199     bl_options = {'INTERNAL'}
200
201     filepath = StringProperty(subtype='FILE_PATH')
202
203     make_internal = BoolProperty(
204         name="Make internal",
205         description="Make module file internal after loading",
206         default=True)
207
208     @classmethod
209     def poll(cls, context):
210         rl = context.scene.render_layers.active
211         return rl and rl.freestyle_settings.mode == 'SCRIPT'
212
213     def invoke(self, context, event):
214         self.freestyle_module = context.freestyle_module
215         wm = context.window_manager
216         wm.fileselect_add(self)
217         return {'RUNNING_MODAL'}
218
219     def execute(self, context):
220         text = bpy.data.texts.load(self.filepath, self.make_internal)
221         self.freestyle_module.script = text
222         return {'FINISHED'}
223
224
225 classes = (
226     SCENE_OT_freestyle_add_edge_marks_to_keying_set,
227     SCENE_OT_freestyle_add_face_marks_to_keying_set,
228     SCENE_OT_freestyle_fill_range_by_selection,
229     SCENE_OT_freestyle_module_open,
230 )