Преглед изворни кода

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

Alexander Rose пре 3 година
родитељ
комит
568be030c3

+ 18 - 13
CHANGELOG.md

@@ -6,6 +6,17 @@ Note that since we don't clearly distinguish between a public and private interf
 
 
 ## [Unreleased]
 ## [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
 - Fix pickScale not taken into account in line/point shader
 - Add pixel-scale, pick-scale & pick-padding GET params to Viewer app
 - Add pixel-scale, pick-scale & pick-padding GET params to Viewer app
 - Fix selecting bonds not adding their atoms in selection manager
 - 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 points & crosses visuals to Line representation
 - Add ``pickPadding`` config option (look around in case target pixel is empty)
 - Add ``pickPadding`` config option (look around in case target pixel is empty)
 - Add ``multipleBonds`` param to bond visuals with options: off, symmetric, offset
 - Add ``multipleBonds`` param to bond visuals with options: off, symmetric, offset
+- Fix ``argparse`` config in servers.
 
 
 ## [v2.3.3] - 2021-10-01
 ## [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)
 - Fixed Measurements UI labels (#166)
 
 
 ## [v2.0.3] - 2021-04-09
 ## [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] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
 - [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
 - [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
 
 
 ## [v2.0.2] - 2021-03-29
 ## [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.
 - 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
 ## [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
 ## [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.
 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",
   "name": "molstar",
-  "version": "2.3.3",
+  "version": "2.3.5",
   "lockfileVersion": 1,
   "lockfileVersion": 1,
   "requires": true,
   "requires": true,
   "dependencies": {
   "dependencies": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "molstar",
   "name": "molstar",
-  "version": "2.3.3",
+  "version": "2.3.5",
   "description": "A comprehensive macromolecular library.",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {
   "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)
     #if defined(dMarkerType_uniform)
         float marker = uMarker;
         float marker = uMarker;
     #elif defined(dMarkerType_groupInstance)
     #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
     #endif
-    marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
 #endif
 #endif
 
 
 #if defined(dRenderVariant_color)
 #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 currentSpec: Spec | undefined;
     let currentCompound: EntityCompound = { chains: [], description: '' };
     let currentCompound: EntityCompound = { chains: [], description: '' };
-    const Compounds: EntityCompound[] = [];
+    const compounds: EntityCompound[] = [];
 
 
     for (let i = lineStart; i < lineEnd; i++) {
     for (let i = lineStart; i < lineEnd; i++) {
         const line = getLine(i);
         const line = getLine(i);
@@ -55,7 +55,7 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
                 chains: [],
                 chains: [],
                 description: ''
                 description: ''
             };
             };
-            Compounds.push(currentCompound);
+            compounds.push(currentCompound);
         } else if (currentSpec === 'MOLECULE') {
         } else if (currentSpec === 'MOLECULE') {
             if (currentCompound.description) currentCompound.description += ' ';
             if (currentCompound.description) currentCompound.description += ' ';
             currentCompound.description += value;
             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) {
 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) {
             if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
                 atomA[atomA.length] = _aI;
                 atomA[atomA.length] = _aI;
                 atomB[atomB.length] = _bI;
                 atomB[atomB.length] = _bI;
-                orders[order.length] = order[i];
+                orders[orders.length] = order[i];
                 flags[flags.length] = flag[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 }),
         expandRadius: PD.Numeric(5, { min: 1, max: 10, step: 1 }),
         targetParams: PD.Group(reprParams, {
         targetParams: PD.Group(reprParams, {
             label: 'Target',
             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, {
         surroundingsParams: PD.Group(reprParams, {
             label: 'Surroundings',
             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.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 newProps.dashCap !== currentProps.dashCap ||
@@ -254,6 +257,9 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 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.sizeFactor !== currentProps.sizeFactor ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
                 !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.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 newProps.dashCap !== currentProps.dashCap ||
@@ -272,6 +275,9 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.linkCap !== currentProps.linkCap ||
                 newProps.linkCap !== currentProps.linkCap ||
+                newProps.aromaticScale !== currentProps.aromaticScale ||
+                newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashScale !== currentProps.dashScale ||
                 newProps.dashCap !== currentProps.dashCap ||
                 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.sizeFactor !== currentProps.sizeFactor ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkScale !== currentProps.linkScale ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
                 newProps.linkSpacing !== currentProps.linkSpacing ||
+                newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.dashCount !== currentProps.dashCount ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
                 !arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
                 !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 = {
 export const BondCylinderParams = {
     ...LinkCylinderParams,
     ...LinkCylinderParams,
     ...BondParams,
     ...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 const DefaultBondCylinderProps = PD.getDefaultValues(BondCylinderParams);
 export type BondCylinderProps = typeof DefaultBondCylinderProps
 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 }),
     linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
     linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
     linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
     linkCap: PD.Boolean(false),
     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 }),
     dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
     dashScale: PD.Numeric(0.8, { min: 0, max: 2, step: 0.1 }),
     dashScale: PD.Numeric(0.8, { min: 0, max: 2, step: 0.1 }),
     dashCap: PD.Boolean(true),
     dashCap: PD.Boolean(true),
@@ -33,6 +36,7 @@ export type LinkCylinderProps = typeof DefaultLinkCylinderProps
 export const LinkLineParams = {
 export const LinkLineParams = {
     linkScale: PD.Numeric(0.5, { min: 0, max: 1, step: 0.1 }),
     linkScale: PD.Numeric(0.5, { min: 0, max: 1, step: 0.1 }),
     linkSpacing: PD.Numeric(0.1, { min: 0, max: 2, step: 0.01 }),
     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 }),
     dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
 };
 };
 export const DefaultLinkLineProps = PD.getDefaultValues(LinkLineParams);
 export const DefaultLinkLineProps = PD.getDefaultValues(LinkLineParams);
@@ -107,7 +111,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
 
 
     if (!linkCount) return Mesh.createEmpty(mesh);
     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 vertexCountEstimate = radialSegments * 2 * linkCount * 2;
     const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh);
     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];
         const [topCap, bottomCap] = dirFlag ? [linkStub, linkCap] : [linkCap, linkStub];
         builderState.currentGroup = edgeIndex;
         builderState.currentGroup = edgeIndex;
 
 
-        const aromaticOffsetFactor = 5.5;
-        const multipleOffsetFactor = 4;
+        const aromaticSegmentCount = aromaticDashCount + 1;
 
 
         if (linkStyle === LinkStyle.Solid) {
         if (linkStyle === LinkStyle.Solid) {
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
             cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
@@ -153,8 +156,8 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
 
 
             addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
             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) {
         } 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 multiRadius = linkRadius * (linkScale / (0.5 * order));
             const absOffset = (linkRadius - multiRadius) * linkSpacing;
             const absOffset = (linkRadius - multiRadius) * linkSpacing;
 
 
@@ -167,21 +170,28 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
                 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;
                 cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
-                v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
+                v3setMagnitude(vShift, vShift, aromaticOffset);
                 v3sub(va, va, vShift);
                 v3sub(va, va, vShift);
                 v3sub(vb, vb, 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) {
                 if (linkStyle === LinkStyle.MirroredAromatic) {
-                    v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, aromaticOffset * 2);
                     v3add(va, va, vShift);
                     v3add(va, va, vShift);
                     v3add(vb, vb, 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) {
             } 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;
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
@@ -193,13 +203,13 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
                 cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
                 cylinderProps.topCap = dirFlag ? linkStub : dashCap;
                 cylinderProps.topCap = dirFlag ? linkStub : dashCap;
                 cylinderProps.bottomCap = dirFlag ? dashCap : linkStub;
                 cylinderProps.bottomCap = dirFlag ? dashCap : linkStub;
-                v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
+                v3setMagnitude(vShift, vShift, multipleOffset);
                 v3sub(va, va, vShift);
                 v3sub(va, va, vShift);
                 v3sub(vb, vb, vShift);
                 v3sub(vb, vb, vShift);
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
                 addCylinder(builderState, va, vb, 0.5, cylinderProps);
 
 
                 if (order === 3) {
                 if (order === 3) {
-                    v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, multipleOffset * 2);
                     v3add(va, va, vShift);
                     v3add(va, va, vShift);
                     v3add(vb, vb, vShift);
                     v3add(vb, vb, vShift);
                     addCylinder(builderState, va, vb, 0.5, cylinderProps);
                     addCylinder(builderState, va, vb, 0.5, cylinderProps);
@@ -236,7 +246,7 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
 
 
     if (!linkCount) return Cylinders.createEmpty(cylinders);
     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 cylindersCountEstimate = linkCount * 2;
     const builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
     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 segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
 
 
-    const aromaticSegmentCount = 3;
+    const aromaticSegmentCount = aromaticDashCount + 1;
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
-    const aromaticOffsetFactor = 5.5;
-    const multipleOffsetFactor = 4;
 
 
     for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
     for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
         if (ignore && ignore(edgeIndex)) continue;
         if (ignore && ignore(edgeIndex)) continue;
@@ -272,8 +280,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
             v3sub(vb, vb, tmpV12);
             v3sub(vb, vb, tmpV12);
             builder.addFixedCountDashes(va, vb, segmentCount, dashScale, dashCap, dashCap, edgeIndex);
             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) {
         } 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 multiScale = linkScale / (0.5 * order);
             const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
             const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
 
 
@@ -283,26 +291,32 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
             if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
             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);
                 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);
                 v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
                 v3sub(vb, vb, tmpV12);
                 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(va, va, vShift);
                 v3sub(vb, vb, 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) {
                 if (linkStyle === LinkStyle.MirroredAromatic) {
-                    v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
+                    v3setMagnitude(vShift, vShift, aromaticOffset * 2);
                     v3add(va, va, vShift);
                     v3add(va, va, vShift);
                     v3add(vb, vb, 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) {
             } 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);
                 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);
                 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);
                 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);
     if (!linkCount) return Lines.createEmpty(lines);
 
 
-    const { linkScale, linkSpacing, dashCount } = props;
+    const { linkScale, linkSpacing, aromaticDashCount, dashCount } = props;
 
 
     const linesCountEstimate = linkCount * 2;
     const linesCountEstimate = linkCount * 2;
     const builder = LinesBuilder.create(linesCountEstimate, linesCountEstimate / 4, lines);
     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 segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
     const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
 
 
-    const aromaticSegmentCount = 3;
+    const aromaticSegmentCount = aromaticDashCount + 1;
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
     const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
     const aromaticOffsetFactor = 4.5;
     const aromaticOffsetFactor = 4.5;
     const multipleOffsetFactor = 3;
     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.'
             'If a property is not specified, cmd line param/OS variable/default value are used.'
         ].join('\n'),
         ].join('\n'),
         required: false,
         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('--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' });
     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.'
             'If a property is not specified, cmd line param/OS variable/default value are used.'
         ].join('\n'),
         ].join('\n'),
         required: false,
         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('--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' });
     parser.add_argument('--cfgTemplate', { help: 'Prints default JSON config template to be modified and exits.', required: false, action: 'store_true' });