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.
This commit is contained in:
Tamito Kajiyama 2012-04-07 17:28:09 +00:00
parent 8ea8b6a1ba
commit e94abda66d
6 changed files with 163 additions and 37 deletions

@ -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":

@ -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:

@ -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;

@ -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)

@ -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;

@ -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");