Source code for ddg.blender.freestyle

import itertools

import bpy

import ddg


[docs]def freestylify_curve(curve): """Enable freestyle on edges of a curve (hacky). This function is a workaround for the problem that freestyle doesn't work on edges that don't belong to a large enough face. The function works by extruding all edges of the curve and slightly nudging the newly created vertices. It then enables freestyle on one edge of each newly created edge pair. The original *curve* is not altered. Creates a copy with freestyle "enabled" and links it to the original curve's collection, or the current scene collection if *curve* is not linked to a collection. Parameters ---------- curve : bpy.types.Object Curve or 1D mesh Returns ------- bpy.types.Object Freestylified clone of *curve*. This is always a mesh object. Notes ----- Blender will output a bunch of "Warning: degenerated triangle detected, correcting" warnings. The nudge amount does not seem to influence this. """ # Create a copy of the curve and link it to a collection. # I think users_collection is the tuple of all collections the object is linked to, # it's not documented particularly well. We choose an arbitrary one. If the object # is unlinked, we link it to the main scene collection. if curve.users_collection: curve_copy_collection = curve.users_collection[0] else: curve_copy_collection = bpy.context.scene.collection curve_copy = ddg.blender.object.copy(curve, collection=curve_copy_collection) curve_copy.name = curve.name + " (freestylified)" # Convert curves to meshes so we can count and extrude edges if curve_copy.type == "CURVE": with ddg.blender.context.mode(curve_copy): curve_copy.select_set(True) bpy.ops.object.convert() # target="MESH" is the default. # Do this here because this only works after conversion to mesh but before extrusion original_num_edges = len(curve_copy.data.edges) # Deselect all objects, in particular the original curve. The context manager does # not handle this and both objects get edited if we don't do this. # We are already in the correct context (object mode) after exiting the last # context. bpy.ops.object.select_all(action="DESELECT") with ddg.blender.context.mode( bobj=curve_copy, mode="EDIT", mesh_select_mode={"EDGE"}, exit_mode="OBJECT" ): bpy.ops.mesh.select_all(action="SELECT") bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={"value": [1e-4, 0, 0]}) # This works empirically, but is it guaranteed that it's always the first # original_num_edges edges that are the original edges of the curve? It would be # pretty weird if this didn't hold. # We have to do this at the end because use_freestyle_mark is inherited by extruded # edges. for edge in itertools.islice(curve_copy.data.edges, original_num_edges): edge.use_freestyle_mark = True return curve_copy
[docs]def mark_all_edges(bobj_or_mesh): """Mark all the edges of a Blender object for freestyle. Parameters ---------- bobj: bpy.types.Object or bpy.types.Mesh The Blender object of which the edges should be marked. """ mesh = ddg.blender.object.get_data(bobj_or_mesh) for edge in mesh.edges: edge.use_freestyle_mark = True return bobj_or_mesh
[docs]def settings(scene=None, view_layer=None): """Initialize freestyle settings. Parameters ---------- scene : bpy.types.Scene (default=None) The scene to apply freestyle to. If `None`, then `bpy.context.scene` will be set to scene. view_layer : bpy.types.ViewLayer (default=None) The view layer of `scene` to set freestyle to. If `None`, then `scene.view_layers[0]` will be set as view layer. Returns ------- bpy.types.FreestyleSettings The newly initialized freestyle settings. """ if scene is None: scene = bpy.context.scene if view_layer is None: view_layer = scene.view_layers[0] # set render parameters for freestyle render = scene.render render.use_freestyle = True render.line_thickness_mode = "RELATIVE" # set view_layer parameters for freestyle view_layer.use_freestyle = True fs_settings = view_layer.freestyle_settings fs_settings.use_view_map_cache = True fs_settings.mode = "EDITOR" # Clean existing linesets linesets = fs_settings.linesets for key in linesets.keys(): lineset = linesets[key] linesets.remove(lineset) return fs_settings
[docs]def lineset(fs_settings, name): """Add a freestyle lineset to the settings. Parameters ---------- fs_settings: bpy.types.FreestyleSettings The freestyle settings to add the lineset to. name: str The name to give to the lineset. Returns ------- bpy.types.FreestyleLineSet """ return fs_settings.linesets.new(name)
[docs]def linestyle(name): """Make a new freestyle linesetyle. Parameters ---------- name: str The name to give to the linestyle. Returns ------- bpy.types.FreestyleLineStyle """ return bpy.data.linestyles.new(name)
[docs]def lineset_to_marked_visible(fs_lineset): """Setup a lineset to match marked and visible edges. Parameters ---------- fs_lineset: bpy.types.FreestyleLineSet The lineset to setup. Returns ------- bpy.types.FreestyleLineSet """ fs_lineset.select_by_edge_types = True fs_lineset.select_by_visibility = True fs_lineset.edge_type_negation = "INCLUSIVE" fs_lineset.edge_type_combination = "OR" fs_lineset.select_edge_mark = True fs_lineset.visibility = "VISIBLE" return fs_lineset
[docs]def lineset_to_marked_hidden(fs_lineset): """Setup a lineset to match marked and hidden edges. Parameters ---------- fs_lineset: bpy.types.FreestyleLineSet The lineset to setup. Returns ------- bpy.types.FreestyleLineSet """ fs_lineset.select_by_edge_types = True fs_lineset.select_by_visibility = True fs_lineset.edge_type_negation = "INCLUSIVE" fs_lineset.edge_type_combination = "OR" fs_lineset.select_edge_mark = True fs_lineset.visibility = "HIDDEN" return fs_lineset
[docs]def linestyle_to_plain(fs_linestyle, thickness=1.3): """Set a linestyle to plain line. Parameters ---------- fs_linestyle: bpy.types.FreestyleLineStyle The lineset to setup. thickness: float The thickness of the line. Returns ------- bpy.types.FreestyleLineStyle """ fs_linestyle.thickness = thickness fs_linestyle.use_dashed_line = False return fs_linestyle
[docs]def linestyle_to_dashed(fs_linestyle, thickness=1, dash=5, gap=5): """Set a linestyle to dashed line. Parameters ---------- fs_linestyle: bpy.types.FreestyleLineStyle The lineset to setup. thickness: float The thickness of the line. dash: float The length of the dashes. gap: float The length of the gaps between the dashes. Returns ------- bpy.types.FreestyleLineStyle """ fs_linestyle.thickness = thickness fs_linestyle.use_dashed_line = True fs_linestyle.dash1 = dash fs_linestyle.gap1 = gap return fs_linestyle
[docs]def setup_freestyle(): """Setup default freestyle settings. Returns ------- bpy.types.FreestyleSettings """ fs_settings = settings() # marked visible edges appear as plain lines plain_edges = lineset(fs_settings, "Plain Edges") lineset_to_marked_visible(plain_edges) plain_style = linestyle("Plain Style") linestyle_to_plain(plain_style) plain_edges.linestyle = plain_style # marked hidden edges appear as dashed lines dashed_edges = lineset(fs_settings, "Dashed Edges") lineset_to_marked_hidden(dashed_edges) dashed_style = linestyle("Dashed Style") linestyle_to_dashed(dashed_style) dashed_edges.linestyle = dashed_style return fs_settings