text: ﻿# ##### BEGIN GPL LICENSE BLOCK #####
  #
  #  This program is free software; you can redistribute it and/or
  #  modify it under the terms of the GNU General Public License
  #  as published by the Free Software Foundation; either version 2
  #  of the License, or (at your option) any later version.
  #
  #  This program is distributed in the hope that it will be useful,
  #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  #  GNU General Public License for more details.
  #
  #  You should have received a copy of the GNU General Public License
  #  along with this program; if not, write to the Free Software Foundation,
  #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  #
  # ##### END GPL LICENSE BLOCK #####
  
  # <pep8 compliant>
  
  import bpy
  
  from bpy.props import (
      BoolProperty,
      EnumProperty,
      StringProperty,
  )
  
  
  class SCENE_OT_freestyle_fill_range_by_selection(bpy.types.Operator):
      """Fill Range by Selection\nFill the Range Min/Max entries by the min/max distance between selected mesh objects and the source object """
      """(either a user-specified object or the active camera)"""
      bl_idname = "scene.freestyle_fill_range_by_selection"
      bl_label = "Fill Range by Selection"
      bl_options = {'INTERNAL'}
  
      type: EnumProperty(
          name="Type", description="Type of the modifier to work on",
          items=(
              ('COLOR', "Color", "Color modifier type"),
              ('ALPHA', "Alpha", "Alpha modifier type"),
              ('THICKNESS', "Thickness", "Thickness modifier type"),
          ),
      )
      name: StringProperty(
          name="Name",
          description="Name of the modifier to work on",
      )
  
      @classmethod
      def poll(cls, context):
          view_layer = context.view_layer
          return view_layer and view_layer.freestyle_settings.linesets.active
  
      def execute(self, context):
          import sys
  
          scene = context.scene
          view_layer = context.view_layer
          lineset = view_layer.freestyle_settings.linesets.active
          linestyle = lineset.linestyle
          # Find the modifier to work on
          if self.type == 'COLOR':
              m = linestyle.color_modifiers[self.name]
          elif self.type == 'ALPHA':
              m = linestyle.alpha_modifiers[self.name]
          else:
              m = linestyle.thickness_modifiers[self.name]
          # Find the reference object
          if m.type == 'DISTANCE_FROM_CAMERA':
              ref = scene.camera
              matrix_to_camera = ref.matrix_world.inverted()
          elif m.type == 'DISTANCE_FROM_OBJECT':
              if m.target is None:
                  self.report({'ERROR'}, "Target object not specified")
                  return {'CANCELLED'}
              ref = m.target
              target_location = ref.location
          else:
              self.report({'ERROR'}, "Unexpected modifier type: " + m.type)
              return {'CANCELLED'}
          # Find selected vertices in editmesh
          ob = context.active_object
          if ob.type == 'MESH' and ob.mode == 'EDIT' and ob.name != ref.name:
              bpy.ops.object.mode_set(mode='OBJECT')
              selected_verts = [v for v in ob.data.vertices if v.select]
              bpy.ops.object.mode_set(mode='EDIT')
              # Compute the min/max distance from the reference to mesh vertices
              min_dist = sys.float_info.max
              max_dist = -min_dist
              if m.type == 'DISTANCE_FROM_CAMERA':
                  ob_to_cam = matrix_to_camera * ob.matrix_world
                  for vert in selected_verts:
                      # dist in the camera space
                      dist = (ob_to_cam * vert.co).length
                      min_dist = min(dist, min_dist)
                      max_dist = max(dist, max_dist)
              elif m.type == 'DISTANCE_FROM_OBJECT':
                  for vert in selected_verts:
                      # dist in the world space
                      dist = (ob.matrix_world * vert.co - target_location).length
                      min_dist = min(dist, min_dist)
                      max_dist = max(dist, max_dist)
              # Fill the Range Min/Max entries with the computed distances
              m.range_min = min_dist
              m.range_max = max_dist
              return {'FINISHED'}
          # Find selected mesh objects
          selection = [ob for ob in scene.objects if ob.select_get() and ob.type == 'MESH' and ob.name != source.name]
          if selection:
              # Compute the min/max distance from the reference to mesh vertices
              min_dist = sys.float_info.max
              max_dist = -min_dist
              if m.type == 'DISTANCE_FROM_CAMERA':
                  for ob in selection:
                      ob_to_cam = matrix_to_camera * ob.matrix_world
                      for vert in ob.data.vertices:
                          # dist in the camera space
                          dist = (ob_to_cam * vert.co).length
                          min_dist = min(dist, min_dist)
                          max_dist = max(dist, max_dist)
              elif m.type == 'DISTANCE_FROM_OBJECT':
                  for ob in selection:
                      for vert in ob.data.vertices:
                          # dist in the world space
                          dist = (ob.matrix_world * vert.co - target_location).length
                          min_dist = min(dist, min_dist)
                          max_dist = max(dist, max_dist)
              # Fill the Range Min/Max entries with the computed distances
              m.range_min = min_dist
              m.range_max = max_dist
          return {'FINISHED'}
  
  
  class SCENE_OT_freestyle_add_edge_marks_to_keying_set(bpy.types.Operator):
      '''Add the data paths to the Freestyle Edge Mark property of selected edges to the active keying set'''
      bl_idname = "scene.freestyle_add_edge_marks_to_keying_set"
      bl_label = "Add Edge Marks to Keying Set"
      bl_options = {'UNDO'}
  
      @classmethod
      def poll(cls, context):
          ob = context.active_object
          return (ob and ob.type == 'MESH')
  
      def execute(self, context):
          # active keying set
          scene = context.scene
          ks = scene.keying_sets.active
          if ks is None:
              ks = scene.keying_sets.new(idname="FreestyleEdgeMarkKeyingSet", name="Freestyle Edge Mark Keying Set")
              ks.bl_description = ""
          # add data paths to the keying set
          ob = context.active_object
          ob_mode = ob.mode
          mesh = ob.data
          bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
          for i, edge in enumerate(mesh.edges):
              if not edge.hide and edge.select:
                  path = 'edges[%d].use_freestyle_mark' % i
                  ks.paths.add(mesh, path, index=0)
          bpy.ops.object.mode_set(mode=ob_mode, toggle=False)
          return {'FINISHED'}
  
  
  class SCENE_OT_freestyle_add_face_marks_to_keying_set(bpy.types.Operator):
      '''Add the data paths to the Freestyle Face Mark property of selected polygons to the active keying set'''
      bl_idname = "scene.freestyle_add_face_marks_to_keying_set"
      bl_label = "Add Face Marks to Keying Set"
      bl_options = {'UNDO'}
  
      @classmethod
      def poll(cls, context):
          ob = context.active_object
          return (ob and ob.type == 'MESH')
  
      def execute(self, context):
          # active keying set
          scene = context.scene
          ks = scene.keying_sets.active
          if ks is None:
              ks = scene.keying_sets.new(idname="FreestyleFaceMarkKeyingSet", name="Freestyle Face Mark Keying Set")
              ks.bl_description = ""
          # add data paths to the keying set
          ob = context.active_object
          ob_mode = ob.mode
          mesh = ob.data
          bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
          for i, polygon in enumerate(mesh.polygons):
              if not polygon.hide and polygon.select:
                  path = 'polygons[%d].use_freestyle_mark' % i
                  ks.paths.add(mesh, path, index=0)
          bpy.ops.object.mode_set(mode=ob_mode, toggle=False)
          return {'FINISHED'}
  
  
  class SCENE_OT_freestyle_module_open(bpy.types.Operator):
      """Open Style Module File\nOpen a style module file"""
      bl_idname = "scene.freestyle_module_open"
      bl_label = "Open Style Module File"
      bl_options = {'INTERNAL'}
  
      filepath: StringProperty(subtype='FILE_PATH')
  
      make_internal: BoolProperty(
          name="Make internal",
          description="Make module file internal after loading",
          default=True)
  
      @classmethod
      def poll(cls, context):
          view_layer = context.view_layer
          return view_layer and view_layer.freestyle_settings.mode == 'SCRIPT'
  
      def invoke(self, context, _event):
          self.freestyle_module = context.freestyle_module
          wm = context.window_manager
          wm.fileselect_add(self)
          return {'RUNNING_MODAL'}
  
      def execute(self, _context):
          text = bpy.data.texts.load(self.filepath, internal=self.make_internal)
          self.freestyle_module.script = text
          return {'FINISHED'}
  
  
  classes = (
      SCENE_OT_freestyle_add_edge_marks_to_keying_set,
      SCENE_OT_freestyle_add_face_marks_to_keying_set,
      SCENE_OT_freestyle_fill_range_by_selection,
      SCENE_OT_freestyle_module_open,
  )
  