Browse Source

Merge branch 'master' of https://github.com/molstar/molstar into shader-tests

Alexander Rose 3 years ago
parent
commit
568be030c3

+ 18 - 13
CHANGELOG.md

@@ -6,6 +6,17 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params)
+- [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation)
+- Fix marker highlight color overriding select color
+
+## [v2.3.5] - 2021-10-19
+
+- Fix sequence viewer for PDB files with COMPND record and multichain entities.
+- Fix index pair bonds order assignment
+
+## [v2.3.4] - 2021-10-12
+
 - Fix pickScale not taken into account in line/point shader
 - Add pixel-scale, pick-scale & pick-padding GET params to Viewer app
 - Fix selecting bonds not adding their atoms in selection manager
@@ -15,6 +26,7 @@ Note that since we don't clearly distinguish between a public and private interf
 - Add points & crosses visuals to Line representation
 - Add ``pickPadding`` config option (look around in case target pixel is empty)
 - Add ``multipleBonds`` param to bond visuals with options: off, symmetric, offset
+- Fix ``argparse`` config in servers.
 
 ## [v2.3.3] - 2021-10-01
 
@@ -145,29 +157,22 @@ Note that since we don't clearly distinguish between a public and private interf
 - Fixed Measurements UI labels (#166)
 
 ## [v2.0.3] - 2021-04-09
-### Added
-- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
 
-### Changed
+- Add support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
 - [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
 - [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
 
 ## [v2.0.2] - 2021-03-29
-### Added
-- ``Canvas3D.getRenderObjects``.
-- [WIP] Animate state interpolating, including model trajectories
 
-### Changed
+- Add ``Canvas3D.getRenderObjects``.
+- [WIP] Animate state interpolating, including model trajectories
 - Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
-
-### Fixed
-- VolumeFromDensityServerCif transform label
-
+- Fix VolumeFromDensityServerCif transform label
 
 ## [v2.0.1] - 2021-03-23
-### Fixed
-- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
 
+- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
 
 ## [v2.0.0] - 2021-03-23
+
 Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.

+ 1 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "2.3.3",
+  "version": "2.3.5",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "2.3.3",
+  "version": "2.3.5",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {

+ 1 - 2
src/mol-gl/shader/chunks/assign-material-color.glsl.ts

@@ -3,9 +3,8 @@ export const assign_material_color = `
     #if defined(dMarkerType_uniform)
         float marker = uMarker;
     #elif defined(dMarkerType_groupInstance)
-        float marker = vMarker;
+        float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win
     #endif
-    marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
 #endif
 
 #if defined(dRenderVariant_color)

+ 27 - 3
src/mol-model-formats/structure/pdb/entity.ts

@@ -25,7 +25,7 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
 
     let currentSpec: Spec | undefined;
     let currentCompound: EntityCompound = { chains: [], description: '' };
-    const Compounds: EntityCompound[] = [];
+    const compounds: EntityCompound[] = [];
 
     for (let i = lineStart; i < lineEnd; i++) {
         const line = getLine(i);
@@ -55,7 +55,7 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
                 chains: [],
                 description: ''
             };
-            Compounds.push(currentCompound);
+            compounds.push(currentCompound);
         } else if (currentSpec === 'MOLECULE') {
             if (currentCompound.description) currentCompound.description += ' ';
             currentCompound.description += value;
@@ -64,7 +64,31 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
         }
     }
 
-    return Compounds;
+    // Define a seprate entity for each chain
+    // --------------------------------------
+    //
+    // This is a workaround for how sequences are currently determined for PDB files.
+    //
+    // The current approach infers the "observed sequence" from the atomic hierarchy.
+    // However, for example for PDB ID 3HHR, this approach fails, since chains B and C
+    // belong to the same entity but contain different observed sequence, which causes display
+    // errors in the sequence viewer (since the sequences are determined "per entity").
+    //
+    // A better approach could be to parse SEQRES categories and use it to construct
+    // entity_poly_seq category. However, this would require constructing label_seq_id (with gaps)
+    // from RES ID pdb column (auth_seq_id), which isn't a trivial exercise.
+    //
+    // (properly formatted) mmCIF structures do not exhibit this issue.
+    const singletons: EntityCompound[] = [];
+    for (const comp of compounds) {
+        for (const chain of comp.chains) {
+            singletons.push({
+                description: comp.description,
+                chains: [chain]
+            });
+        }
+    }
+    return singletons;
 }
 
 export function parseHetnam(lines: Tokens, lineStart: number, lineEnd: number) {

+ 1 - 1
src/mol-model/structure/structure/unit/bonds/intra-compute.ts

@@ -79,7 +79,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
             if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
                 atomA[atomA.length] = _aI;
                 atomB[atomB.length] = _bI;
-                orders[order.length] = order[i];
+                orders[orders.length] = order[i];
                 flags[flags.length] = flag[i];
             }
         }

+ 1 - 1
src/mol-plugin/behavior/dynamic/selection/structure-focus-representation.ts

@@ -25,7 +25,7 @@ const StructureFocusRepresentationParams = (plugin: PluginContext) => {
         expandRadius: PD.Numeric(5, { min: 1, max: 10, step: 1 }),
         targetParams: PD.Group(reprParams, {
             label: 'Target',
-            customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.26, alpha: 0.51 } })
+            customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.26, alpha: 0.51, adjustCylinderLength: true } })
         }),
         surroundingsParams: PD.Group(reprParams, {
             label: 'Surroundings',

+ 6 - 0
src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts

@@ -215,6 +215,9 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
@@ -254,6 +257,9 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||

+ 1 - 0
src/mol-repr/structure/visual/bond-inter-unit-line.ts

@@ -131,6 +131,7 @@ export function InterUnitBondLineVisual(materialId: number): ComplexVisual<Inter
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||

+ 6 - 0
src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts

@@ -228,6 +228,9 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
@@ -272,6 +275,9 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||

+ 1 - 0
src/mol-repr/structure/visual/bond-intra-unit-line.ts

@@ -153,6 +153,7 @@ export function IntraUnitBondLineVisual(materialId: number): UnitsVisual<IntraUn
                 newProps.sizeFactor !== currentProps.sizeFactor ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||

+ 1 - 1
src/mol-repr/structure/visual/util/bond.ts

@@ -28,7 +28,7 @@ export type BondProps = typeof DefaultBondProps
 export const BondCylinderParams = {
     ...LinkCylinderParams,
     ...BondParams,
-    adjustCylinderLength: PD.Boolean(true, { description: 'Shorten cylinders to reduce overlap with spheres.' })
+    adjustCylinderLength: PD.Boolean(false, { description: 'Shorten cylinders to reduce overlap with spheres. Useful for for transparent bonds. Not working well with aromatic bonds.' })
 };
 export const DefaultBondCylinderProps = PD.getDefaultValues(BondCylinderParams);
 export type BondCylinderProps = typeof DefaultBondCylinderProps

+ 41 - 27
src/mol-repr/structure/visual/util/link.ts

@@ -21,6 +21,9 @@ export const LinkCylinderParams = {
     linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
     linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
     linkCap: PD.Boolean(false),
+    aromaticScale: PD.Numeric(0.3, { min: 0, max: 1, step: 0.01 }),
+    aromaticSpacing: PD.Numeric(1.5, { min: 0, max: 3, step: 0.01 }),
+    aromaticDashCount: PD.Numeric(2, { min: 2, max: 6, step: 2 }),
     dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
     dashScale: PD.Numeric(0.8, { min: 0, max: 2, step: 0.1 }),
     dashCap: PD.Boolean(true),
@@ -33,6 +36,7 @@ export type LinkCylinderProps = typeof DefaultLinkCylinderProps
 export const LinkLineParams = {
     linkScale: PD.Numeric(0.5, { min: 0, max: 1, step: 0.1 }),
     linkSpacing: PD.Numeric(0.1, { min: 0, max: 2, step: 0.01 }),
+    aromaticDashCount: PD.Numeric(2, { min: 2, max: 6, step: 2 }),
     dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
 };
 export const DefaultLinkLineProps = PD.getDefaultValues(LinkLineParams);
@@ -107,7 +111,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
 
     if (!linkCount) return Mesh.createEmpty(mesh);
 
-    const { linkScale, linkSpacing, radialSegments, linkCap, dashCount, dashScale, dashCap, stubCap } = props;
+    const { linkScale, linkSpacing, radialSegments, linkCap, aromaticScale, aromaticSpacing, aromaticDashCount, dashCount, dashScale, dashCap, stubCap } = props;
 
     const vertexCountEstimate = radialSegments * 2 * linkCount * 2;
     const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh);
@@ -138,8 +142,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
         const [topCap, bottomCap] = dirFlag ? [linkStub, linkCap] : [linkCap, linkStub];
         builderState.currentGroup = edgeIndex;
 
-        const aromaticOffsetFactor = 5.5;
-        const multipleOffsetFactor = 4;
+        const aromaticSegmentCount = aromaticDashCount + 1;
 
         if (linkStyle === LinkStyle.Solid) {
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
@@ -153,8 +156,8 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
 
             addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
         } else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
-            const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
-                linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
+            const order = (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble) ? 2 :
+                (linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple) ? 3 : 1.5;
             const multiRadius = linkRadius * (linkScale / (0.5 * order));
             const absOffset = (linkRadius - multiRadius) * linkSpacing;
 
@@ -167,21 +170,28 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
 
-                cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius * linkScale;
+                const aromaticOffset = linkRadius + aromaticScale * linkRadius + aromaticScale * linkRadius * aromaticSpacing;
+
+                v3setMagnitude(tmpV12, v3sub(tmpV12, vb, va), linkRadius * 0.5);
+                v3add(va, va, tmpV12);
+                v3sub(vb, vb, tmpV12);
+
+                cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius * aromaticScale;
                 cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
-                v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
+                v3setMagnitude(vShift, vShift, aromaticOffset);
                 v3sub(va, va, vShift);
                 v3sub(vb, vb, vShift);
-                addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
+                addFixedCountDashedCylinder(builderState, va, vb, 0.5, aromaticSegmentCount, cylinderProps);
 
                 if (linkStyle === LinkStyle.MirroredAromatic) {
-                    v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, aromaticOffset * 2);
                     v3add(va, va, vShift);
                     v3add(vb, vb, vShift);
-                    addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
+                    addFixedCountDashedCylinder(builderState, va, vb, 0.5, aromaticSegmentCount, cylinderProps);
                 }
             } else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
-                v3setMagnitude(vShift, vShift, absOffset);
+                const multipleOffset = linkRadius + multiRadius + linkScale * linkRadius * linkSpacing;
+                v3setMagnitude(vShift, vShift, multipleOffset);
 
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
@@ -193,13 +203,13 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
                 cylinderProps.topCap = dirFlag ? linkStub : dashCap;
                 cylinderProps.bottomCap = dirFlag ? dashCap : linkStub;
-                v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
+                v3setMagnitude(vShift, vShift, multipleOffset);
                 v3sub(va, va, vShift);
                 v3sub(vb, vb, vShift);
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
 
                 if (order === 3) {
-                    v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, multipleOffset * 2);
                     v3add(va, va, vShift);
                     v3add(vb, vb, vShift);
                     addCylinder(builderState, va, vb, 0.5, cylinderProps);
@@ -236,7 +246,7 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
 
     if (!linkCount) return Cylinders.createEmpty(cylinders);
 
-    const { linkScale, linkSpacing, linkCap, dashCount, dashScale, dashCap, stubCap } = props;
+    const { linkScale, linkSpacing, linkCap, aromaticScale, aromaticSpacing, aromaticDashCount, dashCount, dashScale, dashCap, stubCap } = props;
 
     const cylindersCountEstimate = linkCount * 2;
     const builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
@@ -250,10 +260,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
     const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
 
-    const aromaticSegmentCount = 3;
+    const aromaticSegmentCount = aromaticDashCount + 1;
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
-    const aromaticOffsetFactor = 5.5;
-    const multipleOffsetFactor = 4;
 
     for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
         if (ignore && ignore(edgeIndex)) continue;
@@ -272,8 +280,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
             v3sub(vb, vb, tmpV12);
             builder.addFixedCountDashes(va, vb, segmentCount, dashScale, dashCap, dashCap, edgeIndex);
         } else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
-            const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
-                linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
+            const order = (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble) ? 2 :
+                (linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple) ? 3 : 1.5;
             const multiScale = linkScale / (0.5 * order);
             const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
 
@@ -283,26 +291,32 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
             if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
                 builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
 
+                const aromaticOffset = linkRadius + aromaticScale * linkRadius + aromaticScale * linkRadius * aromaticSpacing;
+
                 v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
                 v3sub(vb, vb, tmpV12);
 
-                v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
+                v3setMagnitude(tmpV12, v3sub(tmpV12, vb, va), linkRadius * 0.5);
+                v3add(va, va, tmpV12);
+
+                v3setMagnitude(vShift, vShift, aromaticOffset);
                 v3sub(va, va, vShift);
                 v3sub(vb, vb, vShift);
-                builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
+                builder.addFixedCountDashes(va, vb, aromaticSegmentCount, aromaticScale, dashCap, dashCap, edgeIndex);
 
                 if (linkStyle === LinkStyle.MirroredAromatic) {
-                    v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, aromaticOffset * 2);
                     v3add(va, va, vShift);
                     v3add(vb, vb, vShift);
-                    builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
+                    builder.addFixedCountDashes(va, vb, aromaticSegmentCount, aromaticScale, dashCap, dashCap, edgeIndex);
                 }
             } else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
-                v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
+                const multipleOffset = linkRadius + multiScale * linkRadius + linkScale * linkRadius * linkSpacing;
+                v3setMagnitude(vShift, vShift, multipleOffset);
 
                 builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
 
-                v3scale(tmpV12, v3sub(tmpV12, va, vb), linkSpacing * linkScale * 0.2);
+                v3setMagnitude(tmpV12, v3sub(tmpV12, va, vb), linkRadius / 1.5);
                 v3sub(va, va, tmpV12);
 
                 if (order === 3) builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
@@ -335,7 +349,7 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
 
     if (!linkCount) return Lines.createEmpty(lines);
 
-    const { linkScale, linkSpacing, dashCount } = props;
+    const { linkScale, linkSpacing, aromaticDashCount, dashCount } = props;
 
     const linesCountEstimate = linkCount * 2;
     const builder = LinesBuilder.create(linesCountEstimate, linesCountEstimate / 4, lines);
@@ -349,7 +363,7 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
     const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
 
-    const aromaticSegmentCount = 3;
+    const aromaticSegmentCount = aromaticDashCount + 1;
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
     const aromaticOffsetFactor = 4.5;
     const multipleOffsetFactor = 3;

+ 0 - 1
src/servers/model/config.ts

@@ -230,7 +230,6 @@ function addJsonConfigArgs(parser: argparse.ArgumentParser) {
             'If a property is not specified, cmd line param/OS variable/default value are used.'
         ].join('\n'),
         required: false,
-        action: 'store_true'
     });
     parser.add_argument('--printCfg', { help: 'Print current config for validation and exit.', required: false, action: 'store_true' });
     parser.add_argument('--cfgTemplate', { help: 'Prints default JSON config template to be modified and exits.', required: false, action: 'store_true' });

+ 0 - 1
src/servers/volume/config.ts

@@ -90,7 +90,6 @@ function addJsonConfigArgs(parser: argparse.ArgumentParser) {
             'If a property is not specified, cmd line param/OS variable/default value are used.'
         ].join('\n'),
         required: false,
-        action: 'store_true'
     });
     parser.add_argument('--printCfg', { help: 'Print current config for validation and exit.', required: false, action: 'store_true' });
     parser.add_argument('--cfgTemplate', { help: 'Prints default JSON config template to be modified and exits.', required: false, action: 'store_true' });