Commit Graph
312 Commits
Author SHA1 Message Date
BFlat b08e9df95c Merge pull request #6478 from dotCooCoo/fix/3d-member-opacity-masks
3D: an opacity mask on a group member masks its solids
2026-11-11 07:44:06 +07:00
dotCooCoo 24155e1f75 3D: an opacity mask on a group member masks its solids
A 3D effect on a group built its solids from the members' paths and
paint and left out each member's opacity mask, and the mask of a group
inside the 3D group. Clipping groups, blends and symbol instances
resolved for the effect lost their masks the same way.

A member, or a group inside the 3D group, with an opacity mask now
gets a scope like one with its own opacity or blending mode, and the
mask goes on the group that holds that member's surfaces. Revolve
applies it with the member's opacity and blending mode; Extrude &
Bevel, Extrude & Bevel (Classic), Inflate and Rotate draw each solid
on its own when a mask is present and put the masked members' solids
in a group with the mask. A resolved clipping group, blend or symbol
instance keeps its mask. A disabled mask is ignored.
2026-11-10 14:30:10 -08:00
dotCooCoo 7dfe2d9d2a 3D: a symbol's 9-slice guides and overrides count before its art is placed
The check that refuses a symbol instance before its art is placed
counted only the symbol's own objects. Placing art through 9-slice
guides can make up to 28 objects of each one, and an instance's
dynamic-symbol overrides can turn a path that would be cut at the
guides into clip groups in every cell it reaches. Such an instance
passed the check, and its art was placed in full before the work
limit refused it.

The check now counts the symbol's own objects first, then the most
objects placing makes (NineSliceMap::most_placed) of the art with the
instance's overrides applied, and refuses the instance with the same
message before anything is placed.
2026-11-10 14:24:33 -08:00
BFlat 349385fd56 Merge pull request #6418 from dotCooCoo/fix/3d-more-object-kinds
3D: effects build from clipping groups, blends and symbol instances
2026-11-11 05:13:37 +07:00
dotCooCoo 9fced9388e 3D: a symbol holding too many objects is refused before its art is placed
A 3D effect on a symbol instance placed a copy of the symbol's whole art
before counting it against the effect's work limit, so a symbol far past
the limit was copied in full on every check and redraw before it was
refused. The symbol's objects are now counted first, and a symbol that
would pass the limit is refused with the same message without being
placed.
2026-11-10 13:18:08 -08:00
dotCooCoo 6e3f04032d 3D: the group refusal lists groups and clipping groups among what it builds from
The message for a group member that can't become a solid now names groups
and clipping groups too. docs/three-d.md states that an image left out of a
clipping group also needs no visible fills or strokes of its own.
2026-11-10 11:48:39 -08:00
dotCooCoo d7845e7068 3D: Rotate keeps each clipped fill in its place among the strokes
Inside a clipping group, the fills of a shape were cut into one piece in
the top fill's place. Rotate turns every fill, so with a fill, a stroke
and a fill, the lower fill was drawn above the stroke. With Rotate, each
fill now becomes its own piece in its own place. Extrude & Bevel, Inflate
and Revolve build from the top fill and keep one piece there, also when
an object changes from Rotate to another 3D type.
2026-11-10 11:16:25 -08:00
dotCooCoo a900653c9c 3D: Inflate limits the work of filling its grid
Inflate's grid clipped every ring of the outline in each cell the
outline crosses and tested every edge for each cell's center, so its
work grew with the outline's points times the cells: a comb of 4,000
teeth (16,002 points) took 1.1 to 1.3 s and one of 16,000 teeth 8.8 to
9.1 s. Inflate now counts that work (the edge index's entries, the edges
its queries look at and the ring points its cells clip) and refuses the
shape with "The 3D surface is too complex; simplify the shape" once it
passes 268,435,456, VectorCraft's own value (about 0.65 s). The index
takes at most 16,777,216 entries (64 MiB). Each row's inside test reads
the sorted crossings of its line, and a cell clips only the rings whose
bounds meet it, so a 65,536-point circle inflates in 0.66 s instead of
1.0 to 1.2 s.

The effects test of a stroke past the limits uses 40,000 corners, so its
outline passes the 65,536 points a solid takes.

Docs: docs/three-d.md, docs/mcp.md.
2026-11-10 10:43:24 -08:00
dotCooCoo 711b776fae 3D: deep clipping paths and deep groups in resolved art are refused
A clipping path nested past the depth limit, and a group at the limit
that holds further groups, are refused before they are walked, instead
of being followed to any depth. The clipping path's check follows the
members its outline is built from: the first member of a nested
clipping group and the shown members of other groups. The group check
skips hidden members and treats a compound path as one shape, as the
solid builder does.
2026-11-10 00:59:41 -08:00
dotCooCoo d3055e264e 3D: an image a clipping group hides wholly does not refuse its 3D effect
An image in a clipping group whose bounds lie wholly outside the clipping
path's bounds draws nothing, so a 3D effect leaves it out instead of
refusing it. An image with effects, fills or strokes of its own, or one in
a group with effects inside the clipping group, is still refused, since
those can draw it inside the clip.
2026-11-10 00:44:03 -08:00
dotCooCoo cca1977a64 3D: clipped groups keep their members' transparency and count their pieces
- When a group's geometry effects reshape its members inside a clipping
  group, each member's piece goes back into groups with the opacity and
  blending mode of the groups it was in.
- A clipped shape's pieces (its fills and each stroke) are counted against
  the 4,096-object and work limits before they are cut.
2026-11-10 00:36:07 -08:00
dotCooCoo bad4b986f5 3D: resolving art for a 3D effect has a work limit and keeps geometry effects
- Resolving the symbol instances, blends and clipping groups a 3D effect
  builds from counts every object it visits or makes and stops past
  262,144 with an error. Nested symbol instances that end in hidden art
  are refused instead of being expanded at every level. Each object is
  visited once; a group is no longer walked again for each level above it.
- A symbol instance, blend or clipping group inside a 3D group keeps its
  appearance, so its geometry effects reshape the art it shows. A nested
  group's geometry effects now reshape its members inside a 3D group too,
  and inside a clipping group they apply before the clip cuts the art.
- Hidden members and guides of a group inside a clipping group build no
  solid.
- Tests cover an empty clipping group, a blend set to no steps, an
  instance of an empty symbol, a symbol holding a clipping group, the
  paint-copy budget across resolved art, and art past the limits drawing
  as it does without the effect.
2026-11-10 00:29:58 -08:00
Julius van der Vaart 8508c9c620 Merge branch 'main' of github.com:storytold/vectorcraft into 5421-line-arrowhead 2026-11-10 09:13:56 +01:00
dotCooCoo e579c62b2f Merge main into fix/3d-more-object-kinds 2026-11-09 23:24:14 -08:00
dotCooCoo f0dad6010f 3D: effects build from clipping groups, blends and symbol instances
Extrude & Bevel, Extrude & Bevel (Classic), Inflate, Rotate and Revolve
now apply to a clipping group (or a layer with a clipping mask), a blend
or a symbol instance, alone or inside a group or layer. The solid is
built from the art the object shows: the art inside the clipping path
(fills cut to the clip, strokes as their outlines cut to the clip), the
blend's keys and steps, and the symbol's art where the instance places
it. Validation, the canvas, bounds, Expand Appearance, Rasterize and
assets.add3D read the same art from the document.

An image is refused, alone or in a group, with a message to trace it
first with Object > Image Trace > Make. A missing symbol, a symbol that
holds an instance of itself, art nested more than 64 levels deep and
more than 4,096 objects are refused. The refusal messages change, and
their rows change in every catalog.

Docs: docs/three-d.md, docs/revolve.md, docs/mcp.md,
docs/target-app-parity.md.
2026-11-09 23:22:20 -08:00
BFlat c86c535389 Merge pull request #6402 from dotCooCoo/feat/text-glyph-bounds
Type: an optional setting measures point type by its glyph outlines
2026-11-10 14:14:00 +07:00
dotCooCoo 860149a981 Merge main into feat/text-glyph-bounds 2026-11-09 22:03:17 -08:00
dotCooCoo 156613941a Type: glyph bounds take in underline and strikethrough bars
With Measure Type by Glyph Bounds on, point type is measured by its
glyph outlines and its underline and strikethrough bars, as the canvas
draws them. A typed W or H gives what is drawn that size, and underlined
or struck-through spaces are measured by their bars instead of the em
box.

The texts now say that each point type object selected as an object
(not inside a selected group) is measured this way, that moved type
snaps by its ink box, and which Arrange Tools commands follow the
option: Swap Objects, Replace with Top Object, Set Size of Each and
Sort Objects. Arrange in Grid and Group Overlapping Objects keep
measuring visual bounds.
2026-11-09 21:53:22 -08:00
Julius van der Vaart 89482ba940 Stroke: Line Arrow's arms match the line at 100 % only from 0.25 pt (#5421) 2026-11-10 06:48:16 +01:00
dotCooCoo c703cc5650 Revolve: a face drawn on its own keeps its fill or stroke's opacity and blending mode
A face of a group member that paints nothing (its fill or stroke at 0% opacity) was drawn without a group, so its zero opacity was lost and the member drew fully opaque. Such a face now keeps a group with the opacity and blending mode of its fill or stroke.
2026-11-09 21:37:27 -08:00
BFlat 04edd114b1 Merge pull request #6395 from dotCooCoo/fix/revolve-group-depth-order
Revolve: a group's faces keep one back-to-front order when a member is transparent
2026-11-10 12:27:32 +07:00
Julius van der Vaart fe487d8226 Merge branch 'main' of github.com:storytold/vectorcraft into 5421-line-arrowhead
# Conflicts:
#	docs/target-app-parity.md
2026-11-10 06:17:42 +01:00
dotCooCoo a1949095c6 Type: an option fits a text object's bounds to its glyph outlines
Measure Type by Glyph Bounds (`typeGlyphBounds`, off by default) is in
Preferences > Type > Glyph Bounds and the Transform panel menu. With it
on, the bounding box, the Transform, Control bar and Properties X, Y, W
and H, `object.setBounds`, the transform tools' reference point and the
Arrange commands measure point type selected on its own by the ink of
its glyphs instead of its em box. A typed W or H scales the live type
until its ink has that size.

- The engine caches each type object's ink in text space with its
  layout bounds; the cache is not saved.
- Use Preview Bounds adds the characters' strokes, and type with
  geometry effects is measured as drawn. Skewed type is measured by its
  outlines.
- Type that draws nothing, area type, type on a path and type inside a
  selected group are measured as before. Align and Distribute keep
  following Align to Glyph Bounds.

Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md,
object.setBounds params text.
2026-11-09 21:07:55 -08:00
dotCooCoo af2ee68bf3 Revolve: a group's faces keep one back-to-front order when a member is transparent
When any member of a revolved group had an opacity below 100% or a
blending mode, every member's surface was drawn whole in member order,
so the opaque members lost their shared depth sort and a transparent
member drew entirely over or under the others.

Every member's faces are now sorted into one order. Opaque faces go in
that order on their own. A transparent member's faces share one group
with its opacity and blending mode, and its scopes group them further,
unless a face of another member that overlaps them on the page lies
between them in the order; only there is the member split. Faces
overlap when the convex hulls of their outlines come within the 0.75 pt
seam stroke of each other. Faces that paint nothing never split a
member. Past 4,194,304 overlap tests on one group, each further face
of such a member is drawn as a part of its own.

Docs: docs/revolve.md.
2026-11-09 20:49:13 -08:00
dotCooCoo 55e6c0a5b1 Revolve: a hidden Revolve's profile is measured before the effects after it
effect.setParams checks a Revolve before the edit, so turning a hidden Revolve back on measured
its profile after every geometry effect on the object, those after the Revolve included, and could
refuse a profile that draws. The check now measures the profile after only the geometry effects
before the Revolve, hidden or shown, as drawing does. An object holds at most one Revolve, hidden
ones included, so the check finds the same one that draws.
2026-11-09 19:47:40 -08:00
BFlat 4dbaa10e0a Merge pull request #6365 from dotCooCoo/fix/issue-3617
Revolve: type, groups and layers revolve, and the profile and face limits are higher
2026-11-10 10:23:52 +07:00
BFlat 695721f9df Merge pull request #6380 from dotCooCoo/fix/preview-bounds-butt-line
Transform: butt-capped lines measure to their end points with Use Preview Bounds
2026-11-10 09:59:06 +07:00
dotCooCoo 2e81086c38 Revolve: a nested group's opacity applies once to its members together
The opacity of a group inside a revolved group was multiplied into each member's surface, so where members overlap they drew darker than the group does without the effect. A member or group with opacity below 100% or a blending mode other than Normal now holds its members' surfaces in one group with that opacity and mode.
2026-11-09 18:30:41 -08:00
dotCooCoo bed632cc65 Transform: butt-capped lines measure to their end points with Use Preview Bounds
With Use Preview Bounds on, the visual box of a butt-capped stroke on an
open path grew by half the stroke weight past each end of the path, where
a butt cap paints nothing. A 2 pt vertical line set to H: 10 mm in the
Transform panel drew about 9.3 mm long.

The box of an open subpath with butt caps no longer reaches past its
ends. A straight segment's box comes from the four corners of its stroked
rectangle, and a curve's box comes from the stroke at its ends and where
its tangent is square to an axis. Bevel joins, and miter joins the limit
bevels, are measured by the stroked corners, and round joins by the arc
they paint outside the turn, instead of half the weight all round. Round
and projecting caps, arrowheads, closed paths and miter tips measure as
before. Two cases can still measure larger than the stroke paints: a
curve that bends more tightly than half the weight, and a butt-dashed
path whose first or last dash is a gap, which is measured to the path's
ends.

A horizontal or vertical butt line now measures one stroke weight less
along its length in W or H, and Align places it by its end points. A
slanted line's box shrinks by less: the stroke's corners still reach
past the end points across the line. object.setBounds measures as drawn,
after each scale it tries, a butt-capped open path with a segment at a
slant, symbol instances' art included.
2026-11-09 18:16:48 -08:00
dotCooCoo d8d73495bb Revolve: a group member's blending mode applies to its surface
A member of a group with Revolve drew its surface in Normal mode whatever its own blending mode or that of a group it is in. Each mode now applies once, to the member's surface or to the surfaces of the group that has it, nested as the groups are. Expand Appearance keeps every face of a blended member.
2026-11-09 18:05:17 -08:00
dotCooCoo cddebdf7f6 3D: a group's fill or stroke takes no 3D effect
Revolve, Extrude & Bevel, Inflate and Rotate on a group or layer draw only as the group's own effect. On the group's fill or stroke they drew nothing, yet effect.apply and effect.setParams accepted them. Both now refuse that placement.
2026-11-09 17:51:22 -08:00
dotCooCoo bb142cc063 Revolve: the fills and strokes of one object share the paint-copy budget
Each fill or stroke with its own Revolve counted the copies of its paint from zero, so an object with several of them could draw past the 256 MiB budget. They now share one count, and past it the source path draws, as for the members of a group.

effect.apply and effect.setParams on a fill or stroke now check the Revolve in that item's paint, after the item's geometry effects before it, together with the object's other items that carry a Revolve. Before, they checked the object's top fill.

docs/revolve.md marks the axis position, type handling and member paint as VectorCraft's own rules, and docs/mcp.md says how the budget is counted.
2026-11-09 17:19:13 -08:00
dotCooCoo 35db931810 Fix #6316: a group copies each member in place when a member makes more art
A group with a visible member that carries a geometry, Pathfinder or 3D effect, also in an opacity mask, keeps its previous copies. Copies stop at the budget with what each copy's own effects make, and a group's copies count the art of opacity masks.

The PDF and SVG tests for a group's image copies now expect the original on top.
2026-11-09 16:24:13 -08:00
dotCooCoo b07c808093 Revolve: type, groups and layers revolve, and the profile and face limits are higher
Type sweeps as its glyph outlines and stays editable. On a group or layer every visible member sweeps about one axis on the edge of the members' bounds together, each in its own paint.

A surface now takes up to 16,384 profile points and 163,840 faces. Curves take fewer samples, down to 4, when a surface passes those limits, and the command reports the failure below that, after the geometry effects before the Revolve and with the cumulative size of its paint copies counted. Mesh export turns the walls of holes toward them. Expand Appearance works on type with Extrude as well as Revolve.

Fixes #3617
2026-11-09 16:09:03 -08:00
dotCooCoo 59304f9964 Fix #6316: a Transform effect's copies of a group are whole groups below the original
A Transform effect with copies on a group copied each member on its own,
so one member's copies lay over the fills of the next member. Each copy
is now a whole group, the original on top and each copy below the one
before. A path's copies follow the same order. Guides are not copied,
and the copies stop at the node limit. A group with its own fill or
stroke, a clipping group and live type keep their previous copies.
2026-11-09 14:27:25 -08:00
Julius van der Vaart 0153a5d33e Stroke: document where the stroke stops under a local arrowhead (#5421) 2026-11-09 21:17:41 +01:00
Julius van der Vaart 7d73882d3a Stroke: Line Arrow arrowhead (#5421)
Add Line Arrow, a 41st generated arrowhead: two arms as thick as the line
(at 100 % scale) meeting at right angles in a sharp tip, cut square at their
ends. It is saved by the new name `LineArrow`, appended so existing names
stay stable, and listed third in the arrowhead menus after Arrow (open).

Its arms spread four weights either side of the line, twice as wide as the
other heads, so Arrowhead now states its own reach and half width. Visual
bounds and clicks on the canvas (head_reach, head_contains) use them instead
of one fixed 4.6 x 2 box; every other head keeps the sizes it had.

Tests: the outline test checks each head against its own reach and half
width; a new test checks the line arrow's tip, corners, open middle and arm
thickness; outset_covers_arrowheads checks that the wider head grows the
outset; the saved-name list includes LineArrow.
2026-11-09 20:47:31 +01:00
dotCooCoo bd5ea2a1bf Merge main into feat/plugin-effect-paint 2026-11-08 17:59:33 -08:00
dotCooCoo d6a91b6142 Merge main into feat/plugin-effect-paint 2026-11-08 17:26:35 -08:00
BFlat 44a37b0729 Merge pull request #6233 from dotCooCoo/fix/fitted-gradient-turns-with-transform
Effects: fitted gradients turn with the Transform effect
2026-11-09 08:58:30 +08:00
BFlat e8dbb22d4c Merge pull request #6174 from dotCooCoo/feat/3d-classic-extrude-bevel
3D (Classic): Extrude & Bevel (Classic) extrudes art with Wireframe, No Shading, Diffuse and Plastic surfaces
2026-11-09 08:53:49 +08:00
BFlat 64280c64d3 Merge pull request #6152 from dotCooCoo/perf/contours-cover-lists
Path ops: boolean contour passes test the shapes covering each face instead of a flag per shape
2026-11-09 08:03:59 +08:00
dotCooCoo e2bac253f1 Merge main into fix/fitted-gradient-turns-with-transform 2026-11-08 15:56:12 -08:00
BFlat 8fc4464542 Merge pull request #6236 from dotCooCoo/fix/placed-gradient-follows-warp
Effects: placed gradients move and scale with the Warp effects
2026-11-09 07:47:08 +08:00
dotCooCoo 83f1333d1c Effects: a Transform effect that flattens the object leaves a fitted gradient fitted with its options on
With Scale Strokes & Effects or Transform Patterns on, the path and each copy are moved by Node::transform, which fixed a fitted gradient to its fit before a scale of 0 flattened it to one color. Scaling::keep_fit_when_flat keeps the gradient fitted under a map that flattens the object (node::flattens, the gate fixes_fit already used), and the Transform effect's path transforms set it.

docs/target-app-parity.md also says that the Gradient tool still shows a fitted gradient's vector at its unturned fit.
2026-11-08 15:38:07 -08:00
dotCooCoo a7fc513a0c Merge main into feat/3d-classic-extrude-bevel 2026-11-08 15:34:06 -08:00
dotCooCoo 0945eaf2b3 Merge main into perf/contours-cover-lists 2026-11-08 15:00:56 -08:00
dotCooCoo 5c17cfd038 Tests: warped placed gradients check each warp's bounds and movement
The Warp gradient test now compares the baked bounds with the square's
outline mapped through warp_point, for every style and parameter set. It
requires the gradient to move exactly when those bounds move, and pins
39 of the 45 cases as moving the bounds.
2026-11-08 14:20:47 -08:00
dotCooCoo e4d7e588ab Merge main into feat/plugin-effect-paint 2026-11-08 13:48:07 -08:00
dotCooCoo e4e6e0d382 Plug-ins: a painted piece linked to a global swatch follows its color
A piece linked to a global swatch is drawn in the swatch's current color at its own tint, and kept art and rasters follow a color or spot edit and its Undo. A value of `paintOutput` other than true is refused by a translated error. Tests in the effects crate pass the new document argument.
2026-11-08 13:45:17 -08:00