From e94abda66dae7d01260839a7c5c5ff271f3ae33d Mon Sep 17 00:00:00 2001 From: Tamito Kajiyama Date: Sat, 7 Apr 2012 17:28:09 +0000 Subject: [PATCH] New options in the Parameter Editor mode for controling the position of stroke thickness. The new options enable a better control on the position of stroke thickness with respect to stroke backbone geometry. Three predefined positions are: * center: thickness is evenly split to the left and right side of the stroke geometry. * inside: strokes are drawn within object boundary. * outside: strokes are drawn outside the object boundary. Another option called "relative" allows users to specify the relative position by a number between 0 (inside) and 1 (outside). The thickness position options are applied only to strokes of the edge types SILHOUETTE and BORDER, since these are the only edge types defined in terms of object boundary. Strokes of other edge types are always using the "center" option. --- .../style_modules/parameter_editor.py | 160 ++++++++++++++---- .../startup/bl_ui/properties_render.py | 6 + source/blender/blenkernel/intern/linestyle.c | 4 + source/blender/blenloader/intern/readfile.c | 4 + source/blender/makesdna/DNA_linestyle_types.h | 8 + .../blender/makesrna/intern/rna_linestyle.c | 18 ++ 6 files changed, 163 insertions(+), 37 deletions(-) diff --git a/release/scripts/freestyle/style_modules/parameter_editor.py b/release/scripts/freestyle/style_modules/parameter_editor.py index d0999782337..caf262e1397 100644 --- a/release/scripts/freestyle/style_modules/parameter_editor.py +++ b/release/scripts/freestyle/style_modules/parameter_editor.py @@ -88,6 +88,80 @@ class CurveMappingModifier(ScalarBlendModifier): def evaluate(self, t): return self.__mapping(t) +class ThicknessModifierMixIn: + def __init__(self): + scene = Freestyle.getCurrentScene() + self.__persp_camera = (scene.camera.data.type == "PERSP") + def set_thickness(self, sv, outer, inner): + fe = sv.A().getFEdge(sv.B()) + nature = fe.getNature() + if (nature & Nature.BORDER): + if self.__persp_camera: + point = -sv.getPoint3D() + point.normalize() + dir = point.dot(fe.normalB()) + else: + dir = fe.normalB().z + if dir < 0.0: # the back side is visible + outer, inner = inner, outer + elif (nature & Nature.SILHOUETTE): + if fe.isSmooth(): # TODO more tests needed + outer, inner = inner, outer + else: + outer = inner = (outer + inner) / 2 + sv.attribute().setThickness(outer, inner) + +class ThicknessBlenderMixIn(ThicknessModifierMixIn): + def __init__(self, position, ratio): + ThicknessModifierMixIn.__init__(self) + self.__position = position + self.__ratio = ratio + def blend_thickness(self, outer, inner, v): + if self.__position == "CENTER": + outer = self.__modifier.blend(outer, v / 2) + inner = self.__modifier.blend(inner, v / 2) + elif self.__position == "INSIDE": + inner = self.__modifier.blend(inner, v) + elif self.__position == "OUTSIDE": + outer = self.__modifier.blend(outer, v) + elif self.__position == "RELATIVE": + outer = self.__modifier.blend(outer, v * self.ratio) + inner = self.__modifier.blend(inner, v * (1 - self.ratio)) + else: + raise ValueError("unknown thickness position: " + self.__position) + return outer, inner + +class BaseColorShader(ConstantColorShader): + def getName(self): + return "BaseColorShader" + +class BaseThicknessShader(StrokeShader, ThicknessModifierMixIn): + def __init__(self, thickness, position, ratio): + StrokeShader.__init__(self) + ThicknessModifierMixIn.__init__(self) + if position == "CENTER": + self.__outer = thickness / 2 + self.__inner = thickness / 2 + elif position == "INSIDE": + self.__outer = 0 + self.__inner = thickness + elif position == "OUTSIDE": + self.__outer = thickness + self.__inner = 0 + elif position == "RELATIVE": + self.__outer = thickness * ratio + self.__inner = thickness * (1 - ratio) + else: + raise ValueError("unknown thickness position: " + self.position) + def getName(self): + return "BaseThicknessShader" + def shade(self, stroke): + it = stroke.strokeVerticesBegin() + while it.isEnd() == 0: + sv = it.getObject() + self.set_thickness(sv, self.__outer, self.__inner) + it.increment() + # Along Stroke modifiers def iter_t2d_along_stroke(stroke): @@ -125,8 +199,10 @@ class AlphaAlongStrokeShader(CurveMappingModifier): c = self.blend(a, b) attr.setAlpha(c) -class ThicknessAlongStrokeShader(CurveMappingModifier): - def __init__(self, blend, influence, mapping, invert, curve, value_min, value_max): +class ThicknessAlongStrokeShader(ThicknessBlenderMixIn, CurveMappingModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, mapping, invert, curve, value_min, value_max): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) self.__value_min = value_min self.__value_max = value_max @@ -134,12 +210,11 @@ class ThicknessAlongStrokeShader(CurveMappingModifier): return "ThicknessAlongStrokeShader" def shade(self, stroke): for it, t in iter_t2d_along_stroke(stroke): - attr = it.getObject().attribute() - a = attr.getThicknessRL() - a = a[0] + a[1] + sv = it.getObject() + a = sv.attribute().getThicknessRL() b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min) - c = self.blend(a, b) - attr.setThickness(c/2, c/2) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) # Distance from Camera modifiers @@ -188,8 +263,10 @@ class AlphaDistanceFromCameraShader(CurveMappingModifier): c = self.blend(a, b) attr.setAlpha(c) -class ThicknessDistanceFromCameraShader(CurveMappingModifier): - def __init__(self, blend, influence, mapping, invert, curve, range_min, range_max, value_min, value_max): +class ThicknessDistanceFromCameraShader(ThicknessBlenderMixIn, CurveMappingModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, mapping, invert, curve, range_min, range_max, value_min, value_max): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) self.__range_min = range_min self.__range_max = range_max @@ -199,12 +276,11 @@ class ThicknessDistanceFromCameraShader(CurveMappingModifier): return "ThicknessDistanceFromCameraShader" def shade(self, stroke): for it, t in iter_distance_from_camera(stroke, self.__range_min, self.__range_max): - attr = it.getObject().attribute() - a = attr.getThicknessRL() - a = a[0] + a[1] + sv = it.getObject() + a = sv.attribute().getThicknessRL() b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min) - c = self.blend(a, b) - attr.setThickness(c/2, c/2) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) # Distance from Object modifiers @@ -263,8 +339,10 @@ class AlphaDistanceFromObjectShader(CurveMappingModifier): c = self.blend(a, b) attr.setAlpha(c) -class ThicknessDistanceFromObjectShader(CurveMappingModifier): - def __init__(self, blend, influence, mapping, invert, curve, target, range_min, range_max, value_min, value_max): +class ThicknessDistanceFromObjectShader(ThicknessBlenderMixIn, CurveMappingModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, mapping, invert, curve, target, range_min, range_max, value_min, value_max): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) self.__target = target self.__range_min = range_min @@ -277,12 +355,11 @@ class ThicknessDistanceFromObjectShader(CurveMappingModifier): if self.__target is None: return for it, t in iter_distance_from_object(stroke, self.__target, self.__range_min, self.__range_max): - attr = it.getObject().attribute() - a = attr.getThicknessRL() - a = a[0] + a[1] + sv = it.getObject() + a = sv.attribute().getThicknessRL() b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min) - c = self.blend(a, b) - attr.setThickness(c/2, c/2) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) # Material modifiers @@ -376,8 +453,10 @@ class AlphaMaterialShader(CurveMappingModifier): c = self.blend(a, b) attr.setAlpha(c) -class ThicknessMaterialShader(CurveMappingModifier): - def __init__(self, blend, influence, mapping, invert, curve, material_attr, value_min, value_max): +class ThicknessMaterialShader(ThicknessBlenderMixIn, CurveMappingModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, mapping, invert, curve, material_attr, value_min, value_max): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) CurveMappingModifier.__init__(self, blend, influence, mapping, invert, curve) self.__material_attr = material_attr self.__value_min = value_min @@ -386,17 +465,18 @@ class ThicknessMaterialShader(CurveMappingModifier): return "ThicknessMaterialShader" def shade(self, stroke): for it, t in iter_material_value(stroke, self.__material_attr): - attr = it.getObject().attribute() - a = attr.getThicknessRL() - a = a[0] + a[1] + sv = it.getObject() + a = sv.attribute().getThicknessRL() b = self.__value_min + self.evaluate(t) * (self.__value_max - self.__value_min) - c = self.blend(a, b) - attr.setThickness(c/2, c/2) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) # Calligraphic thickness modifier -class CalligraphicThicknessShader(ScalarBlendModifier): - def __init__(self, blend, influence, orientation, min_thickness, max_thickness): +class CalligraphicThicknessShader(ThicknessBlenderMixIn, ScalarBlendModifier): + def __init__(self, thickness_position, thickness_ratio, + blend, influence, orientation, min_thickness, max_thickness): + ThicknessBlenderMixIn.__init__(self, thickness_position, thickness_ratio) ScalarBlendModifier.__init__(self, blend, influence) rad = orientation / 180.0 * math.pi self.__orientation = mathutils.Vector((math.cos(rad), math.sin(rad))) @@ -410,13 +490,12 @@ class CalligraphicThicknessShader(ScalarBlendModifier): orthDir = mathutils.Vector((-dir.y, dir.x)) orthDir.normalize() fac = abs(orthDir * self.__orientation) - attr = it.getObject().attribute() - a = attr.getThicknessRL() - a = a[0] + a[1] + sv = it.getObject() + a = sv.attribute().getThicknessRL() b = self.__min_thickness + fac * (self.__max_thickness - self.__min_thickness) b = max(b, 0.0) - c = self.blend(a, b) - attr.setThickness(c/2, c/2) + c = self.blend_thickness(a[0], a[1], b) + self.set_thickness(sv, c[0], c[1]) it.increment() # Geometry modifiers @@ -1132,8 +1211,10 @@ def process(layer_name, lineset_name): shaders_list.append(Transform2DShader( m.pivot, m.scale_x, m.scale_y, m.angle, m.pivot_u, m.pivot_x, m.pivot_y)) color = linestyle.color - shaders_list.append(ConstantColorShader(color.r, color.g, color.b, linestyle.alpha)) - shaders_list.append(ConstantThicknessShader(linestyle.thickness)) + shaders_list.append(BaseColorShader(color.r, color.g, color.b, linestyle.alpha)) + shaders_list.append(BaseThicknessShader(linestyle.thickness, + linestyle.thickness_position, + linestyle.thickness_ratio)) for m in linestyle.color_modifiers: if not m.use: continue @@ -1175,22 +1256,27 @@ def process(layer_name, lineset_name): continue if m.type == "ALONG_STROKE": shaders_list.append(ThicknessAlongStrokeShader( + linestyle.thickness_position, linestyle.thickness_ratio, m.blend, m.influence, m.mapping, m.invert, m.curve, m.value_min, m.value_max)) elif m.type == "DISTANCE_FROM_CAMERA": shaders_list.append(ThicknessDistanceFromCameraShader( + linestyle.thickness_position, linestyle.thickness_ratio, m.blend, m.influence, m.mapping, m.invert, m.curve, m.range_min, m.range_max, m.value_min, m.value_max)) elif m.type == "DISTANCE_FROM_OBJECT": shaders_list.append(ThicknessDistanceFromObjectShader( + linestyle.thickness_position, linestyle.thickness_ratio, m.blend, m.influence, m.mapping, m.invert, m.curve, m.target, m.range_min, m.range_max, m.value_min, m.value_max)) elif m.type == "MATERIAL": shaders_list.append(ThicknessMaterialShader( + linestyle.thickness_position, linestyle.thickness_ratio, m.blend, m.influence, m.mapping, m.invert, m.curve, m.material_attr, m.value_min, m.value_max)) elif m.type == "CALLIGRAPHY": shaders_list.append(CalligraphicThicknessShader( + linestyle.thickness_position, linestyle.thickness_ratio, m.blend, m.influence, m.orientation, m.min_thickness, m.max_thickness)) if linestyle.caps == "ROUND": diff --git a/release/scripts/startup/bl_ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py index d6af8207553..d38363afafa 100644 --- a/release/scripts/startup/bl_ui/properties_render.py +++ b/release/scripts/startup/bl_ui/properties_render.py @@ -696,6 +696,12 @@ class RENDER_PT_freestyle_linestyle(RenderButtonsPanel, Panel): col.label(text="Base Thickness:") col.prop(linestyle, "thickness") col = layout.column() + row = col.row() + row.prop(linestyle, "thickness_position", expand=True) + row = col.row() + row.prop(linestyle, "thickness_ratio") + row.enabled = linestyle.thickness_position == "RELATIVE" + col = layout.column() col.label(text="Modifiers:") col.operator_menu_enum("scene.freestyle_thickness_modifier_add", "type", text="Add Modifier") for modifier in linestyle.thickness_modifiers: diff --git a/source/blender/blenkernel/intern/linestyle.c b/source/blender/blenkernel/intern/linestyle.c index dcacfeea5bc..b2c2cb27e31 100644 --- a/source/blender/blenkernel/intern/linestyle.c +++ b/source/blender/blenkernel/intern/linestyle.c @@ -75,6 +75,8 @@ static void default_linestyle_settings(FreestyleLineStyle *linestyle) linestyle->r = linestyle->g = linestyle->b = 0.0; linestyle->alpha = 1.0; linestyle->thickness = 1.0; + linestyle->thickness_position = LS_THICKNESS_CENTER; + linestyle->thickness_ratio = 0.5f; linestyle->chaining = LS_CHAINING_PLAIN; linestyle->rounds = 3; linestyle->min_angle = 0.0f; @@ -135,6 +137,8 @@ FreestyleLineStyle *FRS_copy_linestyle(FreestyleLineStyle *linestyle) new_linestyle->b = linestyle->b; new_linestyle->alpha = linestyle->alpha; new_linestyle->thickness = linestyle->thickness; + new_linestyle->thickness_position = linestyle->thickness_position; + new_linestyle->thickness_ratio = linestyle->thickness_ratio; new_linestyle->flag = linestyle->flag; new_linestyle->caps = linestyle->caps; new_linestyle->chaining = linestyle->chaining; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index ccff9ce02ff..dbb09022ddb 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -13566,6 +13566,10 @@ static void do_versions(FileData *fd, Library *lib, Main *main) } } for(linestyle = main->linestyle.first; linestyle; linestyle = linestyle->id.next) { + if (linestyle->thickness_position == 0) { + linestyle->thickness_position= LS_THICKNESS_CENTER; + linestyle->thickness_ratio= 0.5f; + } if (linestyle->chaining == 0) linestyle->chaining= LS_CHAINING_PLAIN; if (linestyle->rounds == 0) diff --git a/source/blender/makesdna/DNA_linestyle_types.h b/source/blender/makesdna/DNA_linestyle_types.h index 1c82443e357..9ffb53e83b6 100644 --- a/source/blender/makesdna/DNA_linestyle_types.h +++ b/source/blender/makesdna/DNA_linestyle_types.h @@ -400,12 +400,20 @@ typedef struct LineStyleThicknessModifier_Calligraphy { #define LS_CAPS_ROUND 2 #define LS_CAPS_SQUARE 3 +/* FreestyleLineStyle::thickness_position */ +#define LS_THICKNESS_CENTER 1 +#define LS_THICKNESS_INSIDE 2 +#define LS_THICKNESS_OUTSIDE 3 +#define LS_THICKNESS_RELATIVE 4 /* thickness_ratio is used */ + typedef struct FreestyleLineStyle { ID id; struct AnimData *adt; float r, g, b, alpha; float thickness; + int thickness_position; + float thickness_ratio; int flag, caps; int chaining; unsigned int rounds; diff --git a/source/blender/makesrna/intern/rna_linestyle.c b/source/blender/makesrna/intern/rna_linestyle.c index dc1942b23b4..abb715f2471 100644 --- a/source/blender/makesrna/intern/rna_linestyle.c +++ b/source/blender/makesrna/intern/rna_linestyle.c @@ -875,6 +875,12 @@ static void rna_def_linestyle(BlenderRNA *brna) {LS_CAPS_ROUND, "ROUND", 0, "Round", "Round cap (half-circle)"}, {LS_CAPS_SQUARE, "SQUARE", 0, "Square", "Square cap (flat and extended)"}, {0, NULL, 0, NULL, NULL}}; + static EnumPropertyItem thickness_position_items[] = { + {LS_THICKNESS_CENTER, "CENTER", 0, "Center", "Stroke is centered along stroke geometry"}, + {LS_THICKNESS_INSIDE, "INSIDE", 0, "Inside", "Stroke is drawn inside stroke geometry"}, + {LS_THICKNESS_OUTSIDE, "OUTSIDE", 0, "Outside", "Stroke is drawn outside stroke geometry"}, + {LS_THICKNESS_RELATIVE, "RELATIVE", 0, "Relative", "Stroke thinkness is split by a user-defined ratio"}, + {0, NULL, 0, NULL, NULL}}; srna= RNA_def_struct(brna, "FreestyleLineStyle", "ID"); RNA_def_struct_ui_text(srna, "Freestyle Line Style", "Freestyle line style, reusable by multiple line sets"); @@ -904,6 +910,18 @@ static void rna_def_linestyle(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Thickness", "Base line thickness, possibly modified by line thickness modifiers"); RNA_def_property_update(prop, NC_LINESTYLE, NULL); + prop= RNA_def_property(srna, "thickness_position", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "thickness_position"); + RNA_def_property_enum_items(prop, thickness_position_items); + RNA_def_property_ui_text(prop, "Thickness Position", "Select the position of stroke thickness"); + RNA_def_property_update(prop, NC_LINESTYLE, NULL); + + prop= RNA_def_property(srna, "thickness_ratio", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "thickness_ratio"); + RNA_def_property_range(prop, 0.f, 1.f); + RNA_def_property_ui_text(prop, "Thickness Ratio", "A number between 0 (inside) and 1 (outside) specifying the relative position of stroke thickness"); + RNA_def_property_update(prop, NC_LINESTYLE, NULL); + prop= RNA_def_property(srna, "color_modifiers", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, NULL, "color_modifiers", NULL); RNA_def_property_struct_type(prop, "LineStyleColorModifier");