算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
scenario-blender-grease-pencil【设计与创意】Use when drawing or animating with Grease Pencil in Blender 5.x from Python: 2D or 2.5D illustration, frame-by-frame animation, a cutout or part-based 2D character, strokes with pressure, filled shapes with holes, in-betweens and interpolation, onion skin, Line Art outlines, strokes on 3D meshes, NPR or hybrid motion graphics. Also when GP code fails…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Expert Grease Pencil work keeps every drawing editable and redrawable, puts meaning in the structure (which object, which layer, which key) and judges the result in motion. The agent draws through the data API (drawings are curves with attributes), builds characters the way 2D studios rig them, and reviews rendered frames instead of a viewport it cannot flip. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.
REQUIRED BACKGROUND: scenario-blender-expert (execution channel, review loop, 5.2 API changes). Shot planning, boards and animatics: scenario-blender-previs-storyboard. Mesh animation principles: scenario-blender-animation.
Toolkit: scripts/bx_gp.py (tested on 5.2.1). import sys; sys.path.append("<this skill>/scripts"); import bx_gp as G.
fill_id != 0; strokes sharing an id fill together with even-odd holes; hide_stroke hides the outline. Material fill toggles are deprecated and holdout "eraser fills" (Grant Abbitt's 4.4 method) are obsolete.use_lights=True, and lit layers take their ambient from World.color (default 0.05), not from the world node tree, so a flat palette renders at about a quarter of its value.| Input | Changes | Default when silent |
|---|---|---|
| Deliverable | still, frame-by-frame shot, cutout rig, hybrid motion graphics, 2.5D over 3D | frame-by-frame shot |
| Format | resolution, fps, length | 1920 x 1080, 24 fps keyed on twos [added]; social motion graphics: 1080 x 1920, 12 fps, 60 frames (Elijah) |
| Look | flat or lit, line system | flat (unlit layers, Standard); ink line with pressure taper |
| Line system | PEN constant width (matches Line Art, Elijah) or INK_PEN pressure on radius (cartoony, Grant) or PENCIL (pressure on radius and opacity) | INK_PEN |
| Palette | material colors | hex sRGB values, one material per paint bucket |
| Camera | ortho multiplane or perspective | ortho, front view, drawing plane XZ |
| Who edits next | how much stays procedural | a human animator: everything bakes to editable strokes |
cam = G.scene_2d(res, fps, frame_range, ortho_scale) (ortho camera at -Y, Standard, background). Plan the multiplane: which elements are separate objects and their depth order. Compute px = G.px_size(); line width in pixels is 2 * radius / px. GATE: a test render shows the frame; planned line widths are at least 1.5 px [added].G.new_object(name, layers=("Fills", "Shadow", "Lines")) per element (bottom to top, unlit), parent parts, place origins at joints. G.material(ob, name, stroke=hex, fill=hex) once per bucket. GATE: [l.name for l in ob.data.layers] matches intent; G.draw_order(objs) lists front to back as designed.d = G.key(ob, "Lines", frame). Lines: G.add_stroke(d, G.smooth_path(control_pts), radius, pressure=G.taper(n), brush="INK_PEN", material=i); overlap then trim rather than landing ends exactly (Grant). Fills: G.add_shape(d, outline, holes=[...], material=i) on the Fills layer, outline on Lines. Bulk: G.add_strokes(d, specs) (3000 strokes in 0.04 s). Fix shapes with G.grab, G.dissolve, G.edit_op(ob, "stroke_smooth", ...). GATE: G.verdict(G.report(ob)) is clean and a full-resolution G.render_frame shows no gaps, leaks or overhangs.blend_mode='MULTIPLY', opacity lowered, use_masks=True, mask_layers.add(fills); paint the shadow shape past the edge and let the mask clip it; design it blocky (Pablo). Lit look: use_lights=True plus World.color as ambient plus lamps (verified: a sun only scales value on a flat drawing, a point light gives a gradient). GATE: sampled pixels match the palette under Standard.G.key(..., mode="blank"), additive mode="copy", hold or cycle mode="instance" / G.cycle(...), end an exposure with G.end_exposure. Every visible layer needs a key at or before frame_start (Elijah). Cutout motion: key object transforms (FK). In-betweens: check G.match(dA, dB), then G.interpolate(ob, layer, frame, step=2, type, easing) (native, segment around frame) or G.inbetween(ob, layer, a, b, frames, spacing=[...] or ease=...) for the animator's own spacing, clip=True when the pair is different. GATE: G.exposures(ob, layer) per layer, G.render_strip(path, frames) read left to right for timing and pops, G.onion(path, frame, before, after) for arcs and spacing.G.lineart(source, radius=width_m) (the modifier radius is the line width). Strokes on meshes: G.project_to_surface(pts, mesh) and discard strokes with missed or occluder-captured points (Pablo's failure modes). Relight: G.relight(ob, canvas, light_dir, shadow, light). Procedural strokes: Geometry Nodes Curves to Grease Pencil with Instances as Layers off and a fill_id attribute, then apply to bake (procedures P7). Depth reorder without changing the image: G.push_depth(ob, delta). GATE: a moving render (strip or playblast) where the surface does not read as 3D (Weigl) and fills do not poke through (Pablo).| Value | Meaning | Source |
|---|---|---|
radius = half line width (m) | width px = 2 x radius / px_size | verified |
| px_size = ortho_scale / max(res_x, res_y) | ortho; perspective uses depth x 2 tan(fov/2) / res | verified |
| 0.01 m | point radius while the radius attribute is absent | verified |
Line Art radius default 0.0025 m | is the line WIDTH: points get radius/2 | verified |
| Elijah's Line Art thickness 60 (4.x) | about 0.006 m width in 5.2 | [inferred] |
use_lights True, World.color 0.05 | new layers render flat colors at about 25 % value | verified |
set_uniform_thickness(thickness=t) | sets radius t/2 | verified |
| ~60 parts, ~100 rig versions, 2 weeks rig for a lead | Ewilan's Quest cutout scale | Tom |
| 12 fps, 60 frames, ortho scale 8, Subdivision 2 to 3 on shape meshes | hybrid motion graphics | Elijah |
| Stabilizer 40 px / 0.9 bundled, Elijah uses ~60 on demand | only for long curves | Elijah, verified defaults |
| 1 light bounce | enough for stylized relighting | Weigl |
GP anti-aliasing aa_samples 8 | scene.grease_pencil_settings | verified |
Measurable (per object, every stage):
for ob in gp_objects:
print(ob.name, G.verdict(G.report(ob))) # first key vs frame_start, lit-without-light,
# invisible strokes, stray micro-strokes, sub-pixel lines,
# fill/stroke alpha 0, empty masks; info: instances, unused materials
print(G.draw_order(gp_objects)) # front -> back vs the staging plan
print(G.match(dA, dB)) # before any interpolation
Visual (open every image): G.render_frame(path, f) at full resolution for line quality, trims, fill gaps and palette; G.render_strip(path, range(a, b, 2)) for timing, holds and pops; G.onion(path, f, before, after) for spacing and arcs (it includes object motion and modifiers, which the viewport onion skin ignores, Falk and Rik); for 2.5D a sequence, never a still. Score against references/critique.md.
| Mistake | Looks like | Fix |
|---|---|---|
Layers left at use_lights=True with no lamps | flat palette renders dark brown or gray | layer.use_lights=False, or raise World.color and add lamps |
| Fill expected from the material | shape outline only | stroke.fill_id=1, cyclic=True; holes share the id |
hide_stroke=True with fill_id=0 | stroke vanishes | give it a fill id or show the stroke |
attributes.new("radius") on a drawing with strokes | existing strokes disappear (radius 0) | fill defaults first (G.add_strokes does) |
| First key after frame_start | drawing pops in, empty frames at the start | key at frame_start (Elijah) |
| Interpolating different drawings | smeared, twisted in-betweens | G.match; redraw, resample, or clip (Tom) |
| Rigging drawings with armature weights | every redraw breaks deformation | object-level cutout, sculpt as deformer (Tom) |
| Stroke-level depth sorting | artist's layer order destroyed | sort groups (objects or mesh islands) (Weigl) |
| One big fill on a curved surface | fill pokes through the mesh | many small fills near the surface (Pablo) |
| Deleting stray points | stroke splits in two | G.dissolve keeps it continuous (Pablo) |
| Line Art radius set like a stroke radius | outline half the intended weight | Line Art radius = width = 2 x stroke radius |
| Judging 2.5D on a still | looks painterly, reads as 3D in motion | render the sequence (Weigl) |
| Calling draw brushes from a script | Invalid operator call, nothing drawn | write strokes as data; see 5.2 notes |
GREASEPENCIL, data bpy.data.grease_pencils; layer.frames.new(n).drawing.add_strokes([...]); old frame.strokes, point.co, pressure, strength, GPENCIL and object.grease_pencil_modifiers are gone. Modifiers are regular: ob.modifiers.new(n, 'GREASE_PENCIL_NOISE'), Line Art is 'LINEART'.grease_pencil.brush_stroke and sculpt_paint have no exec: called with a stroke list in a live session they log "Invalid operator call" and draw nothing (verified GUI); replayed mouse events via event_simulate also drew nothing. Draw and sculpt strokes are data; headless edit operators (stroke_smooth, stroke_simplify, set_uniform_thickness, stroke_subdivide, outline, join_fills, set_curve_type) run in Edit mode through G.edit_op. In a live session G.gui_trim (lasso Trim), G.gui_erase_box and G.gui_reproject(type='SURFACE') work with a 3D-view override.interpolate_sequence runs headless on the active object and layer, fills only the segment around the current frame and inserts BREAKDOWN keys; easing via type and easing.frames.new or frames.copy onto an existing frame raises "Frame already exists"; get_frame_at(n) returns the key exposed at n (None before the first key).layer.mask_layers.add(layer) is the 5.2 mask API; layer groups gp.layer_groups.new, gp.layers.move_to_layer_group; layers can parent to an object or bone (layer.parent, parent_bone) and carry their own transform.object.grease_pencil_add(type='LINEART_OBJECT') have lit layers; objects hidden from render are ignored by Line Art; lineart_bake_strokes bakes the whole scene range.fill_id on the curve domain.references/expert-notes.md: each expert's principles, judgment and disagreements with timestamps; load when making a style or pipeline decision.references/procedures.md: tested raw-bpy procedures (P1 to P8) and bx_gp recipes (cutout character, frame-by-frame, interpolation, 2.5D); load before writing GP code.references/critique.md: the rubric for judging line, fill, staging, timing and 2.5D renders; load at every review gate.references/sources.md: the five source videos, what each is best for, best timestamps.