@@ -102,11 +102,22 @@ class Timeline extends Tab {
102102
103103 } ) ;
104104
105+ this . exportButton = document . createElement ( 'button' ) ;
106+ this . exportButton . className = 'console-copy-button' ;
107+ this . exportButton . title = 'Export' ;
108+ this . exportButton . innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>' ;
109+ this . exportButton . style . padding = '0 10px' ;
110+ this . exportButton . style . lineHeight = '24px' ;
111+ this . exportButton . style . display = 'flex' ;
112+ this . exportButton . style . alignItems = 'center' ;
113+ this . exportButton . addEventListener ( 'click' , ( ) => this . exportData ( ) ) ;
114+
105115 const buttonsGroup = document . createElement ( 'div' ) ;
106116 buttonsGroup . className = 'console-buttons-group' ;
107117 buttonsGroup . appendChild ( this . viewModeButton ) ;
108118 buttonsGroup . appendChild ( this . recordButton ) ;
109119 buttonsGroup . appendChild ( this . recordRefreshButton ) ;
120+ buttonsGroup . appendChild ( this . exportButton ) ;
110121 buttonsGroup . appendChild ( clearButton ) ;
111122
112123 header . style . display = 'flex' ;
@@ -597,8 +608,12 @@ class Timeline extends Tab {
597608
598609 }
599610
600- // Only record method name as requested, skipping detail arguments
601- this . currentFrame . calls . push ( { method : methodLabel } ) ;
611+ const call = { method : methodLabel } ;
612+ const details = this . getCallDetail ( prop , args ) ;
613+
614+ if ( details ) call . details = details ;
615+
616+ this . currentFrame . calls . push ( call ) ;
602617
603618 return originalFunc . apply ( backend , args ) ;
604619
@@ -647,6 +662,108 @@ class Timeline extends Tab {
647662
648663 }
649664
665+ exportData ( ) {
666+
667+ if ( this . frames . length === 0 ) return ;
668+
669+ const data = JSON . stringify ( this . frames , null , '\t' ) ;
670+ const blob = new Blob ( [ data ] , { type : 'application/json' } ) ;
671+ const url = URL . createObjectURL ( blob ) ;
672+ const a = document . createElement ( 'a' ) ;
673+ a . href = url ;
674+ a . download = 'threejs-timeline.json' ;
675+ a . click ( ) ;
676+ URL . revokeObjectURL ( url ) ;
677+
678+ }
679+
680+ getCallDetail ( method , args ) {
681+
682+ switch ( method ) {
683+
684+ case 'draw' : {
685+
686+ const renderObject = args [ 0 ] ;
687+ if ( ! renderObject ) return null ;
688+
689+ const details = { } ;
690+ if ( renderObject . object ) {
691+
692+ details . object = renderObject . object . name || renderObject . object . type ;
693+
694+ if ( renderObject . object . count > 1 ) {
695+
696+ details . instance = renderObject . object . count ;
697+
698+ }
699+
700+ }
701+
702+ if ( renderObject . material ) details . material = renderObject . material . name || renderObject . material . type ;
703+ if ( renderObject . geometry ) details . geometry = renderObject . geometry . name || undefined ;
704+
705+ if ( renderObject . camera ) details . camera = renderObject . camera . name || renderObject . camera . type ;
706+
707+ return details ;
708+
709+ }
710+
711+ case 'updateBinding' : {
712+
713+ const binding = args [ 0 ] ;
714+ return binding ?. name ? { binding : binding . name } : null ;
715+
716+ }
717+
718+ case 'createProgram' : {
719+
720+ const program = args [ 0 ] ;
721+ if ( ! program ) return null ;
722+ return { stage : program . stage , name : program . name || undefined } ;
723+
724+ }
725+
726+ case 'createBindings' : {
727+
728+ const bindGroup = args [ 0 ] ;
729+ return bindGroup ?. name ? { group : bindGroup . name } : null ;
730+
731+ }
732+
733+ case 'createTexture' :
734+ case 'destroyTexture' : {
735+
736+ const texture = args [ 0 ] ;
737+ return texture ?. name ? { texture : texture . name } : null ;
738+
739+ }
740+
741+ }
742+
743+ return null ;
744+
745+ }
746+
747+ formatDetails ( details ) {
748+
749+ const parts = [ ] ;
750+
751+ for ( const key in details ) {
752+
753+ if ( details [ key ] !== undefined ) {
754+
755+ parts . push ( `<span style="opacity: 0.5">${ key } :</span> <span style="color: var(--text-secondary); opacity: 1">${ details [ key ] } </span>` ) ;
756+
757+ }
758+
759+ }
760+
761+ if ( parts . length === 0 ) return '' ;
762+
763+ return `<span style="font-size: 11px; margin-left: 8px; color: var(--text-secondary); opacity: 1;">{ ${ parts . join ( '<span style="opacity: 0.5">, </span>' ) } }</span>` ;
764+
765+ }
766+
650767 renderSlider ( ) {
651768
652769 if ( this . frames . length === 0 ) {
@@ -768,14 +885,15 @@ class Timeline extends Tab {
768885
769886 const call = frame . calls [ i ] ;
770887 const isStructural = call . method . startsWith ( 'begin' ) || call . method . startsWith ( 'finish' ) ;
888+ const formatedDetails = call . details ? this . formatDetails ( call . details ) : '' ;
771889
772- if ( currentGroup && currentGroup . method === call . method && ! isStructural ) {
890+ if ( currentGroup && currentGroup . method === call . method && currentGroup . formatedDetails === formatedDetails && ! isStructural ) {
773891
774892 currentGroup . count ++ ;
775893
776894 } else {
777895
778- currentGroup = { method : call . method , count : 1 } ;
896+ currentGroup = { method : call . method , count : 1 , formatedDetails } ;
779897 groupedCalls . push ( currentGroup ) ;
780898
781899 }
@@ -836,7 +954,7 @@ class Timeline extends Tab {
836954
837955 // Title
838956 const title = document . createElement ( 'span' ) ;
839- title . textContent = call . method + ( call . count > 1 ? ` ( ${ call . count } )` : '' ) ;
957+ title . innerHTML = call . method + ( call . formatedDetails ? call . formatedDetails : '' ) + ( call . count > 1 ? ` <span style="opacity: 0.5"> ( ${ call . count } )</span> ` : '' ) ;
840958 block . appendChild ( title ) ;
841959
842960 block . addEventListener ( 'click' , ( e ) => {
@@ -862,14 +980,14 @@ class Timeline extends Tab {
862980
863981 } else if ( call . method . startsWith ( 'finish' ) ) {
864982
865- block . textContent = call . method + ( call . count > 1 ? ` ( ${ call . count } )` : '' ) ;
983+ block . innerHTML = call . method + ( call . formatedDetails ? call . formatedDetails : '' ) + ( call . count > 1 ? ` <span style="opacity: 0.5"> ( ${ call . count } )</span> ` : '' ) ;
866984
867985 currentIndent = Math . max ( 0 , currentIndent - 1 ) ;
868986 elementStack . pop ( ) ;
869987
870988 } else {
871989
872- block . textContent = call . method + ( call . count > 1 ? ` ( ${ call . count } )` : '' ) ;
990+ block . innerHTML = call . method + ( call . formatedDetails ? call . formatedDetails : '' ) + ( call . count > 1 ? ` <span style="opacity: 0.5"> ( ${ call . count } )</span> ` : '' ) ;
873991
874992 }
875993
0 commit comments