Changelog

Changes: 1338 · Updated 2026-11-03 11:37 UTC

Every change to the viewer, the app and this website, newest first, taken from the project's history. Open a change for its details. The descriptions are in English.

November 2026

    • Intro: the synagogue faces the camera when the last point lands

      Up to the landing the camera's angle around the building follows the intro's time instead of how far it happened to turn, arriving at the front (the facade with the two corner pavilions, a little from the side) as the last point lands, slowing into the turn that goes on after it. Holds, jumps from the progress bar and rewinds give the same angle; a drag hands the camera over to the visitor.

    • Intro: 4D time travel, a progress bar, PBR, glassy machine sounds, a shorter start
      • The cloud alone for 1.5 s, then the points gather in about 5 s (under way at once); the last point's flight 3.8 s.
      • Time travel (4D), the tour's last chapter: the 4D model's stages 1730 to 1936, back to the raw new timber, then year by year its ageing and roof repairs, a warm front sweeping up the walls at each stage, the stage's year and description in the caption. The 4D model (55 MB) differs from stage to stage only in five exterior textures, so scripts/build-intro-time.mjs takes those (fresh and mid, WebP, 1.2 MB) and the stages' table into viewer/intro/time/.
      • A progress bar: one part per stage (cloud, mesh, the tour's chapters), filling as the intro goes; a click jumps there.
      • Shading: PBR (glossy, reflecting the sky, a sun highlight) instead of toon.
      • Sound: futuristic, a machine of glass assembling itself - shards clicking into place over a servo, a resonant rising sweep and faster glass pings for the last point, a reversed swell and a deep blast with a glass lock at its landing, data blips, a ratchet, panels locking.
    • gitignore fixes
    • Updated pnpm docker deps
    • Updated pnpm docker deps
    • Fixed docker
    • Resolved dependency problems
    • Updates for r187
    • Intro tour: the sun's real path over Wolpa; flat shading gone

      The sun study chapter follows the sun over Wolpa (53.37 N, 24.37 E) on 21 June, local time from just before sunrise to just after sunset, then night: the sun itself as a disc, its day's track as dots (the hours larger, the part already travelled golden), a compass ring on the ground and the shadows. The camera eases to a stop meanwhile, so the shadows turn with the sun only. The caption shows the place, day and time.

      The astronomy moves out of editor/sun-study.js into viewer/sun-position.js (no three.js, no viewer), which sun-study.js imports and re-exports and the intro uses as well.

      Shading now wipes through textures, toon and raking light only.

    • Intro tour: only shading modes that keep the textures

      Shading now wipes through textures, flat faces, toon and raking light, all on the model's textures; normals and clay are gone. Toon takes its colour from a blurred mip level of the texture, so it reads as flat patches with bands of light and ink along creases, not grain.

    • Intro: an express feature tour after the model stands

      feature-tour.js, about 20 seconds after the mesh act, each chapter named under the title: sections (a plane sweeps through, the cut glows, the walls' insides filled like a drawing's section), annotations and POIs (pins with labels, the model's height measured), shading (wipes across the screen: textures, normals, clay, toon, raking light), sun study (dawn to night with shadows) and seasons and weather (spring petals, summer, autumn leaves, winter snow settling on the roofs).

      The model's material gains the modes, the section, snow cover and a colour grade (mesh-reveal.js); the sun casts shadows once the model stands. A soft sound cue for each chapter, ticks between. Like the rest, a function of the intro's time: ?at= holds any moment, 'Assemble again' runs it backwards.

    • Updated deps
    • Credits: a button to the changelog
      • The credits window ends with a "Changelog" button (en/pl/de) that opens the website's changelog.html in a new tab; credits.changelog in the viewer settings points elsewhere, or hides it with "" / false.
      • Test: the button, its URL and target in the credits test.
      • docs/publishing.md: the setting.
    • Intro: sound, a subtler last point, and an option in front of the viewer

      Sound (viewer/intro/intro-sound.js), synthesized with Web Audio - no files: a drone, plucks as the points arrive, a rising glass tone for the last point and a quiet bell at its landing, sparks for the vertices, a brushed sweep for the edges, a chord opening with the faces. Off by default, a button top right; ?sound=1, the visitor's choice remembered.

      The last point is much smaller and dimmer, with a thinner trail and a softer flash, ring and wave.

      viewer.intro in viewer-settings.json (viewer/intro-overlay.js): the intro in a full-window layer over the standalone viewer page while the viewer loads underneath; Enter fades it away, Skip and Escape close it. show: session | once | always; ?intro=1/0 overrides. The intro's parameters go in the hash, which survives a server's index.html redirect. The viewer's stutter hint ignores the time the intro is in front.

    • Website: a viewer that follows the format; Working split in two
      • Capabilities: a short note under the four tasks on the toolbar's tools for the kind of model shown (mesh, IFC, point cloud, splats, volume), with its manifest key; en/pl/de.
      • Point clouds, from the scanner to the screen: Working, the tallest card, becomes "Filtering and measuring" (class filter, GPU picking) and "Annotating" (point annotations); three columns on a wide screen.
    • Example list under group names, volumes cut by section planes, projection under Look
      • Example models: each group an optgroup with its name drawn in the line between groups ("── Tempietto ──"), from the list's new "groups"; names within a group say only what sets the file apart (GLB, NRRD, KTX2...). Splats and the three volume samples get groups of their own. The manifest URL suggestions keep the group in the name.
      • Volumes: the section planes cut the marched rays (taken into the box's space before each frame), so the inside shows at the cut; no section fill copy of the volume's box (the crimson box seen before).
      • Menus: the camera projection moves from the navigation to the end of Look.
    • Intro: the last point's landing, then the real mesh

      The last point (the top of the roof) waits at the edge of the cloud, then spirals in slowly with a glint and a trail; its landing flashes, spreads a ring and sends a wave of light through the points.

      Then the model itself (WolpaSynagogue-progressive.glb, which the viewer opens next) takes the points' place: vertices light up, edges are drawn end to end, faces grow out of their centres glowing at the edges, and the textured building remains. The counter shows vertices and faces.

      One intro clock drives it all, so ?at= holds any moment and 'Assemble again' runs the mesh, the last point and the points backwards.

    • Intro page: a point cloud gathering into the Wolpa Synagogue

      intro.html: 200k points drift in a chaotic cloud and, like atoms, gather into the Wolpa Synagogue (foundations first), then a button leads into the viewer with the synagogue while the points fly apart. Drag rotates, the mouse pushes points aside, 'Assemble again' dissolves and rebuilds. PL/EN/DE, ?next= (same origin only), ?at= holds a moment for stills.

      Points sampled from the textured model with colours and normals by scripts/build-intro-points.mjs; a rollup entry built to intro.js.

    • Toolbar: the model's own tools marked by a dot, apart from the active ones

      The gold tint, ring and icon colour looked like an active tool. Now their icons and group background are as any other's, with a small dot in the accent under the icon (at the icon's corner in a menu's row); the gold frame and glow stay for active tools only. Drops an unused export.

    • Toolbar: the tools for the model's kind on top
      • model-kind.js: one answer for what is shown (mesh, bim, pointcloud, splats, volume), cached per model; publish-check and scene-suggest use its sceneContentKind instead of their own copies.
      • ui/format-tools.js: a profile per kind brings its tools out of the folded tray onto the classic bar (after the transform tools, as a group of their own) or as shortcuts into the navigation of the menus, faintly marked in the brand's gold, with the kind in the tooltip; tools the kind has no use for are left out. Two on phones, 5 / 4 on a desktop.
      • pointCloud and volume are no longer primary: their profiles bring them.
      • viewer.editorToolbar.profile (settings, manifest: false or { enabled, promote, hide }) replaces the kind's lists; validated, in the schema, exported with the toolbar state.
      • Tests for the classic toolbar, a phone, the profile and the menus.
    • Point cloud settings on phones: a modal window, minimized at first

      As the Volume panel: on phones the Point cloud panel appears minimized to its icon instead of hidden, opens as a modal window over the viewer and folds back on a tap beside it, Escape, its minimize button or the toolbar's point cloud button. Starting a profile folds the window, so the profile's two clicks reach the cloud. Desktops keep the docked panel.

    • Volume settings on phones: a modal window, minimized at first

      On phones the Volume panel no longer stays hidden: it appears minimized to its icon, and a tap opens it as a modal window centred over the viewer on a dimmed backdrop - its settings would cover the model in the side stack. A tap on the backdrop, Escape, its minimize button or the toolbar's volume button folds it back to its icon, where it was. Desktops keep the docked panel.

      tool-panel-chrome.js: a `modal` option (a value or a function asked at each restore) and setToolPanelMinimized / isToolPanelMinimized, so other panels can do the same.

    • Volumes: CT image slices, .vgi / .xtekct, and .zip by URL
      • PNG / JPEG / BMP slices in a .zip, as micro-CT software exports them: worker/image_slices.py (Pillow, 16 bits kept) and viewer/image-slices-reader.js (createImageBitmap in a Web Worker). The images of one kind in the folder with the most of them, in natural order; next to a model they stay its textures.
      • The voxel size from a SkyScan / Bruker reconstruction log (<name>_rec.log, "Image Pixel Size (um)") next to image or TIFF slices (volumes.skyscan_pixel_size, viewer/slice-stack.js).
      • Industrial CT headers of a raw .vol in raw_volume.py and raw-volume-reader.js: Volume Graphics .vgi (VGStudio) and Nikon .xtekct (CT Pro; its value type told by the .vol's size).
      • A .zip URL the repository has not converted (no <name>_ZIP/ next to it) is downloaded and opened in the browser like a dropped one (Viewer.openZipFromUrl); a converted one loads as before. The DICOM, TIFF / PNG slice, .vgi and .xtekct archives join the example list.
      • The "model loaded" toast names a dropped file, not its blob: URL.
    • Volumes: MRC and Zeiss LSM

      MRC / CCP4 maps (.mrc, .rec, .ccp4 - cryo-EM, electron tomography) in raw_volume.py and raw-volume-reader.js: MRC2014 and older, either byte order, modes 0 (signed by MRC2014 / IMOD's flag, else unsigned), 1, 2, 6 and 12 (float16), extended header skipped, voxel size in ångströms from CELLA. The worker also turns maps stored in another axis order back to x, y, z; the browser leaves those to it.

      Zeiss LSM (.lsm) in tiff_stack.py and tiff-reader.js: one page per plane with the channels as samples, thumbnail pages skipped, voxel size from CZ_LSMINFO, and Zeiss' BitsPerSample-as-offset corrected as tifffile does. An LSM is never taken for a texture next to a model.

      The volume panel shows nm / Å for such small voxels. Samples capsid.mrc and cells.lsm (generate-volume-samples.py) in the example list.

    • Example models: group the list, a line between groups

      examples/index.json entries carry a "group" (tempietto, wolpa, samples, volumes, broken); renderExampleModelOptions() puts an <hr> between consecutive groups and before the page's own data-keep options. The broken test files move to the end of the list.

    • Changed tests
    • DICOM
    • New formats
    • Volumes introduced
    • Manifest changes
    • Bugfixes

October 2026

    • Fix the /exadmin redirect pointing at the viewer container's port

      nginx answered /exadmin with an absolute redirect to http://<host>:3000/exadmin/ - the container's own scheme and port, which the host's nginx and Cloudflare do not serve. Relative redirects instead.

    • Show the terms text beside the checkbox when registering

      The text fields' rule (.upload-panel-auth input { width: 100% }) also made the terms checkbox full width, so on a phone its text sat outside the login panel.

    • Fix iOS app init: set the "3d" attribute without setAttribute

      WebKit on iOS 26 and older rejects setAttribute("3d", ...) with InvalidCharacterError (a name can't start with a digit there), so the viewer's init stopped before the bottom menu and the default model: a grey scene in the iOS app. setModelAttribute() sets an Attr node made by the HTML parser instead.

    • Fix worker tests and public check; header panels in the app open under their button
      • test_account_and_reports imported server when loaded, before test_ai set its WORKER_* variables, so test_ai's validation endpoint answered 404. It now imports server when its tests run.
      • Reword the purchase service's name out of the public worker files (forbidden-words check of the public sync).
      • App: the panels opened from the header (models source, ExHUB, plans, account) dropped to their default place inside the viewer, below the thumbnail gallery. They are now anchored under their button, as on the web page, also when switching sources inside the panel.
    • Add App Store review answers (Guideline 2.1) and pre-resubmission checklist
    • Tests fixes
    • Loading logs unified
    • Styling changes
    • Tests changes
    • Rendering changes
    • Switch to python
    • Changes for website
    • new assets added
    • Blender add-on: import and export IIIF / AIM3D manifests

      blender/aim3d_iiif maps a manifest's first Scene to Blender objects (model root with modelTransform and upAxis, AIM3DViewer view camera and lights, IIIF cameras and lights, comments on points) and writes back only what changed, keeping everything else of the stored manifest as it was. Without an imported manifest it makes one from the scene, with the meshes as GLB.

      Round-trip tests (blender/tests) cover every example manifest, edits, a second export and scripts/inspect-manifest.mjs.

    • Angel lighting examples; splats lit by default, set by manifests

      Four AIM3D manifests light the Angel splats (Angel-lod.rad, with its surface volume) to show what the scene's lights do to them: three-point studio light, raking light, a floodlight at night and golden hour. Each sets every light of the viewer's own rig, since the ones a manifest leaves out keep their default intensity, and turns the capture over (upAxis Y-), as it is stored Y up.

      Splats are now lit by the lights by default, and a manifest may set it: AIM3DViewer.viewer.splatLighting { enabled, strength, occlusion }, imported, exported, validated and inspected; left-out fields go back to the defaults.

      The reference the irradiance is divided by took half of each light, where a surface gets (1 + wrap) / 4 of it on average over its turns: lit splats came out a third darker than the capture. And the renderer's exposure now scales the lit splats too, which Spark draws without tone mapping, so Look > Exposure and a manifest's rendering.exposure reach them as they do the meshes.

    • Toolbar menus: regrouped by theme, with sections
      • Model: shading, wireframe and materials (the surface) join the parts and transforms; the ruler moves to Analysis.
      • Projection moves to the navigation bar, next to Reset camera.
      • Annotation import/export moves from File to Story.
      • Reset settings moves to Settings, renamed "Settings & help".
      • A line between a menu's sections (look/scene, transforms, surface, help and diagnostics...).
    • Splat lighting from a surface volume built on upload

      The worker turns uploaded splats (.splat, .spz, 3DGS .ply) into a voxel volume of their surface (worker/splat_volume.py, <model>.light next to the served model): a structure tensor for the normal, the openness of the air for occlusion and the distance to the surface for shadows. The viewer reads it in the splat lighting modifier: smoother normals, occlusion of the ambient light on the side the camera sees, and soft shadows from the lights that cast them. admin rebuild-splats builds the missing volumes too.

      The lights now reach the splats before Spark regenerates them, once a render: the viewer renders on demand, and a change seen after the regeneration waited for a frame that might not come.

    • Website: Cache-Control for images, Cloudflare purge after publish

      The website (Apache behind Cloudflare) sent no Cache-Control, so its images expired at the edge after Cloudflare's default 4 hours and came back as MISS/REVALIDATED. img/ and legal/ now carry their own .htaccess (a day in browsers, 30 days at Cloudflare); the deploy keeps only the web root's .htaccess the server's own.

      "Purge website cache" clears the pages, img/ and legal/ after each publish (variable WEBSITE_HOST). worker/cloudflare.py takes full URLs too, purged one by one - "/" as a prefix would be the whole host. The README describes the Cache Rule that makes the HTML cacheable.

    • Gaussian splats lit by the scene's lights

      A world modifier (viewer/splat-lighting.js) shades every splat by the scene's ambient, hemisphere, directional, point and spot lights: the normal is the splat's shortest axis, trusted as far as the splat is flat, and the captured colour is scaled by its irradiance against a reference, so neutral lights keep the capture as it was. Lights > Gaussian splats turns it on and sets its strength.

    • Lights fixes and new features
    • ExHUB changes
    • Latest changes
    • "Then and now" as its own icon among the phone's tools

      The app's toolbar gets a camera tool (deviceOnSite) next to AR, motion look and the 360° video, in the device bar of the menus layout. One tap (Viewer.startPhotoOnSite): the open photo, the model's only one, else the photo tapped in the photos panel (a hint says so), or with none an image from the gallery; the same tap closes it. The system's Back closes the camera view too (ui/back-navigation.js).

    • Historical photos on site: the photo over the place itself, through the phone's camera

      Rephotography with only a phone: "On site (camera)" in a photo's card opens the rear camera full screen with the historical photo over it, faded in, cut by a divider, or alone while "Hold: then" is pressed. One finger moves the photo, two pinch and turn it until it lies over the place (the wheel scales on a desktop); the alignment stays with the photo for the session. "Save comparison" keeps the camera frame with the photo over it as a JPEG, shared or downloaded.

      New module viewer/editor/photo-onsite.js; strings in EN/PL/DE; iOS gets a camera usage description; e2e test with a stand-in camera.

    • Face picking on touch screens: tap toggles faces, hint gets annotate/clear buttons

      A tap now adds or removes a face (a phone has no Ctrl), a tap tolerates finger jitter instead of needing the exact pointerdown position, and the picking hint shows touch wording with Add annotation / Clear buttons in place of Enter / Esc.

    • Updated deps
    • Models panel: the example select's import, lost in the merge of main

      The merge of main into #178 kept the new setPendingSavedSettings import and dropped clearExampleModelSelection's, while its calls stayed: opening a model from the repository threw a ReferenceError.

    • Embed options: the model on screen, a movable window led by its preview
      • The embed was of the page's first model: the model's address (fileObject.originalPath) is set once, so after another example or a model from the repository the embed kept the old one - with the camera and section planes of the model on screen, which left the preview empty. The model shown is now kept (shownModelUrl, its full address); a file from the device has none, and the window says so. The section planes go into the address only while one cuts the model.
      • The embedded page no longer shows the page header (the example picker): an embed is the viewer alone.
      • The window: over the page (the viewer may be low), moved by its header, resized by its corner, closed with the preview's viewer stopped. The options in a narrow column of sections (Model, Look, Camera, Behaviour) of label + control rows; the camera by axis (X, Y, Z), the auto-rotate speed beside its switch; the preview takes the rest, at the embed's proportions, reloaded once the typing stops, with reload and open-in-a-new-tab; the address and the iframe code under it.
      • New: the language of the embed (lang) and its proportions (16:9, 4:3, 1:1, 21:9, 3:4, the iframe code's aspect-ratio).
    • Gallery: a low filmstrip of 4:3 frames instead of a row of square blocks

      The views over the viewer took a tall row of bright square plates with a shadow inside, which on a laptop read as blocks. Now frames wider than tall (the renders' models are), a softer plate, the render drawn a little larger so the model fills its frame, a plain small shadow, a tighter panel; the number on a small pill, legible over any render. The strip is about a third lower.

    • Viewer: the example select after other models, windows over the bar, loading and playback progress, details in the Model menu
      • A model opened from the repository, the device, an upload or a manifest leaves the example select on "Other model" (a hidden placeholder, translated) instead of the last example; the example of a ?model= address is found also when written "/examples/..." .
      • The tool panels (materials, the publishing report, ...) stand above the editor toolbar; an open menu of the bar still comes first.
      • The loading circle shows from the start of every load, not only from the first download progress: 3D Tiles and Potree (and other loaders reporting none) move it, the tiles by their own load progress.
      • A minimized tour, timeline or animation player carries its progress as a ring around its icon.
      • "Shading/lighting side by side" for the look comparison (en, pl, de).
      • With the menus on a phone or a tablet on its side, the model's details open from the Model menu; the corner card, under the rail there, goes.
    • Viewer: materials in the AIM3D manifest, heavy models, the publishing count, the renders' front
      • AIM3DViewer.materials: the materials edited, exported while any is edited and imported again (the model's own materials first, then the manifest's); validated (types, colours, ranges), inspected as a feature of the manifest, in the schema (JSON and the reference).
      • Right after loading (and after the progressive swap) the model's triangles and splats are weighed against the device (a GPU in software, a phone with little memory or few cores, a phone, a desktop): a model far beyond what it draws smoothly gets Balanced or Fast offered at once, before the view stutters (editor/performance-rules.js, pure).
      • While the publishing guard is on, the toolbar's Prepare for publishing button carries the number of things to fix and its menu's tab a dot, counted after the tools change, the camera stops or the model is moved (the model's base measured once per pose). The orientation hint waits while another hint is shown.
      • The renders' front: Generate thumbnails turns side0 (and the views from it, the top view with the front below) to the side a chosen start view looks from - a saved camera, a manifest's, Frame the model, the camera when the settings are saved -, in steps of 45°; switchable in the window's steps. Without a chosen start view the file's +Z stays front.
    • Viewer: prepare for publishing, a new materials window, render quality hints

      Prepare for publishing (File menu, editor/publish-check.js with the pure rules in publish-rules.js): before the settings are saved and the renders made, what would show in them or in the saved start view and should not - a model lying on its side (from the flat area at each end of its box; slabs such as reliefs and flat tops are left alone or only pointed out), floating or sunk, cut off or small in the view, too dark or too bright (the model's pixels in a small copy of the canvas), tools left on (sections, measurements, picked faces, highlights, transform and light handles, wireframe, certainty colours), a lowered render quality - each with a fix and "Fix all"; tips (black or invisible materials, types the material names suggest, an implausible size without a unit, a heavy model, the validation report); then the next steps with their state (settings saved in this session or changed since, renders).

      The viewer also speaks up by itself (ui/smart-hint.js, hints with buttons among the notices): a model on its side after loading ("Stand it up"), and things to fix before Save settings, Change main thumbnail or Generate thumbnails ("Review" / "Continue anyway").

      Materials (Look menu, editor/materials-editor.js) rebuilt as a side panel like the other tool panels: the materials with swatches and a search for long lists, picked from the list, by a click on the model or from the context menu, 28 built-in types without textures in five groups (glass, wood, walls and stone, metals, other; material-presets.js), the type a material's name suggests ("Glass_01", "szkło", "Holz"), and the main properties by hand. Edits go to a physical copy put in place of the original (kept, Reset puts it back), are saved with the settings (`materials`, the new Materials tick of Save settings), applied again on load and carried from a progressive preview to the full model.

      Render quality (Rendering menu, editor/performance.js): Full, Balanced, Fast. Six slow seconds in a row - not while loading or just after - offer the next lower level, with Undo.

      Translated into English, Polish and German; tests for all three.

    • App: repository models keep their saved settings, a .rad is paged

      The app downloads a repository model and opens it from a File, so its blob: URL had no folder to find metadata/<file>_viewer.json in: the settings saved in the browser were skipped. The models panel now fetches them with the file and hands them over (like the IFC properties); a copy saved on the device keeps them too.

      A .rad (the level-of-detail tree of Gaussian splats) was downloaded whole and decoded in full instead of paged from the server, coarse splats first. The app now opens it from its URL, as the browser does.

      Opening a file now resets the previous model's state like opening a URL: the old model stayed in helperObjects, so the editing tools and "Save settings" acted on it.

    • Website: fit the header and the sections menu on phones

      On 320 px phones the header was 354 px wide: the page scrolled sideways and the menu button sat off the screen. The logo and the language switch are now smaller there. In the menu on phones the rows are shorter and Author sits beside the last group, so the whole menu fits a typical phone screen. A section opened from the menu no longer starts under the sticky header (scroll-padding-top).

    • Website: keep the privacy page's lastmod in the sitemap
    • Website: group the header sections into themed dropdown menus

      The header row had twelve links and still left out six sections (the name, light, weather, suggested scene, ground, where it runs). The sections now sit in five groups - About, Research, Presentation, Platform, Get started - plus Author: dropdowns on a wide screen (hover, click or keyboard), and every group listed under its name in the folded menu. The row fits down to 1181 px instead of 1480 px. The group with the section in view is marked. Polish and German labels included.

    • Viewer: fewer toasts, an event log window with everything

      Routine notices no longer pop up as toasts: the performance mode, the _viewer.json found or not, the OBJ loader's own "loaded" (the same moment as "Model has been loaded"), and tool states the toolbar already shows (face picking, distance mode, clipping helpers, shading mode, feature toggles, motion look off). They are listed in QUIET_NOTICE_KEYS (ui/event-log-store.js); showToast also takes quiet: true. Errors, warnings, the model loaded and hints for the tool just picked stay.

      Every notice, the quiet ones too, and the console's errors and warnings and uncaught errors go into a log, shown in a new window: Settings -> Event log (ui/event-log.js), with Copy and Clear, in every build (web, Drupal, app). Console arguments are written as plain data or their class name only

      • three.js objects have a toJSON() that would serialize the whole scene.
    • Mobile AR: own drag, turn and pinch gestures

      SceneView's node gestures misbehaved on the placed model: its drag set the anchor to the whole pose of the first hit, feature points with any orientation included, which could turn the model upside down; its pinch rejected every step that left the scale range, so Android's detector piled the rejected steps up and the scale then jumped by all of them at once (and seemed stuck). The AR view now handles the gestures itself once the model stands: one finger moves it over a tracked floor plane (position only, it stays upright), two fingers turn it about its vertical axis and scale it step by step, clamped to the range.

    • Mobile AR: do not destroy the model while its textures still load

      SceneView decodes a model's textures in the background and uploads them frame by frame (ResourceLoader.asyncUpdateLoad). Destroying the model before that finished made Filament upload into freed textures and abort (PreconditionPanic in setImageCommon: "Operation not supported with multisample (119) texture") - on repeated taps in the previous build, and on closing the AR view early in the current one. The ModelLoader's own teardown cancels the loading before destroying its models, so the view leaves that to it.

    • Mobile AR: load the model once, fit large models, drag and pinch

      The model is now loaded as the AR view opens instead of on every tap: each tap loaded the GLB again and destroyed the one still loading, which is the likely crash on repeated taps. It rides the first anchor; dragging moves it and two fingers turn and resize it. Models longer than 3 m (a building) start scaled down to 1 m - at real size the phone stood inside them and nothing showed. A scale pill shows the ratio and switches to the real size. Clearer placing hint, docs updated.

    • Move the box.json example and the manifest schema id to explora4d.eu
    • App: talk to explora4d.eu instead of viewer.thedworak.com

      The default repository (API under /api, models under /files) and the example model and manifest lists (served with the viewer under /viewer/). viewer.thedworak.com stays for older builds and existing links.

    • Mobile AR: declare the camera permission in the app manifest
    • Mobile AR: own ARCore view instead of Scene Viewer

      Scene Viewer loads models only from a web URL: given the exported GLB as a local content:// file it showed its loading screen and closed at once, with no error. The model now opens in ArActivity, the app's own AR view on ARCore with SceneView (Filament, Compose): a ring shows the floor, a tap places the model at its real size, a tap elsewhere moves it, two fingers turn it.

      • ArViewerPlugin starts ArActivity with the file path and the view's texts in the app's language; phones ARCore does not support get their own toast.
      • Kotlin 2.4.10 + Compose and SceneView 4.33.0 (the last release for AGP 8.13 / compileSdk 36) in the Android project.
      • Docs, README and website updated.
    • Private files check
    • gltfpack fixes
    • Chunks test
    • Gallery rebuilt
    • Tests fixes
    • Upload changed
    • Upload changed
    • Ground fixes, website update
    • Ground: its own entry in Look, opening a window of tiles

      The ground presets leave the Background submenu: Look -> Ground opens a window (ui/ground-panel.js) with an icon tile per ground; it stays open after a pick, to compare them. Background keeps its old name and place.

    • Name the Background menu "Background and ground", higher in Look

      It was labelled "Background colour" and sat at the end of the Look menu, below the fold: the ground presets in it were hard to find.

    • Ground presets instead of the checkered plane

      The model now stands on a soft contact shadow by default (no visible floor). The Background menu offers five more grounds: mirror, a technical grid fading out with coloured axes, a turntable with compass and scale bar, a dark room with a pool of light, and a dot grid.

      Chosen by ?ground=, the manifest (scene/globals/AIM3DViewer viewer.ground, also written by the viewer's export), the toolbar or viewer.ground in viewer-settings.json. With the WebGPU renderer the GLSL parts fall back to a blob shadow and a plain grid.

      The contact shadow takes its darkness from the height above the floor and is drawn before the opaque objects without a depth test: the viewer's logarithmic depth buffer left the depth-based version empty.

    • Fixes in PHP
    • New manifests added
    • New release with scene reco
    • Website reader: the suggested scene, step by step

      A new reading in the hero's reader, Suggested scene, in four steps from one view of the Wołpa synagogue: as opened, the window's suggestions, applied for a presentation and for documentation (scene-*.webp, from capture-screenshots.mjs sceneShot). Step buttons take the page's language. The section's screenshot is re-rendered with the same code.

    • Suggested scene: walls seen from a camera outside are a building's interior; website screenshot

      The Wołpa synagogue has its interior modelled, so rays from inside met walls and the rules called it indoors, against its name and a manifest camera outside. Walls all round now count only with the camera inside the box, or with it unknown but no building named; a manifest's camera position is kept for this.

      The website's new section gets a screenshot (capture-screenshots.mjs, scene-suggest): the synagogue in the suggested late-afternoon sun, beside the window with the suggestions.

    • Website: the suggested scene; on a phone the splats join the point cloud shots, and the path, Seeing and Working are one row

      A new section, A scene suggested for the model (en/pl/de): the rules, the place from its name (GeoNames), the language model in the browser and the shape. On a phone the Gaussian splats are the last slide of the point cloud shots (a shorter text), and the scanner-to-screen steps with the Seeing and Working cards swipe as one row. Wider screens are unchanged.

    • Suggested scene: the geometry's measures count for the setting

      Once per model the browser measures the surface's orientations (up, down, sideways), the ground over the footprint and the enclosure: rays from inside the box march through a voxel grid of the surface and tell walls seen from inside (an interior) from a closed shell's backs. With the box's proportions (flat sites, thin plates) the rules weigh them as evidence for the setting and the raking light.

    • Suggested scene: a multilingual language model in the browser reads the texts

      Where keywords miss, a sentence-embedding model (multilingual-e5-small via transformers.js, loaded from a configurable host on first use) compares passages of the texts with descriptions of each setting, purpose and of relief, in English, Polish and German. The rules weigh its probabilities below words found. The first run is on the user's click; once consented it runs by itself where the setting is unsure. Nothing leaves the browser.

    • Suggested scene: the place a model's texts name, found offline in GeoNames

      The worker indexes a GeoNames dump (cities500 in the image) into SQLite and answers POST /api/geo/places with the towns a text names, matched without diacritics, by alternate names and with Polish and German case endings stripped, ranked by context, size and the country named. The suggested scene asks it once per model where no place is set and puts the sun there, marked as a guess when the match is unsure.

    • Suggested scene: lighting, weather and rendering picked by rules

      Look > Suggested scene reads the file name, metadata, manifest texts, the place, the timeline's year and the model's size, kind and materials, decides the purpose (documentation or presentation) and the setting (outdoors, indoors, an object), and suggests the sun, weather, raking light, tone mapping and ambient occlusion with a confidence and reasons each. Nothing changes until Apply; Undo restores the scene from before.

    • Gaussian splats: no "Worker terminate" on switching away; tests wait for the splats to be drawn

      Switching away from a splat model waited at most 5 s for Spark's sort in flight before disposing it. While the next model loads, the sort's worker can answer later than that; dispose() then terminated the worker and rejected the call, which Spark does not catch: "Uncaught (in promise) Error: Worker terminate" (the e2e test "switching away from Gaussian splats while Spark sorts" failed on main with it). The calls still waiting on Spark's sort and LoD workers are now let go before the dispose, and the wait covers them too.

      The splat tests measured the drawn pixels once, right after loading; Spark sorts (and for a paged .rad, walks the tree) a few frames later, so under load they sometimes read 0. They poll for it now.

    • Weather added
    • On a desktop, menus open on hover

      The page header's buttons (upload, models on this device, generated models, example manifests) open their windows below them on hover, and the toolbar's menus open over their tabs - another tab switches to its menu, submenus in a menu open on hover too. Leaving both the trigger and what it opened closes it after a moment; a click on the trigger or a press or typing inside keeps it open (ui/hover-open.js). A short wait before opening, unless a menu of the bar is open already, so passing over the bar opens nothing. Only with a mouse that can hover: touch keeps the taps.

    • Credits as a window; screenshots removed
      • The credits are a window over the viewer instead of a line below it: closed until opened from Help → Credits, dragged by its header, one window at a time, closed by its button, a click on the model or Back. One credit per line with its label ("Created by", "Funded by") back, IIIF and the three.js build set apart below; a logo that does not load is left out instead of showing as a broken image.
      • docs/screenshots/museum-palette removed after review.
    • Restoring a minimized panel by a tap keeps it open; darker browns
      • The round icon restored its panel when the finger lifted, so the tap's click that follows landed on the panel back in place - on its close button, which sits where the icon was: the panel was gone at once. The icon now restores on the click itself (a drag of it still only moves).
      • The palette's browns go towards black: near-black umber panels, a dark coffee brown behind white text (active buttons, the tour's pause) and for success notices.
      • Screenshots in docs/screenshots/museum-palette retaken.
    • Loading progress round the menus toolbar; sand beige instead of green
      • The menus layout hid the toolbar's loading ring without drawing its own ("goes round the dock"): the ring now goes round the dock on a desktop and is a line along the edge of the bar (phone upright) or the rail (phone on its side), filling as the model loads.
      • The accent of the museum palette is a sand beige instead of the patina green; success notices are warm brown with a gold edge.
      • docs/screenshots/museum-palette: the palette in the viewer, for review.
    • Tool panels: move, minimize to an icon, close; tour progress ring; museum palette
      • Every floating tool panel (tour, sun and shadows, timeline, section planes, measurements, point cloud, certainty legend, animation player, material and shader dialogs) gets a grip to drag it, on all devices, and a minimize button that shrinks it to a small round icon (tap: restore, drag: move). Panels without a close button of their own get one: the timeline and the animation player hide until the next model, section planes and the point cloud panel switch off as from the toolbar.
      • Undocked panels sit in the viewer container instead of the side stack, so they no longer jump when the stack's other panels change size.
      • Guided tour: while autoplay waits on a step, a gold ring around the pause button fills over the step's duration.
      • Phones: side-stack panels keep padding and border inside their width, so the tour panel no longer sticks out on the left.
      • Palette: warm umber neutrals instead of slate greys, a bronze-patina green instead of the blue accents; the gold stays.
    • Updated QR code
    • Android release
    • Save settings works on the worker: saved next to the upload, applied when it opens

      In Docker/standalone the viewer is lightweight, and "Save settings" returned before doing anything - no request, no message. It also only knew Drupal's /api/editor/save-metadata, which the worker does not have, and reported success whatever the answer.

      • worker: POST /api/model/settings writes metadata/<file>_viewer.json next to an uploaded model (the file and place of Drupal's SaveMetadataController), for its uploader or an admin; served with no-cache since it is rewritten in place, old gzip copy and Cloudflare's copy dropped.
      • viewer: outside Drupal, Save settings posts there (a model that is not an upload is told so, a foreign one too); a model from the worker reads its saved settings when it opens (metadata.js). Drupal: unchanged, but a failed save now says so.
      • worker-model.js: the job id / settings URL of an uploaded model, shared with thumbnail-capture.js.
    • Public sync: playwright.yml without private folder names, and a check on every pull request

      The sync to the public repository refused #161: playwright.yml (public) named two private folders in its paths-ignore. **/website/** and **/docs/** match them now.

      So that this shows on the pull request instead of after the merge, check-public.sh (next to sync-public.sh) runs the same check for a range of commits - added public lines, and the pull request's title or the commit messages - and public-check.yml (private) runs it on every pull request to main.

    • CI: smoke tests on every push, the full suite with the full-tests label
      • tests/viewer.spec.ts: 17 tests tagged @smoke (loading, point clouds, splats, IIIF round-trips, shading, toolbar, errors, validation) - 2.4 minutes locally instead of about 8.
      • playwright.yml: a push to a pull request runs --grep @smoke; the label full-tests runs everything (and keeps doing so for later pushes); a manual run and the deploy's own run are full. Not run at all for the website, the app's docs or the worker.
      • worker-tests.yml: the worker's Python unit tests on pull requests that change worker/, on a GitHub runner.
      • deploy.yml: a push that changes only the worker (and ignored files) needs no viewer tests, but still rebuilds the images.
      • playwright.config.js: one retry on CI instead of two.
      • pnpm run test:smoke runs the smoke set locally.
    • Website: Gaussian splats streamed as RAD, in the point clouds section and the formats

      A new block under the point cloud views (pointcloud-splats.webp, the Tempietto as a paged .rad, from capture-screenshots.mjs): the RAD tree built on upload and fetched in chunks by range requests. RAD added to the format chips, the per-format table (and the build-lod step for the other splat formats), the standards table, ingest and delivery, in EN, PL and DE.

    • Worker: admin rebuild-splats builds the .rad of splat uploads without one

      server.py admin rebuild-splats [--force] [<job id> ...] runs splats.py's build-lod over the uploads that have no level-of-detail tree (uploaded before build-lod was in the image, or where it failed). --force rebuilds existing trees too; a failed rebuild keeps the old one, the stale gzip copy is removed and the rebuilt URLs are purged from Cloudflare. The model list reads jobs from disk, so no restart is needed.

    • Rebuild dist after merging main (up axis)
    • Mark .rad and .sog binary: dist/**'s eol=lf turned CRLFs in Tempietto-lod.rad
    • Gaussian splats: page a .rad from the server by range requests

      A .rad (Spark's LoD tree, which the worker builds from every uploaded capture) was downloaded whole before anything showed. Spark 2.2 can page it: buildPagedSplatModel() checks that the server answers ranges, and SplatMesh({ url, paged: true }) then reads the header and fetches the tree in 64K-splat chunks, coarse root first and finer chunks where the camera looks. Bounds come from the root chunk, the count from the header. Opened from a File, or when the server answers the probe with 200, the file is loaded whole as before.

      docker/nginx.conf caches .rad in 1 MB slices like Potree's octree.bin, so a range never waits for nginx to copy the whole file.

      Example: examples/tempietto/Tempietto-lod.rad (Tempietto.splat through build-lod --rad); test for the paged path and the 200 fallback.

    • Add upAxis handler for Z-up and upside-down models

      Models whose up axis is not +Y (Z-up CAD/scan exports, models upside down) are turned upright by naming which axis of the file points up. The fix lives under the model root (an inner pivot for groups, the geometry for single meshes/points), so the root's own transform from afterLoad, metadata, config, manifests and the editor composes with it and a saved rotation never carries it twice.

      • viewer/up-axis.js: applyUpAxis / getUpAxis / normalizeUpAxis
      • setupObject applies it before placement; sources: ?upAxis=, the model's _viewer.json, its objectsConfig entry
      • AIM3D manifest: AIM3DViewer.modelTransform.upAxis (schema, docs, validation, export/import)
      • editor: Up axis menu in the toolbar (Model menu), saved with rotation
      • progressive loading keeps the preview's fix; annotation target paths skip the pivot
    • AI: setup problems (key, credit, model access, network) only say "unavailable"

      An empty credit balance reached every viewer as the Claude API's raw error with its request id and a billing hint. Such problems are the server admin's: the worker logs the reason ([ai] <code>: ...) and answers "The AI assistant is temporarily unavailable.", which the viewer shows translated by its code.

    • Splats fixes
    • SPZ fixes
    • UI fixes and admin
    • UI fixes
    • UI fixes
    • Worker image: brotli >= 1.2 for the Claude SDK's HTTP client

      Ubuntu's python3-brotli 1.1 (a matplotlib dependency) has no output_buffer_limit in Decompressor.process(); httpx2 passes it when it decodes a brotli answer, so every Claude request failed with TypeError('process() takes no keyword arguments'), reported as "The AI service could not be reached".

    • Gestures: actions that follow the mode, a two-finger tap steps back
      • Context menu while measuring: finish, remove the last point, discard, stop (a phone's Enter and Backspace); while picking faces: annotate them, clear, stop.
      • On an IFC element, without the AI: select all of this type, show only this element (a step back undoes it), copy the GlobalId; at a point: measure from here, annotate here, cut horizontally here, copy the coordinates; remove sections.
      • A quick two-finger tap is a step back; the fingers of a gesture are not taps on the model. Backspace and a step back remove a measurement's last point.
      • The release of a press and hold no longer clicks the item that opened under the finger.
    • Context menu (right click, press and hold) and a back step that closes the top layer
      • editor/context-menu.js: a right click that did not move, a one-finger press and hold (500 ms) or the ContextMenu key open the actions for the point: on an IFC element it is picked first, then Ask about this element, Hide element, Show hidden elements, Zoom to this point, Reset view. The release of the hold is not taken as a tap.
      • IFC questions about the picked element: the element's fields, storey and property values go to the worker as selection (capped at 4000 characters); the panel shows it with examples about it.
      • ui/back-navigation.js: Escape (when unused), the mouse's back button and, on touch screens and in the app, the back gesture (one history entry while something is open) close the topmost layer: context menu, toolbar menu, dialogs, panel windows, IFC question and properties, metadata, tour, measurement draft, selected faces.
    • AI: log and name the cause when the Claude API cannot be reached
    • AI: clean ANTHROPIC_API_KEY of line ends, spaces and quotes from .env
    • AI errors as 503: Cloudflare replaces an origin's 502/504 with its own page

      The worker answered failed Claude requests with 502/504 and a JSON message; behind Cloudflare the viewer got Cloudflare's HTML page instead, so the real reason (credentials, unreachable API, ...) never showed, and the viewer's 504 fallback wrongly said the answer took too long - after less than a second. All ai_* upstream errors are 503 now; only Cloudflare's 524 means a timeout in the viewer.

    • AI answers instead of a proxy timeout: one attempt within 90 s, nginx waits 100 s

      /api/ai/ went through nginx's /api/ location with the default 60 s read timeout, while the worker allowed 120 s per Claude request and two retries. A slower answer reached the viewer as nginx's HTML 504, shown as the bare "The AI assistant could not answer".

      • docker/nginx.conf: /api/ai/ waits 100 s (Cloudflare's limit).
      • worker/ai.py: WORKER_AI_TIMEOUT default 90 s, no SDK retries; a timeout answers 504 ai_timeout with its own message.
      • worker/server.py: AI errors >= 500 are logged; an unexpected exception answers 500 ai_internal JSON instead of a dropped connection.
      • viewer: a non-JSON 504/524 says the answer took too long, other statuses show their HTTP code.
    • Keep the app's purchase service name out of the public .env.example

      sync-public.sh refuses public commits that add a line from forbidden-words.txt; the AI access comment named it.

    • Rebuild dist/dev and dist/test
    • Menus: one main Model tab instead of Tools and the Model menu under More

      Tools held four entries and Model (under More) three. Merged into a main Model tab: move, rotate, scale and the ruler, the hierarchy and picking, and what only some models have, shown when the model has it - the reconstruction variants to compare and the IFC question (out of Analysis) and the point cloud settings. More keeps File and Settings; the AR/VR tools stay at the end of the navigation bar.

    • Rebuild dist/dev and dist/test
    • Validation report fits the viewer and drags on phones; Tools menu

      The validation report was capped at 80vh, taller than the viewer on a phone, so its end was cut off below it, and its header (the drag handle) scrolled away with the report. Its height now follows the viewer and the header stays at the top; on touch screens the headers of the dragged panel windows show a grip.

      The Measure menu held one tool. It becomes Tools (Narzędzia, Werkzeuge): move, rotate and scale - out of the navigation bar, where navigating (orbit) stays and ends them - and the ruler. On a phone, picking a transform closes the sheet, so the gizmo on the model is in view.

    • Rebuild dist/dev and dist/test
    • IFC questions on a .ifc opened directly; LoC legend movable on phones

      Ask the building model (AI) needed a converted IFC (<name>_ifc.json), so the examples opened as .ifc (web-ifc) had no tool. Their properties and storeys are now read from the model on the first question (loadIfcQueryData), in the converted file's shape; the elements found are highlighted by an overlay of their triangles, and hidden or isolated by a temporary geometry without the others (material groups kept, picking unchanged), restored by Show all. The filter compares IFC classes in any case (web-ifc may name them in capitals).

      The Level of Certainty legend gets the other tool panels' grip and minimize button on touch devices and in the app, so it can be moved off the IFC properties panel or the model; kept across its re-renders.

    • Website: paragraph rows as swipe tiles on phones

      'From the scanner to the screen', the timeline's three notes ('What a stage says'...), the three Technology columns and both lists of 'The same viewer in your pocket' become rows of cards to swipe through on a phone, with dots and a counter like the other rows; wider screens are unchanged.

    • Rebuild dist/dev and dist/test
    • App: the AI tools shown with a lock when the plan does not include them

      GET /api/ai/config lists the server's AI features also when they are restricted for the caller. In the app (feature gating on), such a tool - the IFC question in the toolbar, the validation report's and the variants panel's buttons - shows with a lock; a tap opens the plans panel at Business (showLockedFeature), and nothing is sent to the AI. The web viewer, which sells no plans, still hides them.

    • env example: the AI assistance and point cloud classification settings
    • Website: 'Compare looks' reading as a real swipe comparison

      The reading showed a still with the divider drawn in. It is now the published render (original.webp) and the raking-light side (compare-look-right.webp, rendered by capture-screenshots.mjs from the comparison itself, its divider moved off the crop) under a divider that follows a drag, a tap or the arrow keys, like the comparison in the light section (one bindCompare for both). Dragging holds the slideshow; hidden readings no longer take the pointer.

    • Rebuild dist/dev and dist/test
    • Rebuild dist/dev and dist/test
    • Rebuild dist/test with the AI assistance
    • AI assistance: room for adaptive thinking in answers; access refreshed after login

      max_tokens 16000 (thinking counts against it on the current models); logging in or out re-reads /api/ai/config, so WORKER_AI_ACCESS=users shows the tools without a reload.

    • AI assistance (4/4): point cloud classification before tiling

      worker/classify.py assigns ASPRS classes (ground, low/medium/high vegetation, building, noise) to clouds without classes before the Potree octree is built (WORKER_POINTCLOUD_CLASSIFY=auto|always, off by default). Built-in geometric classifier (numpy): progressive morphological ground filter (SMRF), isolated-voxel noise, covariance shape features per voxel for buildings vs vegetation; or any external model through WORKER_POINTCLOUD_CLASSIFIER=command. Runs after the quick preview; failures fall back to unclassified tiles; 3D Tiles output (no classes) skips it. The point cloud panel says when classes were assigned automatically (tiles/<name>.classification.json).

    • AI assistance (3/4): reconstruction variants compared, paradata reviewed

      The variants panel gets 'Compare and review paradata (AI)': each variant's label, description, source and Level of Certainty, plus what the viewer measures of its nodes (size and heights in metres via the model unit, triangles, shared/exclusive nodes) go to POST /api/ai/variants. The answer: a comparison grounded in those measurements, the differences, and a per-variant paradata review (ok / incomplete / inconsistent, issues, suggestions) - judging the documentation, never which hypothesis is right.

    • AI assistance (2/4): questions to a building model (IFC)

      Analysis > 'Ask the building model (AI)' for converted IFC models: the question and a summary of the IFC data (classes, storeys, materials, property sets with types and a few samples - no elements, no GlobalIds) go to POST /api/ai/ifc-query; the answer is a filter (conditions, aggregate, action), never results. The viewer runs it on every element (ai/ifc-filter.js), so counts and sums come from the data; the panel shows the filter, the count/aggregate, and highlights, isolates or hides the elements found.

    • AI assistance (1/4): validation report explained in plain language

      Optional, off by default: worker/ai.py calls the Claude API (structured JSON answers) when WORKER_AI_ENABLED=true and a key is set. Access by WORKER_AI_ACCESS (all/users/business/admin), hourly limit per caller, in-memory answer cache. GET /api/ai/config tells the viewer what to offer.

      First feature: POST /api/ai/validation/<id> sends the model's validation.json (never the model) and returns a verdict and findings with what to fix; shown in the validation report on a button and included in its JSON download. viewer.ai: false hides it.

    • Camera lens reset between models; credits hidden by default on phones

      Switching examples, the next model was fitted and flown to through the previous manifest's fov, zoom, up and projection. resetLoadedModelState() now starts each model from the default lens.

      On a phone layout the credits line starts hidden; Settings (under More) has a Show/Hide credits toggle, also on desktop.

    • Page header: button opening the example manifests list

      The main page gets a manifests button next to the model buttons: it opens (and closes) the example AIM3D/IIIF manifests list the app already has (ui/examples-panel.js) - grouped by model, each with the icons of what it contains - instead of the dev build's manifest form. Picking one loads it and closes the list. Hidden in the app, which reaches the list through its Models button. dist/dev and dist/test rebuilt.

    • Admin panel: model file check icon; rebuild dist/dev and dist/test

      The admin panel's model rows show the worker's check of the model file (validation.json summary from GET /api/jobs), as the browse-models panel does. dist/dev and dist/test are rebuilt with the validation report, the feature icons and this change.

    • Validation report and feature icons on the model lists
      • Example manifests list: under each manifest, an icon per feature it sets (model, metadata, annotations, timeline, photos, ...), with its count and coloured by the status of its values (manifest inspection), plus the model file check when a worker serves the model.
      • Worker models list: the model file check (validation.json summary from GET /api/jobs) as an icon in each row.
      • New tool File > Validation report (classic toolbar: main menu): a checklist of the open model - the model file (glTF-Validator report of the worker, "Not checked" elsewhere), the IIIF structure, metadata, rights and every feature the manifest sets - each Pass / Needs review / Fail / Not set and opening to its issues, with "X/Y passed" overall, the model's numbers and a JSON download of the whole report.
      • clipping.negated (exported by the viewer) is a known attribute.
    • Manifest inspection: check every attribute value, group into features

      viewer/manifesto/manifest-inspection.js walks every leaf of an AIM3D/IIIF manifest against a description of the known attributes (IIIF Presentation 3/4 resources, bodies, targets, and the whole AIM3DViewer block) and gives each one a level - error / warning / info / ok - with its messages: types, ranges and enums, URLs (spaces, localhost, http for downloaded files), colours, ISO dates, language-map keys, media types, licence URIs, unknown attributes, missing required and recommended keys, plus checks across attributes (duplicate ids, annotation targets, compared variants, zero vectors, stages outside the timeline). validateAIM3DManifest stays the authority: its errors are always included.

      The attributes are grouped into features (model, annotations, timeline, photos, certainty, ...) with a count and a status each - manifestFeatureIcons() is what the manifest lists will show as icons.

      scripts/inspect-manifest.mjs prints the report for manifest files. The enum lists of aim3dviewer-validation.js are exported for reuse.

    • Worker: validation.json with Khronos glTF-Validator

      Every GLB/glTF model (converted, uploaded as GLB, or an uploaded .gltf) is checked by worker/validate.py before gltfpack, and the report is written to <job>/validation.json:

      • validator: glTF-Validator issues grouped by code, KTX2/WebP images the viewer decodes demoted to info
      • stats: triangles, vertices, draw calls, materials, largest texture, ...
      • bounds: world-space bounding box of the default scene in metres
      • checks: no meshes, implausible size (export units), heavy triangle counts and textures
      • status ok / warning / error with counts

      GET /api/model/validation/<id> serves the report; the job status and every job of GET /api/jobs carry its summary. Validation never fails a job. The validator binary is installed in the worker base image.

    • Website: number of the item in view beside the carousel dots
    • Website: manifest code behind a button, tiles as swipeable rows on phones

      On phones both manifest excerpts (the manifest section's and the one under the 4D timeline) are hidden whole behind a "Show the manifest code" button. The capability tasks, the point cloud tools, the manifest's data cards and the deployment tiles become rows to swipe, like the pictured stages, with dots below. The page on a 390 px screen goes from about 22 500 px to 18 700 px; the desktop layout is unchanged.

    • Website: shorten the page on phones

      Below 1340 px the section links fold into a menu behind a button in the header (they were hidden before). On phones the pictured stages of the point cloud, 4D and light sections become swipeable rows with dots, long lists, the manifest's data cards and the two tables show their first items with a button for the rest, the store QR codes give way to the badges side by side, and a button leads back to the top. The page on a 390 px screen goes from about 33 100 px to 22 500 px; the desktop layout is unchanged.

    • Point clouds: class colours and visibility without per-vertex uniform array indexing

      On an Android phone (Adreno 750) point clouds drew for a moment with the plain PointsMaterial, then vanished once the point cloud material took over - no shader error, camera and data in order, Eye-Dome Lighting ruled out. The material's vertex shader read uClassVisible[class] and uClassColors[class] - uniform arrays indexed per vertex, which Adreno drivers are known to get wrong; a misread hides the point.

      Visibility is now a bit mask (uint uClassHiddenMask, bit n = class n hidden) and the class colours a 32x1 texture read with texelFetch.

    • Viewer: ?gldebug=1 shows WebGL and console diagnostics on screen

      For devices without developer tools at hand (phones): the GPU, its WebGL limits (float render targets, point size range, precision, uniforms), the console's errors and warnings - three.js shader errors among them - and the camera and point cloud state, refreshed every second, for a screenshot. Nothing runs without the parameter.

    • Point clouds: Potree and 3D Tiles load after switching models; binary examples no longer corrupted in dist
      • tiles.js: the tileset is oriented/centred as soon as its root is in, before content is requested. A Potree cloud in UTM coordinates stayed out of sight of the previous model's camera, so no tile ever loaded.
      • loaders.js: the hand hint's sweep is stopped for tiled models too. It kept orbiting the camera around the previous model's pose and held the fly-in, so a streamed cloud was seen from the old close-up.
      • .gitattributes: LAS/LAZ, pnts/b3dm/i3dm/cmpt, splats, KTX2, Draco and other binaries marked binary. "dist/** text eol=lf" applied to them and stripped CR bytes from the committed dist copies (points.laz, *.pnts, Tempietto.splat); dist/dev and dist/test rebuilt with intact files.
    • Fixes
    • Fixes
    • PC updates, fixes and optimizations
    • PC updates, fixes and optimizations
    • PC updates, fixes and optimizations
    • PC updates, fixes and optimizations
    • UI Fixes
    • iOS assets added
    • iOS build
    • Changes
    • Website: AR in your room and motion look in the app (Pro+)
    • Website changes
    • Privacy policy
    • Viewer menus: the rail beside the notch no longer adds the safe area twice

      The app and the standalone page already pad themselves by the safe area; the rail added the inset again on top, so on a phone on its side it was twice as wide next to the notch. Now only a fullscreen viewer, which reaches the screen's edges, adds it.

    • Website: one view of the Wołpa synagogue in different light (sun morning, noon, evening; raking light; comparison; night)
    • Website: describe sun and shadows, variant comparison, historical photos, VR/AR and Gaussian splats
    • Viewer menus: AR and motion look set off on phones

      What the phone itself adds stands apart from the navigation: AR in the room (the app's, else the browser's WebXR) as an accent pill with its name, and the app's motion look as a round button lit while on. Upright they stand on the right above the navigation row, on its side under it in the top right corner (the axes keep the bottom right); they make way for an open sheet, and the notices keep above them. Motion look leaves More > Device, where the 360° video stays. On a desktop the browser's VR/AR stays an icon after the navigation.

    • iOS release script: resume a failed build at the archive or the export

      --from archive archives the bundle synced last time, --from export exports or uploads the archive built last time (ios/App/build/App.xcarchive), so a failed export no longer means a whole new build. A resumed build keeps its build number (an export takes the archive's own). A step that fails prints the command that resumes it, with the same options.

    • New version
    • Viewer menus: the bar's position from settings and manifests; names as quiet labels
      • The bar's offset is the toolbar position of the viewer settings and manifests (viewer.editorToolbar.position), as for the classic toolbar: read when the bar is built, stored when it is dragged, applied by applyEditorToolbarConfig.
      • Viewer.getCurrentEditorToolbarState() and Viewer.applyEditorToolbarConfig() were called by the manifest export and import (editor/annotations.js) but never defined on Viewer, so neither toolbar's state reached a manifest or came back from one; now they are.
      • On a desktop a menu's name shows as a small label over its icon, fading in after a moment's rest of the pointer, instead of widening the tab and moving the bar; none while the menu is open.
    • Viewer menus: icon-only tabs on a desktop, a grip to move the bar, notices by the bar only
      • On a desktop the menus show their icons; the name slides out, quietly (the room first, then the name fading in), on hover, keyboard focus and while the menu is open. Phones keep the names under the icons.
      • The grip (dots) moves the bar again, kept inside the viewer; moved up, a menu opens below it instead of above.
      • Notices keep above the bar itself, not above an open menu, and stay put when the bar was moved away from the bottom.
    • Viewer menus: transform modes at hand, projection under Look, Settings instead of Help
      • Orbit, move, rotate and scale join the camera reset, full screen and VR/AR outside the menus (a divider between the two); on a phone upright they stand in a row above the tabs instead of a column.
      • The projection goes to Look.
      • Help becomes Settings: the classic main menu's contents (language, theme, share view, embed) open there, above the help, bug report and diagnostics; the cog of that menu is not shown.
    • Viewer: the toolbar as named menus, laid out for desktop, phone and phone on its side

      A test of the proposed toolbar: the tools of editor-toolbar.js - the same buttons, with their submenus and state - sorted into menus with names (Look, Analysis, Measure, Story, Model, File, Device, Help) instead of one row of 32 icons (ui/menu-toolbar.js, css/menu-toolbar.css). Submenus open in place, as an accordion, and the rows show the tools' names.

      • The sun and the raking light leave Lights for Analysis, with the section planes, certainty and the comparison of variants (shown where there is something to compare); the tour and the historical photos leave Annotate for Story, its imports and exports go to File.
      • Navigation (reset camera, projection, full screen, VR/AR, the app's AR, the settings menu) stays at hand outside the menus.
      • Three layouts by the viewer's size (not the window's): on a desktop a bar in the bottom left corner with a panel above the menu's name; on a phone upright five tabs along the bottom (the rest under More) with a sheet; on a phone on its side the tabs down the left edge with a panel beside them, the model staying in view. Turning the phone, the tabs fly to their new places and turn a quarter the way it turned (not with reduced motion).
      • Notices stay above the bar and an open menu; the app's first-run hints point at More.

      The default on this branch; ?ui=classic (or viewer.ui: "classic") brings the classic toolbar back, and the existing tests ask for it. Labels that only showed as tooltips are now on screen, so the missing ones are translated (ON/OFF, environment map styles, section planes...).

    • iOS added
    • iOS added
    • Viewer: the sun is switched on at the day and hour it is now at the place

      Lights > Sun and shadows started at 21 June, 15:00. It now starts at the current day and time in the model's time zone (location.utcOffset, else the longitude's), taken when the sun is switched on, so a page left open does not keep a stale hour. A day or hour from a manifest, from the panel or from playing the day is kept (sunStudy.dateSet) through switching off and on; what a manifest leaves out (sun.time, sun.date) is now at its place.

    • Viewer: labels of the sun, raking light and ambient occlusion menu entries

      The lights menu relabels the buttons it keeps (lightsSubmenuButtons) when the toolbar is labelled and when the language changes, from a table that did not have the sun (lightSun*), raking light (lightRaking*) and ambient occlusion (renderingAo*) entries: their titles fell back to the keys. The translations were there already.

    • Viewer: optional WebGPU renderer (viewer.renderer / ?renderer=webgpu)

      three's WebGPURenderer, chosen when the viewer starts (viewer/renderer-backend.js): "webgl" stays the default; "webgpu" or "auto" use it where navigator.gpu exists and the model is a mesh. Point clouds (no point size in WebGPU) and Gaussian splats (Spark needs WebGL) keep the WebGL renderer, as does any browser without WebGPU.

      With WebGPU:

      • the loaders' materials become node materials; the dimmed direct specular of setupSingleMaterial() comes from a registered lighting model instead of the GLSL patch
      • post-processing and ambient occlusion are a TSL RenderPipeline (viewer/rendering-webgpu.js): GTAONode on a normal/depth pre-pass, applied to the indirect light; SMAA/FXAA use multisampling (three's nodes return an opaque image over the transparent canvas)
      • section planes clip through clipping groups (the models and the section fill get ClippingGroup's flags)
      • the face selection rectangle draws its ID buffer with node materials and reads it back asynchronously
      • thumbnails read the view back from a render target
      • environment maps use the WebGPU PMREM generator
      • left out, with a message: the custom GLSL scan view, the tone mapping previews, WebXR (Quick Look stays), opening point clouds and splats
      • a work-around drops three's identity "rgba" texture view swizzle, which older Chromium releases reject

      Closed lines (section plane borders, area measurements, annotation regions) are Lines back to their first point: WebGPU draws no LineLoop.

      The WebGPU code is a chunk of its own (rollup manualChunks), so the default WebGL viewer downloads no more than before. Credits name the renderer. Tests run the WebGPU renderer on its WebGL 2 backend (&webgpuBackend=webgl2); the one on a WebGPU device is opt-in (WEBGPU_TESTS=1), as software WebGPU slows the tests beside it.

      Moved onto main from claude/eloquent-cannon-pmr5do, whose own Gaussian splats and WebXR main already has in other forms (#136): WebXR is turned down on the WebGPU renderer in editor/webxr.js (VR and AR off, Quick Look kept), asked again once the renderer is chosen; the tests use main's XR state and its Tempietto.splat example.

    • Viewer: Gaussian splats (spz, splat, ksplat, sog, PLY of splats) via Spark

      Spark is loaded with the first splat file only. Splats are turned upright, framed and measured like any model; the worker keeps splat files (and a PLY of Gaussians) as uploaded. Adds a Tempietto splat example.

    • Viewer: VR and AR in the browser (WebXR), and AR Quick Look on iOS

      VR stands the viewer on the model's ground at real size, with teleport by controller; AR puts the model on a table or at real size in the room. On iPhone and iPad the model is exported to USDZ for AR Quick Look.

    • Viewer: sun and shadows at the model's place, day and hour

      Lights > Sun and shadows puts the sun where it stood at AIM3DViewer.location (latitude, longitude, north in the scene, time zone) on the day and hour of AIM3DViewer.sun, with shadows on the model and the ground. With a 4D timeline the year is the timeline's. The panel sets the hour (or plays the day), the day and the place and shows the sun's bearing, height, sunrise and sunset. The other lights are dimmed to a sky light while it is on; it and the raking light take turns. Example: tempietto-aim3d-sun.json.

    • Viewer: historical photos over the model, from the camera they were taken from

      AIM3DViewer.photos lists photos (an image URL or a IIIF Image API service) with the view they were taken from. Opening one in the new panel (Annotations > Historical photos) flies the camera there, sets the 4D timeline to the photo's year and lays the photo over the canvas, faded or cut by a divider. A photo is added by URL or from a file and lined up by hand: it stays on screen while the camera, the lens and the tilt are matched, then the pose is saved. Example: tempietto-aim3d-photos.json.

    • Viewer: reconstruction variants and a comparison view (swipe or side by side)

      Variants are alternative hypotheses of the same object, each a set of model nodes, read from the model (glTF extras) or the manifest (AIM3DViewer.variants), with a description, a source and a Level of Certainty value. One is shown at a time, chosen in a new panel. The comparison view draws two variants, or two years of the 4D timeline, split by a draggable divider or side by side (AIM3DViewer.compare, ?compare=, ?compareLeft=, ?compareRight=). The timeline panel gets a button that compares its first and last stage.

    • Logo rebuilt
    • Release
    • Styling UI
    • Fixes in UI
    • Updated links
    • Viewer: more visible load percentage; keep annotation Save reachable on mobile

      The loading percentage badge (toolbar and corner) is slightly larger and higher-contrast. The annotation dialog now fits the visual viewport, so the on-screen keyboard no longer hides it, and its action buttons stick to the bottom of the panel with larger touch targets.

    • Restore dist/test (rebuilt locally by mistake in the previous commit)
    • docs: Cloudflare cache rule must cover path-prefixed /files/
    • App: let the WebView and Cloudflare cache /files/ downloads

      Model downloads, IFC properties and the reachability check in the app fetched /files/ with cache: "no-store", which skips the WebView cache and sends Cache-Control: no-cache upstream. Those URLs never change (random job ids, purged on delete), so the default cache mode is safe.

      Document the Cloudflare Cache Rule /files/ needs: .glb, .gltf, .json and the other model formats are not on Cloudflare's default cached extensions, so s-maxage alone leaves them DYNAMIC.

    • Android update
    • Test build
    • Website updates
    • Fixes for Wolpa
    • Fixes
    • Fixes and new features
    • Fixes and new features
    • Deploy: keep the server's docker-compose.override.yml

      COMPOSE_FILE stops compose from reading docker-compose.override.yml by itself, so the deploy step adds it back last when the server has one (local mounts such as the accounts directory). Also a 60 s timeout for the point cloud test (three models; ~35 s on the CI runner) and a guide for moving an old clone to ExPlora4D.

    • Deploy override
    • Fixes for tests
    • Caching enabled
    • Caching enabled
    • Website changes, testing models changed
    • New features and UI
    • ios changes
    • iOS build prepared
    • Timeline added
    • Website updated
    • updates
    • Latest fixes
    • Fixes
    • Fixed logo opacity for mobile
    • Fixes for login
    • Tests fixes
    • IIIF logo added
    • IIIF logo added
    • IIIF logo added
    • updates and fixes
    • New release
    • Website added, new shading modes added
    • Changes in schema and opacity LoC
    • LoC proposed
    • Upload by URL added
    • Updates styles, manifest added
    • Fixed tests
    • Moved to pnpm
    • updated deps
    • Android optimize
    • iOS init build
    • Code cleanup, anno fixes, lang updates

September 2026

    • Styling changes
    • Schema updates:
    • Filters changed
    • Tests fix
    • Annotations fixes
    • Testing update
    • Build changes
    • Added rendering modes
    • Styling for tablets
    • Point Clouds changes
    • Delete account added
    • Privacy policy added
    • Publish release
    • Extended docs
    • Styling changes
    • Bugfixes
    • Package-lock update
    • Updates
    • Icon generated
    • Mobile styling fixes
    • Updated deps
    • Reporting bugs added
    • Update icons
    • Docs updated
    • Updated mobile and fixes
    • Few fixes
    • Package update
    • Mobile changes, ads, fixes
    • Double click aded
    • Double tap / double click focuses the camera on the point

      On the model: the point becomes the orbit centre and the camera comes halfway closer, keeping its viewing direction (orthographic: zoom x2). On empty space: back to the whole model (resetCamera). Mouse uses dblclick; touch and pen taps are detected on the canvas (touch-action is none there), and a second finger cancels them. Skipped while taps place points (ruler, face picking), with interaction off, and on annotation markers.

    • Measurements panel: close button

      Closing works like switching the ruler tool off: measuring ends and the results are cleared (with the ruler already off, the panel only shows model dimensions, which are hidden). Larger touch target on touch screens.

    • Phones: tool panels below the metadata card, hints wrap

      The side stack (section planes, measurements, animation player) sat in the top-right corner under the metadata card, which covered it on a phone; it now starts below the collapsed card, spans the width and stays above an expanded card. Picking and clipping hints wrap instead of running off the screen. Also fixes Polish typos (Przelącz, plaszczyznę).

    • Toolbar on phones: tap-toggled submenus above the card, toasts above it

      On touch screens a tap leaves a tool hovered and focused, so its CSS-opened submenu could not be closed by tapping the tool again. Submenus in the secondary tray now open and close on tap (.submenu-open), one at a time, and a tap outside the toolbar closes them. On phones they open above the whole card instead of over the row above their tool. Toasts are lifted above the toolbar, the expanded card and an open submenu, measured when each toast shows and when these change.

    • Toolbar on phones: secondary tools open as a card above it

      Expanding widened the toolbar and shifted it left by half the added width to stay centred; on a phone there is no room for that, so it slid off the left edge and scrolled. On narrow screens the toolbar now keeps its size and place, and the secondary tray opens above it, wrapped in rows.

    • Initial mobile commit
    • Mobile app: repository address in the app, models kept on the device

      The model browser takes the repository address in the app build and keeps it on the device (mobile.remoteUrl is the default). Single-file models there can be saved to an IndexedDB library (offline-library.js), with their thumbnails. The header's device button now opens that library: saved models plus Browse files, which opens a device file and keeps a copy once it has loaded.

    • Mobile app: repository access online, open files from the device

      remote.js points the worker API calls and the URLs the worker returns at mobile.remoteUrl in the app build (unchanged on a normal page). The app hides sign-in (session cookie is same-site only) and, with no repository configured, upload and the model browser. A header button opens a model or .zip from the device, sharing the drag-and-drop code path.

    • Mobile app scaffold (Capacitor, Android)

      Adds a BUILD=mobile rollup target (dist/mobile, no PHP/admin/source maps), the Capacitor config and generated Android project, and connectivity.js, which tracks online/offline state and hides server-only controls offline.

    • Fixes
    • 3d-tiles-renderer fixes
    • Scale unified
    • Styling for mobiles
    • CF cache & styling
    • IIIF Presentation 4 extended #3
    • IIIF Presentation 4 extended #2
    • IIIF Presentation 4 extended
    • Lights fixes
    • IIIF Presentation 4
    • Camera race condition change
    • LAS added
    • Tests fixed, point clouds init (beta)
    • Tiles added
    • Docker optimize
    • Progressive meshed and Docker optimize
    • Picking faces rebuilt
    • Limits introduced
    • Tours initial state
    • Fixes
    • Clipping planes fixes
    • Animations added
    • Mailing added
    • Deps updated
    • Versioning changed
    • User panel rebuilt
    • WebGL context checking
    • Authors added
    • Admin panel fixes
    • Admin panel fixes
    • Admin panel fixes
    • Admin panel new features
    • Browse files added
    • Worker readme updated
    • IFC worker fixed
    • Admin panel added
    • Build up added
    • Admin changes
    • Readme reorganized
    • Readme updated
    • Test fixes
    • IFC changes
    • IFC metadata extended
    • Major changes
    • Tests fixed
    • Test
    • AIM3D changes
    • Styling and rebuilt
    • Fixed default paths
    • Fixed example file
    • Docker override added
    • Docker fixes
    • Shader button and features added
    • Added shading
    • Fixes
    • Docker rebuild, styling changes
    • Added missing files
    • Styling fixes and docker introduced
    • Styling fixes
    • Fixes
    • Fixed local tests, camera and renderings

August 2026

    • Fixed window resize bug
    • Fixed renderings
    • Updates
    • Updates
    • Fixes
    • Bugfixes
    • Latest fixes
    • Changes
    • Fixes with manifests
    • Toolbar changes
    • Updated deps
    • PNPM deps updated
    • Updated deps
    • Manifesto updated
    • Resize and move viewer added
    • Fullscreen fixed
    • Toolbar fixes, code cleanup
    • Toolbar changes, manifesto updated
    • Share link added
    • Update deps

July 2026

    • XSLT changes
    • XSLT test
    • XSLT test
    • XSLT test
    • XSLT test
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Updated render script
    • Fixes
    • New build
    • Latest fixes
    • Changed packages
    • Fixed playwright tests
    • Env map fixes
    • Fixes
    • Fixes and gallery updated
    • Credits added
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release
    • Latest release

June 2026

    • Code embed fixed
    • required fields fixed
    • Bugfixes
    • AIM3IF parser
    • IIIF JSON extended
    • IIIF fix
    • Changed icon
    • Annotations enriched with IIIF manifesto
    • Fixes for updates
    • Fixes for updates
    • Fixes with env maps
    • Styling changes
    • Latest changes and updates
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixed relative paths
    • Simple admin panel created
    • Documentation updated and extended
    • Documentation updated and extended
    • Readme updated
    • Bugfixes
    • Bugfixes
    • Bugfixes
    • Bugfixes
    • Bugfixes
    • Drush fixes
    • Drupal module fixes

May 2026

    • Toolbar fixes
    • Toolbar fixes
    • Toolbar fixes
    • Toolbar fixes
    • Toolbar movement
    • Drupal fields
    • Bugfixes
    • Bugfixes
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Viewer changes, Drupal new field
    • Viewer changes, Drupal new field
    • Viewer changes, Drupal new field
    • Viewer changes, Drupal new field
    • Viewer changes, Drupal new field
    • Viewer changes, Drupal new field
    • Viewer changes, Drupal new field
    • Viewer changes, Drupal new field
    • Code refactor, materials fixes
    • Styling fixes
    • Sandbox archives added
    • Fixes with canvas size
    • Code cleanup, race condition fixes
    • Code refactor, translations added, materials editor updated
    • Rendering optional when thumbnails exist
    • Render.sh changes
    • Materials rebuilt, convert.sh fixed
    • Added translations, toolbar fixes
    • NPM updates
    • Toolbar changes
    • Added buttons
    • Toolbar fixes
    • Toolbar fixes
    • Toolbar fixes
    • Toolbar fixes
    • Toolbar fixes
    • Toolbar fixes
    • Toolbar fixes
    • Main toolbar fixes
    • Main toolbar fixes
    • Main menu updates
    • Main menu changes
    • Main menu redesigned
    • Fullscreen changes
    • Main menu redesigned
    • Loading changes
    • Loader updated
    • Loader updated
    • Loading bar changed

April 2026

    • Updated code, fixes, translations
    • Fixes
    • Sandbox mode added
    • Fake logs added
    • field_df updated
    • Bugfixes
    • Fixes with url options
    • Fixes
    • Fixes
    • Fixes
    • Tauri added
    • three updated r184
    • Presentation mode updates
    • Presentation mode updates
    • Presentation mode changes
    • Added translations, fixed notifitations
    • Translations, presentation mode, UI fixes
    • Presentation mode entry
    • Reset camera fixes
    • Language selector updated
    • Added language selector, provided basic translations
    • Metadata changes
    • Fixes for notifications
    • Embeding menu, notifications improvements
    • Embeding menu, notifications improvements
    • Embeding menu, notifications improvements
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Drupal module updated
    • Normalization stage
    • Normalization stage
    • Normalization stage
    • Uncompress fixes
    • Uncompress fixes
    • Logging fixes
    • Convert.sh fixed paths
    • Added log timestamp
    • Dist update
    • Updates
    • Updates
    • Packages updates, CSS styling
    • Module fixes
    • Module fixes
    • Module fixes
    • Module fixes
    • Module fixes
    • Module fixes
    • Module fixes
    • Module output log
    • Module output log
    • Export XSLT changes
    • Export XSLT changes
    • Export XSLT changes
    • Export XSLT changes
    • Export XSLT changes
    • Export XSLT changes
    • Export XSLT changes
    • Drupal update
    • Export XSLT changes
    • Export XSLT changes

March 2026

    • XSLT export
    • XSLT export
    • XSLT export
    • XSLT export
    • Menu styling
    • Drupal build update
    • Logs moved to viewer container
    • Metadata styling
    • Styling changes
    • Styling changes
    • WASM changes
    • WASM changes
    • WASM changes
    • WASM changes
    • Bugfixes, optimizations
    • Fixed paths
    • Fixed paths
    • Fixed paths
    • Fixed paths
    • Paths changed
    • Paths changed
    • Paths changed
    • Paths changed
    • Paths changed
    • Hamburger menu, loading metadata from JSON, embedding enabled
    • Fixes
    • Clipping planes, Draco loader and localhost drag&drop added
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Playwright tests fixes
    • Cleanup for tests
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • Drupal paths changes
    • IFC fix for module loading
    • IFC fix for module loading
    • IFC fix for module loading
    • IFC fix for module loading
    • IFC fix for module loading
    • Fixed measurement, fixed camera reset
    • Ruler fixed
    • Ruler fixed
    • Ruler fixed
    • Ruler fixed
    • Ruler fixed
    • Updated dists
    • Codesandbox change
    • Codesandbox change
    • Styling, loader change
    • CSS styling
    • CSS stylins
    • Bugfix
    • Performance changes, CSS, script for tests, playwright changes
    • Playwright tests fix
    • Playwright tests fix
    • Playwright tests fix
    • Playwright tests fix
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Breaking changes with compilation
    • Updated tests
    • Updated dependencies
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • README update, CSS and poller changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Metadata changes
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Code cleanup
    • Rendering changes
    • Rendering changes
    • Make scripts executable
    • Make scripts executable
    • Render fixes
    • Gitignore cleanup
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Initial worker runner kick
    • Rendering changes
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Initial progress loader
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Styling gallery
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Worker changes
    • Code refactor
    • Code refactor
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • New release
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Added debug
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Revert back to presave
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fix for XML

February 2026

    • Fixes for worker
    • Fixes for worker
    • Fixes for worker
    • Drupal module update
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Module updated
    • Updated Worker
    • Updated Worker
    • Updated Worker
    • Code refactor
    • Code refactor
    • Code refactor
    • Updated animate function
    • Updated animate function
    • Updated animate function
    • Updated animate function
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Service for saving created
    • Fixed arguments when loading
    • Fixed arguments when loading
    • Fixed arguments when loading
    • Fixed arguments when loading
    • Fixed arguments when loading
    • Fixed arguments when loading
    • Fixed arguments when loading
    • Fixes for compressed files
    • Fixes for rendering
    • Fixes for bools
    • Logging added
    • Added service for convert and uncompress
    • Added service for convert and uncompress
    • Added service for models
    • Updated README
    • Updated README
    • Updated README and packages
    • Gallery fixes
    • Bugfix metadata styles
    • Bugfix metadata styles
    • Bugfix metadata styles
    • Bugfix metadata styles
    • Bugfix metadata
    • Bugfix metadata
    • Bugfix
    • Bugfix
    • Bugfix
    • Bugfix
    • Bugfix
    • Bugfix
    • Bugfix
    • Bugfix
    • Bugfix
    • Bugfix
    • Updated metadata
    • Metadata styling, fixes with lightweight
    • IFC fixed
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Drupal debug
    • Updated CSS, bugfixes
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script for multiple files
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Render script changed
    • Convert script changed
    • Convert script changed
    • Increased resolution
    • Increased resolution
    • Increased resolution
    • XML export controller
    • XML export controller
    • XML export controller
    • XML export controller
    • XML export controller
    • XML export controller
    • XML export controller
    • XML export controller
    • XML export controller
    • XML export controller
    • XML export controller
    • Bugfixes
    • Bugfixes
    • Bugfixes
    • Bugfixes
    • Bugfixes
    • Bugfixes
    • Bugfixes
    • New release
    • Updated fullscreen fix
    • Updated fullscreen
    • New release

January 2026

    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Test module
    • Updated
    • Updated dists
    • Updated dists
    • Updated dists
    • Code refactor with colors
    • Updated screenshots module
    • Updated screenshots module
    • Updated screenshots module
    • Updated screenshots module
    • Updated screenshots module
    • Updated screenshots module
    • Updated screenshots module
    • Updated screenshots module
    • Updated screenshots module
    • Updated screenshots #8
    • Updated screenshots #7
    • Updated screenshots #6
    • Updated screenshots #6
    • Updated screenshots #5
    • Updated screenshots #4
    • Updated screenshots #3
    • Updated screenshots #2
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated screenshots
    • Updated paths metadata
    • Updated paths metadata
    • Updated rollup paths
    • Updated rollup paths
    • Updated rollup paths
    • Updated rollup paths
    • Updated rollup paths
    • Updated rollup paths
    • Updated rollup paths
    • Updated rollup paths
    • Updated rollup paths
    • Updated rollup paths
    • Updated module structure
    • Updated module structure
    • Playwright tests updated
    • Playwright tests updated
    • Playwright tests updated
    • Playwright tests updated
    • Updated Drupal module
    • Updated Drupal module
    • Updated gitignore
    • Fixes with metadata
    • Metadata fixes
    • Changed dist, animation fixed
    • Animation changed, metadata fixes
    • Camera zoom added
    • Module updated
    • Bugfixes, camera styling, metadata fixed
    • Metadata handling fixed
    • Update
    • Thumbnail check
    • Metadata fixes
    • Cleanup
    • Cleanup
    • Bugfixes with saving metadata
    • Controller changes
    • Controller changes
    • Changed Drupal Controller
    • Fixes, styling
    • Fixes
    • Fixes
    • Fixes
    • Fixes, gallery fix
    • Cleanup
    • Module updates
    • Minor fixes, CSS styling
    • HDR added
    • Fixed CSS
    • Fixed CSS
    • Added initial hand, bugfixes
    • PHP API created, minor fixes
    • Initial tests
    • Bugfixes

December 2025

    • Loader fixes and camera movement
    • Loader fixes and camera movement
    • Updated loaders
    • Updated zip dist
    • Testing release
    • Testing release
    • Fixes of styling
    • Bugfixes
    • Fixes, styling
    • Rebuild for max flexibility
    • Rebuild for max flexibility
    • Test
    • Fixes for fullscreen, resize etc
    • Pushed changes for three.js
    • Styling parent element
    • Pushed changes

November 2025

    • Pushed dist
    • Dist changes and fixes
    • Dist changes
    • Bugfix
    • Bugfix
    • Updated for lightweight version
    • Added IIIF loading areas
    • Update README.md
    • Update README.md
    • Updates for dist
    • Updated packages
    • Readme updated

October 2025

    • Added package building, updated README
    • Updated README
    • Updated documentation
    • Stop tracking generated dist files; keep allowed files per .gitignore
    • Rebuild circularities
    • Fixes
    • gitignore
    • Fixes
    • Changes for lightweight version
    • Model transforms for multiple objects, background bugfixes
    • Fixes for IIIF loading multiple times
    • Bugfixes for IIIF reloading
    • Fixes for IIIF
    • Background fix
    • IIIF changes

September 2025

    • Small bugfix
    • Major fixes and implementations
    • Fixes, threejs update
    • Fixes
    • Fix
    • Updates
    • Changes for Drupal
    • Bugfixes
    • Update
    • Update
    • Rollup build added
    • Tests
    • Fixes
    • Fixes

August 2025

    • Metadata handling refactor
    • Refactor code
    • #
    • External sources after refactor
    • Loading 3D model from IIIF manifesto URL, fixes, refactor

July 2025

    • Fix for glb scaling
    • Fixes, three.js updated to r178
    • Test commit
    • IIIF fixes and standarization
    • Initial setup with IIIF
    • Initial npm commit

June 2025

    • CONFIG updated
    • Initial config rebuild
    • Fixes and viewer-settings.json added

May 2025

    • Updated blender support for ver 4.4
    • Fixed settings name
    • Drupal form updated, bugfixes, lightweight mode changed

April 2025

    • Fixes and changes
    • Bugfixes for editor metadata
    • Fix for non-existing elements
    • Updated to R175
    • Bugfixes, module rebuild

February 2025

    • Update dfg_3dviewer.module
    • Update build_xml.php
    • Update dfg_3dviewer.module
    • Update render.py
    • Update convert.sh
    • Update render.py
    • Rendering fixes
    • Update 2gltf2.py

January 2025

    • Redesigned rendering pipeline
    • Updated Three.js r172
    • Updated Three.js r172
    • Background settings changed for gradient
    • Bugfixes for fullscreen movement, added gradient background

December 2024

    • Changed paths
    • Changed camera placement and fullscreen icon
    • Zoom to fit object changed
    • Removed lil-gui-master repo
    • Upgrade to 171, changed paths

November 2024

    • Added backup scripts
    • Fixes and changes
    • Updated build three r170

October 2024

    • Updated forms
    • Rewritten PHP script for storing metadata
    • Drupal integration with module
    • New form for loading and saving settings

September 2024

    • Permissions added
    • Drupal menu initial commit
    • Updated three.js library

August 2024

    • Fixes

July 2024

    • Clean-up
    • Updated lil-gui
    • Fixes for GML and PHP scripts
    • XSLT export fixed
    • GML format added
    • Convert.sh script updated
    • Convert.sh script updated
    • Conver.sh script updated
    • Updated scripts
    • Fixes for metadata loading
    • Code rebuild, fixes and r166 update
    • Updates, fixes

June 2024

    • Fixes and updates
    • Clipping planes fix
    • Drupal update
    • Drupal update
    • Drupal update

May 2024

    • Release R164 updates and fixes

April 2024

    • Updates for r163
    • Updated three.sj
    • Small fixes

June 2023

    • Changed helperObject
    • Updated readme
    • Drupal fix
    • Readme updated with documentation
    • Bugfix for proxy
    • Bugfix for proxy
    • bugfixes
    • Thumbnail upload updated
    • Buxfixes, initial blender rendering

May 2023

    • Thumbnail and metadata saving fixes

March 2023

    • Fixed MTL when missing, code clean-up, wisski dependencies removed
    • wisski cleanup
    • proxyPath replaced by CONFIG.lightweight
    • Small changes
    • Bugfixes
    • Proxy bugfix
    • Added routing module for Drupal

February 2023

    • Rendering manual added
    • Saving settings fixed
    • Bugfixes
    • Redesigned files hierarchy
    • Fixed saving properties
    • Fixed saving properties
    • ProxyPath updated for non-Drupal
    • ProxyPath updated for non-Drupal
    • Make path prefix configurable
    • Background color added
    • Changed lil-gui version to three.js
    • Changed lil-gui version to three.js
    • Fixes for viewer
    • Updated to three.js 149

January 2023

    • ResponseType XML changed
    • Fixes

December 2022

    • Fix for module
    • Fix for module
    • Update README.md
    • Paths updated

November 2022

    • Paths updated
    • Path changed
    • Downloadmodel disabled for proxy

October 2022

    • Code fixes
    • License added
    • Firefox hotfix
    • Upgraded three.js to 145 version
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • Fixes
    • config.json.example added
    • config.json added

September 2022

    • Bugfixes XYZ and non-automated paths
    • Bugfixes, styling
    • Semi-automatic removed form JS

August 2022

    • Bugfixes, styling
    • Picking faces added
    • Picking faces feature added
    • Paths changed for automatic mapping

July 2022

    • Semi-automatic files handler
    • Styling
    • Major fixes, proxy changes
    • Proxy fixes #2
    • Proxy fixes #1
    • Fixes
    • Fixes
    • Bugfixes

June 2022

    • Fixes, changes and new features
    • Fixes
    • Fixes

May 2022

    • CSS changes and fixes for metadata
    • CSS fixes
    • Renderings fixes
    • Improvements

April 2022

    • Rendering fixes for Drupal
    • Rendering changes

March 2022

    • Code refactor
    • PDC Loader added
    • Clean up project
    • Clean up project
    • Clean up main.js
    • Clean up
    • Clean up
    • Common changes
    • Migrated to 136 build threejs
    • Render.py centered object
    • Fixes for renderer
    • Fixes and off-screen rendering added
    • Added off-screen renderer

February 2022

    • Metadata settings changed
    • Clipping planes added
    • Clipping planes added
    • Added light settings metadata
    • GUI styling 2
    • GUI styling

January 2022

    • DFG Derivative Formatter
    • Fixes
    • Tar fixes
    • TAR files added, fixes
    • RAR upload fixed
    • IFC Convert and rar files added

December 2021

    • TAR decompression added
    • Notifications added
    • Picking and canvas size changed
    • Metadata saving
    • Hierachy changes, lil-gui added
    • Hierarchy material changes
    • Path changed
    • Hierarchy changes
    • Changes of scripts
    • Update README.md
    • Changes of scripts
    • Changes of scripts
    • gitignore added
    • Initial commit
    • Initial commit