@@ -9,18 +9,19 @@ import { Barrier } from "../base/async";
99import { type IDisposable } from "../base/disposable" ;
1010import { first , length , map } from "../base/iterator" ;
1111import { Logger } from "../base/log" ;
12+ import { dirname , normalize_join } from "../base/paths" ;
1213import { is_string , type Constructor } from "../base/types" ;
1314import { KicadPCB , KicadSch , ProjectSettings } from "../kicad" ;
1415import type {
1516 SchematicSheet ,
1617 SchematicSheetInstance ,
1718} from "../kicad/schematic" ;
18- import type { VirtualFileSystem } from "./services/vfs" ;
19+ import type { IFileSystem } from "./services/vfs" ;
1920
2021const log = new Logger ( "kicanvas:project" ) ;
2122
2223export class Project extends EventTarget implements IDisposable {
23- #fs: VirtualFileSystem ;
24+ #fs: IFileSystem ;
2425 #files_by_name: Map < string , KicadPCB | KicadSch | null > = new Map ( ) ;
2526 #pages_by_path: Map < string , ProjectPage > = new Map ( ) ;
2627 #root_schematic_page?: ProjectPage ;
@@ -33,7 +34,7 @@ export class Project extends EventTarget implements IDisposable {
3334 this . #pages_by_path. clear ( ) ;
3435 }
3536
36- public async load ( fs : VirtualFileSystem ) {
37+ public async load ( fs : IFileSystem ) {
3738 log . info ( `Loading project from ${ fs . constructor . name } ` ) ;
3839
3940 this . settings = new ProjectSettings ( ) ;
@@ -42,30 +43,53 @@ export class Project extends EventTarget implements IDisposable {
4243
4344 this . #fs = fs ;
4445
45- let promises = [ ] ;
46+ const proj_promises = [ ] ;
4647
48+ // load project file
4749 for ( const filename of this . #fs. list ( ) ) {
48- promises . push ( this . #load_file( filename ) ) ;
50+ proj_promises . push ( this . #load_file( filename ) ) ;
4951 }
5052
51- await Promise . all ( promises ) ;
53+ await Promise . all ( proj_promises ) ;
5254
53- while ( promises . length ) {
54- // 'Recursively' resolve all schematics until none remain
55- promises = [ ] ;
56- for ( const schematic of this . schematics ( ) ) {
57- for ( const sheet of schematic . sheets ) {
58- const sheet_sch = this . #files_by_name. get (
59- sheet . sheetfile ?? "" ,
60- ) as KicadSch ;
55+ // 'Recursively' resolve all schematics until none remain
56+ let load_new = true ;
57+ const skipped_files : string [ ] = [ ] ;
58+ while ( load_new ) {
59+ load_new = false ;
6160
62- if ( ! sheet_sch && sheet . sheetfile ) {
63- // Missing schematic, attempt to fetch
64- promises . push ( this . #load_file( sheet . sheetfile ) ) ;
61+ const loaded_file = Array . from ( this . schematics ( ) ) ;
62+ for ( const sch of loaded_file ) {
63+ const base_dir = dirname ( sch . filename ) ;
64+ for ( const subsch of sch . sheets ) {
65+ if ( ! subsch . sheetfile ) {
66+ continue ;
67+ }
68+
69+ const new_file = normalize_join ( base_dir , subsch . sheetfile ) ;
70+
71+ const loaded = loaded_file . map ( ( s ) => s . filename ) ;
72+ if (
73+ loaded . includes ( new_file ) ||
74+ skipped_files . includes ( new_file )
75+ ) {
76+ // file loaded or skipped
77+ continue ;
78+ }
79+
80+ load_new = true ;
81+
82+ if ( await this . #fs. has ( new_file ) ) {
83+ // load file, it changes this.#files_by_name and causes calling
84+ // this.schematics() will return a new result.
85+ await this . #load_file( new_file ) ;
86+ } else {
87+ // skip non-existent files to allow loading an incomplete schematics
88+ skipped_files . push ( new_file ) ;
89+ log . warn ( `file "${ new_file } " is not existed, skip it.` ) ;
6590 }
6691 }
6792 }
68- await Promise . all ( promises ) ;
6993 }
7094
7195 this . #determine_schematic_hierarchy( ) ;
@@ -251,6 +275,10 @@ export class Project extends EventTarget implements IDisposable {
251275
252276 // Finally, if no root schematic was found, just use the first one we saw.
253277 this . #root_schematic_page = first ( this . #pages_by_path. values ( ) ) ;
278+
279+ if ( ! this . #root_schematic_page) {
280+ log . error ( "No vaild root schematic was found." ) ;
281+ }
254282 }
255283
256284 public * files ( ) {
0 commit comments