Effect › Distort › Glass… offered only its four built-in textures. The
reference app's Texture menu also has Load Texture, which takes an
image file as the glass surface.
The Glass dialog now has a Load Texture… button. It reads a PNG, JPEG,
GIF, WebP, TIFF, BMP, PSD or PSB file within the pixel allowance the
renderer draws images in, and the Texture menu gains Loaded Texture.
The effect stores the image's gray levels in `textureImage` as a
grayscale PNG in base64, so the document saves the texture with the
effect, and copies of the effect carry it. effect.apply and
effect.setParams take `textureImage` as {path}, {name, dataBase64} or
the stored string; any other value is an error.
The texture lies one image pixel per point at Scaling 100%, centered on
the object and repeated. Each pixel's luma is the surface's height,
from 0 for black to 1 pt for white, and a transparent pixel counts as
middle gray. A Glass without `textureImage` draws and saves as before.
Docs: docs/mcp.md (Glass textures), docs/effects-parity.md,
docs/gaps.md, docs/target-app-parity.md.
Unit fields and length strings read a percentage operand as a share of
a value. After * or /, the percentage scales the term before it:
3cm*50% is 1.5 cm and 3cm/50% is 6 cm. After + or -, it adds or takes
away that share of everything before it: 50pt+25% is 62.5 pt and
100pt+20pt-50% is 60 pt. Before this change these expressions read as
nothing, and the field kept its old value.
The parser now splits a sum at its last + or -, so a percentage applies
to the whole sum before it. Text without % gives the same values as
before, apart from float rounding. A percentage on its own (50%) still
reads as nothing, and percent and degree fields still read % as their
suffix.
Command length strings use the same parser: file.new's width and
height, the distance params and the length preferences.
Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
Exports bake live effects into plain art, and a Transform effect on
type or on its group or layer turned the type into glyph outlines, even
when the effect only moved it or made moved copies. A PDF reader could
not select or search that text.
The export bake now keeps such type as type, moved into each place the
effect puts its outlines. It does this for Transform effects without
scale, rotation, reflection or Random, for type whose characters are in
solid colors without underline or strikethrough, and that has no fills
or strokes of its own, inline graphics or perspective, and when the
copies do not overlap. Type under such an effect on its group or layer
also needs no transparency or effects of its own, and the group or
layer needs no fill, stroke or Pathfinder effect of its own. The export
still outlines other type under geometry effects, and the canvas and
Expand Appearance still draw outlines.
Docs: docs/mcp.md, docs/target-app-parity.md.
A PDF, PDF-compatible .ai or .ait opened from its pages had an empty
File Info. Nothing read the file's document information
dictionary or its XMP packet, so file.info reported no author,
description or keywords, even for a PDF that VectorCraft exported.
vectorcraft_pdf::file_info reads both. The author, description,
keywords, copyright status, notice and URL and the created and modified
dates come from the XMP packet (dc:creator, dc:description, dc:subject
or pdf:Keywords, xmpRights:Marked, dc:rights, xmpRights:WebStatement,
xmp:CreateDate, xmp:ModifyDate), else from the dictionary's Author,
Subject, Keywords, CreationDate and ModDate. document.open,
file.newFromTemplate and file.revert put each value through file.info's
checks and leave out a value that file.info refuses. A packet that is
not well-formed XML leaves the dictionary's values. File Info does not
set the title, and a file restored from the document it carries keeps
that document's File Info. vectorcraft-pdf depends on roxmltree 0.21,
which usvg already brings into Cargo.lock.
Docs: docs/mcp.md (File Info), docs/file-format-parity.md,
docs/target-app-parity.md.
When type had used a font, removing the font while the app ran left
it in the font menus, and the type kept drawing in it until the app
restarted. The font list is the loaded faces plus the scanned catalog,
and a scan replaced the catalog but kept every loaded face.
A scan now drops each loaded face whose file it did not catalog, with
the face's cached outlines. A family with no face left leaves the font
lists, and type set in it lays out in the fallback font and is
reported missing, as when a document opens without the font. When the
font is installed again, the next scan lists it and the type draws in
it again. Bundled fonts and fonts added as bytes have no file and
stay.
Docs: docs/mcp.md, docs/target-app-parity.md.
Spell out dataset.list/capture/update/delete/rename and distinguish the MCP
command_batch tool from the engine command.batch Actions play.
Co-authored-by: Cursor <cursoragent@cursor.com>
Play an action on every file in a folder or every data set, with destination
and Override Open/Save/Export. Actions panel and File › Automate › Batch…
open the dialog; new strings in pt-br.
Co-authored-by: Cursor <cursoragent@cursor.com>
VariableKind::Graph binds a graph root; dataset rows supply CSV as graph.setData.
Make Graph Dynamic, Variables dialog, capture/apply, and pt-br for the new strings.
Co-authored-by: Cursor <cursoragent@cursor.com>
Load and save variable datasets as CSV for data-merge pipelines, and
translate the new Variables panel commands in Brazilian Portuguese.
Co-authored-by: Cursor <cursoragent@cursor.com>
# Conflicts:
# docs/target-app-parity.md
A step whose id is draw_path or draw_shape runs that tool with its params,
checked as tools/call checks arguments; its result is the tool's, so later
steps can use "$N.id". The reply lists the ids the drawing steps made
under created. The unknown-argument check is shared with tools/call.
The drawing tools style the object they draw in the same call: dash, cap
and join go to the stroke.set that strokeWidth already sends, opacity and
name to object.setProps, and effects are applied in order as apply_effect
does. The options are checked (effect ids against effect.list) before
anything is drawn, so a bad one leaves no half-styled object behind.
A gradient given to several objects fits each one's own bounds. With
span: "selection", a linear or radial gradient without its own vector is
placed once on the union of the targets' bounds, at its angle and aspect,
and every target gets that vector in document coordinates, so the ramp
runs on across them. A solid colour, a swatch, a freeform gradient or an
explicit start/end with it is refused before anything changes; the next
object drawn still fits the gradient to itself.
The Curvature tool built its curve as a uniform Catmull-Rom: both handles of a
smooth point a sixth of the chord between its neighbours, the ends of an open
path without handles. Uneven spacing overshot or looped, a J or U from three
points bent sharply at the middle with straight legs, and the end segments
looked flat.
- `curvature_spline` (draw2.rs) makes each stretch of three points follow the
circle through them: a smooth point's tangent is that circle's
(`smooth_handles`), each handle `chord / (3 cos²(α/2))` long, α between
handle and chord capped at 90° (`arc_handle`). The ends of an open path
mirror their neighbour's tangent in their chord (`end_handle`). A turn
tighter than 30° (`TIGHT_TURN`) shortens its handles, so a narrow V comes to
a point. Degenerate points become corners. `path.curvature` and the rubber
band use it.
- `catmull_rom` is unchanged for the Pencil and Paintbrush fitting.
- `bend_last` (from: smooth), `curvature_move` on a smooth point, the point
`curvature_insert` adds on a straight segment, and `path.convertAnchor` to
smooth without x/y (`curvature_smooth`) use the same rule.
- `curvature_extend` gives the new end its end handle unless Alt-clicked
(`corner` param on `path.curvatureEdit`).
- Neighbours of a point dragged on an existing path keep their tangents (a
Pen-drawn shape isn't reshaped); the path being drawn re-curves throughout.
Document Info › Objects counts the paths but didn't say how long they are,
which sign and print work reads there (an LED strip along the letters, a
cut line). `document.info` now adds up the length of every path it counts,
the pieces of compound paths included and guides left out, and returns it
as `pathLength` (pt); the Objects section shows it as Path Length in the
document's units, right after the Paths count, so Selection Only gives the
selected paths' length. The label is translated in the catalogs that
cover the menus; pt-br only takes strings the UI itself shows.
Copy and Cut made the system clipboard's formats in the frame that ran
them. The formats include the SVG markup and a PNG of the selection at
1 px per pt, up to 4,096 px a side. In a release build on Windows, Copy
of 50,002 rectangles on a 1600 x 1200 pt page took 1.00 s in its frame.
The thread that writes background saves and exports now makes the
formats from a copy of the clipboard, and the app writes them in a
later frame. The same Copy takes 0.18 s in its frame, and the SVG and
PNG are the same bytes. While that thread has a save, an export, a print or
a recovery copy to finish, Copy and Cut make and write the formats at
once. A later Copy or Cut, or text copied in a field or with the Type
tool, drops formats that are not written yet. Until they are written,
Paste on the canvas takes the internal clipboard, and while a text
field or the Type tool has the keyboard the app waits for them.
Docs: docs/mcp.md, docs/target-app-parity.md.
Object › Artboards › Fit to Artwork Bounds and Fit to Selected Art ran
artboard.fitToArt and artboard.fitToSelection without an index, so
they resized artboard 1 whichever artboard was active. The Artboards
panel menu's Fit to Artwork Bounds already passed the active artboard.
The app now passes its active artboard as the index of the two
commands when a call names none, as it passes the artboard to Align to
Artboard, All on Active Artboard and Paste in Place, in Front and in
Back. That covers the menu items, keyboard shortcuts, the command
palette and MCP's run_command in remote mode. A batch passes its
artboard as the index to their steps that name none, and a recorded
action leaves the index out, so an action resizes the artboard that is
active when it plays. The params texts of the two commands and of
command.batch describe where the index comes from.
Docs: docs/mcp.md, docs/target-app-parity.md.
The Layers panel narrowed its rows by name (Search All) or to layers
alone (Show Layers Only).
The panel now has a Filter button at the right of Search All. Its
popup holds Clear All and a check box for each object kind under By
Object (Paths, Shapes, Text, Images, Effects, Groups, Linked Files,
Guides) and for each row state under By Layer (Locked, Hidden). A row
stays when it is of a ticked kind, is in a ticked state and has the
search text in its name, and a section with nothing ticked keeps every
row. The rows that hold a kept row stay too, as they do for the
search. The button is lit while a filter is on. The filter lasts the
session and does not change the document, its open rows or the
selection. ui.layersFilter {objects?, layers?} sets it. The button
shows a new icon, assets/icons/dc-filter.svg, with its row in
ASSETS.md.
The panel lists its rows again when the filter changes, as it does
when the search changes. On a layer of 5,000 groups of 10 paths, a
release build lists the rows for a filter in 2.9 to 3.6 ms.
Docs: docs/mcp.md (Layers panel), docs/target-app-parity.md.
In the View menu, Reset Rotate View was the only rotation item. The
status bar's angle menu set its twelve preset angles by writing the
view's rotation itself, and no command took an angle to turn the view
to.
View › Rotate View now lists the status bar's angles (0°, 15°, 30°,
45°, 60°, 90°, 180°, -15°, -30°, -45°, -60° and -90°) and checks the
one the view is turned to. The new UI command `view.rotate {angle}`
turns the view to any angle about the center of the canvas and
returns the angle it set, normalized to (-180, 180] as a Rotate View
drag normalizes it. The menu's items and the status bar's angle menu
run it from one list of angles; the status bar sets the same angles as
before. Without a document the command returns "no document" and the
items are disabled. Run from the command palette, which passes no
angle, Rotate View opens a dialog that asks for the angle, with the
view's current angle filled in.
The de, it, ja, ko, pl, pt-br, ru, uk, vi and zh-hant catalogs have a
new "Rotate View" row. The strings the catalogs cover leave out the
angle labels, as they leave out Type › Size's point sizes.
Docs: docs/mcp.md, docs/target-app-parity.md.
File › Save as Template always wrote a .vctemplate. The save panel
offered only that type, file.saveAsTemplate ignored its format param,
and a name typed with .ait got .vctemplate added. New from Template
already opened a .ait as an untitled document.
Save as Template now also writes a .ait: the PDF-compatible file Save
As writes for .ai, carrying the document. The desktop save panel lists
VectorCraft Template first and the .ait type beside it, and the web
build's Save as Template dialog gets a Format menu with the same two
types. file.saveAsTemplate writes .ait for format: "ait", or for a path
ending in .ait when no format is given, and takes the .ai options;
file.formatOptions answers for ait. .vctemplate stays the default.
Exports and Export As still do not write .ait.
Docs: docs/mcp.md, docs/file-format-parity.md (.ait row),
docs/target-app-parity.md.
A PDF page's UserUnit (PDF 1.6) sets the size of its user space unit
in multiples of 1/72 inch. The PDF reader has applied it since #1894, so
a page with /UserUnit 10 and a 36 × 50.4 media box opens at 360 × 504
pt. A .ai whose editing data has its artboards in the page's own units
then no longer matched its PDF part. It opened from the PDF part,
without its layers, its hidden objects and the art outside its
artboards, and with its artboards in a row instead of where the
editing data puts them. document.pdfInfo gave such a page's size in
points and its boxes in the file's numbers.
Editing data whose first artboard has the page's size in the page's
units, and not in points, is now compared with the PDF part scaled
down by the UserUnit. The document read from it is then scaled up by
the UserUnit, with its stroke weights, dashes, effects and pattern
tiles. When the layers can't be read, the artboard layout taken from
the editing data is scaled the same way. document.pdfInfo gives the
boxes in points, as it gives the size.
Docs: docs/mcp.md, docs/target-app-parity.md.
The reference app resets tracking and kerning to 0 with Cmd/Ctrl+Alt+Q.
VectorCraft had no command for it.
type.resetTracking (Reset Tracking and Kerning, default Cmd+Alt+Q) sets
tracking to 0 and kerning to Auto, which the Character panel shows as 0,
in the text the Type tool has selected, or else in all the text of
every selected type object. At a caret it resets only the kerning of
the character before it, where Alt+Left/Right kerns. It is one undo
step. The range logic of type.step moves into a shared type_ranges.
When a call gives ids, type.step and type.resetTracking change all the
text of those type objects even while the Type tool edits text, as
paint.setFill does. Before, type.step changed the Type tool's selected
text instead.
Docs: docs/mcp.md, docs/target-app-parity.md.
A Ctrl/Cmd+Alt-click on a row's eye in the Layers panel ran Hide Others
or Show All Layers, as an Alt-click does. It now runs Outline Others for
the row's layer (layer.outlineOthers), or Preview All Layers
(layer.previewAll) when every other top-level layer is in Outline
already. Each is one undo step. Alt-click and Ctrl/Cmd-click on an eye
keep their actions.
Docs: docs/mcp.md, docs/target-app-parity.md.
document.export, document.serialize and document.exportSelection write
a PDF-compatible .ai (format: "ai"), and document.formats lists ai as
writable, but none of their params texts named it. The params text of
document.exportSelection named only png, jpg, webp, svg, svgz and pdf,
although the command writes all 20 writable formats. The description
of the MCP export tool and its row in docs/mcp.md named every format
of the tool's format enum except ai, and the range text and the row
said that only PDF writes several artboards.
The three params texts now list every format their command writes,
and document.export's range and useArtboards texts name .ai beside
PDF. The MCP export tool's description names ai, and its artboard and
range texts say that .ai, like PDF, writes one page per artboard. The
range text and the row also say that SVG, SVGZ and EPS write one file
per chosen artboard, and that rasters, DXF, EMF and WMF do so with
options.useArtboards: true.
Docs: docs/mcp.md (the export tool).
The Print section of docs/mcp.md said that File → Print writes the job
as a print-ready PDF and that there is no printer driver. The desktop
app sends the PDF to the system's print service, the web build opens
the browser's print dialog on it, and the PDF File entry in the
Printer list saves it. The section now says so, points to Printing,
and marks the `file.print` reply that follows as the headless one.
Export Selection, Asset Export, the Assets tab of Export for Screens
and the rasterized art of CSS Properties cropped each object to its
visual bounds, which leave out live effects. A path with an 8 pt drop
shadow was exported with the shadow cut off at the path's edge, and
a group's own shadow was cut off the same way.
The crop is now the bounds the renderer measures for what it draws
(`vectorcraft_render::effect_bounds`): `painted_bounds` without its
room for brush art. A brushed path is cropped to its visual bounds as
before, and a Shaper group leaves out its transparent source art as
its visual bounds do. `assets.list` reports the same crop. The
renderer measures a shadow's or glow's reach on every side of the
object, so the image also has transparent room on the side away from
an offset shadow.
Docs: docs/mcp.md, docs/target-app-parity.md.
PDF, EPS, EMF/WMF and raster exports, and SVG exports that outline or
embed fonts, warn when type is set in a font family that isn't
available, but file.print and print.preview gave no such warning. A job
in a missing family printed in Source Sans 3 with no warning, and the
Print dialog's Summary showed none.
Both commands now add the exports' warning for the type that prints:
visible type that fits its frame or path, on the layers that Print
Layers picks, where what it paints (its effects applied) lands on the
paper of an artboard or tile that prints, inside its clipping paths.
The type in the symbols, inline graphics, patterns, scatter brushes,
opacity masks and placed documents of the art that prints counts too.
file.print reads the fonts before raster effects and Print as Bitmap
turn type into images, for PDF and PostScript output.
Docs: docs/mcp.md (Print), docs/target-app-parity.md (Fonts).
dataset.select applied a row's values in the order the variables were
defined, and it skips an object inside a hidden group. When a row
showed a group and set an object inside it, and the object's variable
was defined first, the object was skipped while the group was still
hidden: one selection applied the row in part, and selecting the row
again applied the rest. A text variable on type inside the group was
skipped the same way, and a row that hid a group skipped the objects
inside it whose variables were defined after the group's.
dataset.select, dataset.next and dataset.prev now apply the visibility
values that show objects first, outermost object first, then the other
values in definition order, then the visibility values that hide
objects, innermost object first. Of two variables of the same kind on
one object, the one defined last still wins.
Docs: docs/mcp.md.
A data set can hold values for only some variables: `dataset.new` and
`dataset.set` store only the values they are given, and selecting the
data set leaves the other variables alone. Update Data Set replaced the
active data set's values with the values the art reports for every
variable. After one Update, a data set that set only a visibility
variable also held the current text of the bound type, and selecting it
again wrote that text back. Update also dropped a variable's value when
its objects were locked or inside a hidden or locked layer or group,
because such objects report no value.
`dataset.update` now writes the art's current value for each variable
the active data set holds, and keeps the data set's value when the art
reports none. A variable that the data set does not hold stays out of
it, including one defined after the data set was captured;
`dataset.update {all: true}` also adds the other variables that the art
reports a value for. The reply adds `left`, the variables it did not
write. The Variables panel's Update Data Set button and menu item run
it without `all`.
Docs: docs/mcp.md, docs/target-app-parity.md.
With Align to Artboard chosen, the Align panel's six Distribute Objects
buttons and its two Distribute Spacing buttons acted on the selection.
object.distribute and object.distributeSpacing took no to or artboard
param. Distribute Objects kept the outermost objects where they were
and did nothing with two objects, and Distribute Spacing kept the first
object's left or top edge and the last object's right or bottom edge
where they were.
Both commands now take to ("selection", "artboard" or "key") and
artboard, and the panel sends its Align To choice as to. When to is
"artboard", the first object's left or top edge goes to the artboard's
left or top edge, and the last object's right or bottom edge goes to
the artboard's right or bottom edge. Distribute Objects spaces the
other objects evenly by its button's line, and two objects are enough.
Distribute Spacing without a spacing value makes the gaps between the
objects equal. Both commands use the same artboard as object.align.
The app passes the active artboard to both commands, and recorded
actions leave it out, so an action plays on the artboard that is
active when it plays. With "selection" or "key", or without to, both
commands work as before. Distribute Spacing with a spacing value still
keeps the key object in place with each of the three to values. With
anchor points selected, both commands still space the points among
themselves and do not use to.
Docs: docs/mcp.md, docs/target-app-parity.md.
The Legacy .ai Options dialog gains Hidden Objects (Hidden Layer, Omit, Make Visible) and Text
(Editable, Outlines), translated in every catalog. Docs: type and hidden objects in the legacy
.ai paragraphs, the restored revision rows and roadmap row.
Use automatic vector smooth shading without a separate control.
Enable Cap by default and keep continuous rim curves and bounded seam joins.
Integrate the shared dialog with the other three 3D effects.
A set whose entries sit in long, deeply nested sections kept every section's names again for each entry (up to hundreds of megabytes from a 4 MiB file); the names kept over all entries now stop at 8 MiB. Name matching compares character pairs in linear time, so a 20,000-entry set of long names is matched in about 2 s instead of 17 s.
A shortcut set another app saved as slash-name text (a .kys file whose
text starts with a slash name and "{", such as "/Menus {") opens in
Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts ›
Import… in the desktop app, or from a drop on the window, whether or
not a document is open. The dialog proposes a target for an entry when
an earlier import kept one for that name, or else when one tool,
command or panel matches the name clearly by its id or by one of its
labels. It decodes the file's key and modifier codes from the codes
earlier imports kept, from the example in the AiCommandPalette README
(/Key 48 for the 0 key, with /Modifiers 0 taken as no modifiers), and
from VectorCraft's default shortcuts where at least two entries vote and
four votes in five agree. The user picks the remaining targets, presses
the key for each unknown key code or marks it No equivalent, and chooses
the modifiers for each unknown modifier code. Apply assigns the ready
rows with the Keyboard Shortcuts rules: a tool takes a single key,
optionally with Shift. It goes through the rows from the last one back
and leaves a row out when a later row it applies has the same shortcut
or the same tool, command or panel. Keyboard Shortcuts then shows the
result as unsaved changes until the user clicks OK. Copy Report copies
the set's name, the rows Apply leaves out and the parts of the file that
weren't read.
- shortcutSet.read is a query command: it reads a set and does not
modify the document or the preferences. It returns each entry with
its section, codes and line, and up to 1,000 skipped tokens, each cut
to 80 characters. It reads at most 4 MiB of text and 20,000 entries,
skips blocks nested more than 16 deep and cuts names at 1,024 bytes.
With a path it reads at most 4 MiB and 4 bytes of the file, and with
--automation-read-root (#832) the path must lie inside the read root.
A file whose text starts with "<", such as an XML shortcut file saved
as UTF-8, fails with "This shortcut file is in a form VectorCraft
can't read."
- importMatches.list, importMatches.set and importMatches.reset read
and change the matches kept with the preferences; resetting the
preferences keeps them. With Remember My Choices on (the default),
Apply keeps the targets and codes the user chose, and for the rows it
assigns, it also keeps the matched targets and the codes decoded by
agreement. A later import proposes them.
- The name matcher compares a name with ids by exact match and shared
words. It compares the name with labels, in English and in the
interface language, by exact match, shared words and character
pairs. It proposes a match only when one tool, command or panel
scores at least 0.6 and at least 0.1 more than the next one.
- When Import Shortcut Set is opened from Keyboard Shortcuts, Cancel,
Escape and ui.dialog.cancel close it and return to Keyboard Shortcuts
as it was (dialogs::PARENT). A document's queued Missing Fonts dialog
(#792) opens after Keyboard Shortcuts closes.
- File › Open has a "Shortcut sets" filter, and in the desktop app
Keyboard Shortcuts' Import… shows .json and .kys files.
Docs: docs/mcp.md, docs/control-protocol.md, docs/development.md,
docs/target-app-parity.md, docs/roadmap.md.
The public 3D and Materials panel options give the Light Settings as
Intensity (0–100 %), Rotation (-180° to 180°), Height (0° to 90°),
Softness (0–100 %) and Ambient Light (0–200 %), with the light moved to
the back or the front of the object. The 3D effects (Revolve included)
had Direction and Elevation (an azimuth and elevation), Intensity and
Ambient (0–100 %).
- Rotation turns the light round the object on the page (our reading:
0° on its right, counterclockwise); Height raises it from beside the
object (0°) to straight in front (90°); Light behind the object puts
it at the back. The light gizmo's ring and line are Rotation and
Height.
- Softness wraps the light round the object: at 0 % it falls off with
the angle as before, at 100 % it reaches nothing only straight away.
- Ambient Light runs to 200 %; faces stay no brighter than the art.
- lightRotation, lightHeight, lightBehind and lightSoftness replace
lightAzimuth and lightElevation. An earlier light is read as the same
direction, so it shades exactly as before (old and new code give the
same colours for the four effects and five lights); effect.setParams
turns a stored one into the new form before applying new values, and
the parameter description names lightAzimuth and lightElevation as the
earlier form, so effect.apply and effect.setParams still take them. The
default light is the earlier default's: Rotation 125.26°, Height 30°.
- The dialog's Lighting tab shows the settings in the documented order;
Softness, Ambient Light and Light behind the object are in every
catalog, and the unused Direction, Elevation and Ambient are gone.
Light colour, presets, several lights and shadows are not built.
The public 3D and Materials panel options give Perspective as 0° to 160°
("how close or far the object appears"); it was a 0–100 % strength, for
Revolve, Extrude & Bevel, Inflate and Rotate alike.
- The eye stands where a sphere about the object's centre, √3 times its
half size (the box around it fits inside), fills that angle of the
view: 0° is none, wider brings the eye nearer, and at 160° it is just
outside the sphere, never inside the object. The documentation doesn't
say how the angle places the eye; docs/three-d.md says this is our
reading.
- `perspectiveAngle` (degrees) replaces `perspective` (%). A strength from
an earlier file or script is read as the angle that shows exactly the
same view: sin(angle / 2) = √3 × strength / 4, 100 % about 51.3°. An
angle given too wins. effect.setParams turns a stored strength into the
angle before applying new values. The parameter description names
`perspective` as the earlier form, so effect.apply and effect.setParams
still take it under the key check of #1592. Old and new code draw the four effects identically at 0, 25,
60 and 100 % (bounds equal to 1e-9).
- The dialogs' Perspective fields run 0–160°.
# Conflicts:
# docs/target-app-parity.md
Every symbol instance was measured as its transform applied to the
20 × 20 square around the origin, which symbols made in the app keep
their art in. The SVG and DXF importers keep symbol art as drawn, so
the box of an instance of an imported symbol did not match its art.
The SVG Star instance at x 20, y 30 draws from (20, 30) to (40, 50)
and was measured from (10, 20) to (30, 40). Align moved such an
instance by the wrong amount, clicks and marquees beside its art
selected it, and the canvas, exports and prints left it out when its
art reached the view or the page and the square did not.
An instance is now measured by its symbol's art, which
Document::symbol_boxes looks up in the document each time
(Node::geometric_bounds_with, visual_bounds_with and
reach_bounds_with). With Use Preview Bounds on, the strokes in the art
count at their own weight, as the canvas draws them, and
object.setBounds counts them at that weight when it sizes an instance
or an object that holds one. When strokes do not scale and a width or
height of 0 or more is smaller than what a path's stroke adds,
object.setBounds now gives the path no width or height, as it does
for an instance whose art has strokes; before, it mirrored the path.
With Use Preview Bounds on, object.setBounds now measures the margins
that strokes add object by object, the members of groups and the
objects in symbol instances' art included, and solves for the width,
height and reference point asked for when the width and height are 0
or more. Before, it measured the margins of the whole selection and
could miss the size or the reference point asked for when strokes did
not scale and the objects' strokes added different margins, and when
strokes scaled and the width and height were out of proportion, even
for one path. A clip group, a Shaper Group, a compound path, type, and
a group or instance with effects or an opacity mask of its own are each
measured as one object, and so is an instance in perspective or one
turned or slanted relative to the bounding box, other than by quarter
turns. When the selection holds more than 4,096 objects to measure this
way, each selected object is measured as one. Instances inside symbol
art are measured by their own symbol's art. An instance is measured as
the square when its symbol is missing, when its symbol or a symbol
nested in its art holds an instance of itself, and when symbols nest 32
deep in its art.
Align and Distribute, the Transform panel, the bounding box and the
selection outline, clicks, the marquee, document.inspect and
document.node, Fit to Artwork Bounds and Fit to Selected Art, canvas
culling, thumbnails, the SVG filter region of an instance with a
raster effect, raster effect images, Export Selection, the SVG of an
artboard, printing, and the PDF, EPS, EMF/WMF and DXF writers use the
new box. The renderer also culls an instance by the effects and
brushed strokes in its art, and it culls a blend, envelope or repeat
by the brush art of what it holds and the effects in the art of the
instances in it. It culls an instance with geometry effects of its own
by the effects in its art after those geometry effects reshape the art.
It measures the effects in a symbol's art once for each symbol and
linear part of the transform that places it. For symbols whose art
holds instances, it measures at most 4,096 placements a frame, and at
most 4,096 more for each pattern tile it rasterizes and each placed
document it draws. Past that, it estimates the reach from the symbol's
reach with no transform, which it measures once. When the drawn
document's symbols change, the renderer drops the cached bounds of the
instances and of the objects that hold them.
The PDF and EPS writers, the SVG of an artboard and Skip Blank
Artboards grow the box of an instance whose art holds brushed strokes
by 16 times the widest stroke (at least 1 pt) of the objects in that
art that have a brushed stroke, plus 8 pt, as the renderer grows the
cull bounds of a brushed path. They keep an instance whose brush art
reaches the page. The SVG of an artboard also keeps an object that
holds an instance when what the canvas paints for it, the effects in
the symbol's art and the instance's own included, reaches the artboard.
Not yet: other commands that measure objects, such as Select › All on
Active Artboard, Transform Each and Convert to Artboards, still measure
an instance by the square.
Docs: docs/mcp.md, docs/target-app-parity.md.