1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453
// AdomAltium.pas — Adom Altium bridge in-host dispatcher (original Adom code).
//
// Invoked by the bridge via:
//   X2.EXE -RScriptingSystem:RunScript(ProjectName=...AdomAltium.PrjScr|ProcName=AdomAltium>Run)
// Reads  C:\Users\Public\AdomAltium\request.txt   (simple key=value lines)
// Writes C:\Users\Public\AdomAltium\response.json (hand-built JSON)
//
// Request keys (per command):
//   command = ping | add_lcsc_param | read_params
//   add_lcsc_param: schlib, component, lcsc, mpn, manufacturer, value, description, package, datasheet
//   read_params:    schlib, component

const
    EX_DIR = 'C:\Users\Public\AdomAltium\';

var
    Req           : TStringList;
    LastServerDoc : IServerDocument;   // set by OpenSchLibDoc, used to save
    GStage        : String;            // last stage reached, for error pinpointing
    GReqId        : String;            // id of the request being served (echoed in response)

{ ---- JSON string escaping (minimal, original) ---- }
function JStr(const S : String) : String;
var i : Integer; c : Char; o : String;
begin
    o := '';
    for i := 1 to Length(S) do
    begin
        c := S[i];
        if (c = '\') or (c = '"') then o := o + '\' + c
        else if c = #9  then o := o + '\t'
        else if c = #10 then o := o + '\n'
        else if c = #13 then o := o + '\r'
        else o := o + c;
    end;
    Result := '"' + o + '"';
end;

function BoolStr(b : Boolean) : String;
begin
    if b then Result := 'true' else Result := 'false';
end;

procedure WriteResp(const JsonBody : String);
var R : TStringList;
begin
    if not DirectoryExists(EX_DIR) then ForceDirectories(EX_DIR);
    R := TStringList.Create;
    try
        R.Text := JsonBody;
        R.SaveToFile(EX_DIR + 'response.json');
    finally
        R.Free;
    end;
end;

procedure RespOk(const ExtraPairs : String);
begin
    WriteResp('{ "ok": true, "id": ' + JStr(GReqId) + ExtraPairs + ' }');
end;

procedure RespErr(const Msg : String);
begin
    WriteResp('{ "ok": false, "id": ' + JStr(GReqId) + ', "error": ' + JStr(Msg) + ' }');
end;

{ ---- open a SchLib; returns ISch_Lib and stashes the server doc for saving ---- }
function OpenSchLibDoc(const Path : String) : ISch_Lib;
begin
    Result := nil;
    LastServerDoc := nil;
    if SchServer = nil then Exit;
    LastServerDoc := Client.OpenDocument('SCHLIB', Path);
    if LastServerDoc <> nil then
        Client.ShowDocument(LastServerDoc);
    Result := SchServer.GetSchDocumentByPath(Path);
end;

procedure SaveCurrentDoc;
begin
    if LastServerDoc <> nil then
        LastServerDoc.DoFileSave('SCHLIB');
end;

{ ---- find a component in a SchLib by LibReference (empty = first) ---- }
function FindComponent(SchLib : ISch_Lib; const NameRef : String) : ISch_Component;
var It : ISch_Iterator; Cmp : ISch_Component;
begin
    Result := nil;
    It := SchLib.SchLibIterator_Create;
    It.AddFilter_ObjectSet(MkSet(eSchComponent));
    try
        Cmp := It.FirstSchObject;
        while Cmp <> nil do
        begin
            if (NameRef = '') or (Cmp.LibReference = NameRef) then
            begin
                Result := Cmp;
                Break;
            end;
            Cmp := It.NextSchObject;
        end;
    finally
        SchLib.SchIterator_Destroy(It);
    end;
end;

{ ---- set or replace a parameter on a component ---- }
procedure SetParam(Cmp : ISch_Component; const PName, PVal : String);
var It : ISch_Iterator; P, Existing : ISch_Parameter;
begin
    Existing := nil;
    It := Cmp.SchIterator_Create;
    It.AddFilter_ObjectSet(MkSet(eParameter));
    try
        P := It.FirstSchObject;
        while P <> nil do
        begin
            if P.Name = PName then begin Existing := P; Break; end;
            P := It.NextSchObject;
        end;
    finally
        Cmp.SchIterator_Destroy(It);
    end;

    if Existing <> nil then
        Existing.Text := PVal
    else
    begin
        P := SchServer.SchObjectFactory(eParameter, eCreate_GlobalCopy);
        P.Name := PName;
        P.Text := PVal;
        P.IsHidden := False;
        Cmp.AddSchObject(P);
    end;
end;

procedure MaybeSet(Cmp : ISch_Component; const PName, Key : String);
var V : String;
begin
    V := Req.Values[Key];
    if V <> '' then SetParam(Cmp, PName, V);
end;

{ ---- command: add LCSC + identity params to a component ---- }
procedure CmdAddLcscParam;
var SchLibPath, CompRef : String; SchLib : ISch_Lib; Cmp : ISch_Component;
begin
    SchLibPath := Req.Values['schlib'];
    CompRef    := Req.Values['component'];
    if SchLibPath = '' then begin RespErr('missing schlib'); Exit; end;

    SchLib := OpenSchLibDoc(SchLibPath);
    if SchLib = nil then begin RespErr('could not open schlib: ' + SchLibPath); Exit; end;

    Cmp := FindComponent(SchLib, CompRef);
    if Cmp = nil then begin RespErr('component not found: ' + CompRef); Exit; end;

    MaybeSet(Cmp, 'LCSC Part #',               'lcsc');
    MaybeSet(Cmp, 'Manufacturer Part Number',  'mpn');
    MaybeSet(Cmp, 'Manufacturer',              'manufacturer');
    MaybeSet(Cmp, 'Value',                     'value');
    MaybeSet(Cmp, 'Description',               'description');
    MaybeSet(Cmp, 'Package',                   'package');
    MaybeSet(Cmp, 'Datasheet',                 'datasheet');

    SchLib.GraphicallyInvalidate;
    SaveCurrentDoc;

    RespOk(', "component": ' + JStr(Cmp.LibReference) +
           ', "lcsc": ' + JStr(Req.Values['lcsc']));
end;

{ ---- command: read back a component's parameters (verification) ---- }
procedure CmdReadParams;
var SchLibPath, CompRef, Acc : String; SchLib : ISch_Lib; Cmp : ISch_Component;
    It : ISch_Iterator; P : ISch_Parameter; First : Boolean;
begin
    SchLibPath := Req.Values['schlib'];
    CompRef    := Req.Values['component'];
    SchLib := OpenSchLibDoc(SchLibPath);
    if SchLib = nil then begin RespErr('could not open schlib: ' + SchLibPath); Exit; end;
    Cmp := FindComponent(SchLib, CompRef);
    if Cmp = nil then begin RespErr('component not found: ' + CompRef); Exit; end;

    Acc := ''; First := True;
    It := Cmp.SchIterator_Create;
    It.AddFilter_ObjectSet(MkSet(eParameter));
    try
        P := It.FirstSchObject;
        while P <> nil do
        begin
            if not First then Acc := Acc + ', ';
            Acc := Acc + JStr(P.Name) + ': ' + JStr(P.Text);
            First := False;
            P := It.NextSchObject;
        end;
    finally
        Cmp.SchIterator_Destroy(It);
    end;
    RespOk(', "component": ' + JStr(Cmp.LibReference) + ', "params": { ' + Acc + ' }');
end;

{ ---- read back all params of a component into a JSON object body ---- }
function ParamsJSON(Cmp : ISch_Component) : String;
var It : ISch_Iterator; P : ISch_Parameter; Acc : String; First : Boolean;
begin
    Acc := ''; First := True;
    It := Cmp.SchIterator_Create;
    It.AddFilter_ObjectSet(MkSet(eParameter));
    try
        P := It.FirstSchObject;
        while P <> nil do
        begin
            if not First then Acc := Acc + ', ';
            Acc := Acc + JStr(P.Name) + ': ' + JStr(P.Text);
            First := False;
            P := It.NextSchObject;
        end;
    finally
        Cmp.SchIterator_Destroy(It);
    end;
    Result := '{ ' + Acc + ' }';
end;

{ ---- command: create a new SchLib + component, attach LCSC/identity params,
       read them back, and best-effort save. Proves the keystone end-to-end
       with no external symbol source. Stage-tracked for error pinpointing. ---- }
procedure CmdCreateAndAttach;
var
    CompName, Path : String;
    SD             : IServerDocument;
    SchLib         : ISch_Lib;
    Cmp            : ISch_Component;
    SavedOK        : Boolean;
begin
    CompName := Req.Values['component']; if CompName = '' then CompName := 'TESTPART';
    Path     := Req.Values['schlib'];

    GStage := 'SchServer';
    if SchServer = nil then begin RespErr('SchServer is nil'); Exit; end;

    GStage := 'CreateNewDocumentFromDocumentKind(SchLib)';
    SD := CreateNewDocumentFromDocumentKind('SchLib');
    GStage := 'ShowDocument';
    if SD <> nil then Client.ShowDocument(SD);
    LastServerDoc := SD;

    GStage := 'GetCurrentSchDocument';
    SchLib := SchServer.GetCurrentSchDocument;
    if SchLib = nil then begin RespErr('GetCurrentSchDocument nil at ' + GStage); Exit; end;

    GStage := 'SchObjectFactory(eSchComponent)';
    Cmp := SchServer.SchObjectFactory(eSchComponent, eCreate_Default);
    if Cmp = nil then begin RespErr('component factory nil'); Exit; end;

    GStage := 'set component props';
    Cmp.CurrentPartID    := 1;
    Cmp.DisplayModeCount := 1;
    Cmp.LibReference     := CompName;
    Cmp.Designator.Text  := 'U?';
    Cmp.Comment.Text     := CompName;

    GStage := 'AddSchComponent';
    SchLib.AddSchComponent(Cmp);

    GStage := 'register primitive';
    SchServer.RobotManager.SendMessage(SchLib.I_ObjectAddress, c_BroadCast,
                                       SCHM_PrimitiveRegistration, Cmp.I_ObjectAddress);

    GStage := 'set params';
    MaybeSet(Cmp, 'LCSC Part #',              'lcsc');
    MaybeSet(Cmp, 'Manufacturer Part Number', 'mpn');
    MaybeSet(Cmp, 'Manufacturer',             'manufacturer');
    MaybeSet(Cmp, 'Value',                    'value');
    MaybeSet(Cmp, 'Description',              'description');

    GStage := 'invalidate';
    SchLib.GraphicallyInvalidate;

    GStage := 'save';
    SavedOK := False;
    if (Path <> '') and (LastServerDoc <> nil) then
    begin
        LastServerDoc.SetFileName(Path);
        SavedOK := LastServerDoc.DoFileSave('');
    end;

    GStage := 'done';
    if SavedOK then
        RespOk(', "component": ' + JStr(CompName) +
               ', "saved": true, "saved_to": ' + JStr(Path) +
               ', "params": ' + ParamsJSON(Cmp))
    else
        RespOk(', "component": ' + JStr(CompName) +
               ', "saved": false, "params": ' + ParamsJSON(Cmp));
end;

{ ---- command: create a proper 2-pin resistor symbol with body, params, and a
       footprint implementation link, then save the SchLib ---- }
procedure CmdCreateResistor;
var
    SD          : IServerDocument;
    Lib         : ISch_Lib;
    Cmp         : ISch_Component;
    R           : ISch_Rectangle;
    P1, P2      : ISch_Pin;
    Impl        : ISch_Implementation;
    SchPath, FpName, FpLib, SavedStr : String;
    SavedOK     : Boolean;
begin
    SchPath := Req.Values['schlib'];
    FpName  := Req.Values['footprint'];     if FpName = '' then FpName := 'R0603';
    FpLib   := Req.Values['footprint_lib']; if FpLib  = '' then FpLib  := 'Resistors.PcbLib';

    GStage := 'create SchLib doc';
    SD := CreateNewDocumentFromDocumentKind('SchLib');
    if SD <> nil then Client.ShowDocument(SD);
    LastServerDoc := SD;
    Lib := SchServer.GetCurrentSchDocument;
    if Lib = nil then begin RespErr('no SchLib'); Exit; end;

    GStage := 'create component';
    Cmp := SchServer.SchObjectFactory(eSchComponent, eCreate_Default);
    if Cmp = nil then begin RespErr('component factory nil'); Exit; end;
    Cmp.CurrentPartID := 1;
    Cmp.DisplayMode   := 0;
    Cmp.LibReference  := Req.Values['component'];
    if Cmp.LibReference = '' then Cmp.LibReference := 'R';

    GStage := 'rectangle body';
    R := SchServer.SchObjectFactory(eRectangle, eCreate_Default);
    R.LineWidth := eSmall;
    R.Location  := Point(MilsToCoord(0),   MilsToCoord(-50));
    R.Corner    := Point(MilsToCoord(300), MilsToCoord(50));
    R.IsSolid   := False;
    R.OwnerPartId          := Cmp.CurrentPartID;
    R.OwnerPartDisplayMode := Cmp.DisplayMode;

    GStage := 'pin 1';
    P1 := SchServer.SchObjectFactory(ePin, eCreate_Default);
    P1.Location    := Point(MilsToCoord(-100), MilsToCoord(0));
    P1.Orientation := eRotate0;
    P1.Designator  := '1';
    P1.Name        := '1';
    P1.OwnerPartId          := Cmp.CurrentPartID;
    P1.OwnerPartDisplayMode := Cmp.DisplayMode;

    GStage := 'pin 2';
    P2 := SchServer.SchObjectFactory(ePin, eCreate_Default);
    P2.Location    := Point(MilsToCoord(400), MilsToCoord(0));
    P2.Orientation := eRotate180;
    P2.Designator  := '2';
    P2.Name        := '2';
    P2.OwnerPartId          := Cmp.CurrentPartID;
    P2.OwnerPartDisplayMode := Cmp.DisplayMode;

    GStage := 'add primitives';
    Cmp.AddSchObject(P1);
    Cmp.AddSchObject(P2);
    Cmp.AddSchObject(R);

    GStage := 'designator + params';
    Cmp.Designator.Text := 'R?';
    if Req.Values['value'] <> '' then Cmp.Comment.Text := Req.Values['value'];
    MaybeSet(Cmp, 'LCSC Part #',              'lcsc');
    MaybeSet(Cmp, 'Manufacturer Part Number', 'mpn');
    MaybeSet(Cmp, 'Manufacturer',             'manufacturer');
    MaybeSet(Cmp, 'Value',                    'value');
    MaybeSet(Cmp, 'Description',              'description');

    GStage := 'footprint implementation';
    Impl := Cmp.AddSchImplementation;
    Impl.ClearAllDatafileLinks;
    Impl.ModelName := FpName;
    Impl.ModelType := 'PCBLIB';
    Impl.AddDataFileLink(FpName, '', 'PCBLIB');
    Impl.IsCurrent := True;

    GStage := 'AddSchComponent';
    Lib.AddSchComponent(Cmp);
    SchServer.RobotManager.SendMessage(Lib.I_ObjectAddress, c_BroadCast,
                                       SCHM_PrimitiveRegistration, Cmp.I_ObjectAddress);

    GStage := 'save';
    SavedOK := False;
    if (SchPath <> '') and (LastServerDoc <> nil) then
    begin
        LastServerDoc.SetFileName(SchPath);
        SavedOK := LastServerDoc.DoFileSave('');
    end;
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';

    GStage := 'done';
    RespOk(', "component": ' + JStr(Cmp.LibReference) +
           ', "saved": ' + SavedStr +
           ', "saved_to": ' + JStr(SchPath) +
           ', "footprint_link": ' + JStr(FpLib + ':' + FpName) +
           ', "params": ' + ParamsJSON(Cmp));
end;

{ ---- command: create a 2-pad SMD 0603 footprint in a PcbLib, then save ---- }
procedure CmdCreateFootprint;
var
    SD       : IServerDocument;
    PcbLib   : IPCB_Library;
    FpComp   : IPCB_LibComponent;
    Pad      : IPCB_Pad;
    PcbPath, FpName, SavedStr : String;
    SavedOK  : Boolean;
begin
    PcbPath := Req.Values['pcblib'];
    FpName  := Req.Values['footprint']; if FpName = '' then FpName := 'R0603';

    GStage := 'create PcbLib doc';
    SD := CreateNewDocumentFromDocumentKind('PcbLib');
    if SD <> nil then Client.ShowDocument(SD);
    LastServerDoc := SD;

    GStage := 'GetCurrentPCBLibrary';
    if PCBServer = nil then begin RespErr('PCBServer nil'); Exit; end;
    PcbLib := PCBServer.GetCurrentPCBLibrary;
    if PcbLib = nil then begin RespErr('no current PcbLib'); Exit; end;

    GStage := 'CreatePCBLibComp';
    FpComp := PCBServer.CreatePCBLibComp;
    FpComp.Name := FpName;
    PcbLib.RegisterComponent(FpComp);

    GStage := 'PreProcess';
    PCBServer.PreProcess;

    GStage := 'pads';
    MakePad('1', -30);   { centers ~1.5 mm apart }
    MakePad('2',  30);

    GStage := 'PostProcess';
    PCBServer.PostProcess;
    PcbLib.Board.ViewManager_FullUpdate;

    GStage := 'save';
    SavedOK := False;
    if (PcbPath <> '') and (LastServerDoc <> nil) then
    begin
        LastServerDoc.SetFileName(PcbPath);
        SavedOK := LastServerDoc.DoFileSave('');
    end;
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';

    GStage := 'done';
    RespOk(', "footprint": ' + JStr(FpName) + ', "pads": 2, "saved": ' + SavedStr +
           ', "saved_to": ' + JStr(PcbPath));
end;

{ ---- command: build/append a symbol in a SchLib with N pins + a footprint link.
       pins as pin0..padN (CommaText): designator,name,x,y,orient(0/90/180/270) mils. ---- }
procedure CmdBuildSymbol;
var
    SD       : IServerDocument;
    Lib      : ISch_Lib;
    Cmp      : ISch_Component;
    R        : ISch_Rectangle;
    El       : ISch_Ellipse;
    Pin      : ISch_Pin;
    Impl     : ISch_Implementation;
    flds     : TStringList;
    SchPath, CompName, FpName, DesigPfx, v, SavedStr : String;
    npins, i, ori : Integer;
    SavedOK  : Boolean;
begin
    SchPath  := Req.Values['schlib'];
    CompName := Req.Values['component']; if CompName = '' then CompName := 'COMP';
    FpName   := Req.Values['footprint'];
    DesigPfx := Req.Values['designator']; if DesigPfx = '' then DesigPfx := 'U';
    npins    := StrToIntDef(Req.Values['pincount'], 1);

    GStage := 'open-or-create SchLib';
    if (SchPath <> '') and FileExists(SchPath) then
        SD := Client.OpenDocument('SCHLIB', SchPath)
    else
        SD := CreateNewDocumentFromDocumentKind('SchLib');
    if SD <> nil then Client.ShowDocument(SD);
    LastServerDoc := SD;
    Lib := SchServer.GetCurrentSchDocument;
    if Lib = nil then begin RespErr('no SchLib'); Exit; end;

    GStage := 'create component';
    Cmp := SchServer.SchObjectFactory(eSchComponent, eCreate_Default);
    Cmp.CurrentPartID := 1;
    Cmp.DisplayMode   := 0;
    Cmp.LibReference  := CompName;

    GStage := 'body';
    if Req.Values['body'] = 'circle' then
    begin
        El := SchServer.SchObjectFactory(eEllipse, eCreate_Default);
        El.Location        := Point(MilsToCoord(50), MilsToCoord(0));
        El.Radius          := MilsToCoord(50);
        El.SecondaryRadius := MilsToCoord(50);
        El.LineWidth       := eSmall;
        El.OwnerPartId          := Cmp.CurrentPartID;
        El.OwnerPartDisplayMode := Cmp.DisplayMode;
        Cmp.AddSchObject(El);
    end
    else
    begin
        R := SchServer.SchObjectFactory(eRectangle, eCreate_Default);
        R.LineWidth := eSmall;
        R.Location  := Point(MilsToCoord(0),   MilsToCoord(-50));
        R.Corner    := Point(MilsToCoord(200), MilsToCoord(50));
        R.OwnerPartId          := Cmp.CurrentPartID;
        R.OwnerPartDisplayMode := Cmp.DisplayMode;
        Cmp.AddSchObject(R);
    end;

    GStage := 'pins';
    flds := TStringList.Create;
    try
        for i := 0 to npins - 1 do
        begin
            v := Req.Values['pin' + IntToStr(i)];
            if v = '' then Continue;
            flds.CommaText := v;          { designator,name,x,y,orient }
            Pin := SchServer.SchObjectFactory(ePin, eCreate_Default);
            Pin.Location := Point(MilsToCoord(StrToIntDef(flds[2], -100)),
                                  MilsToCoord(StrToIntDef(flds[3], 0)));
            ori := StrToIntDef(flds[4], 0);
            if      ori = 90  then Pin.Orientation := eRotate90
            else if ori = 180 then Pin.Orientation := eRotate180
            else if ori = 270 then Pin.Orientation := eRotate270
            else                   Pin.Orientation := eRotate0;
            Pin.Designator := flds[0];
            Pin.Name       := flds[1];
            Pin.OwnerPartId          := Cmp.CurrentPartID;
            Pin.OwnerPartDisplayMode := Cmp.DisplayMode;
            Cmp.AddSchObject(Pin);
        end;
    finally
        flds.Free;
    end;

    GStage := 'designator + params';
    Cmp.Designator.Text := DesigPfx + '?';
    if Req.Values['value'] <> '' then Cmp.Comment.Text := Req.Values['value'];
    MaybeSet(Cmp, 'LCSC Part #',              'lcsc');
    MaybeSet(Cmp, 'Manufacturer Part Number', 'mpn');
    MaybeSet(Cmp, 'Manufacturer',             'manufacturer');
    MaybeSet(Cmp, 'Value',                    'value');
    MaybeSet(Cmp, 'Description',              'description');

    GStage := 'footprint link';
    if FpName <> '' then
    begin
        Impl := Cmp.AddSchImplementation;
        Impl.ClearAllDatafileLinks;
        Impl.ModelName := FpName;
        Impl.ModelType := 'PCBLIB';
        Impl.AddDataFileLink(FpName, '', 'PCBLIB');
        Impl.IsCurrent := True;
    end;

    GStage := 'AddSchComponent';
    Lib.AddSchComponent(Cmp);
    SchServer.RobotManager.SendMessage(Lib.I_ObjectAddress, c_BroadCast,
                                       SCHM_PrimitiveRegistration, Cmp.I_ObjectAddress);

    GStage := 'save';
    SavedOK := False;
    if (SchPath <> '') and (LastServerDoc <> nil) then
    begin
        LastServerDoc.SetFileName(SchPath);
        SavedOK := LastServerDoc.DoFileSave('');
    end;
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';

    RespOk(', "component": ' + JStr(CompName) + ', "footprint": ' + JStr(FpName) +
           ', "pins": ' + IntToStr(npins) + ', "saved": ' + SavedStr);
end;

{ ---- command: build an Altium footprint from a pad spec + optional STEP 3D body.
       pads passed as pad0..padN (CommaText): smd,x,y,w,h,drill,name  (all mils). ---- }
procedure CmdBuildFootprint;
var
    SD       : IServerDocument;
    PcbLib   : IPCB_Library;
    Fp       : IPCB_LibComponent;
    Pad      : IPCB_Pad;
    Body     : IPCB_ComponentBody;
    Mdl      : IPCB_Model;
    PcbPath, FpName, StepPath, v, SavedStr, BodyNote : String;
    n, i     : Integer;
    flds     : TStringList;
    SavedOK  : Boolean;
begin
    PcbPath  := Req.Values['pcblib'];
    FpName   := Req.Values['footprint']; if FpName = '' then FpName := 'FP';
    StepPath := Req.Values['step'];
    n        := StrToIntDef(Req.Values['padcount'], 0);

    GStage := 'open-or-create PcbLib doc';
    if (PcbPath <> '') and FileExists(PcbPath) then
        SD := Client.OpenDocument('PCBLIB', PcbPath)   { append to existing lib }
    else
        SD := CreateNewDocumentFromDocumentKind('PcbLib');
    if SD <> nil then Client.ShowDocument(SD);
    LastServerDoc := SD;

    GStage := 'GetCurrentPCBLibrary';
    if PCBServer = nil then begin RespErr('PCBServer nil'); Exit; end;
    PcbLib := PCBServer.GetCurrentPCBLibrary;
    if PcbLib = nil then begin RespErr('no current PcbLib'); Exit; end;

    GStage := 'CreatePCBLibComp';
    Fp := PCBServer.CreatePCBLibComp;
    Fp.Name := FpName;
    PcbLib.RegisterComponent(Fp);
    PCBServer.PreProcess;

    GStage := 'pads';
    flds := TStringList.Create;
    try
        for i := 0 to n - 1 do
        begin
            v := Req.Values['pad' + IntToStr(i)];
            if v = '' then Continue;
            flds.CommaText := v;            { smd,x,y,w,h,drill,name,shape (mils) }
            Pad := PCBServer.PCBObjectFactory(ePadObject, eNoDimension, eCreate_Default);
            Pad.X        := MilsToCoord(StrToIntDef(flds[1], 0));
            Pad.Y        := MilsToCoord(StrToIntDef(flds[2], 0));
            Pad.TopXSize := MilsToCoord(StrToIntDef(flds[3], 50));
            Pad.TopYSize := MilsToCoord(StrToIntDef(flds[4], 50));
            if (flds.Count >= 8) and ((flds[7] = 'circle') or (flds[7] = 'oval')) then
                Pad.TopShape := eRounded
            else
                Pad.TopShape := eRectangular;
            if flds[0] = '1' then
            begin
                Pad.HoleSize := 0;
                Pad.Layer    := eTopLayer;       { SMD }
            end
            else
            begin
                Pad.HoleSize := MilsToCoord(StrToIntDef(flds[5], 0));
                Pad.Layer    := eMultiLayer;     { through-hole }
            end;
            Pad.Name := flds[6];
            Fp.AddPCBObject(Pad);
            PCBServer.SendMessageToRobots(Fp.I_ObjectAddress, c_Broadcast,
                                          PCBM_BoardRegisteration, Pad.I_ObjectAddress);
        end;
    finally
        flds.Free;
    end;

    GStage := 'step body';
    BodyNote := 'none';
    if StepPath <> '' then
    begin
        Body := PCBServer.PCBObjectFactory(eComponentBodyObject, eNoDimension, eCreate_Default);
        Mdl  := Body.ModelFactory_FromFilename(StepPath, False);
        if Mdl <> nil then
        begin
            Body.SetState_FromModel;
            Body.Model := Mdl;
            Fp.AddPCBObject(Body);
            BodyNote := 'added';
        end
        else BodyNote := 'model load failed';
    end;

    GStage := 'PostProcess';
    PCBServer.PostProcess;

    GStage := 'save';
    SavedOK := False;
    if (PcbPath <> '') and (LastServerDoc <> nil) then
    begin
        LastServerDoc.SetFileName(PcbPath);
        SavedOK := LastServerDoc.DoFileSave('');
    end;
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';

    RespOk(', "footprint": ' + JStr(FpName) + ', "pads": ' + IntToStr(n) +
           ', "step_body": ' + JStr(BodyNote) + ', "saved": ' + SavedStr +
           ', "saved_to": ' + JStr(PcbPath));
end;

{ ---- command: compile a Library Package (.LibPkg) into a .IntLib ---- }
procedure CmdCompileIntLib;
var
    LibPkgPath, OutDir : String;
    Proj               : IProject;
begin
    LibPkgPath := Req.Values['libpkg'];
    OutDir     := Req.Values['output'];
    if LibPkgPath = '' then begin RespErr('missing libpkg'); Exit; end;

    GStage := 'DM_OpenProject';
    Proj := GetWorkspace.DM_OpenProject(LibPkgPath, True);
    if Proj = nil then begin RespErr('could not open LibPkg: ' + LibPkgPath); Exit; end;

    GStage := 'DM_Compile';
    Proj.DM_Compile;

    GStage := 'CreateIntegratedLibrary';
    IntegratedLibraryManager.CreateIntegratedLibrary(Proj, OutDir, True);

    GStage := 'done';
    RespOk(', "libpkg": ' + JStr(LibPkgPath) + ', "output_dir": ' + JStr(OutDir));
end;

{ ---- command: extract the editable sources (SchLib + PcbLib) from a compiled
       .IntLib, then report the extracted document paths so the caller can edit
       them (e.g. add an LCSC param) before re-installing/compiling. ---- }
procedure CmdExtractIntLib;
var
    IntLibPath, P, SchAcc, PcbAcc : String;
    WS    : IWorkspace;
    Proj  : IProject;
    Doc   : IDocument;
    i, j  : Integer;
    FirstS, FirstP : Boolean;
begin
    IntLibPath := Req.Values['intlib'];
    if IntLibPath = '' then begin RespErr('missing intlib'); Exit; end;

    GStage := 'ExtractSources';
    IntegratedLibraryManager.ExtractSources(IntLibPath);

    GStage := 'scan projects';
    WS := GetWorkspace;
    SchAcc := ''; PcbAcc := '';
    FirstS := True; FirstP := True;
    if WS <> nil then
    for i := 0 to WS.DM_ProjectCount - 1 do
    begin
        Proj := WS.DM_Projects(i);
        if Proj = nil then Continue;
        for j := 0 to Proj.DM_LogicalDocumentCount - 1 do
        begin
            Doc := Proj.DM_LogicalDocuments(j);
            if Doc = nil then Continue;
            P := Doc.DM_FullPath;
            if Pos('.SchLib', P) > 0 then
            begin
                if not FirstS then SchAcc := SchAcc + ', ';
                SchAcc := SchAcc + JStr(P); FirstS := False;
            end
            else if Pos('.PcbLib', P) > 0 then
            begin
                if not FirstP then PcbAcc := PcbAcc + ', ';
                PcbAcc := PcbAcc + JStr(P); FirstP := False;
            end;
        end;
    end;

    GStage := 'done';
    RespOk(', "intlib": ' + JStr(IntLibPath) +
           ', "schlibs": [ ' + SchAcc + ' ]' +
           ', "pcblibs": [ ' + PcbAcc + ' ]');
end;

{ ---- helper: does S contain any of the comma-separated lowercase needles? ---- }
function ContainsAny(const S, CsvLower : String) : Boolean;
var parts : TStringList; i : Integer;
begin
    Result := False;
    parts := TStringList.Create;
    try
        parts.CommaText := CsvLower;
        for i := 0 to parts.Count - 1 do
            if (parts[i] <> '') and (Pos(parts[i], S) > 0) then
            begin Result := True; Break; end;
    finally
        parts.Free;
    end;
end;

{ ---- command: list open documents — disabled (Client.DocumentCount is not a
       valid DelphiScript identifier on AD26; calling it threw a script-error
       dialog). Returns an empty list instead of raising. ---- }
procedure CmdListDocs;
begin
    RespOk(', "docs": [], "note": "doc enumeration unsupported on this Altium build"');
end;

{ ---- command: rename a component in a SchLib (in place), optionally updating its
       Description, footprint link, and designator. Preserves the exact symbol
       graphics/pins/params — only identity/metadata changes. ---- }
procedure CmdRenameComponent;
var
    SchPath, OldName, NewName, FpName, Desig, SavedStr : String;
    Lib     : ISch_Lib;
    Cmp     : ISch_Component;
    It      : ISch_Iterator;
    Impl    : ISch_Implementation;
    Found   : Boolean;
    SavedOK : Boolean;
begin
    SchPath := Req.Values['schlib'];
    OldName := Req.Values['component'];
    NewName := Req.Values['newname'];
    FpName  := Req.Values['footprint'];
    Desig   := Req.Values['designator'];
    if (SchPath = '') or (OldName = '') or (NewName = '') then
    begin RespErr('need schlib + component + newname'); Exit; end;

    GStage := 'open schlib';
    Lib := OpenSchLibDoc(SchPath);
    if Lib = nil then begin RespErr('could not open: ' + SchPath); Exit; end;

    GStage := 'find component';
    Cmp := FindComponent(Lib, OldName);
    if Cmp = nil then begin RespErr('component not found: ' + OldName); Exit; end;

    GStage := 'set identity';
    Cmp.LibReference := NewName;
    Cmp.DesignItemID := NewName;
    if Desig <> '' then Cmp.Designator.Text := Desig;

    GStage := 'description';
    MaybeSet(Cmp, 'Description', 'description');

    if FpName <> '' then
    begin
        GStage := 'update footprint link';
        Found := False;
        It := Cmp.SchIterator_Create;
        It.AddFilter_ObjectSet(MkSet(eImplementation));
        try
            Impl := It.FirstSchObject;
            while Impl <> nil do
            begin
                Impl.ClearAllDatafileLinks;
                Impl.ModelName := FpName;
                Impl.ModelType := 'PCBLIB';
                Impl.AddDataFileLink(FpName, '', 'PCBLIB');
                Impl.IsCurrent := True;
                Found := True;
                Impl := It.NextSchObject;
            end;
        finally
            Cmp.SchIterator_Destroy(It);
        end;
        if not Found then
        begin
            Impl := Cmp.AddSchImplementation;
            Impl.ClearAllDatafileLinks;
            Impl.ModelName := FpName;
            Impl.ModelType := 'PCBLIB';
            Impl.AddDataFileLink(FpName, '', 'PCBLIB');
            Impl.IsCurrent := True;
        end;
    end;

    GStage := 'save';
    Lib.GraphicallyInvalidate;
    SavedOK := False;
    if LastServerDoc <> nil then SavedOK := LastServerDoc.DoFileSave('');
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';
    RespOk(', "renamed": ' + JStr(OldName) + ' -> ' + JStr(NewName) +
           ', "footprint": ' + JStr(FpName) + ', "saved": ' + SavedStr);
end;

{ ---- command: rename a footprint (IPCB_LibComponent) inside a PcbLib, in place ---- }
procedure CmdRenameFootprint;
var
    PcbPath, OldName, NewName, SavedStr : String;
    SD      : IServerDocument;
    PcbLib  : IPCB_Library;
    Fp      : IPCB_LibComponent;
    i       : Integer;
    Found   : Boolean;
    SavedOK : Boolean;
begin
    PcbPath := Req.Values['pcblib'];
    OldName := Req.Values['footprint'];
    NewName := Req.Values['newname'];
    if (PcbPath = '') or (NewName = '') then
    begin RespErr('need pcblib + newname'); Exit; end;

    GStage := 'open pcblib';
    SD := Client.OpenDocument('PCBLIB', PcbPath);
    if SD <> nil then Client.ShowDocument(SD);
    LastServerDoc := SD;
    if PCBServer = nil then begin RespErr('PCBServer nil'); Exit; end;
    PcbLib := PCBServer.GetCurrentPCBLibrary;
    if PcbLib = nil then begin RespErr('no current PcbLib'); Exit; end;

    GStage := 'rename component';
    Found := False;
    for i := 0 to PcbLib.ComponentCount - 1 do
    begin
        Fp := PcbLib.GetComponent(i);
        if Fp = nil then Continue;
        if (OldName = '') or (Fp.Name = OldName) then
        begin
            Fp.Name := NewName;
            Found := True;
            if OldName <> '' then Break;   { rename only the first if OldName given }
        end;
    end;
    if not Found then begin RespErr('footprint not found: ' + OldName); Exit; end;

    GStage := 'save';
    PcbLib.Board.ViewManager_FullUpdate;
    SavedOK := False;
    if LastServerDoc <> nil then SavedOK := LastServerDoc.DoFileSave('');
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';
    RespOk(', "renamed_footprint": ' + JStr(OldName) + ' -> ' + JStr(NewName) +
           ', "saved": ' + SavedStr);
end;

{ ---- command: import a part in ONE round-trip. Given the already-extracted source
       SchLib+PcbLib, it: renames the footprint -> friendly name + saves-as into the
       target PcbLib; attaches LCSC/identity params + renames the symbol + links it
       to the footprint + saves-as into the target SchLib; installs both; and reads
       the params back. Collapses ~8 separate X2 launches into one. Every primitive
       used here is already proven by the per-step verbs. ---- }
procedure CmdImportPart;
var
    SrcSch, SrcPcb, TgtSch, TgtPcb, NewName, NewFp, Desig, OldComp : String;
    PcbSD, SchSD : IServerDocument;
    PcbLib : IPCB_Library;
    Fp     : IPCB_LibComponent;
    SchLib : ISch_Lib;
    Cmp    : ISch_Component;
    It     : ISch_Iterator;
    Impl   : ISch_Implementation;
    i      : Integer;
    FoundFp, FoundImpl, Saved1, Saved2 : Boolean;
begin
    SrcSch  := Req.Values['src_sch'];
    SrcPcb  := Req.Values['src_pcb'];
    TgtSch  := Req.Values['tgt_sch'];
    TgtPcb  := Req.Values['tgt_pcb'];
    NewName := Req.Values['name'];
    NewFp   := Req.Values['footprint'];
    Desig   := Req.Values['designator']; if Desig = '' then Desig := 'U?';
    if (SrcSch = '') or (SrcPcb = '') or (TgtSch = '') or (TgtPcb = '')
       or (NewName = '') or (NewFp = '') then
    begin RespErr('need src_sch, src_pcb, tgt_sch, tgt_pcb, name, footprint'); Exit; end;

    { 1) footprint: rename first footprint -> NewFp, save-as into target PcbLib }
    GStage := 'open src pcb';
    PcbSD := Client.OpenDocument('PCBLIB', SrcPcb);
    if PcbSD <> nil then Client.ShowDocument(PcbSD);
    if PCBServer = nil then begin RespErr('PCBServer nil'); Exit; end;
    PcbLib := PCBServer.GetCurrentPCBLibrary;
    if PcbLib = nil then begin RespErr('no current PcbLib'); Exit; end;

    GStage := 'rename footprint';
    FoundFp := False;
    for i := 0 to PcbLib.ComponentCount - 1 do
    begin
        Fp := PcbLib.GetComponent(i);
        if Fp <> nil then begin Fp.Name := NewFp; FoundFp := True; Break; end;
    end;
    if not FoundFp then begin RespErr('no footprint in ' + SrcPcb); Exit; end;
    PcbLib.Board.ViewManager_FullUpdate;

    GStage := 'save pcb as target';
    PcbSD.SetFileName(TgtPcb);
    Saved1 := PcbSD.DoFileSave('');

    { 2) symbol: params + rename + footprint link, save-as into target SchLib }
    GStage := 'open src sch';
    SchSD := Client.OpenDocument('SCHLIB', SrcSch);
    if SchSD <> nil then Client.ShowDocument(SchSD);
    SchLib := SchServer.GetSchDocumentByPath(SrcSch);
    if SchLib = nil then begin RespErr('open src sch failed'); Exit; end;
    LastServerDoc := SchSD;

    GStage := 'find component';
    Cmp := FindComponent(SchLib, '');   { first component }
    if Cmp = nil then begin RespErr('no component in ' + SrcSch); Exit; end;
    OldComp := Cmp.LibReference;

    GStage := 'params';
    MaybeSet(Cmp, 'LCSC Part #',              'lcsc');
    MaybeSet(Cmp, 'Manufacturer Part Number', 'mpn');
    MaybeSet(Cmp, 'Manufacturer',             'manufacturer');
    MaybeSet(Cmp, 'Value',                    'value');
    MaybeSet(Cmp, 'Description',              'description');
    MaybeSet(Cmp, 'Package',                  'package');

    GStage := 'rename + link';
    Cmp.LibReference    := NewName;
    Cmp.DesignItemID    := NewName;
    Cmp.Designator.Text := Desig;
    FoundImpl := False;
    It := Cmp.SchIterator_Create;
    It.AddFilter_ObjectSet(MkSet(eImplementation));
    try
        Impl := It.FirstSchObject;
        while Impl <> nil do
        begin
            Impl.ClearAllDatafileLinks;
            Impl.ModelName := NewFp;
            Impl.ModelType := 'PCBLIB';
            Impl.AddDataFileLink(NewFp, '', 'PCBLIB');
            Impl.IsCurrent := True;
            FoundImpl := True;
            Impl := It.NextSchObject;
        end;
    finally
        Cmp.SchIterator_Destroy(It);
    end;
    if not FoundImpl then
    begin
        Impl := Cmp.AddSchImplementation;
        Impl.ClearAllDatafileLinks;
        Impl.ModelName := NewFp;
        Impl.ModelType := 'PCBLIB';
        Impl.AddDataFileLink(NewFp, '', 'PCBLIB');
        Impl.IsCurrent := True;
    end;
    SchLib.GraphicallyInvalidate;

    GStage := 'save sch as target';
    SchSD.SetFileName(TgtSch);
    Saved2 := SchSD.DoFileSave('');

    GStage := 'install';
    IntegratedLibraryManager.InstallLibrary(TgtPcb);
    IntegratedLibraryManager.InstallLibrary(TgtSch);

    GStage := 'done';
    RespOk(', "component": ' + JStr(NewName) + ', "footprint": ' + JStr(NewFp) +
           ', "from": ' + JStr(OldComp) +
           ', "saved_pcb": ' + BoolStr(Saved1) + ', "saved_sch": ' + BoolStr(Saved2) +
           ', "params": ' + ParamsJSON(Cmp));
end;

{ ---- command: remove a component (by LibReference) from a SchLib, then save ---- }
procedure CmdRemoveComponent;
var SchLibPath, CompName : String; Lib : ISch_Lib; Cmp, Target : ISch_Component;
    It : ISch_Iterator; SavedOK : Boolean;
begin
    SchLibPath := Req.Values['schlib'];
    CompName   := Req.Values['component'];
    if (SchLibPath = '') or (CompName = '') then begin RespErr('need schlib + component'); Exit; end;

    GStage := 'open schlib';
    Lib := OpenSchLibDoc(SchLibPath);
    if Lib = nil then begin RespErr('could not open: ' + SchLibPath); Exit; end;

    GStage := 'find component';
    Target := nil;
    It := Lib.SchLibIterator_Create;
    It.AddFilter_ObjectSet(MkSet(eSchComponent));
    try
        Cmp := It.FirstSchObject;
        while Cmp <> nil do
        begin
            if Cmp.LibReference = CompName then begin Target := Cmp; Break; end;
            Cmp := It.NextSchObject;
        end;
    finally
        Lib.SchIterator_Destroy(It);
    end;
    if Target = nil then begin RespErr('component not found: ' + CompName); Exit; end;

    GStage := 'remove component';
    Lib.RemoveSchComponent(Target);

    GStage := 'save';
    SavedOK := False;
    if LastServerDoc <> nil then SavedOK := LastServerDoc.DoFileSave('');
    if SavedOK then RespOk(', "removed_component": ' + JStr(CompName) + ', "saved": true')
    else RespOk(', "removed_component": ' + JStr(CompName) + ', "saved": false');
end;

{ ---- command: close an open document by its file path (no save prompt if unmodified) ---- }
procedure CmdCloseDoc;
var Path, Kind : String; SD : IServerDocument;
begin
    Path := Req.Values['path'];
    if Path = '' then begin RespErr('missing path'); Exit; end;
    GStage := 'get handle';
    Kind := 'SCHLIB';
    if Pos('.pcblib', LowerCase(Path)) > 0 then Kind := 'PCBLIB';
    SD := Client.OpenDocument(Kind, Path);
    if SD = nil then begin RespOk(', "closed": false, "note": "no handle for path"'); Exit; end;
    GStage := 'CloseDocument';
    Client.CloseDocument(SD);
    RespOk(', "closed": true, "kind": ' + JStr(Kind) + ', "path": ' + JStr(Path));
end;

{ ---- command: list all components (LibReference) in a SchLib ---- }
procedure CmdListSymbols;
var SchLibPath, Acc : String; Lib : ISch_Lib; Cmp : ISch_Component; It : ISch_Iterator; First : Boolean;
begin
    SchLibPath := Req.Values['schlib'];
    Lib := OpenSchLibDoc(SchLibPath);
    if Lib = nil then begin RespErr('could not open: ' + SchLibPath); Exit; end;
    Acc := ''; First := True;
    It := Lib.SchLibIterator_Create;
    It.AddFilter_ObjectSet(MkSet(eSchComponent));
    try
        Cmp := It.FirstSchObject;
        while Cmp <> nil do
        begin
            if not First then Acc := Acc + ', ';
            Acc := Acc + JStr(Cmp.LibReference);
            First := False;
            Cmp := It.NextSchObject;
        end;
    finally
        Lib.SchIterator_Destroy(It);
    end;
    RespOk(', "components": [ ' + Acc + ' ]');
end;

{ ---- command: clone a template component's graphics exactly into a new component,
       set a fresh footprint link + designator. ---- }
procedure CmdCloneSymbol;
var
    SchPath, TemplateName, NewName, FpName, DesigPfx, SavedStr : String;
    Lib     : ISch_Lib;
    Tmpl, NewCmp : ISch_Component;
    It      : ISch_Iterator;
    Prim, NewPrim : ISch_GraphicalObject;
    Impl    : ISch_Implementation;
    SavedOK : Boolean;
    o       : Integer;
begin
    SchPath      := Req.Values['schlib'];
    TemplateName := Req.Values['template'];
    NewName      := Req.Values['component'];
    FpName       := Req.Values['footprint'];
    DesigPfx     := Req.Values['designator']; if DesigPfx = '' then DesigPfx := 'MC';

    GStage := 'open schlib';
    Lib := OpenSchLibDoc(SchPath);
    if Lib = nil then begin RespErr('open failed'); Exit; end;

    GStage := 'find template';
    Tmpl := FindComponent(Lib, TemplateName);
    if Tmpl = nil then begin RespErr('template not found: ' + TemplateName); Exit; end;

    GStage := 'create new component';
    NewCmp := SchServer.SchObjectFactory(eSchComponent, eCreate_Default);
    NewCmp.CurrentPartID := 1;
    NewCmp.DisplayMode   := 0;
    NewCmp.LibReference  := NewName;

    GStage := 'copy graphics by ObjectId';
    It := Tmpl.SchIterator_Create;     { no type filter — pick graphics by ObjectId }
    try
        Prim := It.FirstSchObject;
        while Prim <> nil do
        begin
            o := Prim.ObjectId;
            { copy pins + ALL graphics; skip only metadata that's set explicitly:
              25=designator, 27=parameter, 45=comment, 46=implementation }
            if (o <> 25) and (o <> 27) and (o <> 45) and (o <> 46) then
            begin
                NewPrim := Prim.Replicate;
                NewPrim.OwnerPartId          := NewCmp.CurrentPartID;
                NewPrim.OwnerPartDisplayMode := NewCmp.DisplayMode;
                NewCmp.AddSchObject(NewPrim);
            end;
            Prim := It.NextSchObject;
        end;
    finally
        Tmpl.SchIterator_Destroy(It);
    end;

    GStage := 'designator';
    NewCmp.Designator.Text := DesigPfx + '?';

    GStage := 'footprint link';
    if FpName <> '' then
    begin
        Impl := NewCmp.AddSchImplementation;
        Impl.ClearAllDatafileLinks;
        Impl.ModelName := FpName;
        Impl.ModelType := 'PCBLIB';
        Impl.AddDataFileLink(FpName, '', 'PCBLIB');
        Impl.IsCurrent := True;
    end;

    GStage := 'AddSchComponent';
    Lib.AddSchComponent(NewCmp);
    SchServer.RobotManager.SendMessage(Lib.I_ObjectAddress, c_BroadCast,
                                       SCHM_PrimitiveRegistration, NewCmp.I_ObjectAddress);

    GStage := 'save';
    SavedOK := False;
    if LastServerDoc <> nil then SavedOK := LastServerDoc.DoFileSave('');
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';

    RespOk(', "cloned": ' + JStr(TemplateName) + ' -> ' + JStr(NewName) +
           ', "footprint": ' + JStr(FpName) + ', "saved": ' + SavedStr);
end;

{ ---- command: dump a component's primitives (ObjectId + location) ---- }
procedure CmdReadSymbol;
var SchLibPath, CompName, Acc : String; Lib : ISch_Lib; Cmp : ISch_Component;
    It : ISch_Iterator; Prim : ISch_GraphicalObject; First : Boolean;
begin
    SchLibPath := Req.Values['schlib']; CompName := Req.Values['component'];
    Lib := OpenSchLibDoc(SchLibPath);
    if Lib = nil then begin RespErr('open failed'); Exit; end;
    Cmp := FindComponent(Lib, CompName);
    if Cmp = nil then begin RespErr('component not found: ' + CompName); Exit; end;
    Acc := ''; First := True;
    It := Cmp.SchIterator_Create;
    try
        Prim := It.FirstSchObject;
        while Prim <> nil do
        begin
            if not First then Acc := Acc + ', ';
            Acc := Acc + IntToStr(Prim.ObjectId);
            First := False;
            Prim := It.NextSchObject;
        end;
    finally
        Cmp.SchIterator_Destroy(It);
    end;
    RespOk(', "component": ' + JStr(CompName) + ', "primitives": [ ' + Acc + ' ]');
end;

{ ---- command: install a file-based library (.SchLib/.PcbLib/.IntLib) into Available Libraries ---- }
procedure CmdInstallLibrary;
var Path : String;
begin
    Path := Req.Values['path'];
    if Path = '' then begin RespErr('missing path'); Exit; end;
    GStage := 'InstallLibrary';
    IntegratedLibraryManager.InstallLibrary(Path);
    RespOk(', "installed": ' + JStr(Path));
end;

{ ---- command: uninstall available libraries whose path matches any needle ---- }
procedure CmdCleanupLibs;
var Match, Path, Removed : String; i, n : Integer;
begin
    Match := LowerCase(Req.Values['match']);
    if Match = '' then begin RespErr('missing match (comma-separated path substrings)'); Exit; end;
    Removed := '';
    GStage := 'InstalledLibraryCount';
    n := IntegratedLibraryManager.InstalledLibraryCount;
    GStage := 'iterate+uninstall';
    for i := n - 1 downto 0 do
    begin
        Path := IntegratedLibraryManager.InstalledLibraryPath(i);
        if ContainsAny(LowerCase(Path), Match) then
        begin
            IntegratedLibraryManager.UninstallLibrary(Path);
            if Removed <> '' then Removed := Removed + ', ';
            Removed := Removed + JStr(Path);
        end;
    end;
    RespOk(', "removed": [ ' + Removed + ' ]');
end;

procedure CmdPing;
var WS : IWorkspace; n : Integer;
begin
    n := -1;
    try
        WS := GetWorkspace;
        if WS <> nil then n := WS.DM_ProjectCount;
    except
    end;
    RespOk(', "addin": "AdomAltium", "open_project_count": ' + IntToStr(n));
end;

{ ---- command: attach a simple extruded 3D body (a box) to a footprint, sized in
       mils. Gives passives a 3D model without needing a STEP file. ---- }
procedure CmdAddBody;
var
    PcbPath, FpName, SavedStr : String;
    SD      : IServerDocument;
    PcbLib  : IPCB_Library;
    Fp      : IPCB_LibComponent;
    Body    : IPCB_ComponentBody;
    Contour : IPCB_Contour;
    bw, bh, hgt, soff, i : Integer;
    Found, SavedOK : Boolean;
begin
    PcbPath := Req.Values['pcblib'];
    FpName  := Req.Values['footprint'];
    bw   := StrToIntDef(Req.Values['bw_mil'], 39);   { body length, mils }
    bh   := StrToIntDef(Req.Values['bh_mil'], 20);   { body width, mils  }
    hgt  := StrToIntDef(Req.Values['h_mil'], 16);    { body height, mils }
    soff := StrToIntDef(Req.Values['so_mil'], 1);    { standoff, mils    }
    if (PcbPath = '') or (FpName = '') then begin RespErr('need pcblib + footprint'); Exit; end;

    GStage := 'open pcblib';
    SD := Client.OpenDocument('PCBLIB', PcbPath);
    if SD <> nil then Client.ShowDocument(SD);
    LastServerDoc := SD;
    if PCBServer = nil then begin RespErr('PCBServer nil'); Exit; end;
    PcbLib := PCBServer.GetCurrentPCBLibrary;
    if PcbLib = nil then begin RespErr('no current PcbLib'); Exit; end;

    GStage := 'find footprint';
    Found := False;
    for i := 0 to PcbLib.ComponentCount - 1 do
    begin
        Fp := PcbLib.GetComponent(i);
        if (Fp <> nil) and (Fp.Name = FpName) then begin Found := True; Break; end;
    end;
    if not Found then begin RespErr('footprint not found: ' + FpName); Exit; end;

    GStage := 'create body';
    PCBServer.PreProcess;
    Body := PCBServer.PCBObjectFactory(eComponentBodyObject, eNoDimension, eCreate_Default);
    Body.BodyProjection := eBoardSide_Top;
    Body.StandoffHeight := MilsToCoord(soff);
    Body.OverallHeight  := MilsToCoord(soff + hgt);
    Body.BodyColor3D    := $00202020;

    GStage := 'outline';
    Contour := PCBServer.PCBContourFactory;
    Contour.AddPoint(MilsToCoord(-(bw div 2)), MilsToCoord(-(bh div 2)));
    Contour.AddPoint(MilsToCoord( (bw div 2)), MilsToCoord(-(bh div 2)));
    Contour.AddPoint(MilsToCoord( (bw div 2)), MilsToCoord( (bh div 2)));
    Contour.AddPoint(MilsToCoord(-(bw div 2)), MilsToCoord( (bh div 2)));
    Body.SetOutlineContour(Contour);

    GStage := 'add to footprint';
    Fp.AddPCBObject(Body);
    PCBServer.SendMessageToRobots(Fp.I_ObjectAddress, c_Broadcast,
                                  PCBM_BoardRegisteration, Body.I_ObjectAddress);
    PCBServer.PostProcess;

    GStage := 'save';
    LastServerDoc.SetFileName(PcbPath);
    SavedOK := LastServerDoc.DoFileSave('');
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';
    RespOk(', "footprint": ' + JStr(FpName) + ', "body": "extruded", "saved": ' + SavedStr);
end;

{ ---- command: set which parameters are VISIBLE on a component. Names in 'visible'
       (comma list) stay shown; every other parameter is hidden, and the Comment is
       hidden too unless 'Comment' is listed. Controls what appears when the symbol
       is placed on a schematic. ---- }
procedure CmdSetVisibility;
var
    SchPath, CompRef, Vis, SavedStr : String;
    Lib     : ISch_Lib;
    Cmp     : ISch_Component;
    It      : ISch_Iterator;
    P       : ISch_Parameter;
    visList : TStringList;
    SavedOK : Boolean;
    shown, hidden : Integer;
begin
    SchPath := Req.Values['schlib'];
    CompRef := Req.Values['component'];
    Vis     := Req.Values['visible']; if Vis = '' then Vis := 'Value';
    if SchPath = '' then begin RespErr('missing schlib'); Exit; end;

    GStage := 'open schlib';
    Lib := OpenSchLibDoc(SchPath);
    if Lib = nil then begin RespErr('could not open: ' + SchPath); Exit; end;
    Cmp := FindComponent(Lib, CompRef);
    if Cmp = nil then begin RespErr('component not found: ' + CompRef); Exit; end;

    visList := TStringList.Create;
    visList.CommaText := Vis;          { case-insensitive IndexOf by default }
    shown := 0; hidden := 0;
    try
        GStage := 'set parameter visibility';
        It := Cmp.SchIterator_Create;
        It.AddFilter_ObjectSet(MkSet(eParameter));
        try
            P := It.FirstSchObject;
            while P <> nil do
            begin
                if visList.IndexOf(P.Name) >= 0 then
                begin P.IsHidden := False; Inc(shown); end
                else
                begin P.IsHidden := True; Inc(hidden); end;
                P := It.NextSchObject;
            end;
        finally
            Cmp.SchIterator_Destroy(It);
        end;

        GStage := 'comment visibility';
        if visList.IndexOf('Comment') < 0 then Cmp.Comment.IsHidden := True
        else Cmp.Comment.IsHidden := False;
    finally
        visList.Free;
    end;

    GStage := 'save';
    Lib.GraphicallyInvalidate;
    SavedOK := False;
    if LastServerDoc <> nil then SavedOK := LastServerDoc.DoFileSave('');
    if SavedOK then SavedStr := 'true' else SavedStr := 'false';
    RespOk(', "component": ' + JStr(Cmp.LibReference) + ', "visible": ' + JStr(Vis) +
           ', "shown": ' + IntToStr(shown) + ', "hidden": ' + IntToStr(hidden) +
           ', "saved": ' + SavedStr);
end;

{ ---- shared command dispatch. Assumes the global Req is loaded and GReqId set. ---- }
procedure Dispatch;
var Cmd : String;
begin
    try
        Cmd := Req.Values['command'];
        GStage := 'dispatch ' + Cmd;
        if      Cmd = 'ping'             then CmdPing
        else if Cmd = 'add_lcsc_param'   then CmdAddLcscParam
        else if Cmd = 'read_params'      then CmdReadParams
        else if Cmd = 'create_and_attach' then CmdCreateAndAttach
        else if Cmd = 'compile_intlib'   then CmdCompileIntLib
        else if Cmd = 'extract_intlib'   then CmdExtractIntLib
        else if Cmd = 'cleanup_libs'     then CmdCleanupLibs
        else if Cmd = 'create_resistor'  then CmdCreateResistor
        else if Cmd = 'list_docs'        then CmdListDocs
        else if Cmd = 'remove_component' then CmdRemoveComponent
        else if Cmd = 'close_doc'        then CmdCloseDoc
        else if Cmd = 'build_footprint'  then CmdBuildFootprint
        else if Cmd = 'build_symbol'     then CmdBuildSymbol
        else if Cmd = 'install_library'  then CmdInstallLibrary
        else if Cmd = 'list_symbols'     then CmdListSymbols
        else if Cmd = 'read_symbol'      then CmdReadSymbol
        else if Cmd = 'clone_symbol'     then CmdCloneSymbol
        else if Cmd = 'rename_component' then CmdRenameComponent
        else if Cmd = 'rename_footprint' then CmdRenameFootprint
        else if Cmd = 'import_part'      then CmdImportPart
        else if Cmd = 'add_body'         then CmdAddBody
        else if Cmd = 'set_visibility'   then CmdSetVisibility
        else RespErr('unknown command: ' + Cmd);
    except
        RespErr('exception at stage: ' + GStage);
    end;
end;

{ ---- one-shot entry (X2 ...>Run): read request.txt, dispatch, write response. ---- }
procedure Run;
begin
    Req := TStringList.Create;
    try
        try
            if FileExists(EX_DIR + 'request.txt') then
                Req.LoadFromFile(EX_DIR + 'request.txt');
            GReqId := Req.Values['id'];
            Dispatch;
        except
            RespErr('exception at stage: ' + GStage);
        end;
    finally
        Req.Free;
    end;
end;