With an effect selected, Delete removed its whole layer and Ctrl+D
duplicated the layer. Both now act on the effect, like the clipboard
shortcuts; effect commands default to the selected effect.
An effect's context menu copies, cuts and pastes it, and Ctrl+C,
Ctrl+X and Ctrl+V act on a selected effect (pasting a copied one above
the selected effect, or on top of the selected content or mask).
Painted strokes come along, resampled to the target set; generators
and other one-stack effects go to their stack, and a fill reading an
anchor point needs one below its new layer. effect.copy, effect.cut
and effect.paste share the layer clipboard.
The History panel lists every undone step too (dimmed, next first) and
a click on any step goes back or forward to it, the current one
highlighted. edit.undo and edit.redo take {steps?} for the same jump.
Ctrl (Cmd on macOS) + right-drag in the 3D or 2D view changes the
brush size (left/right) and hardness (up/down), Ctrl + left-drag its
flow and angle, instead of orbiting or painting. The brush cursor stays
where the drag began, with the values beside it, and the Brush
properties name the gesture.
Each mesh map slot of the Texture Set Settings takes an image resource:
drop one from the Assets panel on the slot, or pick it from the slot's
menu, which also clears the slot. The image is resampled to the size of
the set's other mesh maps (or its bake size), its values read as data,
a normal map in the project's normal format.
textureSet.meshMap.set {ts?, map, image} and
textureSet.meshMap.clear {ts?, map} give agents the same.
Effect rows in the layer stack can be dragged within their stack (a
drop lands above the row, or below it from its lower half), and their
context menu duplicates an effect just above itself, painted pixels
included. effect.move {effect, index} and effect.duplicate {effect}
give agents the same.
Nine procedural materials for the shelf: three metals from measured
reflectance (zinc with crystalline spangles), bark with deep furrows,
fine-grained sand, mud with glossy wet patches, ice with cloudy air
pockets under transmission, and snow and bone with a little subsurface
scattering.
Three adjustment filters for masks and content: Histogram Scan slides a
ramp through the values (contrast narrows it to a step), Histogram Range
squeezes them towards a position, and Histogram Shift offsets them with
wrap-around. Names and parameters are translated into all nine other UI
languages.
The browser build threw unsaved changes away on New project, Open,
Close, Open sample project or a dropped file, and when the tab closed.
They now wait on the desktop's Save / Don't save / Cancel dialog (Save
downloads the project), and closing or reloading the tab with unsaved
changes makes the browser ask first.
The Levels effect's properties now show a histogram of its input, the
mask below it or (on layer content) a channel picked above it, with the
input range marked. effect.histogram {effect, channel?, bins?} gives
agents the same counts. Large sets are sampled (at most 256 tiles), so
the cost stays flat up to 8K.
New project, Open, a recent project, a dropped file, Open sample
project and Close replaced a project with unsaved changes without a
word. They now wait on the same Save / Don't save / Cancel dialog as
quitting, and go ahead once the changes are saved or let go.
Carbon Fiber, Fabric Cotton, Denim and Velvet were all the same plain
weave in different colours. A new Twill weave pattern (2/2: diagonal
ribs, the weft darker than the warp) now gives Denim its diagonal and
Carbon Fiber its light and dark tows under a clear coat; Velvet is a
soft pile with no weave under its sheen; Fabric Cotton keeps the plain
weave. Twill weave is also a fill pattern, translated into all nine
other UI languages.
Fixes#97.
Closing the window (or File > Quit) with unsaved changes now keeps the
app open and asks: Save, Don't save or Cancel. A cancelled or failed
save keeps the app and the dialog open. Strings translated into all
nine other UI languages.
Fixes#100.
Alt+click on a channel of a fill's or the brush's Material section turns
that channel on and every other one off (one undo step for a fill).
tool.material and layer.fill.set take solo: true for the same.
Fixes#74.
The baked maps are most of a sample project's size, so a 2048 sample is
as large as a 1024 one; the usage and the control protocol now say so.
Fixes#66.
The Assets shelf has a Brushes tab: presets grouped as Basic (hard, soft,
airbrush, square, dots), Wear and grunge (dry brush, chalk, spray, grunge,
splatter, scratches, chips, cracks, drips) and Details (stars, stitches,
rivets, fur), each with a preview of its stroke. A click sets the stroke (alpha,
spacing, flow, hardness, angle, path following, jitters, particles) and keeps
the brush's size, alignment, culling, symmetry and lazy mouse. My brushes saves
the current brush as a .tcbrush file in the brushes folder and lists them.
Commands: brush.preset.list, brush.preset.apply {preset}, brush.preset.save
{path} and brush.preset.load {path} (an image alpha from another project is
dropped). Labels translated into the other nine UI languages.
The app opens on the sample project, which left no obvious place to pick a
project. A start window now offers New project, Open, the recent projects and
Continue with the sample project, with Show at startup (also Settings >
Interface > Start window). It opens at launch in the desktop and web apps, but
not when a file is given on the command line or the app runs with --control.
Labels translated into the other nine UI languages; a UI test covers New
project, Continue and the startup setting.
resource.text draws one or more lines of text, shaped with harfrust (kerning,
ligatures, joined scripts) from the font's outlines (skrifa) and filled by a
non-zero scanline rasterizer, cropped to the ink, as an image resource: white
for the brush alpha, or a color for a decal. text.fonts lists the installed
families and styles, read from each font file's name table only (246 families in
80 ms here); Ubuntu Light and Hack from the UI's built-in fonts are always there
and are the fallback in the browser.
- Properties > Alpha > Text: the text, a font and style, Use as brush alpha and
Place as decal (in the tool's base color).
- Image alphas keep their proportions (their longer side spans the stamp), so a
line of text stays a line; image thumbnails are letterboxed and show alpha.
- harfrust 0.12 and skrifa 0.44 are the versions egui already builds, so the
lockfile gains no packages.
Fixes#40
Arabic text needs a font with Arabic glyphs; egui already shapes it and orders
right-to-left runs. The script joins the CJK ones in the lazy fallback fonts:
Noto Sans Arabic from craft-fonts (desktop and web, 0.8 MB), else Segoe UI,
Tahoma or Arial on Windows, SF Arabic or Geeza Pro on macOS, and Noto Sans
Arabic or DejaVu Sans on Linux and FreeBSD. Without any, the picker lists Arabic
by its English name, as for CJK.
ATTRIBUTION, the localization table (panels keep the left-to-right layout),
features, README and roadmap count ten languages.
Importers fan-triangulate quads and n-gons, and the 3D and UV wireframes drew
every triangle edge, so a quad mesh showed its triangulation. Meshes now carry a
per-triangle mask of the diagonals the importer added (Mesh::diagonals), and the
wireframe leaves them out (Mesh::wire_edges), unless a UV seam split one from its
twin.
- OBJ reads polygons whole (tobj without triangulation) and fans them itself.
- OBJ, PLY and the polygon sink (FBX, COLLADA, USD, Alembic) share one fan,
RawMesh::polygon, which records the diagonals.
- The built-in meshes mark their grid quads.
- glTF and STL store triangles only, so they keep every edge.
Fixes#41
The workspace, bake, export, rendering and light shots now show an original 1970s
portable TV with seven texture sets and a named layer stack instead of the material
ball, and the status bar reads 0.2.0.
Adds the full Arabic translation table covering all 827 keys, with exact placeholder parity, ellipsis handling, and locale detection ('ar'). Updates localization parity documentation accordingly.
The desktop app wrote its control token to <temp>/texturecraft-control-<port>.token with
default permissions (world-readable in a shared /tmp), from a hash-based generator, and
compared it non-constant-time with no connection limit. Any local user could read the token
and drive the app, whose command.run takes host paths.
- Token files are created new (remove, then O_EXCL) with mode 0600, so a planted file or
symlink can neither keep its permissions nor redirect the write; one that cannot be
replaced disables the server with a hint to set TEXTURECRAFT_CONTROL_TOKEN.
- Clients only read a token file that is a regular file owned by them with no group or
other permissions, opened without following symlinks or blocking on a FIFO.
- The app's control server uses the shared security module: OS-random 256-bit token,
constant-time compare, validated and trimmed env token, bounded lines and at most 16
connections.
- Connections must authenticate within 10 s (AuthDeadline) and get one attempt.
- The app and `serve --port` bind before writing the token file, so a server whose port is
busy no longer replaces the running server's token.
Regression tests cover owner-only files over an existing open file, symlink and FIFO
handling, the auth deadline, the connection limit and the app's token checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The worn-to-brass paint came from curvature masks that also caught the flat
front of the top plate, so it read as yellow blotches. The wear is now painted
by hand with brush strokes in UV space along the top plate's outer edge,
rounded ends and lip, the bottom plate's corners, the dial rims, the release
button and the strap lugs; the flat faces stay clean black satin paint.