From ea4c411d5ce384ad6fac114baa5035cf1e7571db Mon Sep 17 00:00:00 2001 From: Jose Duarte Date: Fri, 8 May 2026 19:32:57 -0700 Subject: [PATCH 1/7] model-server: fix omit_water boolean parsing for REST GET requests Co-Authored-By: Claude Opus 4.7 --- src/servers/model/CHANGELOG.md | 3 +++ src/servers/model/server/api.ts | 2 +- src/servers/model/version.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/servers/model/CHANGELOG.md b/src/servers/model/CHANGELOG.md index b6414cb23..fc3c98925 100644 --- a/src/servers/model/CHANGELOG.md +++ b/src/servers/model/CHANGELOG.md @@ -1,3 +1,6 @@ +# 0.9.13 +* /surroundingLigands: honor `omit_water=true|false` for REST GET requests (boolean parser previously coerced both to `false`) + # 0.9.12 * add `health-check` endpoint + `healthCheckPath` config prop to report service health diff --git a/src/servers/model/server/api.ts b/src/servers/model/server/api.ts index 70d864251..f5cdec422 100644 --- a/src/servers/model/server/api.ts +++ b/src/servers/model/server/api.ts @@ -295,7 +295,7 @@ function _normalizeQueryParams(params: { [p: string]: string }, paramList: Query case QueryParamType.String: el = value; break; case QueryParamType.Integer: el = parseInt(value); break; case QueryParamType.Float: el = parseFloat(value); break; - case QueryParamType.Boolean: el = Boolean(+value); break; + case QueryParamType.Boolean: el = isTrue(value); break; } if (p.validation) p.validation(el); diff --git a/src/servers/model/version.ts b/src/servers/model/version.ts index d700f6931..65cb9e431 100644 --- a/src/servers/model/version.ts +++ b/src/servers/model/version.ts @@ -4,4 +4,4 @@ * @author David Sehnal */ -export const VERSION = '0.9.12'; \ No newline at end of file +export const VERSION = '0.9.13'; \ No newline at end of file From 5ca9020cbfcca04e48a8248b2ce5f4da740c1ce3 Mon Sep 17 00:00:00 2001 From: Jose Duarte Date: Fri, 8 May 2026 19:33:09 -0700 Subject: [PATCH 2/7] mol-model: fix water leak in surroundingLigands query Co-Authored-By: Claude Opus 4.7 --- src/mol-model/structure/query/queries/modifiers.ts | 6 ++++++ src/servers/model/CHANGELOG.md | 1 + 2 files changed, 7 insertions(+) diff --git a/src/mol-model/structure/query/queries/modifiers.ts b/src/mol-model/structure/query/queries/modifiers.ts index e736b62fc..a0a9b5c45 100644 --- a/src/mol-model/structure/query/queries/modifiers.ts +++ b/src/mol-model/structure/query/queries/modifiers.ts @@ -545,6 +545,12 @@ export function surroundingLigands({ query, radius, includeWater }: SurroundingL continue; } + // Water is handled exclusively by the `includeWater` 3D-lookup branch below. + // A single water pulled in via a struct_conn metalc/covale edge would + // otherwise match every other water in the chain (all share label_seq_id + // and label_comp_id) and leak the entire chain. + if (StructureProperties.entity.type(l) === 'water') continue; + residuesIt.setSegment(chainSegment); while (residuesIt.hasNext) { const residueSegment = residuesIt.move(); diff --git a/src/servers/model/CHANGELOG.md b/src/servers/model/CHANGELOG.md index fc3c98925..395c69d28 100644 --- a/src/servers/model/CHANGELOG.md +++ b/src/servers/model/CHANGELOG.md @@ -1,5 +1,6 @@ # 0.9.13 * /surroundingLigands: honor `omit_water=true|false` for REST GET requests (boolean parser previously coerced both to `false`) +* /surroundingLigands: stop leaking the asymmetric unit's water chain into the result when `omit_water=true` (water residues pulled in via struct_conn covale/metalc edges no longer match every other water in the chain) # 0.9.12 * add `health-check` endpoint + `healthCheckPath` config prop to report service health From e2e26c7e9c458f84d7b8c4aa73be2cf4e209d8fb Mon Sep 17 00:00:00 2001 From: Jose Duarte Date: Sat, 9 May 2026 22:28:38 -0700 Subject: [PATCH 3/7] Updating changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39b93620f..c59166854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that since we don't clearly distinguish between a public and private interf - Add mesoscale representation preset - Add presets option to `ObjectList` param definition - Fix memory leak in `State.dispose()` not invoking transformer `dispose` callbacks for live cells +- Fix bugs in ModelServer surroundingLigands endpoint, resulting in omitWater not honored ## [v5.9.0] - 2026-05-03 - Fix edge case when `PluginSpec.animations` is empty From 86bf859a63fed573384346c1d4c9e0df588ebfa4 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 16 May 2026 22:15:12 -0700 Subject: [PATCH 4/7] Fix SSAO half/quarter resolution textures for multi-scale --- CHANGELOG.md | 1 + src/mol-canvas3d/passes/ssao.ts | 48 ++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d8687ccb..3155c49f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Note that since we don't clearly distinguish between a public and private interf - Add presets option to `ObjectList` param definition - Fix memory leak in `State.dispose()` not invoking transformer `dispose` callbacks for live cells - Fix `Volume` and `Isosurface` getBoundingSphere ignoring instances +- Fix SSAO half/quarter resolution textures for multi-scale ## [v5.9.0] - 2026-05-03 - Fix edge case when `PluginSpec.animations` is empty diff --git a/src/mol-canvas3d/passes/ssao.ts b/src/mol-canvas3d/passes/ssao.ts index 9e3dc17bb..b362e39dd 100644 --- a/src/mol-canvas3d/passes/ssao.ts +++ b/src/mol-canvas3d/passes/ssao.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2026 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose * @author Áron Samuel Kovács @@ -484,27 +484,45 @@ export class SsaoPass { if (isTimingMode) this.webgl.timer.markEnd('SSAO.downsample'); } - if (isTimingMode) this.webgl.timer.mark('SSAO.half'); if (multiScale) { + // half-resolution viewport (matches dimensions of depthHalfTarget*) + const hsx = Math.floor(sx * 0.5); + const hsy = Math.floor(sy * 0.5); + const hsw = Math.ceil(sw * 0.5); + const hsh = Math.ceil(sh * 0.5); + state.viewport(hsx, hsy, hsw, hsh); + state.scissor(hsx, hsy, hsw, hsh); + + if (isTimingMode) this.webgl.timer.mark('SSAO.half'); this.depthHalfTargetOpaque.bind(); this.depthHalfRenderableOpaque.render(); - } - if (multiScale && includeTransparent) { - this.depthHalfTargetTransparent.bind(); - this.depthHalfRenderableTransparent.render(); - } - if (isTimingMode) this.webgl.timer.markEnd('SSAO.half'); + if (includeTransparent) { + this.depthHalfTargetTransparent.bind(); + this.depthHalfRenderableTransparent.render(); + } + if (isTimingMode) this.webgl.timer.markEnd('SSAO.half'); - if (isTimingMode) this.webgl.timer.mark('SSAO.quarter'); - if (multiScale) { + // quarter-resolution viewport (matches dimensions of depthQuarterTarget*) + const qsx = Math.floor(sx * 0.25); + const qsy = Math.floor(sy * 0.25); + const qsw = Math.ceil(sw * 0.25); + const qsh = Math.ceil(sh * 0.25); + state.viewport(qsx, qsy, qsw, qsh); + state.scissor(qsx, qsy, qsw, qsh); + + if (isTimingMode) this.webgl.timer.mark('SSAO.quarter'); this.depthQuarterTargetOpaque.bind(); this.depthQuarterRenderableOpaque.render(); + if (includeTransparent) { + this.depthQuarterTargetTransparent.bind(); + this.depthQuarterRenderableTransparent.render(); + } + if (isTimingMode) this.webgl.timer.markEnd('SSAO.quarter'); + + // restore full-scale viewport for SSAO + blur passes + state.viewport(sx, sy, sw, sh); + state.scissor(sx, sy, sw, sh); } - if (multiScale && includeTransparent) { - this.depthQuarterTargetTransparent.bind(); - this.depthQuarterRenderableTransparent.render(); - } - if (isTimingMode) this.webgl.timer.markEnd('SSAO.quarter'); if (isTimingMode) this.webgl.timer.mark('SSAO.opaque'); this.ssaoDepthTexture.attachFramebuffer(this.framebuffer, 'color0'); From 8d2a44983e6ea190f9d1b1798f2a6b7ab5df3bab Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 16 May 2026 22:29:55 -0700 Subject: [PATCH 5/7] remove superfluous enableAnimation param --- src/mol-canvas3d/canvas3d.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 09683137f..22d909966 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -98,7 +98,6 @@ export const Canvas3DParams = { transparentBackground: PD.Boolean(false), checkeredTransparentBackground: PD.Boolean(false), dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }), - enableAnimation: PD.Boolean(true, { description: 'Enable GPU time-based animations (wiggle/tumble).' }), pickPadding: PD.Numeric(3, { min: 0, max: 10, step: 1 }, { description: 'Extra pixels to around target to check in case target is empty.' }), userInteractionReleaseMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time before the user is not considered interacting anymore.' }), @@ -480,7 +479,6 @@ namespace Canvas3D { const hiZ = new HiZPass(webgl, passes.draw, canvas, p.hiZ); const renderer = Renderer.create(webgl, p.renderer); - renderer.setProps({ enableAnimation: p.enableAnimation }); renderer.setOcclusionTest(hiZ.isOccluded); const shaderManager = new ShaderManager(webgl, scene); @@ -677,7 +675,7 @@ namespace Canvas3D { const xrChanged = xrManager.update(xrFrame); if (!xrChanged && xrFrame) return false; - const activeAnimation = p.enableAnimation && scene.hasAnimation; + const activeAnimation = renderer.props.enableAnimation && scene.hasAnimation; const shouldRender = force || cameraChanged || resized || forceNextRender || xrChanged || activeAnimation; forceNextRender = false; @@ -1071,7 +1069,6 @@ namespace Canvas3D { transparentBackground: p.transparentBackground, checkeredTransparentBackground: p.checkeredTransparentBackground, dpoitIterations: p.dpoitIterations, - enableAnimation: p.enableAnimation, pickPadding: p.pickPadding, userInteractionReleaseMs: p.userInteractionReleaseMs, viewport: p.viewport, @@ -1317,10 +1314,6 @@ namespace Canvas3D { if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground; if (props.checkeredTransparentBackground !== undefined) p.checkeredTransparentBackground = props.checkeredTransparentBackground; if (props.dpoitIterations !== undefined) p.dpoitIterations = props.dpoitIterations; - if (props.enableAnimation !== undefined) { - p.enableAnimation = props.enableAnimation; - renderer.setProps({ enableAnimation: p.enableAnimation }); - } if (props.pickPadding !== undefined) { p.pickPadding = props.pickPadding; pickHelper.setPickPadding(p.pickPadding); From 7da4a85459572f50c99107d553075750a60c7f66 Mon Sep 17 00:00:00 2001 From: giagitom Date: Tue, 19 May 2026 16:43:00 +0200 Subject: [PATCH 6/7] Fix cel-shaded ambient color being stripped to luminance --- CHANGELOG.md | 1 + src/mol-gl/shader/chunks/apply-light-color.glsl.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cdf9bd44..f5a7b7592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file, following t Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here. ## [Unreleased] +- Fix cel-shaded ambient color being stripped to luminance (now uses full RGB, matching the classic lighting path) - Fix empty transforms default in `ShapeFromPly` - Use morton order for spheres in dot visual with lod-levels - Add `Camera.changed` event and rotation/translation setter/getter diff --git a/src/mol-gl/shader/chunks/apply-light-color.glsl.ts b/src/mol-gl/shader/chunks/apply-light-color.glsl.ts index 626fae8c0..b3b58cdfb 100644 --- a/src/mol-gl/shader/chunks/apply-light-color.glsl.ts +++ b/src/mol-gl/shader/chunks/apply-light-color.glsl.ts @@ -78,7 +78,7 @@ export const apply_light_color = ` } #pragma unroll_loop_end - outgoingLight += physicalMaterial.diffuseColor * luminance(uAmbientColor); + outgoingLight += physicalMaterial.diffuseColor * uAmbientColor; #else ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0)); From 9de8334af5ec3dd0ef5fe4cba4231b167502bd12 Mon Sep 17 00:00:00 2001 From: giagitom Date: Tue, 26 May 2026 13:05:24 +0200 Subject: [PATCH 7/7] Fix exported image artifacts on transparent background --- CHANGELOG.md | 1 + src/mol-util/image.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5a7b7592..1c8481605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file, following t Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here. ## [Unreleased] +- Fix exported image artifacts on transparent background with emissive, bloom, or antialiasing - Fix cel-shaded ambient color being stripped to luminance (now uses full RGB, matching the classic lighting path) - Fix empty transforms default in `ShapeFromPly` - Use morton order for spheres in dot visual with lod-levels diff --git a/src/mol-util/image.ts b/src/mol-util/image.ts index 6108047c2..99fdbc7fa 100644 --- a/src/mol-util/image.ts +++ b/src/mol-util/image.ts @@ -2,6 +2,7 @@ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose + * @author Gianluca Tomasello */ export { PixelData }; @@ -37,12 +38,14 @@ namespace PixelData { /** to undo pre-multiplied alpha */ export function divideByAlpha(pixelData: PixelData): PixelData { const { array } = pixelData; - const factor = (array instanceof Uint8Array) ? 255 : 1; + // clamp: emissive, bloom and antialiasing can lift premul RGB above alpha; without it Uint8Array silently wraps. + const max = (array instanceof Uint8Array) ? 255 : 1; for (let i = 0, il = array.length; i < il; i += 4) { - const a = array[i + 3] / factor; - array[i] /= a; - array[i + 1] /= a; - array[i + 2] /= a; + const a = array[i + 3] / max; + if (a === 0) continue; + array[i] = Math.min(max, array[i] / a); + array[i + 1] = Math.min(max, array[i + 1] / a); + array[i + 2] = Math.min(max, array[i + 2] / a); } return pixelData; }