freestyle.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. # <pep8 compliant>
  19. import bpy
  20. from bpy.props import (
  21. BoolProperty,
  22. EnumProperty,
  23. StringProperty,
  24. )
  25. class SCENE_OT_freestyle_fill_range_by_selection(bpy.types.Operator):
  26. """Fill the Range Min/Max entries by the min/max distance between selected mesh objects and the source object """
  27. """(either a user-specified object or the active camera)"""
  28. bl_idname = "scene.freestyle_fill_range_by_selection"
  29. bl_label = "Fill Range by Selection"
  30. bl_options = {'INTERNAL'}
  31. type: EnumProperty(
  32. name="Type", description="Type of the modifier to work on",
  33. items=(
  34. ('COLOR', "Color", "Color modifier type"),
  35. ('ALPHA', "Alpha", "Alpha modifier type"),
  36. ('THICKNESS', "Thickness", "Thickness modifier type"),
  37. ),
  38. )
  39. name: StringProperty(
  40. name="Name",
  41. description="Name of the modifier to work on",
  42. )
  43. @classmethod
  44. def poll(cls, context):
  45. view_layer = context.view_layer
  46. return view_layer and view_layer.freestyle_settings.linesets.active
  47. def execute(self, context):
  48. import sys
  49. scene = context.scene
  50. view_layer = context.view_layer
  51. lineset = view_layer.freestyle_settings.linesets.active
  52. linestyle = lineset.linestyle
  53. # Find the modifier to work on
  54. if self.type == 'COLOR':
  55. m = linestyle.color_modifiers[self.name]
  56. elif self.type == 'ALPHA':
  57. m = linestyle.alpha_modifiers[self.name]
  58. else:
  59. m = linestyle.thickness_modifiers[self.name]
  60. # Find the reference object
  61. if m.type == 'DISTANCE_FROM_CAMERA':
  62. ref = scene.camera
  63. matrix_to_camera = ref.matrix_world.inverted()
  64. elif m.type == 'DISTANCE_FROM_OBJECT':
  65. if m.target is None:
  66. self.report({'ERROR'}, "Target object not specified")
  67. return {'CANCELLED'}
  68. ref = m.target
  69. target_location = ref.location
  70. else:
  71. self.report({'ERROR'}, "Unexpected modifier type: " + m.type)
  72. return {'CANCELLED'}
  73. # Find selected vertices in editmesh
  74. ob = context.active_object
  75. if ob.type == 'MESH' and ob.mode == 'EDIT' and ob.name != ref.name:
  76. bpy.ops.object.mode_set(mode='OBJECT')
  77. selected_verts = [v for v in ob.data.vertices if v.select]
  78. bpy.ops.object.mode_set(mode='EDIT')
  79. # Compute the min/max distance from the reference to mesh vertices
  80. min_dist = sys.float_info.max
  81. max_dist = -min_dist
  82. if m.type == 'DISTANCE_FROM_CAMERA':
  83. ob_to_cam = matrix_to_camera * ob.matrix_world
  84. for vert in selected_verts:
  85. # dist in the camera space
  86. dist = (ob_to_cam * vert.co).length
  87. min_dist = min(dist, min_dist)
  88. max_dist = max(dist, max_dist)
  89. elif m.type == 'DISTANCE_FROM_OBJECT':
  90. for vert in selected_verts:
  91. # dist in the world space
  92. dist = (ob.matrix_world * vert.co - target_location).length
  93. min_dist = min(dist, min_dist)
  94. max_dist = max(dist, max_dist)
  95. # Fill the Range Min/Max entries with the computed distances
  96. m.range_min = min_dist
  97. m.range_max = max_dist
  98. return {'FINISHED'}
  99. # Find selected mesh objects
  100. selection = [ob for ob in scene.objects if ob.select_get() and ob.type == 'MESH' and ob.name != source.name]
  101. if selection:
  102. # Compute the min/max distance from the reference to mesh vertices
  103. min_dist = sys.float_info.max
  104. max_dist = -min_dist
  105. if m.type == 'DISTANCE_FROM_CAMERA':
  106. for ob in selection:
  107. ob_to_cam = matrix_to_camera * ob.matrix_world
  108. for vert in ob.data.vertices:
  109. # dist in the camera space
  110. dist = (ob_to_cam * vert.co).length
  111. min_dist = min(dist, min_dist)
  112. max_dist = max(dist, max_dist)
  113. elif m.type == 'DISTANCE_FROM_OBJECT':
  114. for ob in selection:
  115. for vert in ob.data.vertices:
  116. # dist in the world space
  117. dist = (ob.matrix_world * vert.co - target_location).length
  118. min_dist = min(dist, min_dist)
  119. max_dist = max(dist, max_dist)
  120. # Fill the Range Min/Max entries with the computed distances
  121. m.range_min = min_dist
  122. m.range_max = max_dist
  123. return {'FINISHED'}
  124. class SCENE_OT_freestyle_add_edge_marks_to_keying_set(bpy.types.Operator):
  125. '''Add the data paths to the Freestyle Edge Mark property of selected edges to the active keying set'''
  126. bl_idname = "scene.freestyle_add_edge_marks_to_keying_set"
  127. bl_label = "Add Edge Marks to Keying Set"
  128. bl_options = {'UNDO'}
  129. @classmethod
  130. def poll(cls, context):
  131. ob = context.active_object
  132. return (ob and ob.type == 'MESH')
  133. def execute(self, context):
  134. # active keying set
  135. scene = context.scene
  136. ks = scene.keying_sets.active
  137. if ks is None:
  138. ks = scene.keying_sets.new(idname="FreestyleEdgeMarkKeyingSet", name="Freestyle Edge Mark Keying Set")
  139. ks.bl_description = ""
  140. # add data paths to the keying set
  141. ob = context.active_object
  142. ob_mode = ob.mode
  143. mesh = ob.data
  144. bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
  145. for i, edge in enumerate(mesh.edges):
  146. if not edge.hide and edge.select:
  147. path = 'edges[%d].use_freestyle_mark' % i
  148. ks.paths.add(mesh, path, index=0)
  149. bpy.ops.object.mode_set(mode=ob_mode, toggle=False)
  150. return {'FINISHED'}
  151. class SCENE_OT_freestyle_add_face_marks_to_keying_set(bpy.types.Operator):
  152. '''Add the data paths to the Freestyle Face Mark property of selected polygons to the active keying set'''
  153. bl_idname = "scene.freestyle_add_face_marks_to_keying_set"
  154. bl_label = "Add Face Marks to Keying Set"
  155. bl_options = {'UNDO'}
  156. @classmethod
  157. def poll(cls, context):
  158. ob = context.active_object
  159. return (ob and ob.type == 'MESH')
  160. def execute(self, context):
  161. # active keying set
  162. scene = context.scene
  163. ks = scene.keying_sets.active
  164. if ks is None:
  165. ks = scene.keying_sets.new(idname="FreestyleFaceMarkKeyingSet", name="Freestyle Face Mark Keying Set")
  166. ks.bl_description = ""
  167. # add data paths to the keying set
  168. ob = context.active_object
  169. ob_mode = ob.mode
  170. mesh = ob.data
  171. bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
  172. for i, polygon in enumerate(mesh.polygons):
  173. if not polygon.hide and polygon.select:
  174. path = 'polygons[%d].use_freestyle_mark' % i
  175. ks.paths.add(mesh, path, index=0)
  176. bpy.ops.object.mode_set(mode=ob_mode, toggle=False)
  177. return {'FINISHED'}
  178. class SCENE_OT_freestyle_module_open(bpy.types.Operator):
  179. """Open a style module file"""
  180. bl_idname = "scene.freestyle_module_open"
  181. bl_label = "Open Style Module File"
  182. bl_options = {'INTERNAL'}
  183. filepath: StringProperty(subtype='FILE_PATH')
  184. make_internal: BoolProperty(
  185. name="Make internal",
  186. description="Make module file internal after loading",
  187. default=True)
  188. @classmethod
  189. def poll(cls, context):
  190. view_layer = context.view_layer
  191. return view_layer and view_layer.freestyle_settings.mode == 'SCRIPT'
  192. def invoke(self, context, _event):
  193. self.freestyle_module = context.freestyle_module
  194. wm = context.window_manager
  195. wm.fileselect_add(self)
  196. return {'RUNNING_MODAL'}
  197. def execute(self, _context):
  198. text = bpy.data.texts.load(self.filepath, internal=self.make_internal)
  199. self.freestyle_module.script = text
  200. return {'FINISHED'}
  201. classes = (
  202. SCENE_OT_freestyle_add_edge_marks_to_keying_set,
  203. SCENE_OT_freestyle_add_face_marks_to_keying_set,
  204. SCENE_OT_freestyle_fill_range_by_selection,
  205. SCENE_OT_freestyle_module_open,
  206. )