@@ -136,6 +136,7 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
136136 const highlightComponentRef = useRef < any > ( null ) ;
137137 const isMounted = useRef ( true ) ;
138138 const onHoverRef = useRef ( onHover ) ;
139+ const viewerInstanceId = useRef ( crypto . randomUUID ( ) ) ; // Unique ID for custom schemes
139140
140141 // Update ref when prop changes
141142 useEffect ( ( ) => {
@@ -1761,6 +1762,16 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
17611762
17621763 console . log ( "Coloring Debug:" , { currentColoring, repType, hasValidCustomRules, rules : customColors } ) ;
17631764
1765+ // Capture the base coloring intent (e.g. 'chainid') to use as fallback in our custom scheme
1766+ const baseColoringForScheme = currentColoring ;
1767+
1768+ // If we have custom rules, we want to bypass the manual 'chainid' and 'charge' blocks below
1769+ // and fall through to the generic 'else' block where our new Custom Scheme logic lives.
1770+ // We force currentColoring to a value that won't match the specific if-blocks.
1771+ if ( hasValidCustomRules ) {
1772+ currentColoring = 'custom_override' as any ;
1773+ }
1774+
17641775 // --- STRATEGY: MULTI-REPRESENTATION OVERLAY (RESTORED & IMPROVED) ---
17651776 // NGL Custom Schemes proved fragile for this user.
17661777 // We implementation "High Contrast Chain Coloring" by EXPLICITLY adding a representation for each chain.
@@ -1857,7 +1868,58 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
18571868 return PALETTES [ p ] || undefined ;
18581869 } ;
18591870
1860- // Unified cartoon with optimized parameters for arrows and helices
1871+ // 2. Custom Color Scheme with Smooth Blending
1872+ // Instead of overlays (which cause jagged geometry), we create a custom NGL scheme
1873+ // that delegates to the base scheme for non-overridden atoms.
1874+
1875+ let finalColoring = currentColoring ;
1876+
1877+ if ( hasValidCustomRules ) {
1878+ const schemeId = `custom_scheme_${ viewerInstanceId . current } ` ;
1879+ const atomColorMap = new Map < number , number > ( ) ;
1880+
1881+ // Pre-calculate atom colors
1882+ customColors . forEach ( rule => {
1883+ if ( rule . color && rule . target ) {
1884+ const colorHex = new window . NGL . Color ( rule . color ) . getHex ( ) ;
1885+ try {
1886+ // Efficiently populate map using NGL selection
1887+ component . structure . eachAtom ( ( atom : any ) => {
1888+ atomColorMap . set ( atom . index , colorHex ) ;
1889+ } , new window . NGL . Selection ( rule . target ) ) ;
1890+ } catch ( e ) {
1891+ console . warn ( "Invalid selection for custom rule:" , rule . target ) ;
1892+ }
1893+ }
1894+ } ) ;
1895+
1896+ // Register (or overwrite) the custom scheme
1897+ // We capture 'atomColorMap' and 'currentColoring' in the closure
1898+ window . NGL . ColormakerRegistry . addScheme ( function ( this : any , params : any ) {
1899+ this . parameters = params ;
1900+
1901+ // Instantiate the base scheme (fallback)
1902+ // We must handle 'standard' or other aliases if necessary, but usually NGL names match
1903+ // If currentColoring is not a registered scheme, fallback to 'uniform' (white)
1904+ const BaseSchemeClass = window . NGL . ColormakerRegistry . getScheme ( baseColoringForScheme ) ;
1905+ this . baseScheme = BaseSchemeClass ? new BaseSchemeClass ( params ) : null ;
1906+
1907+ this . atomColor = function ( atom : any ) {
1908+ const custom = atomColorMap . get ( atom . index ) ;
1909+ if ( custom !== undefined ) return custom ;
1910+
1911+ if ( this . baseScheme ) {
1912+ return this . baseScheme . atomColor ( atom ) ;
1913+ }
1914+ return 0xFFFFFF ; // Fallback white
1915+ } ;
1916+ } , schemeId ) ;
1917+
1918+ finalColoring = schemeId ;
1919+ console . log ( `Applied smooth custom coloring: ${ schemeId } with ${ atomColorMap . size } atom overrides` ) ;
1920+ }
1921+
1922+ // Unified cartoon with optimized parameters
18611923 if ( repType === 'cartoon' ) {
18621924 // Force recalculation of secondary structure
18631925 try {
@@ -1867,10 +1929,10 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
18671929 } catch ( e ) { }
18681930
18691931 const params : any = {
1870- color : currentColoring ,
1871- aspectRatio : 5 , // Flat arrows for sheets
1872- subdiv : 12 , // Smooth curves
1873- radialSegments : 20 , // Smooth helix cylinders
1932+ color : finalColoring ,
1933+ aspectRatio : 5 ,
1934+ subdiv : 12 ,
1935+ radialSegments : 20 ,
18741936 } ;
18751937
18761938 const scale = getColorScale ( colorPalette ) ;
@@ -1881,7 +1943,7 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
18811943 } else {
18821944 // Non-cartoon representations
18831945 const params : any = {
1884- color : currentColoring
1946+ color : finalColoring
18851947 } ;
18861948 const scale = getColorScale ( colorPalette ) ;
18871949 if ( scale && scale . length > 0 ) {
@@ -1891,25 +1953,6 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
18911953 }
18921954 }
18931955
1894- // 2. Add Custom Representations (Overlay)
1895- if ( hasValidCustomRules ) {
1896- console . log ( "Applying Custom Rules as Overlays:" , customColors . length ) ;
1897- customColors . forEach ( ( rule , idx ) => {
1898- if ( rule . color && rule . target ) {
1899- try {
1900- // Add a separate representation for this rule
1901- component . addRepresentation ( repType , {
1902- color : new NGL . Color ( rule . color ) . getHex ( ) ,
1903- sele : rule . target ,
1904- name : `custom_rule_${ idx } `
1905- } ) ;
1906- } catch ( e ) {
1907- console . warn ( "Failed to apply custom rule:" , rule , e ) ;
1908- }
1909- }
1910- } ) ;
1911- }
1912-
19131956 // --- OVERLAYS ---
19141957 const tryApply = ( r : string , c : string , sele : string , params : any = { } ) => {
19151958 try { component . addRepresentation ( r , { color : c , sele : sele , ...params } ) ; } catch ( e ) { }
0 commit comments