From ddf8be012c739369b0b8800efcedd93bb383ea9e Mon Sep 17 00:00:00 2001 From: nordic-style <11313330+nordic-style@users.noreply.github.com> Date: Tue, 18 Aug 2026 02:29:22 +0200 Subject: [PATCH 1/2] fix(schematic): update every placed component unit Reference-only component mutations selected the first matching symbol block, so deleting left orphan units behind while moving, rotating, editing, annotating, grouping, and replacing tore one logical component into inconsistent placements. Apply shared mutations to every placed unit, preserve relative placement and rotation, target region moves by UUID, aggregate component and pin-location reads, and reject ambiguous unit overrides before writing. Shared property edits now escape values and repair partially populated legacy units in one revision-aware command. Refs #182 --- .../konnect-core/src/tools/sch_components.rs | 1255 ++++++++++++----- tool-directory.md | 18 +- 2 files changed, 882 insertions(+), 391 deletions(-) diff --git a/crates/konnect-core/src/tools/sch_components.rs b/crates/konnect-core/src/tools/sch_components.rs index cc3dd2c5..077ca12a 100644 --- a/crates/konnect-core/src/tools/sch_components.rs +++ b/crates/konnect-core/src/tools/sch_components.rs @@ -7,9 +7,8 @@ use crate::mcp::protocol::CallToolResult; use crate::tool; use crate::tools::{ - find_all_symbol_instance_blocks, find_symbol_instance_block, get_path, opt_f64, opt_str, - reembed_lib_symbols, require_array, require_f64, require_str, ReembedOutcome, ToolContext, - ToolDef, + find_all_symbol_instance_blocks, get_path, opt_f64, opt_str, reembed_lib_symbols, + require_array, require_f64, require_str, ReembedOutcome, ToolContext, ToolDef, }; use konnect_schematic_editor as cse; use konnect_sexp::{ @@ -101,7 +100,7 @@ pub fn tools() -> Vec { ), tool!( "delete_schematic_component", - "Remove a symbol instance from the schematic by its reference designator.", + "Remove a component by reference designator, including every placed unit of a multi-unit symbol.", json!({ "type": "object", "properties": { @@ -114,7 +113,7 @@ pub fn tools() -> Vec { ), tool!( "edit_schematic_component", - "Update fields (Reference, Value, Footprint, custom properties) of a symbol instance.", + "Update fields (Reference, Value, Footprint, custom properties) consistently across every placed unit of a component.", json!({ "type": "object", "properties": { @@ -135,7 +134,7 @@ pub fn tools() -> Vec { ), tool!( "get_schematic_component", - "Get all properties, position, and pin locations for a symbol instance.", + "Get a component's shared properties and every placed unit's position. Use get_schematic_pin_locations for pins.", json!({ "type": "object", "properties": { @@ -161,7 +160,7 @@ pub fn tools() -> Vec { ), tool!( "move_schematic_component", - "Move a symbol to a new position. Does NOT adjust connected wires.", + "Move a component's lowest-numbered unit to a new position and translate every other placed unit by the same delta. Does NOT adjust connected wires.", json!({ "type": "object", "properties": { @@ -176,7 +175,7 @@ pub fn tools() -> Vec { ), tool!( "rotate_schematic_component", - "Rotate a symbol by setting its absolute rotation angle (0/90/180/270).", + "Set the lowest-numbered unit's absolute rotation and rotate every other placed unit by the same delta.", json!({ "type": "object", "properties": { @@ -234,7 +233,7 @@ pub fn tools() -> Vec { ), tool!( "get_schematic_pin_locations", - "Get the exact schematic-space (X,Y) coordinates of every pin on a symbol, \ + "Get the exact schematic-space (X,Y) coordinates of every pin on every placed unit of a component, \ accounting for rotation and mirroring. Uses the canonical pin transform. \ Each pin also reports 'orientation_degrees', the direction leading away \ from the symbol body (0 = east) — a net label at the pin must read that \ @@ -270,7 +269,7 @@ pub fn tools() -> Vec { ), tool!( "add_component_annotation", - "Add a custom property (annotation) to a symbol instance in the schematic.", + "Add or update a custom property consistently across every placed unit of a component.", json!({ "type": "object", "properties": { @@ -285,7 +284,7 @@ pub fn tools() -> Vec { ), tool!( "group_components", - "Add a group property to multiple components in the schematic.", + "Add or update a group property on every placed unit of multiple components.", json!({ "type": "object", "properties": { @@ -303,14 +302,14 @@ pub fn tools() -> Vec { ), tool!( "replace_component", - "Replace a component's lib_id with a new library symbol (swap the component type).", + "Replace every placed unit of a component with a new library symbol while preserving unit numbers. A unit override is accepted only for a single placement.", json!({ "type": "object", "properties": { "schematic": { "type": "string", "description": "Path to .kicad_sch file" }, "reference": { "type": "string", "description": "Component reference designator (e.g. 'U1')" }, "new_lib_id": { "type": "string", "description": "New Library:Symbol identifier (e.g. 'Device:C')" }, - "unit": { "type": "integer", "description": "Optional unit number for multi-unit symbols; validated against the new symbol's unit count. When omitted the existing unit is kept." } + "unit": { "type": "integer", "description": "Optional unit number for a single placed unit; rejected as ambiguous when the reference has multiple placements. When omitted, every existing unit number is preserved and validated against the new symbol." } }, "required": ["schematic", "reference", "new_lib_id"] }), @@ -690,15 +689,21 @@ async fn handle_delete_schematic_component( let mut sch = cse::Schematic::load(&sch_path)?; - match sch.symbols.remove_by_reference(&reference) { - Some(_) => { - sch.overwrite()?; - Ok(CallToolResult::json(&json!({ "deleted": reference }))) - } - None => Ok(CallToolResult::error(format!( + let before = sch.symbols.len(); + sch.symbols + .retain(|symbol| symbol.reference() != Some(reference.as_str())); + let deleted_units = before - sch.symbols.len(); + if deleted_units == 0 { + Ok(CallToolResult::error(format!( "Component '{}' not found in schematic", reference - ))), + ))) + } else { + sch.overwrite()?; + Ok(CallToolResult::json(&json!({ + "deleted": reference, + "deleted_units": deleted_units + }))) } } @@ -710,61 +715,48 @@ fn is_reserved_property(name: &str) -> bool { matches!(name, "Reference" | "Value" | "Footprint" | "Datasheet") } -/// Does `reference`'s symbol block already carry a `name` property? -fn property_exists(content: &str, reference: &str, name: &str) -> bool { - find_symbol_instance_block(content, reference).is_some_and(|(start, end)| { - content[start..end].contains(&format!(r#"(property "{name}" ""#)) - }) +#[derive(Debug, Clone, Copy)] +struct PropertyWriteCounts { + updated: usize, + added: usize, } -/// Update the value of an existing `(property "field" "…")` inside -/// `reference`'s symbol block, in place. Returns the reason on failure so the -/// caller can report it instead of silently claiming success. Shared by -/// `edit_schematic_component` and `add_component_annotation` (#203) — the -/// second used to append a duplicate instead. -fn update_property_value( - content: &str, - reference: &str, - field: &str, - new_val: &str, -) -> Result { - let (sym_start, sym_end) = find_symbol_instance_block(content, reference) - .ok_or_else(|| format!("symbol '{reference}' not found in this schematic"))?; - let sym_block = &content[sym_start..sym_end]; - let field_search = format!(r#"(property "{field}" ""#); - let field_offset = sym_block - .find(&field_search) - .map(|o| sym_start + o + field_search.len()) - .ok_or_else(|| format!("'{reference}' has no '{field}' property"))?; - // Find the closing quote of the current value - let val_end = content[field_offset..] - .find('"') - .map(|o| field_offset + o) - .ok_or_else(|| format!("'{field}' property on '{reference}' is malformed"))?; - Ok(format!( - "{}{}{}", - &content[..field_offset], - new_val, - &content[val_end..] - )) +fn escape_property_text(value: &str) -> String { + value + .replace('\\', "\\\\") + .replace('"', "\\\"") + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('\t', "\\t") +} + +fn closing_quote(content: &str, value_start: usize) -> Option { + let mut escaped = false; + for (offset, ch) in content[value_start..].char_indices() { + if escaped { + escaped = false; + } else if ch == '\\' { + escaped = true; + } else if ch == '"' { + return Some(value_start + offset); + } + } + None } -/// Append a new `(property …)` to `reference`'s symbol block. +/// Build the insertion for a custom property in one placed unit's block. /// -/// Anchored at the symbol's own `(at …)` and written hidden: a custom field is -/// data, not something to draw over the sheet, and KiCad 10's canonical -/// instance form puts `(hide yes)` as a sibling before `(effects …)` (#96). -/// The `(at …)` is mandatory — a property written without one is defaulted to -/// the sheet origin, which is how every `#PWR` reference once piled up in the -/// top-left corner (#95). -fn append_property( +/// The property is anchored at that unit's own placement and inherits its +/// indentation, so applying this to every block neither piles fields at the +/// origin nor rewrites an eeschema-formatted file wholesale. +fn property_insert_edit( content: &str, reference: &str, + start: usize, + end: usize, name: &str, value: &str, -) -> Result { - let (start, end) = find_symbol_instance_block(content, reference) - .ok_or_else(|| format!("symbol '{reference}' not found in this schematic"))?; +) -> Result { let block = &content[start..end]; // The symbol's placement, to anchor the new property on. @@ -791,22 +783,74 @@ fn append_property( }) .unwrap_or_else(|| "\t\t".to_string()); - let escaped = value.replace('\\', "\\\\").replace('"', "\\\""); + let escaped_name = escape_property_text(name); + let escaped_value = escape_property_text(value); let prop = format!( - "\n{indent}(property \"{name}\" \"{escaped}\"\n{indent}\t(at {x} {y} 0)\n\ + "\n{indent}(property \"{escaped_name}\" \"{escaped_value}\"\n{indent}\t(at {x} {y} 0)\n\ {indent}\t(hide yes)\n{indent}\t(effects\n{indent}\t\t(font\n{indent}\t\t\t\ (size 1.27 1.27)\n{indent}\t\t)\n{indent}\t)\n{indent})" ); // Insert before the block's closing paren so the property stays inside it. - let close = content[..end] + let close = block .rfind(')') + .map(|offset| start + offset) .ok_or_else(|| format!("symbol block for '{reference}' is malformed"))?; - Ok(format!( - "{}{}{}", - &content[..close], - prop, - &content[close..] + Ok(SexpEdit::insert(close, prop)) +} + +/// Set one shared component property in every placed unit. +/// +/// Built-in properties must already exist in every unit (`add_missing=false`). +/// Custom fields may be present on only some units in a legacy/broken sheet; +/// `add_missing=true` updates those copies and fills the missing ones in the +/// same atomic document command. +fn set_property_value( + content: &str, + reference: &str, + field: &str, + new_value: &str, + add_missing: bool, +) -> Result<(String, PropertyWriteCounts), String> { + let blocks = find_all_symbol_instance_blocks(content, reference); + if blocks.is_empty() { + return Err(format!("symbol '{reference}' not found in this schematic")); + } + + let escaped_field = escape_property_text(field); + let field_search = format!(r#"(property "{escaped_field}" ""#); + let escaped_value = escape_property_text(new_value); + let mut edits = Vec::new(); + let mut updated = 0; + let mut added = 0; + + for (start, end) in blocks { + let block = &content[start..end]; + if let Some(relative) = block.find(&field_search) { + let value_start = start + relative + field_search.len(); + let value_end = closing_quote(content, value_start) + .ok_or_else(|| format!("'{field}' property on '{reference}' is malformed"))?; + edits.push(SexpEdit::replace( + value_start, + value_end, + escaped_value.clone(), + )); + updated += 1; + } else if add_missing { + edits.push(property_insert_edit( + content, reference, start, end, field, new_value, + )?); + added += 1; + } else { + return Err(format!( + "'{reference}' is missing the shared '{field}' property on one of its placed units" + )); + } + } + + Ok(( + apply_edits(content.to_string(), edits), + PropertyWriteCounts { updated, added }, )) } @@ -872,10 +916,13 @@ async fn handle_edit_schematic_component( // and a closure capturing them mutably would lock both for its lifetime. macro_rules! apply { ($field:expr, $new_val:expr) => { - match update_property_value(&content, &reference, $field, $new_val) { - Ok(updated) => { + match set_property_value(&content, &reference, $field, $new_val, false) { + Ok((updated, counts)) => { content = updated; - changed.push(format!("{} → {}", $field, $new_val)); + changed.push(format!( + "{} → {} ({} unit(s))", + $field, $new_val, counts.updated + )); } Err(why) => errors.push(format!("{}: {}", $field, why)), } @@ -925,16 +972,15 @@ async fn handle_edit_schematic_component( )); continue; } - if property_exists(&content, &reference, name) { - apply!(name.as_str(), value); - } else { - match append_property(&content, &reference, name, value) { - Ok(updated) => { - content = updated; - changed.push(format!("{name} → {value} (added)")); - } - Err(why) => errors.push(format!("{name}: {why}")), + match set_property_value(&content, &reference, name, value, true) { + Ok((updated, counts)) => { + content = updated; + changed.push(format!( + "{name} → {value} ({} updated, {} added)", + counts.updated, counts.added + )); } + Err(why) => errors.push(format!("{name}: {why}")), } } } @@ -958,11 +1004,11 @@ async fn handle_edit_schematic_component( } if !changed.is_empty() { - let item_id = symbol_item_id(&expected, &reference)?; - let command = SchematicCommand::replace_item_from_document( + let item_ids = symbol_item_ids(&expected, &reference)?; + let command = SchematicCommand::replace_items_from_document( &expected, &content, - item_id, + item_ids, format!("Edit {reference}"), )?; commit_command(&sch_path, &command)?; @@ -990,29 +1036,50 @@ async fn handle_get_schematic_component( let sch = cse::Schematic::load(&sch_path)?; - match sch.symbols.by_reference(&reference) { - Some(sym) => { - let (x, y) = sym.position(); - let rotation = sym.at.rotation.unwrap_or(0.0); - let mirror = sym.mirror.as_deref().unwrap_or(""); - Ok(CallToolResult::json(&json!({ - "reference": sym.reference().unwrap_or("?"), - "value": sym.value_str().unwrap_or(""), - "footprint": sym.footprint().unwrap_or(""), - "lib_id": sym.lib_id, - "x": x, - "y": y, - "rotation": rotation, - "mirror_x": mirror.contains('x'), - "mirror_y": mirror.contains('y'), - "uuid": sym.uuid - }))) - } - None => Ok(CallToolResult::error(format!( + let placed: Vec<_> = sch + .symbols + .iter() + .filter(|symbol| symbol.reference() == Some(reference.as_str())) + .collect(); + let Some(anchor) = placed.iter().copied().min_by_key(|symbol| symbol.unit) else { + return Ok(CallToolResult::error(format!( "Component '{}' not found", reference - ))), - } + ))); + }; + let (x, y) = anchor.position(); + let rotation = anchor.at.rotation.unwrap_or(0.0); + let mirror = anchor.mirror.as_deref().unwrap_or(""); + let units: Vec<_> = placed + .iter() + .map(|symbol| { + let (unit_x, unit_y) = symbol.position(); + let unit_mirror = symbol.mirror.as_deref().unwrap_or(""); + json!({ + "unit": symbol.unit, + "x": unit_x, + "y": unit_y, + "rotation": symbol.at.rotation.unwrap_or(0.0), + "mirror_x": unit_mirror.contains('x'), + "mirror_y": unit_mirror.contains('y'), + "uuid": symbol.uuid + }) + }) + .collect(); + Ok(CallToolResult::json(&json!({ + "reference": anchor.reference().unwrap_or("?"), + "value": anchor.value_str().unwrap_or(""), + "footprint": anchor.footprint().unwrap_or(""), + "lib_id": anchor.lib_id, + "x": x, + "y": y, + "rotation": rotation, + "mirror_x": mirror.contains('x'), + "mirror_y": mirror.contains('y'), + "uuid": anchor.uuid, + "unit_count": units.len(), + "units": units + }))) } async fn handle_list_schematic_components( @@ -1070,16 +1137,37 @@ async fn handle_move_schematic_component( let mut sch = cse::Schematic::load(&sch_path)?; - match sch.symbols.by_reference_mut(&reference) { - Some(sym) => { - sym.move_to(new_x, new_y); - sch.overwrite()?; - Ok(CallToolResult::json( - &json!({ "moved": reference, "x": new_x, "y": new_y }), - )) - } - None => Err(anyhow::anyhow!("Component '{}' not found", reference)), + let Some(anchor) = sch + .symbols + .iter() + .filter(|symbol| symbol.reference() == Some(reference.as_str())) + .min_by_key(|symbol| symbol.unit) + else { + return Err(anyhow::anyhow!("Component '{}' not found", reference)); + }; + let (old_x, old_y) = anchor.position(); + let (dx, dy) = (new_x - old_x, new_y - old_y); + let mut placements = Vec::new(); + for symbol in sch + .symbols + .iter_mut() + .filter(|symbol| symbol.reference() == Some(reference.as_str())) + { + symbol.translate(dx, dy); + placements.push(json!({ + "unit": symbol.unit, + "x": symbol.at.x, + "y": symbol.at.y + })); } + sch.overwrite()?; + Ok(CallToolResult::json(&json!({ + "moved": reference, + "x": new_x, + "y": new_y, + "moved_units": placements.len(), + "placements": placements + }))) } async fn handle_rotate_schematic_component( @@ -1098,16 +1186,32 @@ async fn handle_rotate_schematic_component( let mut sch = cse::Schematic::load(&sch_path)?; - match sch.symbols.by_reference_mut(&reference) { - Some(sym) => { - sym.set_rotation(rotation); - sch.overwrite()?; - Ok(CallToolResult::json( - &json!({ "rotated": reference, "rotation": rotation }), - )) - } - None => Err(anyhow::anyhow!("Component '{}' not found", reference)), + let Some(anchor) = sch + .symbols + .iter() + .filter(|symbol| symbol.reference() == Some(reference.as_str())) + .min_by_key(|symbol| symbol.unit) + else { + return Err(anyhow::anyhow!("Component '{}' not found", reference)); + }; + let rotation_delta = rotation - anchor.at.rotation.unwrap_or(0.0); + let mut placements = Vec::new(); + for symbol in sch + .symbols + .iter_mut() + .filter(|symbol| symbol.reference() == Some(reference.as_str())) + { + let new_rotation = symbol.at.rotation.unwrap_or(0.0) + rotation_delta; + symbol.set_rotation(new_rotation); + placements.push(json!({ "unit": symbol.unit, "rotation": new_rotation })); } + sch.overwrite()?; + Ok(CallToolResult::json(&json!({ + "rotated": reference, + "rotation": rotation, + "rotated_units": placements.len(), + "placements": placements + }))) } async fn handle_move_connected( @@ -1156,29 +1260,44 @@ async fn handle_move_region( let mut sch = cse::Schematic::load(&sch_path)?; - // Collect references of symbols within the bounding box - let refs_to_move: Vec = sch + // Select placements by UUID, not reference. A multi-unit reference may + // have one unit inside the rectangle and another outside; resolving the + // selected reference back through `by_reference_mut` moved unit 1 every + // time, and could move it twice when both units were selected (#182). + let uuids_to_move: std::collections::HashSet = sch .symbols .within_rectangle(x1, y1, x2, y2) .iter() - .filter_map(|s| s.reference().map(String::from)) + .map(|symbol| symbol.uuid.clone()) .collect(); - let mut moved = Vec::new(); - for reference in &refs_to_move { - if let Some(sym) = sch.symbols.by_reference_mut(reference) { - let (ox, oy) = sym.position(); - let (nx, ny) = snap_point(ox + dx, oy + dy, 1.27); - sym.move_to(nx, ny); - moved.push(reference.clone()); + let mut moved_references = Vec::new(); + let mut placements = Vec::new(); + for symbol in sch.symbols.iter_mut() { + if uuids_to_move.contains(&symbol.uuid) { + let (old_x, old_y) = symbol.position(); + let (new_x, new_y) = snap_point(old_x + dx, old_y + dy, 1.27); + symbol.move_to(new_x, new_y); + let reference = symbol.reference().unwrap_or("?").to_string(); + if !moved_references.contains(&reference) { + moved_references.push(reference.clone()); + } + placements.push(json!({ + "reference": reference, + "unit": symbol.unit, + "x": new_x, + "y": new_y + })); } } sch.overwrite()?; Ok(CallToolResult::json(&json!({ - "moved_count": moved.len(), - "moved": moved + "moved_count": moved_references.len(), + "moved": moved_references, + "moved_unit_count": placements.len(), + "placements": placements }))) } @@ -1202,87 +1321,97 @@ async fn handle_get_schematic_pin_locations( }; let (_, tree) = read_schematic(&sch_path)?; - let instances = extract_symbol_instances(&tree); - let inst = match instances.iter().find(|i| i.reference == reference) { - Some(i) => i, - None => { - return Ok(CallToolResult::error(format!( - "Component '{}' not found", - reference - ))) - } - }; + match pin_locations_for_reference(&tree, &reference) { + Ok(result) => Ok(CallToolResult::json(&result)), + Err(error) => Ok(CallToolResult::error(error)), + } +} - // Find the library symbol definition within the schematic's lib_symbols section +fn pin_locations_for_reference( + tree: &konnect_sexp::SexpNode, + reference: &str, +) -> Result { + let instances = extract_symbol_instances(tree); + let placed: Vec<_> = instances + .iter() + .filter(|instance| instance.reference == reference) + .collect(); + let Some(anchor) = placed.iter().copied().min_by_key(|instance| instance.unit) else { + return Err(format!("Component '{reference}' not found")); + }; let lib_syms = tree .find("lib_symbols") - .map(|n| n.find_all("symbol")) + .map(|node| node.find_all("symbol")) .unwrap_or_default(); - let lib_sym = find_lib_symbol(&lib_syms, inst); - // A missing embedded definition is an error, not an empty pin list — - // silently returning [] hid every bad-lib_id component until wiring or - // netlisting failed much later (#34). - let Some(sym) = lib_sym else { - return Ok(CallToolResult::error(format!( - "Component '{}' has no embedded definition for '{}' in this \ - schematic's lib_symbols — it was likely added with a lib_id that \ - doesn't exist in the installed libraries, so it is invisible to \ - KiCAD's netlister. Re-add it with a valid lib_id \ - (delete_schematic_component + add_schematic_component).", - reference, - inst.lib_symbol_name() - ))); - }; - // Unit-aware: only this instance's unit (plus _0_1 commons), not every - // unit's pins superimposed (#35). - let lib_pins = extract_lib_pins_for_unit(sym, inst.unit); - // A definition that resolves but has ZERO pins is almost always an - // `(extends "Parent")` stub — kicad-cli can't resolve those either (the - // netlist shows a pinless part), so silent pins:[] hides real breakage. - // The #34 guard above only catches MISSING definitions. - if lib_pins.is_empty() { - if let Some(parent) = sym.find_str("extends") { - return Ok(CallToolResult::error(format!( - "Component '{}': the embedded definition for '{}' is an \ - (extends \"{}\") stub with no pins of its own. kicad-cli \ - cannot resolve extends stubs (the netlist gets a pinless \ - part). Re-add the component (delete_schematic_component + \ - add_schematic_component) so the definition is embedded in \ - full, or place the parent symbol '{}' directly.", + let mut all_pins = Vec::new(); + let mut units = Vec::new(); + for instance in placed.iter().copied() { + // A missing embedded definition is an error, not an empty pin list — + // silently returning [] hid bad lib_ids until netlisting (#34). + let Some(symbol) = find_lib_symbol(&lib_syms, instance) else { + return Err(format!( + "Component '{}' unit {} has no embedded definition for '{}' in this \ + schematic's lib_symbols — re-add it with a valid lib_id", reference, - inst.lib_symbol_name(), - parent, - parent - ))); + instance.unit, + instance.lib_symbol_name() + )); + }; + let lib_pins = extract_lib_pins_for_unit(symbol, instance.unit); + if lib_pins.is_empty() { + if let Some(parent) = symbol.find_str("extends") { + return Err(format!( + "Component '{}' unit {}: the embedded definition for '{}' is an \ + (extends \"{}\") stub with no pins — re-add the component so the \ + definition is embedded in full", + reference, + instance.unit, + instance.lib_symbol_name(), + parent + )); + } } - } - let t = inst.pin_transform(); - let pins: Vec = lib_pins - .iter() - .map(|p| { - let (sx, sy) = pin_endpoint(p, t); - json!({ - "number": p.number, - "name": p.name, - "x": sx, - "y": sy, - // Which way the pin faces away from the body (0 = east). A - // label here should read that way, or it runs back over the - // symbol's pin names. - "orientation_degrees": pin_outward_direction(p, t), - "length_mm": p.length + + let transform = instance.pin_transform(); + let pins: Vec = lib_pins + .iter() + .map(|pin| { + let (x, y) = pin_endpoint(pin, transform); + json!({ + "number": pin.number, + "name": pin.name, + "unit": instance.unit, + "x": x, + "y": y, + "orientation_degrees": pin_outward_direction(pin, transform), + "length_mm": pin.length + }) }) - }) - .collect(); + .collect(); + all_pins.extend(pins.iter().cloned()); + units.push(json!({ + "unit": instance.unit, + "x": instance.x, + "y": instance.y, + "rotation": instance.rotation, + "pins": pins + })); + } - Ok(CallToolResult::json(&json!({ + Ok(json!({ "reference": reference, - "component_x": inst.x, - "component_y": inst.y, - "rotation": inst.rotation, - "pins": pins - }))) + // Preserve the original single-placement fields as the logical + // component anchor while exposing every real placement below. + "component_x": anchor.x, + "component_y": anchor.y, + "x": anchor.x, + "y": anchor.y, + "rotation": anchor.rotation, + "unit_count": units.len(), + "units": units, + "pins": all_pins + })) } async fn handle_batch_get_pin_locations( @@ -1301,64 +1430,14 @@ async fn handle_batch_get_pin_locations( }; let (_, tree) = read_schematic(&sch_path)?; // single read - let instances = extract_symbol_instances(&tree); - let lib_syms = tree - .find("lib_symbols") - .map(|n| n.find_all("symbol")) - .unwrap_or_default(); - let results: Vec = refs .iter() - .map(|reference| { - let inst = match instances.iter().find(|i| &i.reference == reference) { - Some(i) => i, - None => return json!({ "reference": reference, "error": "not found" }), - }; - let lib_sym = find_lib_symbol(&lib_syms, inst); - // Per-entry error rather than a silent empty pin list (#34). - let Some(sym) = lib_sym else { - return json!({ - "reference": reference, - "error": format!( - "no embedded definition for '{}' in lib_symbols — \ - likely added with a nonexistent lib_id", - inst.lib_symbol_name() - ) - }); - }; - let lib_pins = extract_lib_pins_for_unit(sym, inst.unit); - // Zero pins from a resolving definition = extends stub (#35); - // mirror the single-component handler's structured error. - if lib_pins.is_empty() { - if let Some(parent) = sym.find_str("extends") { - return json!({ - "reference": reference, - "error": format!( - "embedded definition for '{}' is an (extends \"{}\") \ - stub with no pins — re-add the component so it is \ - embedded in full", - inst.lib_symbol_name(), parent - ) - }); - } - } - let t = inst.pin_transform(); - let pins: Vec = lib_pins - .iter() - .map(|p| { - let (sx, sy) = pin_endpoint(p, t); - json!({ - "number": p.number, - "name": p.name, - "x": sx, - "y": sy, - "orientation_degrees": pin_outward_direction(p, t), - "length_mm": p.length - }) - }) - .collect(); - json!({ "reference": reference, "x": inst.x, "y": inst.y, "pins": pins }) - }) + .map( + |reference| match pin_locations_for_reference(&tree, reference) { + Ok(component) => component, + Err(error) => json!({ "reference": reference, "error": error }), + }, + ) .collect(); Ok(CallToolResult::json(&json!({ "components": results }))) @@ -1438,35 +1517,21 @@ async fn handle_add_component_annotation( let content = read_consistent(&sch_path)?; let expected = content.clone(); - if find_symbol_instance_block(&content, &reference).is_none() { - return Ok(CallToolResult::error(format!( - "Component '{}' not found", - reference - ))); - } - // An existing key is updated in place; appending a second `(property // "KEY" …)` gives eeschema two fields with one name — it shows both, // edits the wrong one, and the duplicate survives save/reload (#203). - // A new key goes through append_property, which anchors at the symbol's - // own position and matches the block's indentation, rather than the - // hardcoded origin-anchored form this handler used to write. - let (new_content, updated_existing) = if property_exists(&content, &reference, &key) { - match update_property_value(&content, &reference, &key, &value) { - Ok(updated) => (updated, true), - Err(why) => return Ok(CallToolResult::error(format!("{key}: {why}"))), - } - } else { - match append_property(&content, &reference, &key, &value) { - Ok(updated) => (updated, false), - Err(why) => return Ok(CallToolResult::error(format!("{key}: {why}"))), - } + // A new key is anchored separately at every unit's own position and uses + // that block's indentation. A partially populated legacy component is + // repaired by updating existing copies and adding only the missing ones. + let (new_content, counts) = match set_property_value(&content, &reference, &key, &value, true) { + Ok(updated) => updated, + Err(why) => return Ok(CallToolResult::error(format!("{key}: {why}"))), }; - let item_id = symbol_item_id(&expected, &reference)?; - let command = SchematicCommand::replace_item_from_document( + let item_ids = symbol_item_ids(&expected, &reference)?; + let command = SchematicCommand::replace_items_from_document( &expected, &new_content, - item_id, + item_ids, format!("Add {key} property to {reference}"), )?; commit_command(&sch_path, &command)?; @@ -1475,18 +1540,27 @@ async fn handle_add_component_annotation( "reference": reference, "added_property": key, "value": value, - "updated_existing": updated_existing + "updated_existing": counts.updated > 0, + "updated_units": counts.updated, + "added_units": counts.added }))) } -fn symbol_item_id(content: &str, reference: &str) -> anyhow::Result { - let (start, end) = find_symbol_instance_block(content, reference) - .ok_or_else(|| anyhow::anyhow!("component '{reference}' not found"))?; - let symbol = parse_sexp(&content[start..end])?; - let uuid = symbol - .find_str("uuid") - .ok_or_else(|| anyhow::anyhow!("component '{reference}' has no UUID"))?; - Ok(ItemId::new(uuid.to_owned())?) +fn symbol_item_ids(content: &str, reference: &str) -> anyhow::Result> { + let blocks = find_all_symbol_instance_blocks(content, reference); + if blocks.is_empty() { + anyhow::bail!("component '{reference}' not found"); + } + blocks + .into_iter() + .map(|(start, end)| { + let symbol = parse_sexp(&content[start..end])?; + let uuid = symbol.find_str("uuid").ok_or_else(|| { + anyhow::anyhow!("component '{reference}' has a unit without UUID") + })?; + ItemId::new(uuid.to_owned()).map_err(Into::into) + }) + .collect() } async fn handle_group_components( @@ -1517,24 +1591,14 @@ async fn handle_group_components( let mut item_ids = Vec::new(); for reference in &refs { - let (sym_start, sym_end) = match find_symbol_instance_block(&content, reference) { - Some(r) => r, - None => continue, - }; - - let sym_block = &content[sym_start..sym_end]; - let insert_rel = sym_block - .find("(instances") - .unwrap_or(sym_block.rfind(')').unwrap_or(sym_block.len() - 1)); - let insert_abs = sym_start + insert_rel; - - let prop_sexp = format!( - " (property \"Group\" \"{group_name}\"\n (at 0 0 0)\n (effects (font (size 1.27 1.27)) (hide yes))\n )\n " - ); - - content = apply_edits(content, vec![SexpEdit::insert(insert_abs, prop_sexp)]); - item_ids.push(symbol_item_id(&expected, reference)?); - grouped.push(reference.clone()); + match set_property_value(&content, reference, "Group", &group_name, true) { + Ok((updated, _)) => { + content = updated; + item_ids.extend(symbol_item_ids(&expected, reference)?); + grouped.push(reference.clone()); + } + Err(_) => continue, + } } if !item_ids.is_empty() { @@ -1794,81 +1858,129 @@ async fn handle_replace_component( let mut content = read_consistent(&sch_path)?; let expected = content.clone(); - // Find the symbol block for this reference - let (sym_start, sym_end) = match find_symbol_instance_block(&content, &reference) { - Some(r) => r, - None => { - return Ok(CallToolResult::error(format!( - "Component '{}' not found", - reference - ))) - } - }; - - // Find the (lib_id "OLD") and replace it — searching only within this - // symbol's block, so a malformed instance can't reach into the next one. - let sym_block = &content[sym_start..sym_end]; - let lib_id_pat = "(lib_id \""; - let lib_id_rel = match sym_block.find(lib_id_pat) { - Some(o) => o, - None => { - return Ok(CallToolResult::error( - "Could not find lib_id in symbol block", - )) - } - }; - let lib_id_abs = sym_start + lib_id_rel + lib_id_pat.len(); - let lib_id_end = match content[lib_id_abs..].find('"') { - Some(o) => lib_id_abs + o, - None => return Ok(CallToolResult::error("Malformed lib_id")), - }; - - let old_lib_id = content[lib_id_abs..lib_id_end].to_string(); + let blocks = find_all_symbol_instance_blocks(&content, &reference); + if blocks.is_empty() { + return Ok(CallToolResult::error(format!( + "Component '{}' not found", + reference + ))); + } + if blocks.len() > 1 && new_unit.is_some() { + return Ok(CallToolResult::error(format!( + "Component '{}' has {} placed units; the 'unit' override is only \ + unambiguous for a single placement. Omit it to preserve each unit.", + reference, + blocks.len() + ))); + } - let new_content = apply_edits( - content, - vec![SexpEdit::replace( - lib_id_abs, - lib_id_end, - new_lib_id.clone(), - )], - ); - content = new_content; + let parsed = parse_sexp(&content)?; + let current_units: Vec = extract_symbol_instances(&parsed) + .into_iter() + .filter(|instance| instance.reference == reference) + .map(|instance| instance.unit) + .collect(); - // Optional unit change, validated against the NEW symbol's unit count - // (#35). Applied before the embed so all edits land in one write. let src = crate::tools::library::KiCadSymbolSource::for_file(&sch_path); + let embedded_unit_count = parsed + .find("lib_symbols") + .and_then(|libraries| { + libraries.find_all("symbol").into_iter().find(|symbol| { + symbol.get(1).and_then(|value| value.as_str()) == Some(new_lib_id.as_str()) + }) + }) + .map(|symbol| { + symbol + .find_all("symbol") + .into_iter() + .filter_map(|unit| { + unit.get(1) + .and_then(|value| value.as_str()) + .and_then(konnect_sexp::schematic::parse_subsymbol_unit) + }) + .max() + .unwrap_or(1) + .max(1) + }); + let unit_count = embedded_unit_count + .or_else(|| cse::library::symbol_unit_count(&new_lib_id, &src)) + .unwrap_or(1); if let Some(unit) = new_unit { - let unit_count = cse::library::symbol_unit_count(&new_lib_id, &src).unwrap_or(1); if unit < 1 || unit > unit_count { return Ok(CallToolResult::error(format!( "Invalid unit {} for '{}': the symbol has {} unit(s) (valid: 1..={}).", unit, new_lib_id, unit_count, unit_count ))); } - // Re-find the block (offsets moved with the lib_id edit), then update - // every `(unit N)` inside it — the symbol's own and the one in its - // (instances …) entry. - if let Some((s, e)) = find_symbol_instance_block(&content, &reference) { - let block = &content[s..e]; - let mut edits = Vec::new(); - let mut from = 0usize; - while let Some(rel) = block[from..].find("(unit ") { - let num_start = from + rel + "(unit ".len(); - let Some(close) = block[num_start..].find(')') else { - break; - }; - edits.push(SexpEdit::replace( - s + num_start, - s + num_start + close, - unit.to_string(), - )); - from = num_start + close; - } - content = apply_edits(content, edits); + } else if let Some(invalid) = current_units + .iter() + .find(|unit| **unit < 1 || **unit > unit_count) + { + return Ok(CallToolResult::error(format!( + "Cannot replace '{}' with '{}': placed unit {} does not exist in the \ + new {}-unit symbol. Delete and re-place the component deliberately.", + reference, new_lib_id, invalid, unit_count + ))); + } + + // Replace the library id in every unit block. Shared component identity + // must not leave one unit pointing at the old symbol (#182). + let lib_id_pat = "(lib_id \""; + let escaped_lib_id = escape_property_text(&new_lib_id); + let mut edits = Vec::new(); + let mut old_lib_ids = Vec::new(); + for (start, end) in &blocks { + let block = &content[*start..*end]; + let Some(relative) = block.find(lib_id_pat) else { + return Ok(CallToolResult::error(format!( + "A unit of '{}' has no lib_id", + reference + ))); + }; + let value_start = *start + relative + lib_id_pat.len(); + let Some(value_end) = closing_quote(&content, value_start) else { + return Ok(CallToolResult::error("Malformed lib_id")); + }; + old_lib_ids.push(content[value_start..value_end].to_string()); + edits.push(SexpEdit::replace( + value_start, + value_end, + escaped_lib_id.clone(), + )); + } + + // Add the optional unit edits without a second source read. The multi-unit + // guard above means this scan has at most one block. + if let Some(unit) = new_unit { + let (start, end) = blocks[0]; + let block = &content[start..end]; + let mut from = 0usize; + while let Some(relative) = block[from..].find("(unit ") { + let number_start = from + relative + "(unit ".len(); + let Some(close) = block[number_start..].find(')') else { + break; + }; + edits.push(SexpEdit::replace( + start + number_start, + start + number_start + close, + unit.to_string(), + )); + from = number_start + close; } } + old_lib_ids.sort(); + old_lib_ids.dedup(); + if old_lib_ids.len() != 1 { + return Ok(CallToolResult::error(format!( + "Component '{}' already has inconsistent library ids across its units: {}", + reference, + old_lib_ids.join(", ") + ))); + } + let old_lib_id = old_lib_ids.remove(0); + content = apply_edits(content, edits); + // Ensure the new library symbol definition is present. Bail BEFORE writing: // a replace that can't embed its definition would leave the component // netlist-invisible (#34). @@ -1881,7 +1993,8 @@ async fn handle_replace_component( "reference": reference, "old_lib_id": old_lib_id, "new_lib_id": new_lib_id, - "unit": new_unit + "unit": new_unit, + "units_replaced": blocks.len() }))) } @@ -3236,8 +3349,8 @@ mod tests { } /// The old path hardcoded (at 0 0 0) — the annotation rendered at the - /// sheet origin, far from its symbol. append_property anchors on the - /// symbol's own position. + /// sheet origin, far from its symbol. The shared property writer anchors + /// each property on its own placed unit. #[tokio::test] async fn add_component_annotation_anchors_at_the_symbol_not_the_origin() { let (_symdir, _env) = stub_symbol_dir(); @@ -3722,3 +3835,381 @@ mod move_connected_tests { ); } } + +#[cfg(test)] +mod multi_unit_component_tests { + use super::*; + use crate::mcp::protocol::ToolContent; + use crate::tools::ServerConfig; + use std::sync::Arc; + + const SCHEMATIC: &str = r#"(kicad_sch + (version 20260306) + (uuid "11111111-1111-4111-8111-111111111111") + (lib_symbols + (symbol "Test:DUAL" + (symbol "DUAL_1_1" + (pin input line (at 0 0 0) (length 0) (name "A") (number "1")) + ) + (symbol "DUAL_2_1" + (pin output line (at 0 0 0) (length 0) (name "Y") (number "2")) + ) + ) + (symbol "Test:DUAL_NEW" + (symbol "DUAL_NEW_1_1" + (pin input line (at 0 0 0) (length 0) (name "A") (number "1")) + ) + (symbol "DUAL_NEW_2_1" + (pin output line (at 0 0 0) (length 0) (name "Y") (number "2")) + ) + ) + ) + (symbol + (lib_id "Test:DUAL") + (at 100 100 0) + (unit 1) + (uuid "22222222-2222-4222-8222-222222222222") + (property "Reference" "U1" (at 100 98 0)) + (property "Value" "OLD" (at 100 102 0)) + (property "Footprint" "" (at 100 100 0)) + (property "Datasheet" "" (at 100 100 0)) + (property "Note" "OLD" (at 100 100 0) (hide yes)) + (instances + (project "multi" + (path "/11111111-1111-4111-8111-111111111111" + (reference "U1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Test:DUAL") + (at 100 120 180) + (unit 2) + (uuid "33333333-3333-4333-8333-333333333333") + (property "Reference" "U1" (at 100 118 0)) + (property "Value" "OLD" (at 100 122 0)) + (property "Footprint" "" (at 100 120 0)) + (property "Datasheet" "" (at 100 120 0)) + (instances + (project "multi" + (path "/11111111-1111-4111-8111-111111111111" + (reference "U1") + (unit 2) + ) + ) + ) + ) + (sheet_instances (path "/" (page "1"))) +) +"#; + + fn context() -> ToolContext { + ToolContext::new( + ServerConfig { + kicad_cli: String::new(), + kicad_binary: String::new(), + ipc_address: String::new(), + project_dir: None, + jlcpcb_db_path: None, + auto_load_toolsets: false, + eager_toolsets: false, + }, + Arc::new(crate::router::ToolRouter::new()), + ) + } + + fn fixture() -> (tempfile::TempDir, std::path::PathBuf) { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("multi.kicad_sch"); + std::fs::write(&path, SCHEMATIC).unwrap(); + (directory, path) + } + + fn body(result: CallToolResult) -> serde_json::Value { + assert!(!result.is_error, "mutation unexpectedly failed"); + let ToolContent::Text { text } = &result.content[0] else { + panic!("expected text result"); + }; + serde_json::from_str(text).unwrap() + } + + fn instances(path: &std::path::Path) -> Vec { + let (_, tree) = read_schematic(path).unwrap(); + extract_symbol_instances(&tree) + .into_iter() + .filter(|instance| instance.reference == "U1" || instance.reference == "U9") + .collect() + } + + #[tokio::test] + async fn delete_removes_every_placed_unit() { + let (_directory, path) = fixture(); + let result = body( + handle_delete_schematic_component( + &json!({ "schematic": path, "reference": "U1" }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["deleted_units"], 2); + assert!(instances(&path).is_empty()); + } + + #[tokio::test] + async fn move_translates_every_unit_by_one_shared_delta() { + let (_directory, path) = fixture(); + let result = body( + handle_move_schematic_component( + &json!({ + "schematic": path, + "reference": "U1", + "x": 110.0, + "y": 110.0 + }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["moved_units"], 2); + let mut placed = instances(&path); + placed.sort_by_key(|instance| instance.unit); + assert!((placed[0].x - placed[1].x).abs() < 0.001); + assert!(((placed[1].y - placed[0].y) - 20.0).abs() < 0.001); + } + + #[tokio::test] + async fn rotate_preserves_the_units_relative_orientation() { + let (_directory, path) = fixture(); + let result = body( + handle_rotate_schematic_component( + &json!({ + "schematic": path, + "reference": "U1", + "rotation": 90.0 + }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["rotated_units"], 2); + let mut placed = instances(&path); + placed.sort_by_key(|instance| instance.unit); + assert_eq!(placed[0].rotation, 90.0); + assert_eq!(placed[1].rotation, 270.0); + } + + #[tokio::test] + async fn edit_updates_shared_fields_on_every_unit() { + let (_directory, path) = fixture(); + body( + handle_edit_schematic_component( + &json!({ + "schematic": path, + "reference": "U1", + "value": "NEW", + "fields": { "MPN": "A\\\"B" } + }), + &context(), + ) + .await + .unwrap(), + ); + let source = std::fs::read_to_string(&path).unwrap(); + assert_eq!(source.matches("(property \"Value\" \"NEW\"").count(), 2); + assert_eq!( + source.matches("(property \"MPN\" \"A\\\\\\\"B\"").count(), + 2 + ); + } + + #[tokio::test] + async fn rename_updates_rendered_and_netlist_references_on_every_unit() { + let (_directory, path) = fixture(); + body( + handle_edit_schematic_component( + &json!({ + "schematic": path, + "reference": "U1", + "new_reference": "U9" + }), + &context(), + ) + .await + .unwrap(), + ); + let source = std::fs::read_to_string(&path).unwrap(); + assert_eq!(source.matches("(property \"Reference\" \"U9\"").count(), 2); + assert_eq!(source.matches("(reference \"U9\")").count(), 2); + assert_eq!(instances(&path).len(), 2); + } + + #[tokio::test] + async fn annotation_repairs_a_field_missing_from_one_unit() { + let (_directory, path) = fixture(); + let result = body( + handle_add_component_annotation( + &json!({ + "schematic": path, + "reference": "U1", + "key": "Note", + "value": "NEW" + }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["updated_units"], 1); + assert_eq!(result["added_units"], 1); + let source = std::fs::read_to_string(&path).unwrap(); + assert_eq!(source.matches("(property \"Note\" \"NEW\"").count(), 2); + } + + #[tokio::test] + async fn grouping_adds_one_property_to_every_unit() { + let (_directory, path) = fixture(); + body( + handle_group_components( + &json!({ + "schematic": path, + "group_name": "Logic", + "references": ["U1"] + }), + &context(), + ) + .await + .unwrap(), + ); + let source = std::fs::read_to_string(&path).unwrap(); + assert_eq!(source.matches("(property \"Group\" \"Logic\"").count(), 2); + } + + #[tokio::test] + async fn pin_locations_include_every_units_real_placement() { + let (_directory, path) = fixture(); + let result = body( + handle_get_schematic_pin_locations( + &json!({ "schematic": path, "reference": "U1" }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["unit_count"], 2); + assert_eq!(result["pins"].as_array().unwrap().len(), 2); + assert!(result["pins"] + .as_array() + .unwrap() + .iter() + .any(|pin| pin["number"] == "1" && pin["unit"] == 1 && pin["y"] == 100.0)); + assert!(result["pins"] + .as_array() + .unwrap() + .iter() + .any(|pin| pin["number"] == "2" && pin["unit"] == 2 && pin["y"] == 120.0)); + } + + #[tokio::test] + async fn component_summary_lists_every_placement() { + let (_directory, path) = fixture(); + let result = body( + handle_get_schematic_component( + &json!({ "schematic": path, "reference": "U1" }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["unit_count"], 2); + assert_eq!( + result["unit_count"].as_u64().unwrap() as usize, + result["units"].as_array().unwrap().len() + ); + assert!(result["units"] + .as_array() + .unwrap() + .iter() + .any(|unit| unit["unit"] == 2 && unit["y"] == 120.0)); + } + + #[tokio::test] + async fn replace_changes_every_unit_and_preserves_unit_numbers() { + let (_directory, path) = fixture(); + let result = body( + handle_replace_component( + &json!({ + "schematic": path, + "reference": "U1", + "new_lib_id": "Test:DUAL_NEW" + }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["units_replaced"], 2); + let mut placed = instances(&path); + placed.sort_by_key(|instance| instance.unit); + assert_eq!( + placed + .iter() + .map(|instance| instance.unit) + .collect::>(), + vec![1, 2] + ); + assert!(placed + .iter() + .all(|instance| instance.lib_id == "Test:DUAL_NEW")); + } + + #[tokio::test] + async fn replace_rejects_an_ambiguous_unit_override_without_writing() { + let (_directory, path) = fixture(); + let before = std::fs::read(&path).unwrap(); + let result = handle_replace_component( + &json!({ + "schematic": path, + "reference": "U1", + "new_lib_id": "Test:DUAL_NEW", + "unit": 1 + }), + &context(), + ) + .await + .unwrap(); + assert!(result.is_error); + assert_eq!(std::fs::read(&path).unwrap(), before); + } + + #[tokio::test] + async fn move_region_moves_the_selected_unit_not_unit_one() { + let (_directory, path) = fixture(); + let result = body( + handle_move_region( + &json!({ + "schematic": path, + "x1": 95.0, + "y1": 115.0, + "x2": 105.0, + "y2": 125.0, + "dx": 10.0, + "dy": 0.0 + }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["moved_unit_count"], 1); + assert_eq!(result["placements"][0]["unit"], 2); + let mut placed = instances(&path); + placed.sort_by_key(|instance| instance.unit); + assert_eq!(placed[0].x, 100.0, "unit 1 must stay put"); + assert_ne!(placed[1].x, 100.0, "selected unit 2 must move"); + } +} diff --git a/tool-directory.md b/tool-directory.md index ccddf4a0..a278dedf 100644 --- a/tool-directory.md +++ b/tool-directory.md @@ -68,20 +68,20 @@ Six tools, grouped into *discovery/routing* and *observability*. | `create_schematic` | Create a new blank `.kicad_sch` schematic file, on A4 unless another paper size is given. Use `set_schematic_page` to change it later. | | `set_schematic_page` | Set the sheet's paper size (A0–A5, A–E, US Letter/Legal/Ledger) and orientation. Returns the size in mm — content outside the frame still exports and still nets up, so a too-small page is a silent defect. | | `add_schematic_component` | Add a symbol from a KiCAD library to the schematic. Snaps to the 1.27mm grid. | -| `delete_schematic_component` | Remove a symbol instance from the schematic by its reference designator. | -| `edit_schematic_component` | Update fields (Reference, Value, Footprint, custom properties) of a symbol instance. | -| `get_schematic_component` | Get all properties, position, and pin locations for a symbol instance. | +| `delete_schematic_component` | Remove a component and all of its placed units by reference designator. | +| `edit_schematic_component` | Update shared fields consistently across every placed unit of a component. | +| `get_schematic_component` | Get shared properties and every placed unit's position for a component. | | `list_schematic_components` | List all symbol instances with positions, values, footprints, and pin locations. | -| `move_schematic_component` | Move a symbol to a new position. Does NOT adjust connected wires. | -| `rotate_schematic_component` | Rotate a symbol by setting its absolute rotation angle (0/90/180/270). | +| `move_schematic_component` | Move the lowest-numbered unit to a new position and translate every other unit by the same delta. Does NOT adjust connected wires. | +| `rotate_schematic_component` | Set the lowest-numbered unit's absolute rotation and rotate every other unit by the same delta. | | `move_connected` | Move a symbol and stretch/shrink connected wire stubs to preserve connections. | | `move_region` | Move all symbols within a bounding box by a given offset. | | `annotate_schematic` | Run kicad-cli to auto-assign reference designators (`R?` → `R1`, `U?` → `U1`, etc.). | -| `get_schematic_pin_locations` | Get exact (X,Y) coordinates of every pin on a symbol, accounting for rotation/mirroring, plus each pin's `orientation_degrees` (the direction leading away from the body, 0 = east) and `length_mm`. | +| `get_schematic_pin_locations` | Get exact (X,Y) coordinates of every pin on every placed unit, accounting for rotation/mirroring, plus each pin's `orientation_degrees` and `length_mm`. | | `batch_get_schematic_pin_locations` | Get pin locations for multiple components in a single file read, with the same per-pin fields. | -| `add_component_annotation` | Add a custom property (annotation) to a symbol instance. | -| `group_components` | Add a group property to multiple components in the schematic. | -| `replace_component` | Replace a component's `lib_id` with a new library symbol (swap the component type). | +| `add_component_annotation` | Add or update a custom property across every placed unit of a component. | +| `group_components` | Add or update a group property across every placed unit of multiple components. | +| `replace_component` | Replace every placed unit's `lib_id` while preserving and validating its unit number. | | `update_symbols_from_library` | Re-embed placed symbols' definitions from their libraries, like KiCad's "Update Symbols from Library". Refuses a symbol whose pins moved or disappeared (wires attach at pin coordinates) unless `allow_pin_moves` is set. | | `reset_schematic_field_positions` | Move each symbol's Reference and Value text back to its library anchor, through the symbol's rotation — KiCad's "Reset field text positions". Repairs sheets whose fields sit at a uniform offset. | | `get_schematic_view` | Render a sheet with kicad-cli and return the path to the SVG it wrote. There is no PNG — KiCad has no schematic rasteriser. The file lands in a temp directory; use `export_schematic_svg` to choose the location. | From 7aa9a1bb5de7d44f134b39b1b9fb6d2ed918b153 Mon Sep 17 00:00:00 2001 From: mixelpixx Date: Tue, 25 Aug 2026 14:38:25 -0400 Subject: [PATCH 2/2] test(sch): pin rotation normalization and the eeschema dialect for multi-unit ops Two fixups on top of the multi-unit port: - The delta arithmetic could write an unnormalized angle: a unit at 270 following a +90 turn computed 360, which eeschema never stores (it keeps 0/90/180/270 and re-saves anything else, silently diverging from what the response reported). Angles now normalize with rem_euclid(360) before writing, and a two-turn test pins that 270 + 90 stores 0. - The module's hand-written fixture shares Konnect's own serialization habits (2-space, LF, single-line properties), so the indentation- and dialect-matching branches were untested against what KiCad actually writes. New fixture: the ecc83 demo schematic verbatim (tabs, CRLF, U1 placed as three units of the embedded dual triode). Two tests: the whole component is seen (unit_count 3, units [1,2,3]), and an added annotation lands tab-indented in every unit block. Co-Authored-By: Claude Fable 5 --- .../konnect-core/src/tools/sch_components.rs | 119 +- .../tests/fixtures/ecc83_multiunit.kicad_sch | 3545 +++++++++++++++++ 2 files changed, 3663 insertions(+), 1 deletion(-) create mode 100644 crates/konnect-core/tests/fixtures/ecc83_multiunit.kicad_sch diff --git a/crates/konnect-core/src/tools/sch_components.rs b/crates/konnect-core/src/tools/sch_components.rs index 077ca12a..9cda9946 100644 --- a/crates/konnect-core/src/tools/sch_components.rs +++ b/crates/konnect-core/src/tools/sch_components.rs @@ -1201,7 +1201,13 @@ async fn handle_rotate_schematic_component( .iter_mut() .filter(|symbol| symbol.reference() == Some(reference.as_str())) { - let new_rotation = symbol.at.rotation.unwrap_or(0.0) + rotation_delta; + // The delta lands each unit at its own angle, so a unit already at + // 270° asked to follow a +90° turn computes 360° — normalize into + // [0, 360) before writing; eeschema only ever stores 0/90/180/270 + // and re-saves anything else, so an unnormalized angle survives only + // until KiCad touches the file and then silently diverges from what + // this response reported. + let new_rotation = (symbol.at.rotation.unwrap_or(0.0) + rotation_delta).rem_euclid(360.0); symbol.set_rotation(new_rotation); placements.push(json!({ "unit": symbol.unit, "rotation": new_rotation })); } @@ -3927,6 +3933,81 @@ mod multi_unit_component_tests { (directory, path) } + /// A real eeschema save (KiCad's ecc83 demo): tabs, CRLF, and U1 placed + /// as units 2 and 3 of the embedded `ecc83-pp:ECC83` dual triode. The + /// hand-written `SCHEMATIC` above shares this module's own serialization + /// habits, so only this file exercises the indentation- and + /// dialect-matching branches against what KiCad actually writes. + fn eeschema_fixture() -> (tempfile::TempDir, std::path::PathBuf) { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("ecc83.kicad_sch"); + std::fs::write( + &path, + include_str!("../../tests/fixtures/ecc83_multiunit.kicad_sch"), + ) + .unwrap(); + (directory, path) + } + + #[tokio::test] + async fn a_real_eeschema_multi_unit_component_is_seen_whole() { + let (_directory, path) = eeschema_fixture(); + let result = body( + handle_get_schematic_component( + &json!({ "schematic": path, "reference": "U1" }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!( + result["unit_count"], 3, + "U1 is placed as both triodes plus the heater unit" + ); + let mut units: Vec = result["units"] + .as_array() + .unwrap() + .iter() + .map(|unit| unit["unit"].as_i64().unwrap()) + .collect(); + units.sort_unstable(); + assert_eq!(units, [1, 2, 3]); + } + + #[tokio::test] + async fn annotating_a_real_eeschema_file_reaches_every_unit_in_its_own_dialect() { + let (_directory, path) = eeschema_fixture(); + let result = body( + handle_add_component_annotation( + &json!({ + "schematic": path, + "reference": "U1", + "key": "MPN", + "value": "ECC83-JJ" + }), + &context(), + ) + .await + .unwrap(), + ); + assert_eq!(result["added_units"], 3, "{result}"); + let source = std::fs::read_to_string(&path).unwrap(); + assert_eq!( + source.matches("(property \"MPN\" \"ECC83-JJ\"").count(), + 3, + "the property lands in every unit block" + ); + // The inserted lines must follow the file's own indentation (tabs) — + // a 2-space insert in a tab-indented eeschema file is exactly the + // drift the KiCad-authored fixture exists to catch. + for line in source.lines().filter(|line| line.contains("\"MPN\"")) { + assert!( + line.starts_with('\t'), + "inserted property must be tab-indented like its file: {line:?}" + ); + } + } + fn body(result: CallToolResult) -> serde_json::Value { assert!(!result.is_error, "mutation unexpectedly failed"); let ToolContent::Text { text } = &result.content[0] else { @@ -4003,6 +4084,42 @@ mod multi_unit_component_tests { assert_eq!(placed[1].rotation, 270.0); } + /// The delta arithmetic can push a trailing unit past 360° — a unit at + /// 270° following a +90° turn computes 360°, which eeschema never writes + /// (it stores 0/90/180/270 and re-saves anything else). The stored and + /// reported angle must be the normalized one, or the response diverges + /// from the file the moment KiCad touches it. + #[tokio::test] + async fn rotation_past_a_full_turn_normalizes_instead_of_writing_360() { + let (_directory, path) = fixture(); + for target in [90.0, 180.0] { + body( + handle_rotate_schematic_component( + &json!({ + "schematic": path, + "reference": "U1", + "rotation": target + }), + &context(), + ) + .await + .unwrap(), + ); + } + let mut placed = instances(&path); + placed.sort_by_key(|instance| instance.unit); + assert_eq!(placed[0].rotation, 180.0); + assert_eq!( + placed[1].rotation, 0.0, + "270° + 90° must store 0°, not 360°" + ); + let source = std::fs::read_to_string(&path).unwrap(); + assert!( + !source.contains("(at 40 20 360)") && !source.contains(" 360)"), + "no unnormalized angle may reach the file" + ); + } + #[tokio::test] async fn edit_updates_shared_fields_on_every_unit() { let (_directory, path) = fixture(); diff --git a/crates/konnect-core/tests/fixtures/ecc83_multiunit.kicad_sch b/crates/konnect-core/tests/fixtures/ecc83_multiunit.kicad_sch new file mode 100644 index 00000000..97a3340e --- /dev/null +++ b/crates/konnect-core/tests/fixtures/ecc83_multiunit.kicad_sch @@ -0,0 +1,3545 @@ +(kicad_sch + (version 20250114) + (generator "eeschema") + (generator_version "9.0") + (uuid "28f865a0-4433-4a53-bbd7-b62f276848e4") + (paper "A4") + (title_block + (title "ECC Push-Pull") + (date "Sat 21 Mar 2015") + (rev "0.1") + ) + (lib_symbols + (symbol "ecc83-pp:C" + (pin_numbers + (hide yes) + ) + (pin_names + (offset 0.254) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "C" + (at 0.635 2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "C" + (at 0.635 -2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 0.9652 -3.81 0) + (effects + (font + (size 0.762 0.762) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "C? C_????_* C_???? SMD*_c Capacitor*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "C_0_1" + (polyline + (pts + (xy -2.032 0.762) (xy 2.032 0.762) + ) + (stroke + (width 0.508) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -2.032 -0.762) (xy 2.032 -0.762) + ) + (stroke + (width 0.508) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "C_1_1" + (pin passive line + (at 0 3.81 270) + (length 2.794) + (name "~" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "1" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 2.794) + (name "~" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "2" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "ecc83-pp:CONN_1" + (pin_numbers + (hide yes) + ) + (pin_names + (offset 0.762) + (hide yes) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "P" + (at 2.032 0 0) + (effects + (font + (size 1.016 1.016) + ) + (justify left) + ) + ) + (property "Value" "CONN_1" + (at 0 1.397 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "MountingHole:MountingHole_3.2mm_M3_DIN965_Pad" + (at 0 -1.27 0) + (effects + (font + (size 0.254 0.254) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "CONN_1_0_1" + (polyline + (pts + (xy -0.762 0) (xy -1.27 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 0 0) + (radius 0.7874) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "CONN_1_1_1" + (pin passive line + (at -3.81 0 0) + (length 2.54) + (name "1" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (number "1" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "ecc83-pp:CONN_2" + (pin_names + (offset 1.016) + (hide yes) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "P" + (at -1.27 0 90) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Value" "CONN_2" + (at 1.27 0 90) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "CONN_2_0_1" + (rectangle + (start -2.54 3.81) + (end 2.54 -3.81) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "CONN_2_1_1" + (pin passive inverted + (at -8.89 2.54 0) + (length 6.35) + (name "P1" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (number "1" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + ) + (pin passive inverted + (at -8.89 -2.54 0) + (length 6.35) + (name "PM" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (number "2" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "ecc83-pp:CP" + (pin_numbers + (hide yes) + ) + (pin_names + (offset 0.254) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "C" + (at 0.635 2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "CP" + (at 0.635 -2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 0.9652 -3.81 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Polarised capacitor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "cap capacitor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "CP_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "CP_0_1" + (rectangle + (start -2.286 0.508) + (end -2.286 1.016) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (rectangle + (start -2.286 0.508) + (end 2.286 0.508) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -1.778 2.286) (xy -0.762 2.286) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -1.27 2.794) (xy -1.27 1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (rectangle + (start 2.286 1.016) + (end -2.286 1.016) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (rectangle + (start 2.286 1.016) + (end 2.286 0.508) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (rectangle + (start 2.286 -0.508) + (end -2.286 -1.016) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + ) + (symbol "CP_1_1" + (pin passive line + (at 0 3.81 270) + (length 2.794) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 2.794) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "ecc83-pp:ECC83" + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "U" + (at 3.302 7.874 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "ECC83" + (at 8.89 -7.62 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "VALVE-NOVAL_P" + (at 10.16 -8.89 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_locked" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "ECC83_0_1" + (polyline + (pts + (xy -5.08 2.54) (xy -5.08 -2.54) (xy -5.08 -2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start -5.08 2.54) + (mid 0 7.5979) + (end 5.08 2.54) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start 5.08 -2.54) + (mid 0 -7.5979) + (end -5.08 -2.54) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 5.08 2.54) (xy 5.08 -2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "ECC83_1_0" + (polyline + (pts + (xy -2.54 -5.08) (xy -2.54 -7.62) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 5.08) (xy 0 7.62) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "ECC83_1_1" + (polyline + (pts + (xy -5.08 0) (xy -3.175 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -2.54 5.08) (xy 2.794 5.08) (xy 2.794 5.08) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -1.905 0) (xy -3.175 0) + ) + (stroke + (width 0.1524) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -0.635 0) (xy 0.635 0) + ) + (stroke + (width 0.1524) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start -2.54 -5.08) + (mid 0 -3.0968) + (end 2.54 -5.08) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 1.905 0) (xy 3.175 0) + ) + (stroke + (width 0.1524) + (type default) + ) + (fill + (type none) + ) + ) + (pin input line + (at -7.62 0 0) + (length 2.54) + (name "G" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "7" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + (pin passive line + (at -2.54 -10.16 90) + (length 2.54) + (name "K" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "8" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + (pin passive line + (at 0 10.16 270) + (length 2.54) + (name "A" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "6" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + ) + (symbol "ECC83_2_0" + (polyline + (pts + (xy -2.54 -5.08) (xy -2.54 -7.62) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 5.08) (xy 0 7.62) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "ECC83_2_1" + (polyline + (pts + (xy -5.08 0) (xy -3.175 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -2.54 5.08) (xy 2.794 5.08) (xy 2.794 5.08) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -1.905 0) (xy -3.175 0) + ) + (stroke + (width 0.1524) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -0.635 0) (xy 0.635 0) + ) + (stroke + (width 0.1524) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start -2.54 -5.08) + (mid 0 -3.0968) + (end 2.54 -5.08) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 1.905 0) (xy 3.175 0) + ) + (stroke + (width 0.1524) + (type default) + ) + (fill + (type none) + ) + ) + (pin input line + (at -7.62 0 0) + (length 2.54) + (name "G" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "2" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + (pin passive line + (at -2.54 -10.16 90) + (length 2.54) + (name "K" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "3" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + (pin passive line + (at 0 10.16 270) + (length 2.54) + (name "A" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "1" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + ) + (symbol "ECC83_3_1" + (arc + (start -2.54 -6.35) + (mid -1.27 -5.5651) + (end 0 -6.35) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start 0 -6.35) + (mid 1.27 -5.5651) + (end 2.54 -6.35) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (pin input line + (at -2.54 -11.43 90) + (length 5.08) + (name "F1" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "4" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + (pin input line + (at 0 -11.43 90) + (length 5.08) + (name "F2" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "9" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + (pin input line + (at 2.54 -11.43 90) + (length 5.08) + (name "F1" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (number "5" + (effects + (font + (size 1.016 1.016) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "ecc83-pp:GND" + (power) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "#PWR" + (at 0 -6.35 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 0 -3.81 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "GND_0_1" + (polyline + (pts + (xy 0 0) (xy 0 -1.27) (xy 1.27 -1.27) (xy 0 -2.54) (xy -1.27 -1.27) (xy 0 -1.27) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "GND_1_1" + (pin power_in line + (at 0 0 270) + (length 0) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "ecc83-pp:PWR_FLAG" + (power) + (pin_numbers + (hide yes) + ) + (pin_names + (offset 0) + (hide yes) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "#FLG" + (at 0 2.413 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Value" "PWR_FLAG" + (at 0 4.572 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "PWR_FLAG_0_0" + (pin power_out line + (at 0 0 90) + (length 0) + (name "pwr" + (effects + (font + (size 0.508 0.508) + ) + ) + ) + (number "1" + (effects + (font + (size 0.508 0.508) + ) + ) + ) + ) + ) + (symbol "PWR_FLAG_0_1" + (polyline + (pts + (xy 0 0) (xy 0 1.27) (xy -1.905 2.54) (xy 0 3.81) (xy 1.905 2.54) (xy 0 1.27) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "ecc83-pp:R" + (pin_numbers + (hide yes) + ) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "R" + (at 2.032 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 0 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at -1.778 0 90) + (effects + (font + (size 0.762 0.762) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (effects + (font + (size 0.762 0.762) + ) + ) + ) + (property "Description" "" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "R_* Resistor_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "R_0_1" + (rectangle + (start -1.016 -2.54) + (end 1.016 2.54) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "R_1_1" + (pin passive line + (at 0 3.81 270) + (length 1.27) + (name "~" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (number "1" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 1.27) + (name "~" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (number "2" + (effects + (font + (size 1.524 1.524) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + ) + (junction + (at 76.2 50.8) + (diameter 1.016) + (color 0 0 0 0) + (uuid "10ef5c17-5272-4e7f-89a5-ecf737e33e26") + ) + (junction + (at 185.42 76.2) + (diameter 1.016) + (color 0 0 0 0) + (uuid "28b3893d-db59-42a0-9c46-cef29fab5b3a") + ) + (junction + (at 86.36 50.8) + (diameter 1.016) + (color 0 0 0 0) + (uuid "5ff87081-c0a0-47ce-a04e-7b4396f38ef3") + ) + (junction + (at 60.96 88.9) + (diameter 1.016) + (color 0 0 0 0) + (uuid "75bb7da6-039f-40b8-ba94-99186b61d8b3") + ) + (junction + (at 144.78 107.95) + (diameter 1.016) + (color 0 0 0 0) + (uuid "da0797c6-d142-4a5b-b008-3d4d0d2550ff") + ) + (junction + (at 50.8 55.88) + (diameter 1.016) + (color 0 0 0 0) + (uuid "e98b95f3-e3ac-46a0-b2c5-310dc77f3348") + ) + (junction + (at 157.48 93.98) + (diameter 1.016) + (color 0 0 0 0) + (uuid "eb94e8ae-d252-44ad-9a8c-740b5c98d893") + ) + (junction + (at 157.48 76.2) + (diameter 1.016) + (color 0 0 0 0) + (uuid "ecb56094-6b3e-4511-801e-afbe298ca102") + ) + (no_connect + (at 154.94 177.8) + (uuid "07e6e92c-c8a8-42b9-a8a3-099796ec9615") + ) + (no_connect + (at 154.94 180.34) + (uuid "230887ce-f305-4883-86d7-72df3a1a2372") + ) + (no_connect + (at 154.94 185.42) + (uuid "4102cf29-cf4e-42c4-96cb-330b1b756d96") + ) + (no_connect + (at 154.94 182.88) + (uuid "517db1d3-5d33-4d54-a6be-6a1fc40799a4") + ) + (wire + (pts + (xy 185.42 76.2) (xy 198.12 76.2) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "06655b90-3a73-4865-8781-04208e869f47") + ) + (wire + (pts + (xy 198.12 81.28) (xy 195.58 81.28) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "10537025-ad57-4a68-a3ad-97783b6e906f") + ) + (wire + (pts + (xy 86.36 53.34) (xy 86.36 50.8) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "1a7e2049-1cef-423f-9044-882a24cd3080") + ) + (wire + (pts + (xy 195.58 81.28) (xy 195.58 83.82) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "1bfc9e1d-9730-4ad2-b53c-049bbdc4f2eb") + ) + (wire + (pts + (xy 138.43 116.84) (xy 138.43 113.03) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "31e24174-4181-446f-9f24-5cffc59c241d") + ) + (wire + (pts + (xy 144.78 107.95) (xy 144.78 123.19) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "3211000a-11bc-4174-b47e-920409d772ed") + ) + (wire + (pts + (xy 50.8 57.15) (xy 50.8 55.88) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "38870f5c-4bbe-464a-b4a4-7b080932d227") + ) + (wire + (pts + (xy 157.48 76.2) (xy 157.48 81.28) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "43ff79b9-e7f1-4ce8-8f5d-a3e328a0951b") + ) + (wire + (pts + (xy 149.86 64.77) (xy 149.86 93.98) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "492f5cda-ba4e-4ac1-9675-e6be96092062") + ) + (wire + (pts + (xy 185.42 76.2) (xy 185.42 81.28) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "58ca8983-4450-4fe2-8e2b-0e30d48402fa") + ) + (wire + (pts + (xy 86.36 50.8) (xy 160.02 50.8) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "5cb2363c-c774-4f15-8526-d847a6a0f944") + ) + (wire + (pts + (xy 50.8 50.8) (xy 76.2 50.8) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "67a136ce-9a43-4815-bf37-5a48e3c5ba88") + ) + (wire + (pts + (xy 171.45 76.2) (xy 157.48 76.2) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "6e244d34-c7f4-4b73-b2e4-7cd6a9e68746") + ) + (wire + (pts + (xy 138.43 113.03) (xy 135.89 113.03) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "7573f100-0025-455b-9269-cc3c743be43c") + ) + (wire + (pts + (xy 179.07 76.2) (xy 185.42 76.2) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "78e27532-fd91-4f5a-a2d8-e6fdf5d8048d") + ) + (wire + (pts + (xy 60.96 88.9) (xy 60.96 86.36) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "7b559062-4412-4d9e-9c3b-32723558239a") + ) + (wire + (pts + (xy 76.2 49.53) (xy 76.2 50.8) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "84963a83-b6da-4751-9910-f093c3a03bee") + ) + (wire + (pts + (xy 157.48 74.93) (xy 157.48 76.2) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "87a9b2c0-1c64-462f-bd1f-2bce76398152") + ) + (wire + (pts + (xy 63.5 93.98) (xy 50.8 93.98) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "8e3fe7d9-4894-46e6-9f30-60c30a67eb4d") + ) + (wire + (pts + (xy 86.36 62.23) (xy 86.36 60.96) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "93661ccf-845f-4e28-a2f3-792cd2d0f455") + ) + (wire + (pts + (xy 53.34 55.88) (xy 50.8 55.88) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "936bde2e-ed95-450c-9794-94948f30b67d") + ) + (wire + (pts + (xy 63.5 86.36) (xy 63.5 93.98) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "96d8a739-fd6e-4a71-af88-21b52a62647d") + ) + (wire + (pts + (xy 50.8 88.9) (xy 60.96 88.9) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "986dd788-f18e-4db3-a5a5-6a0a989a7f34") + ) + (wire + (pts + (xy 149.86 93.98) (xy 157.48 93.98) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "9b04edba-dede-4967-8f9b-38a56988a081") + ) + (wire + (pts + (xy 157.48 93.98) (xy 157.48 97.79) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "9edfba25-39d5-4b5e-a81e-c68bb365f583") + ) + (wire + (pts + (xy 135.89 107.95) (xy 144.78 107.95) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "ac0e9f32-a4a3-4405-b25f-0b2daa84fbf8") + ) + (wire + (pts + (xy 160.02 50.8) (xy 160.02 54.61) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "af9c8561-1608-47a7-8aa7-1d378b72d4c1") + ) + (wire + (pts + (xy 154.94 130.81) (xy 154.94 132.08) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "b7a0ea88-eaff-457a-afff-3c743cbd7651") + ) + (wire + (pts + (xy 152.4 64.77) (xy 149.86 64.77) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "b9ea24f5-9437-4489-8f03-fb33f32ab984") + ) + (wire + (pts + (xy 157.48 88.9) (xy 157.48 93.98) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "c3e30b89-963c-45be-9c7c-93532b58206c") + ) + (wire + (pts + (xy 144.78 130.81) (xy 144.78 132.08) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "dc9271ea-f970-4a19-a75f-7e6f5ed9c7b4") + ) + (wire + (pts + (xy 76.2 50.8) (xy 86.36 50.8) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "dd8fad86-da9c-4a52-b908-cd4a2e7bd87e") + ) + (wire + (pts + (xy 66.04 88.9) (xy 66.04 86.36) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "df86ea1e-527e-4c0f-b20e-3a0e986faf20") + ) + (wire + (pts + (xy 154.94 118.11) (xy 154.94 123.19) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "e35a19d0-80c7-4a83-8507-037b6833d50c") + ) + (wire + (pts + (xy 185.42 90.17) (xy 185.42 88.9) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "e9c31df6-6f42-45a2-8981-394de7ce2e5a") + ) + (wire + (pts + (xy 60.96 88.9) (xy 66.04 88.9) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "eb2a78d6-b4d5-4ef8-9a1a-3d9ed3db2fee") + ) + (wire + (pts + (xy 144.78 107.95) (xy 149.86 107.95) + ) + (stroke + (width 0) + (type solid) + ) + (uuid "eb6832ef-d0fe-479d-8f32-b253c6621b1a") + ) + (symbol + (lib_id "ecc83-pp:R") + (at 157.48 85.09 180) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f38a") + (property "Reference" "R1" + (at 154.94 85.09 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "1.5K" + (at 157.48 85.09 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" + (at 159.512 85.0392 90) + (effects + (font + (size 0.254 0.254) + ) + ) + ) + (property "Datasheet" "" + (at 157.48 85.09 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 157.48 85.09 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "490a35ba-1ba2-44c5-adce-e27a045257ab") + ) + (pin "2" + (uuid "51355c82-d1cf-445b-b079-21c660dd8989") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "R1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:R") + (at 154.94 127 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f39d") + (property "Reference" "R2" + (at 152.4 127 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "1.5K" + (at 154.94 127 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" + (at 156.8196 127 90) + (effects + (font + (size 0.762 0.762) + ) + ) + ) + (property "Datasheet" "" + (at 154.94 127 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 154.94 127 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "d54c7402-b55e-42a6-b24e-c8231bb401fe") + ) + (pin "2" + (uuid "e7039a2b-a55a-45c3-8a0e-2abf3a015549") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "R2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:R") + (at 144.78 127 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f3a2") + (property "Reference" "R4" + (at 142.24 125.73 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "47K" + (at 144.78 127 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" + (at 146.7104 127.0508 90) + (effects + (font + (size 0.762 0.762) + ) + ) + ) + (property "Datasheet" "" + (at 144.78 127 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 144.78 127 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "c270f478-7e23-4604-bfdf-038c11b27bdf") + ) + (pin "2" + (uuid "0ad09d20-5006-493e-8278-f356e26f66f7") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "R4") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:R") + (at 185.42 85.09 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f3ad") + (property "Reference" "R3" + (at 182.88 85.09 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "100K" + (at 185.42 85.09 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal" + (at 187.4266 85.09 90) + (effects + (font + (size 0.254 0.254) + ) + ) + ) + (property "Datasheet" "" + (at 185.42 85.09 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 185.42 85.09 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "39435a49-8935-4ddf-b360-6a015d358cfb") + ) + (pin "2" + (uuid "3f332333-6f91-4a13-8c53-eebb9573b799") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "R3") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:C") + (at 175.26 76.2 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f3be") + (property "Reference" "C2" + (at 175.26 72.39 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "680nF" + (at 175.26 80.01 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Capacitor_THT:C_Disc_D4.7mm_W2.5mm_P5.00mm" + (at 175.26 81.28 90) + (effects + (font + (size 0.254 0.254) + ) + ) + ) + (property "Datasheet" "" + (at 175.26 76.2 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 175.26 76.2 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "eadc305d-a101-4c9c-94d9-1ecaf23352ac") + ) + (pin "2" + (uuid "44dabcab-6c7b-4e96-b86a-4fb6dd8f5b97") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "C2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CONN_2") + (at 127 110.49 180) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f464") + (property "Reference" "P1" + (at 127 105.41 0) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Value" "IN" + (at 127 110.49 0) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Footprint" "Footprints:Altech_AK300_1x02_P5.00mm_45-Degree" + (at 127 115.57 0) + (effects + (font + (size 0.381 0.381) + ) + ) + ) + (property "Datasheet" "" + (at 127 110.49 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 127 110.49 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "4e008eff-1d48-4641-80fd-f3bdec030729") + ) + (pin "2" + (uuid "7bc904bf-18da-4a24-be4f-4a6e9fd74076") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "P1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CONN_2") + (at 207.01 78.74 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f46c") + (property "Reference" "P2" + (at 207.01 73.66 0) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Value" "OUT" + (at 208.28 78.74 90) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Footprint" "Footprints:Altech_AK300_1x02_P5.00mm_45-Degree" + (at 207.01 83.82 0) + (effects + (font + (size 0.381 0.381) + ) + ) + ) + (property "Datasheet" "" + (at 207.01 78.74 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 207.01 78.74 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "5052135e-1b72-4faa-add1-da0cf01aa026") + ) + (pin "2" + (uuid "0a09657c-35de-4de7-9dfe-7d3a0d792dc3") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "P2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CONN_2") + (at 41.91 53.34 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f4a5") + (property "Reference" "P3" + (at 41.91 48.26 0) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Value" "POWER" + (at 40.64 53.34 90) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Footprint" "Footprints:Altech_AK300_1x02_P5.00mm_45-Degree" + (at 41.91 58.42 0) + (effects + (font + (size 0.381 0.381) + ) + ) + ) + (property "Datasheet" "" + (at 41.91 53.34 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 41.91 53.34 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "54a114eb-3233-4517-9c5d-b1447c552b5c") + ) + (pin "2" + (uuid "357ce97b-c23c-459c-af19-1a501705cbb4") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "P3") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CP") + (at 86.36 57.15 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-00004549f4be") + (property "Reference" "C1" + (at 92.71 55.88 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "10uF" + (at 93.98 58.42 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Capacitor_THT:CP_Radial_D10.0mm_P5.00mm" + (at 91.44 59.69 0) + (effects + (font + (size 0.254 0.254) + ) + ) + ) + (property "Datasheet" "" + (at 86.36 57.15 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 86.36 57.15 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "6f18cb93-a67b-4a40-887d-5dbe82da9cf7") + ) + (pin "2" + (uuid "f905ad9c-2fd8-4770-9c67-d7f99b1c26af") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "C1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CONN_2") + (at 41.91 91.44 180) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000456a8acc") + (property "Reference" "P4" + (at 43.18 91.44 90) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Value" "CONN_2" + (at 40.64 91.44 90) + (effects + (font + (size 1.016 1.016) + ) + ) + ) + (property "Footprint" "Footprints:Altech_AK300_1x02_P5.00mm_45-Degree" + (at 43.18 96.52 0) + (effects + (font + (size 0.381 0.381) + ) + ) + ) + (property "Datasheet" "" + (at 41.91 91.44 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 41.91 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "0b2cb6e8-aee2-426a-a772-436c4f8fd1de") + ) + (pin "2" + (uuid "f4c26ce8-3b51-4b5c-839b-1fa8af9cd764") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "P4") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:PWR_FLAG") + (at 53.34 55.88 270) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000457dbac0") + (property "Reference" "#FLG05" + (at 60.198 55.88 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "PWR_FLAG" + (at 59.182 55.88 0) + (effects + (font + (size 0.762 0.762) + ) + ) + ) + (property "Footprint" "" + (at 53.34 55.88 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 53.34 55.88 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 53.34 55.88 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "8508cfae-2f39-4086-9b3c-8362f92ce148") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#FLG05") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:GND") + (at 144.78 132.08 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000457dbaef") + (property "Reference" "#PWR04" + (at 144.78 132.08 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 144.78 133.858 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "" + (at 144.78 132.08 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 144.78 132.08 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 144.78 132.08 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "7b3ac387-2d8f-4fc2-98a0-74502639828d") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#PWR04") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:GND") + (at 154.94 132.08 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000457dbaf1") + (property "Reference" "#PWR03" + (at 154.94 132.08 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 154.94 133.858 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "" + (at 154.94 132.08 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 154.94 132.08 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 154.94 132.08 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "1f35e760-d662-44a6-8970-5b1f4f0c5299") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#PWR03") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:GND") + (at 185.42 90.17 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000457dbaf5") + (property "Reference" "#PWR02" + (at 185.42 90.17 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 185.42 91.948 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "" + (at 185.42 90.17 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 185.42 90.17 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 185.42 90.17 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "45409752-92eb-492b-8722-9657988f10c8") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#PWR02") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:GND") + (at 195.58 83.82 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000457dbaf8") + (property "Reference" "#PWR01" + (at 195.58 83.82 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 195.58 85.598 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "" + (at 195.58 83.82 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 195.58 83.82 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 195.58 83.82 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "ffd8d9c0-407c-42f3-9854-4c5baebeb6bc") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#PWR01") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:ECC83") + (at 160.02 64.77 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-000048b4f256") + (property "Reference" "U1" + (at 163.83 55.88 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "ECC83" + (at 153.67 72.39 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Footprints:Valve_ECC-83-1" + (at 166.37 64.77 90) + (effects + (font + (size 0.762 0.762) + ) + ) + ) + (property "Datasheet" "" + (at 160.02 64.77 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 160.02 64.77 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "6" + (uuid "290267e7-4016-4193-aa9b-8e119f086579") + ) + (pin "7" + (uuid "57816012-691f-43c4-891a-0a9875701932") + ) + (pin "8" + (uuid "dfa2a830-6e8c-46c0-9986-7a866e30ad89") + ) + (pin "1" + (uuid "d071bc8d-0a42-4749-ab3b-bc91c0b12307") + ) + (pin "2" + (uuid "91389f1c-a07a-4e24-a880-42977189f2e3") + ) + (pin "3" + (uuid "5e94a278-17ab-494c-b84d-2518c4915ca9") + ) + (pin "4" + (uuid "d9fb4f9d-c0e3-4555-b79f-5f73479f12da") + ) + (pin "5" + (uuid "b87ff3e3-fb45-4272-93a9-dfdea2efee10") + ) + (pin "9" + (uuid "da18c681-21b7-4160-90d8-28ac0b07192c") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "U1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:ECC83") + (at 157.48 107.95 0) + (unit 2) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-000048b4f263") + (property "Reference" "U1" + (at 161.29 99.06 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "ECC83" + (at 162.56 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Footprints:Valve_ECC-83-1" + (at 162.56 118.11 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 157.48 107.95 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 157.48 107.95 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "6" + (uuid "80ec201d-a52d-4fd2-ba38-1cdc1981bbfe") + ) + (pin "7" + (uuid "d54d8a7a-7cd2-40e2-af4a-a93696020627") + ) + (pin "8" + (uuid "8c3c98bc-57bf-4abd-86bc-7b589d000949") + ) + (pin "1" + (uuid "5ddcc9fb-67b8-4a28-ab44-71770d18fdc9") + ) + (pin "2" + (uuid "0644856d-0b21-4dec-9d81-e558c1bbc258") + ) + (pin "3" + (uuid "802841aa-9aa8-4683-9a8b-c79629d12511") + ) + (pin "4" + (uuid "137be269-0f98-42d1-91e1-2262f482b268") + ) + (pin "5" + (uuid "012226b2-d408-4aca-bdb4-5434ce0353d8") + ) + (pin "9" + (uuid "94a316aa-51cc-4d72-a077-b17481f80966") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "U1") + (unit 2) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:ECC83") + (at 63.5 74.93 0) + (unit 3) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-000048b4f266") + (property "Reference" "U1" + (at 63.5 63.5 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "ECC83" + (at 63.5 66.04 0) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Footprints:Valve_ECC-83-1" + (at 57.15 74.93 90) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 63.5 74.93 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 63.5 74.93 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "6" + (uuid "e1edffc8-a42d-4e92-819d-af672c5b448f") + ) + (pin "7" + (uuid "d47b30f2-1df2-4c12-a2e7-cd034702a671") + ) + (pin "8" + (uuid "d0c889d7-c38a-41ea-a23a-653d6aaec11d") + ) + (pin "1" + (uuid "6940fbb4-6bc6-403e-9a2e-59d49828a683") + ) + (pin "2" + (uuid "e7f8c376-bfb6-4458-bb95-ab4f9cd52db2") + ) + (pin "3" + (uuid "d780e109-c85e-4185-9956-dda4fde43edd") + ) + (pin "4" + (uuid "ff7c5f3b-943d-45e2-9097-0672973e1c9d") + ) + (pin "5" + (uuid "cd044e93-d16e-49c6-9c3f-ff2d37031cd6") + ) + (pin "9" + (uuid "9ca87f9c-db08-4bb2-b129-650dccc6a99d") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "U1") + (unit 3) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:GND") + (at 86.36 62.23 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-000053b6f370") + (property "Reference" "#PWR06" + (at 86.36 62.23 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 86.36 64.008 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "" + (at 86.36 62.23 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 86.36 62.23 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 86.36 62.23 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "0925899f-4041-41ea-abb1-12963c2e5446") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#PWR06") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CONN_1") + (at 158.75 177.8 0) + (unit 1) + (exclude_from_sim yes) + (in_bom no) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-000054a5890a") + (property "Reference" "P5" + (at 160.782 177.8 0) + (effects + (font + (size 1.016 1.016) + ) + (justify left) + ) + ) + (property "Value" "MOUNTING_HOLE" + (at 158.75 176.403 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "Footprints:MountingHole_3.2mm_M3_DIN965_Pad" + (at 158.75 177.8 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 158.75 177.8 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 158.75 177.8 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "b47c1d61-902c-48ae-8802-734a54efb1a1") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "P5") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CONN_1") + (at 158.75 180.34 0) + (unit 1) + (exclude_from_sim yes) + (in_bom no) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-000054a58c65") + (property "Reference" "P6" + (at 160.782 180.34 0) + (effects + (font + (size 1.016 1.016) + ) + (justify left) + ) + ) + (property "Value" "MOUNTING_HOLE" + (at 158.75 178.943 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "Footprints:MountingHole_3.2mm_M3_DIN965_Pad" + (at 158.75 180.34 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 158.75 180.34 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 158.75 180.34 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "f56a8913-ef96-4f3b-aa8b-1a13f8229c54") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "P6") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CONN_1") + (at 158.75 182.88 0) + (unit 1) + (exclude_from_sim yes) + (in_bom no) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-000054a58c8a") + (property "Reference" "P7" + (at 160.782 182.88 0) + (effects + (font + (size 1.016 1.016) + ) + (justify left) + ) + ) + (property "Value" "MOUNTING_HOLE" + (at 158.75 181.483 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "Footprints:MountingHole_3.2mm_M3_DIN965_Pad" + (at 158.75 182.88 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 158.75 182.88 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 158.75 182.88 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "ca1e588f-0267-46c9-b6cd-66fd0b13056c") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "P7") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:CONN_1") + (at 158.75 185.42 0) + (unit 1) + (exclude_from_sim yes) + (in_bom no) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-000054a58ca3") + (property "Reference" "P8" + (at 160.782 185.42 0) + (effects + (font + (size 1.016 1.016) + ) + (justify left) + ) + ) + (property "Value" "MOUNTING_HOLE" + (at 158.75 184.023 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "Footprints:MountingHole_3.2mm_M3_DIN965_Pad" + (at 158.75 185.42 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 158.75 185.42 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 158.75 185.42 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "37f3c682-6c02-4995-a68d-ba2bc9fac1ab") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "P8") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:PWR_FLAG") + (at 76.2 49.53 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000550ea992") + (property "Reference" "#FLG07" + (at 76.2 42.672 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "PWR_FLAG" + (at 76.2 43.688 0) + (effects + (font + (size 0.762 0.762) + ) + ) + ) + (property "Footprint" "" + (at 76.2 49.53 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 76.2 49.53 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 76.2 49.53 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "b1b98e40-b69b-4b60-8486-e01c25c7ae90") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#FLG07") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:GND") + (at 50.8 57.15 0) + (mirror y) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000550eab37") + (property "Reference" "#PWR08" + (at 50.8 57.15 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 50.8 58.928 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "" + (at 50.8 57.15 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Datasheet" "" + (at 50.8 57.15 0) + (effects + (font + (size 1.524 1.524) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 50.8 57.15 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "2441dfd7-a2b0-441b-ab95-dc80d3bfb10c") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#PWR08") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "ecc83-pp:GND") + (at 138.43 116.84 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "00000000-0000-0000-0000-0000550eaf5a") + (property "Reference" "#PWR09" + (at 138.43 116.84 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Value" "GND" + (at 138.43 118.618 0) + (effects + (font + (size 0.762 0.762) + ) + (hide yes) + ) + ) + (property "Footprint" "" + (at 138.43 116.84 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Datasheet" "" + (at 138.43 116.84 0) + (effects + (font + (size 1.524 1.524) + ) + ) + ) + (property "Description" "" + (at 138.43 116.84 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "a8c7d88f-5a85-486a-8858-7ed63775f7db") + ) + (instances + (project "ecc83-pp" + (path "/28f865a0-4433-4a53-bbd7-b62f276848e4" + (reference "#PWR09") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) + (embedded_fonts no) +)