New options in the Parameter Editor mode for controling the position of stroke thickness.
authorTamito Kajiyama <rd6t-kjym@asahi-net.or.jp>
Sat, 7 Apr 2012 17:28:09 +0000 (17:28 +0000)
committerTamito Kajiyama <rd6t-kjym@asahi-net.or.jp>
Sat, 7 Apr 2012 17:28:09 +0000 (17:28 +0000)
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.

release/scripts/freestyle/style_modules/parameter_editor.py
release/scripts/startup/bl_ui/properties_render.py
source/blender/blenkernel/intern/linestyle.c
source/blender/blenloader/intern/readfile.c
source/blender/makesdna/DNA_linestyle_types.h
source/blender/makesrna/intern/rna_linestyle.c

index d099978233741cdffb56297e74d96c502d62ae9f..caf262e13979910d4864ae92641390313ce224eb 100644 (file)
@@ -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":
index d6af8207553a0c5179d8cf183aad020506223ce8..d38363afafaa71fac1707b8347e04ceeafffac29 100644 (file)
@@ -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:
index dcacfeea5bc2b43f7db9466fbb8d0487a439e70a..b2c2cb27e31dc6452626ce9e11367396a8e434d1 100644 (file)
@@ -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;
index ccff9ce02ff4e2ddf8ff5846fd67c62e98f0647b..dbb09022ddbab3132a93b65d5b8194ff9af1af2b 100644 (file)
@@ -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)
index 1c82443e35754a19a2e9879dd1587bc431b47433..9ffb53e83b6940b6543f93a18d2bd20fe8435ce5 100644 (file)
@@ -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;
index dc1942b23b425ce45078c5a29bd5ac8fe365569e..abb715f2471e0552f24058b6eab7145b040b8d7a 100644 (file)
@@ -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");