Files
test2/scripts/startup/bl_ui/properties_freestyle.py
Nika Kutsniashvili 5dd53fdb29 Fix: Avoid relative imports in UI code
Relative imports are intentionally avoided in UI code (`scripts/startup/bl_ui`) because it makes it impossible to run individual files in isolation.

Common workflow for making mock-ups and quick UI edits is to load the UI file in Blender's text editor with "Edit Source" operator, make changes, and run the script to see the changes. But in the current version it's impossible because files include relative imports, which don't allow Python to run scripts individually.

Pull Request: https://projects.blender.org/blender/blender/pulls/138246
2025-05-04 13:26:03 +02:00

1349 lines
46 KiB
Python

# SPDX-FileCopyrightText: 2012-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Menu, Panel, UIList
from bl_ui.space_properties import PropertiesAnimationMixin
# Render properties
class RenderFreestyleButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "render"
# COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
@classmethod
def poll(cls, context):
scene = context.scene
with_freestyle = bpy.app.build_options.freestyle
return scene and with_freestyle and (context.engine in cls.COMPAT_ENGINES)
class RENDER_PT_freestyle(RenderFreestyleButtonsPanel, Panel):
bl_label = "Freestyle"
bl_options = {'DEFAULT_CLOSED'}
bl_order = 10
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
def draw_header(self, context):
rd = context.scene.render
self.layout.prop(rd, "use_freestyle", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
rd = context.scene.render
layout.active = rd.use_freestyle
layout.row().prop(rd, "line_thickness_mode", expand=True, text="Line Thickness Mode")
if rd.line_thickness_mode == 'ABSOLUTE':
layout.prop(rd, "line_thickness")
# Render layer properties
class ViewLayerFreestyleButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "view_layer"
bl_order = 10
# COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
@classmethod
def poll(cls, context):
scene = context.scene
rd = scene.render
with_freestyle = bpy.app.build_options.freestyle
return (
scene and
with_freestyle and
rd.use_freestyle and
(context.engine in cls.COMPAT_ENGINES)
)
class ViewLayerFreestyleEditorButtonsPanel(ViewLayerFreestyleButtonsPanel):
# COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
@classmethod
def poll(cls, context):
if not super().poll(context):
return False
view_layer = context.view_layer
return (
view_layer and
view_layer.use_freestyle and
view_layer.freestyle_settings.mode == 'EDITOR'
)
class ViewLayerFreestyleLineStyle(ViewLayerFreestyleEditorButtonsPanel):
# Freestyle Linestyle Panels
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
@classmethod
def poll(cls, context):
if not super().poll(context):
return False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
if lineset is None:
return False
linestyle = lineset.linestyle
if linestyle is None:
return False
return True
class ViewLayerFreestyleLinestyleStrokesSubPanel(ViewLayerFreestyleLineStyle):
# Freestyle Linestyle Strokes sub panels
bl_parent_id = "VIEWLAYER_PT_freestyle_linestyle_strokes"
class VIEWLAYER_UL_linesets(UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, index):
lineset = item
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.prop(lineset, "name", text="", emboss=False, icon_value=icon)
layout.prop(lineset, "show_render", text="", index=index)
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.label(text="", icon_value=icon)
class RENDER_MT_lineset_context_menu(Menu):
bl_label = "Lineset Specials"
def draw(self, _context):
layout = self.layout
layout.operator("scene.freestyle_lineset_copy", icon='COPYDOWN')
layout.operator("scene.freestyle_lineset_paste", icon='PASTEDOWN')
class VIEWLAYER_PT_freestyle(ViewLayerFreestyleButtonsPanel, Panel):
bl_label = "Freestyle"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
def draw_header(self, context):
view_layer = context.view_layer
rd = context.scene.render
layout = self.layout
layout.active = rd.use_freestyle
layout.prop(view_layer, "use_freestyle", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
layout.active = view_layer.use_freestyle
col = layout.column(align=True)
col.prop(freestyle, "mode", text="Control Mode")
col.prop(freestyle, "use_view_map_cache", text="View Map Cache")
col.prop(freestyle, "as_render_pass", text="As Render Pass")
class VIEWLAYER_PT_freestyle_edge_detection(ViewLayerFreestyleButtonsPanel, Panel):
bl_label = "Edge Detection"
bl_parent_id = "VIEWLAYER_PT_freestyle"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
layout.active = view_layer.use_freestyle
col = layout.column()
col.prop(freestyle, "crease_angle")
col.prop(freestyle, "use_culling")
col.prop(freestyle, "use_smoothness")
if freestyle.mode == 'SCRIPT':
col.prop(freestyle, "use_material_boundaries")
if freestyle.mode == 'SCRIPT':
col.prop(freestyle, "use_ridges_and_valleys")
col.prop(freestyle, "use_suggestive_contours")
col.prop(freestyle, "sphere_radius")
col.prop(freestyle, "kr_derivative_epsilon")
class VIEWLAYER_PT_freestyle_style_modules(ViewLayerFreestyleButtonsPanel, Panel):
bl_label = "Style Modules"
bl_parent_id = "VIEWLAYER_PT_freestyle"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
@classmethod
def poll(cls, context):
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
return freestyle.mode == 'SCRIPT'
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
layout.active = view_layer.use_freestyle
col = layout.column()
col.use_property_split = False
row = col.row()
row.operator("scene.freestyle_module_add", text="Add")
for module in freestyle.modules:
box = col.box()
box.context_pointer_set("freestyle_module", module)
row = box.row(align=True)
row.prop(module, "use", text="")
row.prop(module, "script", text="")
row.operator("scene.freestyle_module_open", icon='FILEBROWSER', text="")
row.operator("scene.freestyle_module_remove", icon='X', text="")
row.operator("scene.freestyle_module_move", icon='TRIA_UP', text="").direction = 'UP'
row.operator("scene.freestyle_module_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
class VIEWLAYER_PT_freestyle_lineset(ViewLayerFreestyleEditorButtonsPanel, Panel):
bl_label = "Freestyle Line Set"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
def draw_edge_type_buttons(self, box, lineset, edge_type):
# property names
select_edge_type = "select_" + edge_type
exclude_edge_type = "exclude_" + edge_type
# draw edge type buttons
row = box.row(align=True)
row.prop(lineset, select_edge_type)
sub = row.column(align=True)
sub.prop(lineset, exclude_edge_type, text="")
sub.active = getattr(lineset, select_edge_type)
def draw(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
row = layout.row()
is_sortable = len(freestyle.linesets) > 1
rows = 3
if is_sortable:
rows = 5
row.template_list(
"VIEWLAYER_UL_linesets",
"",
freestyle,
"linesets",
freestyle.linesets,
"active_index",
rows=rows,
)
col = row.column(align=True)
col.operator("scene.freestyle_lineset_add", icon='ADD', text="")
col.operator("scene.freestyle_lineset_remove", icon='REMOVE', text="")
col.separator()
col.menu("RENDER_MT_lineset_context_menu", icon='DOWNARROW_HLT', text="")
if is_sortable:
col.separator()
col.operator("scene.freestyle_lineset_move", icon='TRIA_UP', text="").direction = 'UP'
col.operator("scene.freestyle_lineset_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
if lineset:
layout.template_ID(lineset, "linestyle", new="scene.freestyle_linestyle_new")
layout.separator()
col = layout.column(heading="Select by")
col.use_property_split = True
col.use_property_decorate = False
col.prop(lineset, "select_by_image_border", text="Image Border")
# Freestyle Lineset Sub Panels
class VIEWLAYER_PT_freestyle_lineset_visibilty(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Visibility"
bl_parent_id = "VIEWLAYER_PT_freestyle_lineset"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
def draw_header(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
layout.prop(lineset, "select_by_visibility", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
layout.active = lineset.select_by_visibility
col = layout.column(align=True, heading="Type")
col.prop(lineset, "visibility", text="Type", expand=False)
if lineset.visibility == 'RANGE':
col = layout.column(align=True)
col.use_property_split = True
col.prop(lineset, "qi_start")
col.prop(lineset, "qi_end")
class VIEWLAYER_PT_freestyle_lineset_edgetype(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Edge Type"
bl_parent_id = "VIEWLAYER_PT_freestyle_lineset"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
def draw_header(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
layout.prop(lineset, "select_by_edge_types", text="")
def draw_edge_type_buttons(self, box, lineset, edge_type):
# property names
select_edge_type = "select_" + edge_type
exclude_edge_type = "exclude_" + edge_type
# draw edge type buttons
row = box.row(align=True)
row.prop(lineset, select_edge_type)
sub = row.column(align=True)
sub.prop(lineset, exclude_edge_type, text="")
sub.active = getattr(lineset, select_edge_type)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
layout.active = lineset.select_by_edge_types
layout.row().prop(lineset, "edge_type_negation", expand=True, text="Negation")
layout.row().prop(lineset, "edge_type_combination", expand=True, text="Combination")
col = layout.column(heading="Type")
self.draw_edge_type_buttons(col, lineset, "silhouette")
self.draw_edge_type_buttons(col, lineset, "crease")
self.draw_edge_type_buttons(col, lineset, "border")
self.draw_edge_type_buttons(col, lineset, "edge_mark")
self.draw_edge_type_buttons(col, lineset, "contour")
self.draw_edge_type_buttons(col, lineset, "external_contour")
self.draw_edge_type_buttons(col, lineset, "material_boundary")
self.draw_edge_type_buttons(col, lineset, "suggestive_contour")
self.draw_edge_type_buttons(col, lineset, "ridge_valley")
class VIEWLAYER_PT_freestyle_lineset_facemarks(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Face Marks"
bl_parent_id = "VIEWLAYER_PT_freestyle_lineset"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
layout.prop(lineset, "select_by_face_marks", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
layout.active = lineset.select_by_face_marks
layout.row().prop(lineset, "face_mark_negation", expand=True, text="Negation")
layout.row().prop(lineset, "face_mark_condition", expand=True, text="Condition")
class VIEWLAYER_PT_freestyle_lineset_collection(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Collection"
bl_parent_id = "VIEWLAYER_PT_freestyle_lineset"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
layout.prop(lineset, "select_by_collection", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
layout.active = lineset.select_by_collection
layout.row().prop(lineset, "collection", text="Line Set Collection")
layout.row().prop(lineset, "collection_negation", expand=True, text="Negation")
# Linestyle Modifier Drawing code
def draw_modifier_box_header(box, modifier):
row = box.row()
row.use_property_split = False
row.context_pointer_set("modifier", modifier)
if modifier.expanded:
icon = 'TRIA_DOWN'
else:
icon = 'TRIA_RIGHT'
row.prop(modifier, "expanded", text="", icon=icon, emboss=False)
sub = row.row(align=True)
sub.prop(modifier, "name", text="")
if modifier.use:
icon = 'RESTRICT_RENDER_OFF'
else:
icon = 'RESTRICT_RENDER_ON'
sub.prop(modifier, "use", text="", icon=icon)
sub.operator("scene.freestyle_modifier_copy", icon='DUPLICATE', text="")
sub = row.row(align=True)
sub.operator("scene.freestyle_modifier_move", icon='TRIA_UP', text="").direction = 'UP'
sub.operator("scene.freestyle_modifier_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
row.operator("scene.freestyle_modifier_remove", icon='X', text="", emboss=False)
def draw_modifier_box_error(box, _modifier, message):
row = box.row()
row.label(text=message, icon='ERROR')
def draw_modifier_common(box, modifier):
col = box.column()
col.prop(modifier, "blend", text="Blend Mode")
col.prop(modifier, "influence")
def draw_modifier_color_ramp_common(box, modifier, has_range):
box.template_color_ramp(modifier, "color_ramp", expand=True)
if has_range:
col = box.column(align=True)
col.prop(modifier, "range_min", text="Range Min")
col.prop(modifier, "range_max", text="Max")
def draw_modifier_curve_common(box, modifier, has_range, has_value):
row = box.row()
row.prop(modifier, "mapping", text="Mapping")
if modifier.mapping == 'LINEAR':
box.prop(modifier, "invert")
if has_range:
col = box.column(align=True)
col.prop(modifier, "range_min", text="Range Min")
col.prop(modifier, "range_max", text="Max")
if has_value:
col = box.column(align=True)
col.prop(modifier, "value_min", text="Value Min")
col.prop(modifier, "value_max", text="Max")
if modifier.mapping == 'CURVE':
box.template_curve_mapping(modifier, "curve")
class VIEWLAYER_PT_freestyle_linestyle_strokes(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Freestyle Strokes"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
layout.active = view_layer.use_freestyle
if lineset is None:
return
linestyle = lineset.linestyle
if linestyle is None:
return
row = layout.row(align=True)
row.alignment = 'LEFT'
row.label(text=lineset.name, icon='LINE_DATA', translate=False)
row.label(text="", icon='RIGHTARROW')
row.label(text=linestyle.name, translate=False)
col = layout.column(align=True)
col.prop(linestyle, "caps", expand=False)
class VIEWLAYER_PT_freestyle_linestyle_strokes_chaining(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
bl_label = "Chaining"
def draw_header(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
linestyle = lineset.linestyle
layout.prop(linestyle, "use_chaining", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
linestyle = lineset.linestyle
layout.active = linestyle.use_chaining
layout.row().prop(linestyle, "chaining", expand=True, text="Method")
if linestyle.chaining == 'SKETCHY':
layout.prop(linestyle, "rounds")
layout.prop(linestyle, "use_same_object")
class VIEWLAYER_PT_freestyle_linestyle_strokes_splitting(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
bl_label = "Splitting"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
linestyle = lineset.linestyle
row = layout.row(align=False, heading="Min 2D Angle")
row.prop(linestyle, "use_angle_min", text="")
sub = row.row()
sub.active = linestyle.use_angle_min
sub.prop(linestyle, "angle_min", text="")
row = layout.row(align=False, heading="Max 2D Angle")
row.prop(linestyle, "use_angle_max", text="")
sub = row.row()
sub.active = linestyle.use_angle_max
sub.prop(linestyle, "angle_max", text="")
row = layout.row(align=False, heading="2D Length")
row.prop(linestyle, "use_split_length", text="")
sub = row.row()
sub.active = linestyle.use_split_length
sub.prop(linestyle, "split_length", text="")
layout.prop(linestyle, "material_boundary")
class VIEWLAYER_PT_freestyle_linestyle_strokes_splitting_pattern(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
bl_label = "Split Pattern"
bl_parent_id = "VIEWLAYER_PT_freestyle_linestyle_strokes_splitting"
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
linestyle = lineset.linestyle
layout.prop(linestyle, "use_split_pattern", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
linestyle = lineset.linestyle
layout.active = linestyle.use_split_pattern
col = layout.column(align=True)
col.prop(linestyle, "split_dash1", text="Dash 1")
col.prop(linestyle, "split_dash2", text="2")
col.prop(linestyle, "split_dash3", text="3")
col = layout.column(align=True)
col.prop(linestyle, "split_gap1", text="Gap 1")
col.prop(linestyle, "split_gap2", text="2")
col.prop(linestyle, "split_gap3", text="3")
class VIEWLAYER_PT_freestyle_linestyle_strokes_sorting(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
bl_label = "Sorting"
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
linestyle = lineset.linestyle
layout.prop(linestyle, "use_sorting", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
linestyle = lineset.linestyle
layout.active = linestyle.use_sorting
layout.prop(linestyle, "sort_key")
row = layout.row()
row.active = linestyle.sort_key in {
'DISTANCE_FROM_CAMERA',
'PROJECTED_X',
'PROJECTED_Y',
}
row.prop(linestyle, "integration_type")
layout.row().prop(linestyle, "sort_order", expand=True, text="Sort Order")
class VIEWLAYER_PT_freestyle_linestyle_strokes_selection(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
bl_label = "Selection"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
linestyle = lineset.linestyle
row = layout.row(align=False, heading="Min 2D Length")
row.prop(linestyle, "use_length_min", text="")
sub = row.row()
sub.active = linestyle.use_length_min
sub.prop(linestyle, "length_min", text="")
row = layout.row(align=False, heading="Max 2D Length")
row.prop(linestyle, "use_length_max", text="")
sub = row.row()
sub.active = linestyle.use_length_max
sub.prop(linestyle, "length_max", text="")
row = layout.row(align=False, heading="Chain Count")
row.prop(linestyle, "use_chain_count", text="")
sub = row.row()
sub.active = linestyle.use_chain_count
sub.prop(linestyle, "chain_count", text="")
class VIEWLAYER_PT_freestyle_linestyle_strokes_dashedline(ViewLayerFreestyleLinestyleStrokesSubPanel, Panel):
bl_label = "Dashed Line"
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
layout = self.layout
view_layer = context.view_layer
freestyle = view_layer.freestyle_settings
lineset = freestyle.linesets.active
linestyle = lineset.linestyle
layout.prop(linestyle, "use_dashed_line", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
linestyle = lineset.linestyle
layout.active = linestyle.use_dashed_line
col = layout.column(align=True)
col.prop(linestyle, "dash1", text="Dash 1")
col.prop(linestyle, "dash2", text="2")
col.prop(linestyle, "dash3", text="3")
col = layout.column(align=True)
col.prop(linestyle, "gap1", text="Gap 1")
col.prop(linestyle, "gap2", text="2")
col.prop(linestyle, "gap3", text="3")
class VIEWLAYER_PT_freestyle_linestyle_color(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Freestyle Color"
bl_options = {'DEFAULT_CLOSED'}
def draw_color_modifier(self, context, modifier):
layout = self.layout
col = layout.column(align=True)
draw_modifier_box_header(col.box(), modifier)
if modifier.expanded:
box = col.box()
draw_modifier_common(box, modifier)
if modifier.type == 'ALONG_STROKE':
draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'DISTANCE_FROM_OBJECT':
box.prop(modifier, "target")
draw_modifier_color_ramp_common(box, modifier, True)
props = box.operator("scene.freestyle_fill_range_by_selection")
props.type = 'COLOR'
props.name = modifier.name
elif modifier.type == 'DISTANCE_FROM_CAMERA':
draw_modifier_color_ramp_common(box, modifier, True)
props = box.operator("scene.freestyle_fill_range_by_selection")
props.type = 'COLOR'
props.name = modifier.name
elif modifier.type == 'MATERIAL':
row = box.row()
row.prop(modifier, "material_attribute", text="Material Attribute")
sub = box.column()
sub.prop(modifier, "use_ramp")
if modifier.material_attribute in {'LINE', 'DIFF', 'SPEC'}:
sub.active = True
show_ramp = modifier.use_ramp
else:
sub.active = False
show_ramp = True
if show_ramp:
draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'TANGENT':
draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'NOISE':
subcol = box.column(align=True)
subcol.prop(modifier, "amplitude")
subcol.prop(modifier, "period")
subcol.prop(modifier, "seed")
draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'CREASE_ANGLE':
subcol = box.column(align=True)
subcol.prop(modifier, "angle_min", text="Angle Min")
subcol.prop(modifier, "angle_max", text="Max")
draw_modifier_color_ramp_common(box, modifier, False)
elif modifier.type == 'CURVATURE_3D':
subcol = box.column(align=True)
subcol.prop(modifier, "curvature_min", text="Curvature Min")
subcol.prop(modifier, "curvature_max", text="Max")
draw_modifier_color_ramp_common(box, modifier, False)
freestyle = context.view_layer.freestyle_settings
if not freestyle.use_smoothness:
message = "Enable Face Smoothness to use this modifier"
draw_modifier_box_error(col.box(), modifier, message)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
layout.active = view_layer.use_freestyle
if lineset is None:
return
linestyle = lineset.linestyle
if linestyle is None:
return
row = layout.row(align=True)
row.alignment = 'LEFT'
row.label(text=lineset.name, icon='LINE_DATA', translate=False)
row.label(text="", icon='RIGHTARROW')
row.label(text=linestyle.name, translate=False)
col = layout.column()
row = col.row()
row.prop(linestyle, "color", text="Base Color")
col.operator_menu_enum("scene.freestyle_color_modifier_add", "type", text="Add Modifier")
for modifier in linestyle.color_modifiers:
self.draw_color_modifier(context, modifier)
class VIEWLAYER_PT_freestyle_linestyle_alpha(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Freestyle Alpha"
bl_options = {'DEFAULT_CLOSED'}
def draw_alpha_modifier(self, context, modifier):
layout = self.layout
col = layout.column(align=True)
draw_modifier_box_header(col.box(), modifier)
if modifier.expanded:
box = col.box()
draw_modifier_common(box, modifier)
if modifier.type == 'ALONG_STROKE':
draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'DISTANCE_FROM_OBJECT':
box.prop(modifier, "target")
draw_modifier_curve_common(box, modifier, True, False)
props = box.operator("scene.freestyle_fill_range_by_selection")
props.type = 'ALPHA'
props.name = modifier.name
elif modifier.type == 'DISTANCE_FROM_CAMERA':
draw_modifier_curve_common(box, modifier, True, False)
props = box.operator("scene.freestyle_fill_range_by_selection")
props.type = 'ALPHA'
props.name = modifier.name
elif modifier.type == 'MATERIAL':
box.prop(modifier, "material_attribute", text="Material Attribute")
draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'TANGENT':
draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'NOISE':
col = box.column(align=True)
col.prop(modifier, "amplitude")
col.prop(modifier, "period")
col.prop(modifier, "seed")
draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'CREASE_ANGLE':
col = box.column(align=True)
col.prop(modifier, "angle_min", text="Angle Min")
col.prop(modifier, "angle_max", text="Max")
draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'CURVATURE_3D':
draw_modifier_curve_common(box, modifier, False, False)
subcol = box.column(align=True)
subcol.prop(modifier, "curvature_min", text="Curvature Min")
subcol.prop(modifier, "curvature_max", text="Max")
freestyle = context.view_layer.freestyle_settings
if not freestyle.use_smoothness:
message = "Enable Face Smoothness to use this modifier"
draw_modifier_box_error(col.box(), modifier, message)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
layout.active = view_layer.use_freestyle
if lineset is None:
return
linestyle = lineset.linestyle
if linestyle is None:
return
row = layout.row(align=True)
row.alignment = 'LEFT'
row.label(text=lineset.name, icon='LINE_DATA', translate=False)
row.label(text="", icon='RIGHTARROW')
row.label(text=linestyle.name, translate=False)
col = layout.column()
row = col.row()
row.prop(linestyle, "alpha", text="Base Transparency")
col.operator_menu_enum("scene.freestyle_alpha_modifier_add", "type", text="Add Modifier")
for modifier in linestyle.alpha_modifiers:
self.draw_alpha_modifier(context, modifier)
class VIEWLAYER_PT_freestyle_linestyle_thickness(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Freestyle Thickness"
bl_options = {'DEFAULT_CLOSED'}
def draw_thickness_modifier(self, context, modifier):
layout = self.layout
col = layout.column(align=True)
draw_modifier_box_header(col.box(), modifier)
if modifier.expanded:
box = col.box()
draw_modifier_common(box, modifier)
if modifier.type == 'ALONG_STROKE':
draw_modifier_curve_common(box, modifier, False, True)
elif modifier.type == 'DISTANCE_FROM_OBJECT':
box.prop(modifier, "target")
draw_modifier_curve_common(box, modifier, True, True)
props = box.operator("scene.freestyle_fill_range_by_selection")
props.type = 'THICKNESS'
props.name = modifier.name
elif modifier.type == 'DISTANCE_FROM_CAMERA':
draw_modifier_curve_common(box, modifier, True, True)
props = box.operator("scene.freestyle_fill_range_by_selection")
props.type = 'THICKNESS'
props.name = modifier.name
elif modifier.type == 'MATERIAL':
box.prop(modifier, "material_attribute", text="Material Attribute")
draw_modifier_curve_common(box, modifier, False, True)
elif modifier.type == 'CALLIGRAPHY':
box.prop(modifier, "orientation")
subcol = box.column(align=True)
subcol.prop(modifier, "thickness_min", text="Thickness Min")
subcol.prop(modifier, "thickness_max", text="Max")
elif modifier.type == 'TANGENT':
self.mapping = 'CURVE'
subcol = box.column(align=True)
subcol.prop(modifier, "thickness_min", text="Thickness Min")
subcol.prop(modifier, "thickness_max", text="Max")
draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'NOISE':
col = box.column(align=True)
col.prop(modifier, "amplitude")
col.prop(modifier, "period")
col = box.column(align=True)
col.prop(modifier, "seed")
col.prop(modifier, "use_asymmetric")
elif modifier.type == 'CREASE_ANGLE':
col = box.column(align=True)
col.prop(modifier, "thickness_min", text="Thickness Min")
col.prop(modifier, "thickness_max", text="Max")
col = box.column(align=True)
col.prop(modifier, "angle_min", text="Angle Min")
col.prop(modifier, "angle_max", text="Max")
draw_modifier_curve_common(box, modifier, False, False)
elif modifier.type == 'CURVATURE_3D':
subcol = box.column(align=True)
subcol.prop(modifier, "thickness_min", text="Thickness Min")
subcol.prop(modifier, "thickness_max", text="Max")
subcol = box.column(align=True)
subcol.prop(modifier, "curvature_min")
subcol.prop(modifier, "curvature_max")
draw_modifier_curve_common(box, modifier, False, False)
freestyle = context.view_layer.freestyle_settings
if not freestyle.use_smoothness:
message = "Enable Face Smoothness to use this modifier"
draw_modifier_box_error(col.box(), modifier, message)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
layout.active = view_layer.use_freestyle
if lineset is None:
return
linestyle = lineset.linestyle
if linestyle is None:
return
row = layout.row(align=True)
row.alignment = 'LEFT'
row.label(text=lineset.name, icon='LINE_DATA', translate=False)
row.label(text="", icon='RIGHTARROW')
row.label(text=linestyle.name, translate=False)
col = layout.column()
row = col.row()
row.prop(linestyle, "thickness", text="Base Thickness")
subcol = col.column()
subcol.active = linestyle.chaining == 'PLAIN' and linestyle.use_same_object
row = subcol.row()
row.prop(linestyle, "thickness_position", expand=False)
if linestyle.thickness_position == 'RELATIVE':
row = subcol.row()
row.prop(linestyle, "thickness_ratio")
col = layout.column()
col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier")
for modifier in linestyle.thickness_modifiers:
self.draw_thickness_modifier(context, modifier)
class VIEWLAYER_PT_freestyle_linestyle_geometry(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Freestyle Geometry"
bl_options = {'DEFAULT_CLOSED'}
def draw_geometry_modifier(self, _context, modifier):
layout = self.layout
col = layout.column(align=True)
draw_modifier_box_header(col.box(), modifier)
if modifier.expanded:
box = col.box()
if modifier.type == 'SAMPLING':
box.prop(modifier, "sampling")
elif modifier.type == 'BEZIER_CURVE':
box.prop(modifier, "error")
elif modifier.type == 'SINUS_DISPLACEMENT':
col = box.column(align=True)
col.prop(modifier, "wavelength")
col.prop(modifier, "amplitude")
col = box.column(align=True)
col.prop(modifier, "phase")
elif modifier.type == 'SPATIAL_NOISE':
col = box.column(align=True)
col.prop(modifier, "amplitude")
col.prop(modifier, "scale")
col.prop(modifier, "octaves")
col = box.column(align=True)
col.prop(modifier, "smooth")
col.prop(modifier, "use_pure_random")
elif modifier.type == 'PERLIN_NOISE_1D':
col = box.column(align=True)
col.prop(modifier, "frequency")
col.prop(modifier, "amplitude")
col.prop(modifier, "seed")
col = box.column(align=True)
col.prop(modifier, "octaves")
col.prop(modifier, "angle")
elif modifier.type == 'PERLIN_NOISE_2D':
col = box.column(align=True)
col.prop(modifier, "frequency")
col.prop(modifier, "amplitude")
col.prop(modifier, "seed")
col = box.column(align=True)
col.prop(modifier, "octaves")
col.prop(modifier, "angle")
elif modifier.type == 'BACKBONE_STRETCHER':
box.prop(modifier, "backbone_length")
elif modifier.type == 'TIP_REMOVER':
box.prop(modifier, "tip_length")
elif modifier.type == 'POLYGONIZATION':
box.prop(modifier, "error")
elif modifier.type == 'GUIDING_LINES':
box.prop(modifier, "offset")
elif modifier.type == 'BLUEPRINT':
row = box.row()
row.prop(modifier, "shape", expand=True)
box.prop(modifier, "rounds")
subcol = box.column(align=True)
if modifier.shape in {'CIRCLES', 'ELLIPSES'}:
subcol.prop(modifier, "random_radius", text="Random Radius")
subcol.prop(modifier, "random_center", text="Center")
elif modifier.shape == 'SQUARES':
subcol.prop(modifier, "backbone_length", text="Backbone Length")
subcol.prop(modifier, "random_backbone", text="Randomness")
elif modifier.type == '2D_OFFSET':
subcol = box.column(align=True)
subcol.prop(modifier, "start")
subcol.prop(modifier, "end")
subcol = box.column(align=True)
subcol.prop(modifier, "x")
subcol.prop(modifier, "y")
elif modifier.type == '2D_TRANSFORM':
box.prop(modifier, "pivot")
if modifier.pivot == 'PARAM':
box.prop(modifier, "pivot_u")
elif modifier.pivot == 'ABSOLUTE':
subcol = box.column(align=True)
subcol.prop(modifier, "pivot_x", text="Pivot X")
subcol.prop(modifier, "pivot_y", text="Y")
subcol = box.column(align=True)
subcol.prop(modifier, "scale_x", text="Scale X")
subcol.prop(modifier, "scale_y", text="Y")
box.prop(modifier, "angle")
elif modifier.type == 'SIMPLIFICATION':
box.prop(modifier, "tolerance")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
layout.active = view_layer.use_freestyle
if lineset is None:
return
linestyle = lineset.linestyle
if linestyle is None:
return
row = layout.row(align=True)
row.alignment = 'LEFT'
row.label(text=lineset.name, icon='LINE_DATA', translate=False)
row.label(text="", icon='RIGHTARROW')
row.label(text=linestyle.name, translate=False)
col = layout.column()
col.operator_menu_enum("scene.freestyle_geometry_modifier_add", "type", text="Add Modifier")
for modifier in linestyle.geometry_modifiers:
self.draw_geometry_modifier(context, modifier)
class VIEWLAYER_PT_freestyle_linestyle_texture(ViewLayerFreestyleLineStyle, Panel):
bl_label = "Freestyle Texture"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
view_layer = context.view_layer
lineset = view_layer.freestyle_settings.linesets.active
layout.active = view_layer.use_freestyle
if lineset is None:
return
linestyle = lineset.linestyle
if linestyle is None:
return
row = layout.row(align=True)
row.alignment = 'LEFT'
row.label(text=lineset.name, icon='LINE_DATA', translate=False)
row.label(text="", icon='RIGHTARROW')
row.label(text=linestyle.name, translate=False)
layout.prop(linestyle, "use_nodes")
layout.prop(linestyle, "texture_spacing", text="Spacing Along Stroke")
row = layout.row()
props = row.operator(
"wm.properties_context_change",
text="Go to Linestyle Textures Properties",
icon='TEXTURE',
)
props.context = 'TEXTURE'
class VIEWLAYER_PT_freestyle_animation(ViewLayerFreestyleButtonsPanel, PropertiesAnimationMixin, Panel):
bl_label = "Freestyle Animation"
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
@classmethod
def _animated_id(cls, context):
try:
return context.view_layer.freestyle_settings.linesets.active.linestyle
except AttributeError:
return None
# Material properties
class MaterialFreestyleButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
# COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
@classmethod
def poll(cls, context):
scene = context.scene
material = context.material
with_freestyle = bpy.app.build_options.freestyle
return (
with_freestyle and
material and
scene and
scene.render.use_freestyle and
(context.engine in cls.COMPAT_ENGINES)
)
class MATERIAL_PT_freestyle_line(MaterialFreestyleButtonsPanel, Panel):
bl_label = "Freestyle Line"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE_NEXT',
'BLENDER_WORKBENCH',
}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
mat = context.material
col = layout.column()
col.prop(mat, "line_color")
col.prop(mat, "line_priority", text="Priority")
classes = (
RENDER_PT_freestyle,
VIEWLAYER_UL_linesets,
RENDER_MT_lineset_context_menu,
VIEWLAYER_PT_freestyle,
VIEWLAYER_PT_freestyle_edge_detection,
VIEWLAYER_PT_freestyle_style_modules,
VIEWLAYER_PT_freestyle_lineset,
VIEWLAYER_PT_freestyle_lineset_visibilty,
VIEWLAYER_PT_freestyle_lineset_edgetype,
VIEWLAYER_PT_freestyle_lineset_facemarks,
VIEWLAYER_PT_freestyle_lineset_collection,
VIEWLAYER_PT_freestyle_linestyle_strokes,
VIEWLAYER_PT_freestyle_linestyle_strokes_chaining,
VIEWLAYER_PT_freestyle_linestyle_strokes_splitting,
VIEWLAYER_PT_freestyle_linestyle_strokes_splitting_pattern,
VIEWLAYER_PT_freestyle_linestyle_strokes_sorting,
VIEWLAYER_PT_freestyle_linestyle_strokes_selection,
VIEWLAYER_PT_freestyle_linestyle_strokes_dashedline,
VIEWLAYER_PT_freestyle_linestyle_color,
VIEWLAYER_PT_freestyle_linestyle_alpha,
VIEWLAYER_PT_freestyle_linestyle_thickness,
VIEWLAYER_PT_freestyle_linestyle_geometry,
VIEWLAYER_PT_freestyle_linestyle_texture,
VIEWLAYER_PT_freestyle_animation,
MATERIAL_PT_freestyle_line,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)