From deb03b9c486b8c073c33ec334ba7a79a8a177832 Mon Sep 17 00:00:00 2001 From: Milind Sharma Date: Fri, 20 Feb 2026 18:56:32 +0800 Subject: [PATCH] feat(board): add UpdateBoardStackup API and CLI command --- README.md | 7 +- docs/TEST_CLI.md | 1 + src/client.rs | 250 ++++++++++++++++++++++++++++++++-- src/model/board.rs | 1 + test-scripts/kicad-ipc-cli.rs | 16 ++- 5 files changed, 262 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3426aa9..3ad8fda 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Deferred manual/runtime verification (implemented after 2026-02-20 while user un - `ParseAndCreateItemsFromString` - `SetNetClasses` - `SetTextVariables` +- `UpdateBoardStackup` ## KiCad v10 RC1.1 API Completion Matrix @@ -67,9 +68,9 @@ Legend: | Common (base) | 6 | 6 | 100% | | Common editor/document | 23 | 23 | 100% | | Project manager | 5 | 5 | 100% | -| Board editor (PCB) | 22 | 20 | 91% | +| Board editor (PCB) | 22 | 21 | 95% | | Schematic editor (dedicated proto commands) | 0 | 0 | n/a | -| **Total** | **56** | **54** | **96%** | +| **Total** | **56** | **55** | **98%** | ### Common (base) @@ -125,7 +126,7 @@ Legend: | KiCad Command | Status | Rust API | | --- | --- | --- | | `GetBoardStackup` | Implemented | `KiCadClient::get_board_stackup_raw`, `KiCadClient::get_board_stackup` | -| `UpdateBoardStackup` | Not yet | - | +| `UpdateBoardStackup` | Implemented | `KiCadClient::update_board_stackup_raw`, `KiCadClient::update_board_stackup` | | `GetBoardEnabledLayers` | Implemented | `KiCadClient::get_board_enabled_layers` | | `SetBoardEnabledLayers` | Implemented | `KiCadClient::set_board_enabled_layers` | | `GetGraphicsDefaults` | Implemented | `KiCadClient::get_graphics_defaults_raw`, `KiCadClient::get_graphics_defaults` | diff --git a/docs/TEST_CLI.md b/docs/TEST_CLI.md index f4a013c..b9b7943 100644 --- a/docs/TEST_CLI.md +++ b/docs/TEST_CLI.md @@ -341,6 +341,7 @@ Show typed stackup/graphics/appearance: ```bash cargo run --bin kicad-ipc-cli -- stackup +cargo run --bin kicad-ipc-cli -- update-stackup cargo run --bin kicad-ipc-cli -- graphics-defaults cargo run --bin kicad-ipc-cli -- appearance ``` diff --git a/src/client.rs b/src/client.rs index 9cc6609..a4e2480 100644 --- a/src/client.rs +++ b/src/client.rs @@ -59,6 +59,7 @@ const CMD_SET_VISIBLE_LAYERS: &str = "kiapi.board.commands.SetVisibleLayers"; const CMD_GET_BOARD_ORIGIN: &str = "kiapi.board.commands.GetBoardOrigin"; const CMD_SET_BOARD_ORIGIN: &str = "kiapi.board.commands.SetBoardOrigin"; const CMD_GET_BOARD_STACKUP: &str = "kiapi.board.commands.GetBoardStackup"; +const CMD_UPDATE_BOARD_STACKUP: &str = "kiapi.board.commands.UpdateBoardStackup"; const CMD_GET_GRAPHICS_DEFAULTS: &str = "kiapi.board.commands.GetGraphicsDefaults"; const CMD_GET_BOARD_EDITOR_APPEARANCE_SETTINGS: &str = "kiapi.board.commands.GetBoardEditorAppearanceSettings"; @@ -1524,6 +1525,32 @@ impl KiCadClient { Ok(map_board_stackup(response.stackup.unwrap_or_default())) } + pub async fn update_board_stackup_raw( + &self, + stackup: BoardStackup, + ) -> Result { + let command = board_commands::UpdateBoardStackup { + board: Some(self.current_board_document_proto().await?), + stackup: Some(board_stackup_to_proto(stackup)), + }; + + let response = self + .send_command(envelope::pack_any(&command, CMD_UPDATE_BOARD_STACKUP)) + .await?; + + response_payload_as_any(response, RES_BOARD_STACKUP_RESPONSE) + } + + pub async fn update_board_stackup( + &self, + stackup: BoardStackup, + ) -> Result { + let payload = self.update_board_stackup_raw(stackup).await?; + let response: board_commands::BoardStackupResponse = + decode_any(&payload, RES_BOARD_STACKUP_RESPONSE)?; + Ok(map_board_stackup(response.stackup.unwrap_or_default())) + } + pub async fn get_graphics_defaults_raw(&self) -> Result { let command = board_commands::GetGraphicsDefaults { board: Some(self.current_board_document_proto().await?), @@ -2510,6 +2537,28 @@ fn map_board_stackup_layer_type(value: i32) -> BoardStackupLayerType { } } +fn board_stackup_layer_type_to_proto(value: BoardStackupLayerType) -> i32 { + match value { + BoardStackupLayerType::Copper => board_proto::BoardStackupLayerType::BsltCopper as i32, + BoardStackupLayerType::Dielectric => { + board_proto::BoardStackupLayerType::BsltDielectric as i32 + } + BoardStackupLayerType::Silkscreen => { + board_proto::BoardStackupLayerType::BsltSilkscreen as i32 + } + BoardStackupLayerType::SolderMask => { + board_proto::BoardStackupLayerType::BsltSoldermask as i32 + } + BoardStackupLayerType::SolderPaste => { + board_proto::BoardStackupLayerType::BsltSolderpaste as i32 + } + BoardStackupLayerType::Undefined => { + board_proto::BoardStackupLayerType::BsltUndefined as i32 + } + BoardStackupLayerType::Unknown(value) => value, + } +} + fn map_board_layer_class(value: i32) -> BoardLayerClass { match board_proto::BoardLayerClass::try_from(value) { Ok(board_proto::BoardLayerClass::BlcSilkscreen) => BoardLayerClass::Silkscreen, @@ -2616,6 +2665,7 @@ fn map_board_stackup(stackup: board_proto::BoardStackup) -> BoardStackup { .map(|impedance| impedance.is_controlled) .unwrap_or(false); let edge = stackup.edge.unwrap_or_default(); + let edge_has_connector = edge.connector.is_some(); let edge_has_castellated_pads = edge .castellation .map(|value| value.has_castellated_pads) @@ -2654,12 +2704,75 @@ fn map_board_stackup(stackup: board_proto::BoardStackup) -> BoardStackup { BoardStackup { finish_type_name, impedance_controlled, + edge_has_connector, edge_has_castellated_pads, edge_has_edge_plating, layers, } } +fn board_stackup_to_proto(stackup: BoardStackup) -> board_proto::BoardStackup { + board_proto::BoardStackup { + finish: (!stackup.finish_type_name.is_empty()).then_some(board_proto::BoardFinish { + type_name: stackup.finish_type_name, + }), + impedance: Some(board_proto::BoardImpedanceControl { + is_controlled: stackup.impedance_controlled, + }), + edge: Some(board_proto::BoardEdgeSettings { + connector: stackup + .edge_has_connector + .then_some(board_proto::BoardEdgeConnector {}), + castellation: Some(board_proto::Castellation { + has_castellated_pads: stackup.edge_has_castellated_pads, + }), + plating: Some(board_proto::EdgePlating { + has_edge_plating: stackup.edge_has_edge_plating, + }), + }), + layers: stackup + .layers + .into_iter() + .map(board_stackup_layer_to_proto) + .collect(), + } +} + +fn board_stackup_layer_to_proto(layer: BoardStackupLayer) -> board_proto::BoardStackupLayer { + board_proto::BoardStackupLayer { + thickness: layer + .thickness_nm + .map(|value_nm| common_types::Distance { value_nm }), + layer: layer.layer.id, + enabled: layer.enabled, + r#type: board_stackup_layer_type_to_proto(layer.layer_type), + dielectric: (!layer.dielectric_layers.is_empty()).then(|| { + board_proto::BoardStackupDielectricLayer { + layer: layer + .dielectric_layers + .into_iter() + .map(|dielectric| board_proto::BoardStackupDielectricProperties { + epsilon_r: dielectric.epsilon_r, + loss_tangent: dielectric.loss_tangent, + material_name: dielectric.material_name, + thickness: dielectric + .thickness_nm + .map(|value_nm| common_types::Distance { value_nm }), + }) + .collect(), + } + }), + color: layer.color.map(|color| common_types::Color { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + }), + material_name: layer.material_name, + user_name: layer.user_name, + } +} + fn map_graphics_defaults(defaults: board_proto::GraphicsDefaults) -> GraphicsDefaults { GraphicsDefaults { layers: defaults @@ -3495,17 +3608,20 @@ fn default_client_name() -> String { #[cfg(test)] mod tests { use super::{ - any_to_pretty_debug, board_editor_appearance_settings_to_proto, commit_action_to_proto, - drc_severity_to_proto, ensure_item_deletion_status_ok, ensure_item_request_ok, - ensure_item_status_ok, layer_to_model, map_commit_session, map_hit_test_result, - map_item_bounding_boxes, map_merge_mode_to_proto, map_polygon_with_holes, - map_run_action_status, model_document_to_proto, normalize_socket_uri, - pad_netlist_from_footprint_items, response_payload_as_any, select_single_board_document, - select_single_project_path, selection_item_detail, summarize_item_details, - summarize_selection, text_horizontal_alignment_to_proto, text_spec_to_proto, - PCB_OBJECT_TYPES, + any_to_pretty_debug, board_editor_appearance_settings_to_proto, board_stackup_to_proto, + commit_action_to_proto, drc_severity_to_proto, ensure_item_deletion_status_ok, + ensure_item_request_ok, ensure_item_status_ok, layer_to_model, map_board_stackup, + map_commit_session, map_hit_test_result, map_item_bounding_boxes, map_merge_mode_to_proto, + map_polygon_with_holes, map_run_action_status, model_document_to_proto, + normalize_socket_uri, pad_netlist_from_footprint_items, response_payload_as_any, + select_single_board_document, select_single_project_path, selection_item_detail, + summarize_item_details, summarize_selection, text_horizontal_alignment_to_proto, + text_spec_to_proto, PCB_OBJECT_TYPES, }; use crate::error::KiCadError; + use crate::model::board::{ + BoardLayerInfo, BoardStackup, BoardStackupLayer, BoardStackupLayerType, + }; use crate::model::common::{ CommitAction, DocumentSpecifier, DocumentType, ProjectInfo, TextAttributesSpec, TextHorizontalAlignment, TextSpec, @@ -3724,6 +3840,122 @@ mod tests { ); } + #[test] + fn map_board_stackup_defaults_missing_optional_messages() { + let mapped = map_board_stackup(crate::proto::kiapi::board::BoardStackup::default()); + assert_eq!(mapped.finish_type_name, ""); + assert!(!mapped.impedance_controlled); + assert!(!mapped.edge_has_connector); + assert!(!mapped.edge_has_castellated_pads); + assert!(!mapped.edge_has_edge_plating); + assert!(mapped.layers.is_empty()); + } + + #[test] + fn map_board_stackup_maps_unknown_layer_type_enum() { + let mapped = map_board_stackup(crate::proto::kiapi::board::BoardStackup { + finish: None, + impedance: None, + edge: None, + layers: vec![crate::proto::kiapi::board::BoardStackupLayer { + thickness: None, + layer: crate::proto::kiapi::board::types::BoardLayer::BlFCu as i32, + enabled: true, + r#type: 777, + dielectric: None, + color: None, + material_name: String::new(), + user_name: String::new(), + }], + }); + assert!(matches!( + mapped.layers.first().map(|layer| layer.layer_type), + Some(BoardStackupLayerType::Unknown(777)) + )); + } + + #[test] + fn board_stackup_to_proto_maps_unknown_layer_type_and_missing_nested_messages() { + let proto = board_stackup_to_proto(BoardStackup { + finish_type_name: String::new(), + impedance_controlled: false, + edge_has_connector: false, + edge_has_castellated_pads: false, + edge_has_edge_plating: false, + layers: vec![BoardStackupLayer { + layer: BoardLayerInfo { + id: crate::proto::kiapi::board::types::BoardLayer::BlFCu as i32, + name: "BL_F_Cu".to_string(), + }, + user_name: "F.Cu".to_string(), + material_name: "Copper".to_string(), + enabled: true, + thickness_nm: None, + layer_type: BoardStackupLayerType::Unknown(321), + color: None, + dielectric_layers: Vec::new(), + }], + }); + + assert!(proto.finish.is_none()); + assert_eq!( + proto + .impedance + .expect("impedance should always be present") + .is_controlled, + false + ); + let edge = proto.edge.expect("edge should always be present"); + assert!(edge.connector.is_none()); + assert_eq!( + edge.castellation + .expect("castellation should be present") + .has_castellated_pads, + false + ); + assert_eq!( + edge.plating + .expect("plating should be present") + .has_edge_plating, + false + ); + let layer = proto.layers.first().expect("one layer should be present"); + assert!(layer.thickness.is_none()); + assert_eq!(layer.r#type, 321); + assert!(layer.dielectric.is_none()); + assert!(layer.color.is_none()); + } + + #[test] + fn board_stackup_to_proto_preserves_edge_connector_presence() { + let proto = board_stackup_to_proto(BoardStackup { + finish_type_name: "ENIG".to_string(), + impedance_controlled: true, + edge_has_connector: true, + edge_has_castellated_pads: true, + edge_has_edge_plating: true, + layers: Vec::new(), + }); + assert_eq!( + proto.finish.expect("finish should be present").type_name, + "ENIG" + ); + let edge = proto.edge.expect("edge should be present"); + assert!(edge.connector.is_some()); + assert_eq!( + edge.castellation + .expect("castellation should be present") + .has_castellated_pads, + true + ); + assert_eq!( + edge.plating + .expect("plating should be present") + .has_edge_plating, + true + ); + } + #[test] fn response_payload_as_any_validates_type_url() { let response = crate::proto::kiapi::common::ApiResponse { diff --git a/src/model/board.rs b/src/model/board.rs index c289065..86b5d36 100644 --- a/src/model/board.rs +++ b/src/model/board.rs @@ -164,6 +164,7 @@ pub struct BoardStackupLayer { pub struct BoardStackup { pub finish_type_name: String, pub impedance_controlled: bool, + pub edge_has_connector: bool, pub edge_has_castellated_pads: bool, pub edge_has_edge_plating: bool, pub layers: Vec, diff --git a/test-scripts/kicad-ipc-cli.rs b/test-scripts/kicad-ipc-cli.rs index f9fd45d..2821748 100644 --- a/test-scripts/kicad-ipc-cli.rs +++ b/test-scripts/kicad-ipc-cli.rs @@ -166,6 +166,7 @@ enum Command { BoardAsString, SelectionAsString, Stackup, + UpdateStackup, GraphicsDefaults, Appearance, SetAppearance { @@ -840,6 +841,11 @@ async fn run() -> Result<(), KiCadError> { let stackup = client.get_board_stackup().await?; println!("{stackup:#?}"); } + Command::UpdateStackup => { + let stackup = client.get_board_stackup().await?; + let updated = client.update_board_stackup(stackup).await?; + println!("{updated:#?}"); + } Command::GraphicsDefaults => { let defaults = client.get_graphics_defaults().await?; println!("{defaults:#?}"); @@ -1837,6 +1843,7 @@ fn parse_args_from(mut args: Vec) -> Result<(CliConfig, Command), KiCadE "board-as-string" => Command::BoardAsString, "selection-as-string" => Command::SelectionAsString, "stackup" => Command::Stackup, + "update-stackup" => Command::UpdateStackup, "graphics-defaults" => Command::GraphicsDefaults, "appearance" => Command::Appearance, "set-appearance" => { @@ -2046,7 +2053,7 @@ fn default_config() -> CliConfig { fn print_help() { println!( - "kicad-ipc-cli\n\nUSAGE:\n cargo run --bin kicad-ipc-cli -- [--socket URI] [--token TOKEN] [--client-name NAME] [--timeout-ms N] [command options]\n\nCOMMANDS:\n ping Check IPC connectivity\n version Fetch KiCad version\n kicad-binary-path [--binary-name ]\n Resolve absolute path for a KiCad binary (default: kicad-cli)\n plugin-settings-path [--identifier ]\n Resolve writeable plugin settings directory (default: kicad-ipc-rust)\n open-docs [--type ] List open docs (default type: pcb)\n project-path Get current project path from open PCB docs\n board-open Exit non-zero if no PCB doc is open\n net-classes List project netclass definitions\n set-net-classes [--merge-mode ]\n Write current netclass set back with selected merge mode\n text-variables List text variables for current board document\n set-text-variables [--merge-mode ] [--var ...]\n Set text variables for current board document\n expand-text-variables Expand variables in provided text values\n Options: --text (repeatable)\n text-extents Measure text bounding box\n Options: --text \n text-as-shapes Convert text to rendered shapes\n Options: --text (repeatable)\n nets List board nets (requires one open PCB)\n netlist-pads Emit pad-level netlist data (with footprint context)\n items-by-id --id ... Show parsed details for specific item IDs\n item-bbox --id ... Show bounding boxes for item IDs\n hit-test --id --x-nm --y-nm [--tolerance-nm ]\n Hit-test one item at a point\n types-pcb List PCB KiCad object type IDs from proto enum\n items-raw --type-id ... Dump raw Any payloads for requested item type IDs\n items-raw-all-pcb [--debug] Dump all PCB item payloads across all PCB object types\n pad-shape-polygon --pad-id ... --layer-id [--debug]\n Dump pad polygons on a target layer\n padstack-presence --item-id ... --layer-id ... [--debug]\n Check padstack shape presence matrix across layers\n title-block Show title block fields\n board-as-string Dump board as KiCad s-expression text\n selection-as-string Dump current selection as KiCad s-expression text\n stackup Show typed board stackup\n graphics-defaults Show typed graphics defaults\n appearance Show typed editor appearance settings\n set-appearance --inactive-layer-display \n --net-color-display \n --board-flip \n --ratsnest-display \n Set editor appearance settings\n inject-drc-error --severity --message [--x-nm --y-nm ] [--item-id ...]\n Inject a DRC marker (severity: warning|error|exclusion|ignore|info|action|debug|undefined)\n refill-zones [--zone-id ...]\n Refill all zones or a provided subset\n netclass Show typed netclass map for current board nets\n proto-coverage-board-read Print board-read command coverage vs proto\n board-read-report [--out P] Write markdown board reconstruction report\n enabled-layers List enabled board layers\n set-enabled-layers --copper-layer-count [--layer-id ...]\n Set enabled board layer set\n active-layer Show active board layer\n set-active-layer --layer-id \n Set active board layer\n visible-layers Show currently visible board layers\n set-visible-layers --layer-id ...\n Set visible board layers\n board-origin [--type ] Show board origin (`grid` default, or `drill`)\n set-board-origin --type --x-nm --y-nm \n Set board origin (`grid` or `drill`)\n refresh-editor [--frame ] Refresh a specific editor frame (default: pcb)\n begin-commit Start staged commit and print commit ID\n end-commit --id [--action ] [--message ]\n End staged commit with commit/drop action\n save-doc Save current board document\n save-copy --path [--overwrite] [--include-project]\n Save current board document to a new location\n revert-doc Revert current board document from disk\n run-action --action Run a raw KiCad tool action\n create-items --item = ... [--container-id ]\n Create raw Any payload items in current board document\n update-items --item = ...\n Update raw Any payload items in current board document\n delete-items --id ...\n Delete item IDs from current board document\n parse-create-items --contents \n Parse s-expression and create resulting items\n add-to-selection --id ...\n Add items to current selection\n remove-from-selection --id ...\n Remove items from current selection\n clear-selection Clear current item selection\n selection-summary Show current selection item type counts\n selection-details Show parsed details for selected items\n selection-raw Show raw Any payload bytes for selected items\n smoke ping + version + board-open summary\n help Show help\n\nTYPES:\n schematic | symbol | pcb | footprint | drawing-sheet | project\n" + "kicad-ipc-cli\n\nUSAGE:\n cargo run --bin kicad-ipc-cli -- [--socket URI] [--token TOKEN] [--client-name NAME] [--timeout-ms N] [command options]\n\nCOMMANDS:\n ping Check IPC connectivity\n version Fetch KiCad version\n kicad-binary-path [--binary-name ]\n Resolve absolute path for a KiCad binary (default: kicad-cli)\n plugin-settings-path [--identifier ]\n Resolve writeable plugin settings directory (default: kicad-ipc-rust)\n open-docs [--type ] List open docs (default type: pcb)\n project-path Get current project path from open PCB docs\n board-open Exit non-zero if no PCB doc is open\n net-classes List project netclass definitions\n set-net-classes [--merge-mode ]\n Write current netclass set back with selected merge mode\n text-variables List text variables for current board document\n set-text-variables [--merge-mode ] [--var ...]\n Set text variables for current board document\n expand-text-variables Expand variables in provided text values\n Options: --text (repeatable)\n text-extents Measure text bounding box\n Options: --text \n text-as-shapes Convert text to rendered shapes\n Options: --text (repeatable)\n nets List board nets (requires one open PCB)\n netlist-pads Emit pad-level netlist data (with footprint context)\n items-by-id --id ... Show parsed details for specific item IDs\n item-bbox --id ... Show bounding boxes for item IDs\n hit-test --id --x-nm --y-nm [--tolerance-nm ]\n Hit-test one item at a point\n types-pcb List PCB KiCad object type IDs from proto enum\n items-raw --type-id ... Dump raw Any payloads for requested item type IDs\n items-raw-all-pcb [--debug] Dump all PCB item payloads across all PCB object types\n pad-shape-polygon --pad-id ... --layer-id [--debug]\n Dump pad polygons on a target layer\n padstack-presence --item-id ... --layer-id ... [--debug]\n Check padstack shape presence matrix across layers\n title-block Show title block fields\n board-as-string Dump board as KiCad s-expression text\n selection-as-string Dump current selection as KiCad s-expression text\n stackup Show typed board stackup\n update-stackup Round-trip current stackup through UpdateBoardStackup\n graphics-defaults Show typed graphics defaults\n appearance Show typed editor appearance settings\n set-appearance --inactive-layer-display \n --net-color-display \n --board-flip \n --ratsnest-display \n Set editor appearance settings\n inject-drc-error --severity --message [--x-nm --y-nm ] [--item-id ...]\n Inject a DRC marker (severity: warning|error|exclusion|ignore|info|action|debug|undefined)\n refill-zones [--zone-id ...]\n Refill all zones or a provided subset netclass Show typed netclass map for current board nets\n proto-coverage-board-read Print board-read command coverage vs proto\n board-read-report [--out P] Write markdown board reconstruction report\n enabled-layers List enabled board layers\n set-enabled-layers --copper-layer-count [--layer-id ...]\n Set enabled board layer set\n active-layer Show active board layer\n set-active-layer --layer-id \n Set active board layer\n visible-layers Show currently visible board layers\n set-visible-layers --layer-id ...\n Set visible board layers\n board-origin [--type ] Show board origin (`grid` default, or `drill`)\n set-board-origin --type --x-nm --y-nm \n Set board origin (`grid` or `drill`)\n refresh-editor [--frame ] Refresh a specific editor frame (default: pcb)\n begin-commit Start staged commit and print commit ID\n end-commit --id [--action ] [--message ]\n End staged commit with commit/drop action\n save-doc Save current board document\n save-copy --path [--overwrite] [--include-project]\n Save current board document to a new location\n revert-doc Revert current board document from disk\n run-action --action Run a raw KiCad tool action\n create-items --item = ... [--container-id ]\n Create raw Any payload items in current board document\n update-items --item = ...\n Update raw Any payload items in current board document\n delete-items --id ...\n Delete item IDs from current board document\n parse-create-items --contents \n Parse s-expression and create resulting items\n add-to-selection --id ...\n Add items to current selection\n remove-from-selection --id ...\n Remove items from current selection\n clear-selection Clear current item selection\n selection-summary Show current selection item type counts\n selection-details Show parsed details for selected items\n selection-raw Show raw Any payload bytes for selected items\n smoke ping + version + board-open summary\n help Show help\n\nTYPES:\n schematic | symbol | pcb | footprint | drawing-sheet | project\n" ); } @@ -3109,4 +3116,11 @@ mod tests { other => panic!("unexpected command variant: {other:?}"), } } + + #[test] + fn parse_args_parses_update_stackup() { + let (_, command) = parse_args_from(vec!["update-stackup".to_string()]) + .expect("update-stackup should parse"); + assert!(matches!(command, Command::UpdateStackup)); + } }