kicad-sch-api 0.4.1__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kicad_sch_api/__init__.py +67 -2
- kicad_sch_api/cli/kicad_to_python.py +169 -0
- kicad_sch_api/collections/__init__.py +23 -8
- kicad_sch_api/collections/base.py +369 -59
- kicad_sch_api/collections/components.py +1376 -187
- kicad_sch_api/collections/junctions.py +129 -289
- kicad_sch_api/collections/labels.py +391 -287
- kicad_sch_api/collections/wires.py +202 -316
- kicad_sch_api/core/__init__.py +37 -2
- kicad_sch_api/core/component_bounds.py +34 -12
- kicad_sch_api/core/components.py +146 -7
- kicad_sch_api/core/config.py +25 -12
- kicad_sch_api/core/connectivity.py +692 -0
- kicad_sch_api/core/exceptions.py +175 -0
- kicad_sch_api/core/factories/element_factory.py +3 -1
- kicad_sch_api/core/formatter.py +24 -7
- kicad_sch_api/core/geometry.py +94 -5
- kicad_sch_api/core/managers/__init__.py +4 -0
- kicad_sch_api/core/managers/base.py +76 -0
- kicad_sch_api/core/managers/file_io.py +3 -1
- kicad_sch_api/core/managers/format_sync.py +3 -2
- kicad_sch_api/core/managers/graphics.py +3 -2
- kicad_sch_api/core/managers/hierarchy.py +661 -0
- kicad_sch_api/core/managers/metadata.py +4 -2
- kicad_sch_api/core/managers/sheet.py +52 -14
- kicad_sch_api/core/managers/text_elements.py +3 -2
- kicad_sch_api/core/managers/validation.py +3 -2
- kicad_sch_api/core/managers/wire.py +112 -54
- kicad_sch_api/core/parsing_utils.py +63 -0
- kicad_sch_api/core/pin_utils.py +103 -9
- kicad_sch_api/core/schematic.py +343 -29
- kicad_sch_api/core/types.py +79 -7
- kicad_sch_api/exporters/__init__.py +10 -0
- kicad_sch_api/exporters/python_generator.py +610 -0
- kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
- kicad_sch_api/geometry/__init__.py +15 -3
- kicad_sch_api/geometry/routing.py +211 -0
- kicad_sch_api/parsers/elements/label_parser.py +30 -8
- kicad_sch_api/parsers/elements/symbol_parser.py +255 -83
- kicad_sch_api/utils/logging.py +555 -0
- kicad_sch_api/utils/logging_decorators.py +587 -0
- kicad_sch_api/utils/validation.py +16 -22
- kicad_sch_api/wrappers/__init__.py +14 -0
- kicad_sch_api/wrappers/base.py +89 -0
- kicad_sch_api/wrappers/wire.py +198 -0
- kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
- kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
- kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
- mcp_server/__init__.py +34 -0
- mcp_server/example_logging_integration.py +506 -0
- mcp_server/models.py +252 -0
- mcp_server/server.py +357 -0
- mcp_server/tools/__init__.py +32 -0
- mcp_server/tools/component_tools.py +516 -0
- mcp_server/tools/connectivity_tools.py +532 -0
- mcp_server/tools/consolidated_tools.py +1216 -0
- mcp_server/tools/pin_discovery.py +333 -0
- mcp_server/utils/__init__.py +38 -0
- mcp_server/utils/logging.py +127 -0
- mcp_server/utils.py +36 -0
- kicad_sch_api-0.4.1.dist-info/METADATA +0 -491
- kicad_sch_api-0.4.1.dist-info/RECORD +0 -87
- kicad_sch_api-0.4.1.dist-info/entry_points.txt +0 -2
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.4.1.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,6 +10,7 @@ from typing import Any, Dict, List, Optional
|
|
|
10
10
|
|
|
11
11
|
import sexpdata
|
|
12
12
|
|
|
13
|
+
from ...core.parsing_utils import parse_bool_property
|
|
13
14
|
from ...core.types import Point
|
|
14
15
|
from ..base import BaseElementParser
|
|
15
16
|
|
|
@@ -38,6 +39,7 @@ class SymbolParser(BaseElementParser):
|
|
|
38
39
|
"pins": [],
|
|
39
40
|
"in_bom": True,
|
|
40
41
|
"on_board": True,
|
|
42
|
+
"instances": [],
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
for sub_item in item[1:]:
|
|
@@ -61,6 +63,11 @@ class SymbolParser(BaseElementParser):
|
|
|
61
63
|
prop_data = self._parse_property(sub_item)
|
|
62
64
|
if prop_data:
|
|
63
65
|
prop_name = prop_data.get("name")
|
|
66
|
+
|
|
67
|
+
# Store original S-expression for format preservation
|
|
68
|
+
sexp_key = f"__sexp_{prop_name}"
|
|
69
|
+
symbol_data["properties"][sexp_key] = sub_item
|
|
70
|
+
|
|
64
71
|
if prop_name == "Reference":
|
|
65
72
|
symbol_data["reference"] = prop_data.get("value")
|
|
66
73
|
elif prop_name == "Value":
|
|
@@ -74,9 +81,20 @@ class SymbolParser(BaseElementParser):
|
|
|
74
81
|
prop_value = str(prop_value).replace('\\"', '"')
|
|
75
82
|
symbol_data["properties"][prop_name] = prop_value
|
|
76
83
|
elif element_type == "in_bom":
|
|
77
|
-
symbol_data["in_bom"] =
|
|
84
|
+
symbol_data["in_bom"] = parse_bool_property(
|
|
85
|
+
sub_item[1] if len(sub_item) > 1 else None,
|
|
86
|
+
default=True
|
|
87
|
+
)
|
|
78
88
|
elif element_type == "on_board":
|
|
79
|
-
symbol_data["on_board"] =
|
|
89
|
+
symbol_data["on_board"] = parse_bool_property(
|
|
90
|
+
sub_item[1] if len(sub_item) > 1 else None,
|
|
91
|
+
default=True
|
|
92
|
+
)
|
|
93
|
+
elif element_type == "instances":
|
|
94
|
+
# Parse instances section
|
|
95
|
+
instances = self._parse_instances(sub_item)
|
|
96
|
+
if instances:
|
|
97
|
+
symbol_data["instances"] = instances
|
|
80
98
|
|
|
81
99
|
return symbol_data
|
|
82
100
|
|
|
@@ -95,6 +113,67 @@ class SymbolParser(BaseElementParser):
|
|
|
95
113
|
"value": item[2] if len(item) > 2 else None,
|
|
96
114
|
}
|
|
97
115
|
|
|
116
|
+
def _parse_instances(self, item: List[Any]) -> List[Dict[str, Any]]:
|
|
117
|
+
"""
|
|
118
|
+
Parse instances section from S-expression.
|
|
119
|
+
|
|
120
|
+
Format:
|
|
121
|
+
(instances
|
|
122
|
+
(project "project_name"
|
|
123
|
+
(path "/root_uuid/sheet_uuid"
|
|
124
|
+
(reference "R1")
|
|
125
|
+
(unit 1))))
|
|
126
|
+
"""
|
|
127
|
+
from ...core.types import SymbolInstance
|
|
128
|
+
|
|
129
|
+
instances = []
|
|
130
|
+
|
|
131
|
+
for sub_item in item[1:]:
|
|
132
|
+
if not isinstance(sub_item, list) or len(sub_item) == 0:
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
element_type = str(sub_item[0]) if isinstance(sub_item[0], sexpdata.Symbol) else None
|
|
136
|
+
|
|
137
|
+
if element_type == "project":
|
|
138
|
+
# Parse project instance
|
|
139
|
+
project = sub_item[1] if len(sub_item) > 1 else None
|
|
140
|
+
|
|
141
|
+
# Find path section within project
|
|
142
|
+
for project_sub in sub_item[2:]:
|
|
143
|
+
if not isinstance(project_sub, list) or len(project_sub) == 0:
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
path_type = str(project_sub[0]) if isinstance(project_sub[0], sexpdata.Symbol) else None
|
|
147
|
+
|
|
148
|
+
if path_type == "path":
|
|
149
|
+
# Extract path value
|
|
150
|
+
path = project_sub[1] if len(project_sub) > 1 else "/"
|
|
151
|
+
reference = None
|
|
152
|
+
unit = 1
|
|
153
|
+
|
|
154
|
+
# Parse reference and unit from path subsections
|
|
155
|
+
for path_sub in project_sub[2:]:
|
|
156
|
+
if not isinstance(path_sub, list) or len(path_sub) == 0:
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
path_sub_type = str(path_sub[0]) if isinstance(path_sub[0], sexpdata.Symbol) else None
|
|
160
|
+
|
|
161
|
+
if path_sub_type == "reference":
|
|
162
|
+
reference = path_sub[1] if len(path_sub) > 1 else None
|
|
163
|
+
elif path_sub_type == "unit":
|
|
164
|
+
unit = int(path_sub[1]) if len(path_sub) > 1 else 1
|
|
165
|
+
|
|
166
|
+
# Create instance
|
|
167
|
+
if path and reference:
|
|
168
|
+
instance = SymbolInstance(
|
|
169
|
+
path=path,
|
|
170
|
+
reference=reference,
|
|
171
|
+
unit=unit
|
|
172
|
+
)
|
|
173
|
+
instances.append(instance)
|
|
174
|
+
|
|
175
|
+
return instances
|
|
176
|
+
|
|
98
177
|
|
|
99
178
|
def _symbol_to_sexp(self, symbol_data: Dict[str, Any], schematic_uuid: str = None) -> List[Any]:
|
|
100
179
|
"""Convert symbol to S-expression."""
|
|
@@ -132,40 +211,85 @@ class SymbolParser(BaseElementParser):
|
|
|
132
211
|
# Add properties with proper positioning and effects
|
|
133
212
|
lib_id = symbol_data.get("lib_id", "")
|
|
134
213
|
is_power_symbol = "power:" in lib_id
|
|
214
|
+
rotation = symbol_data.get("rotation", 0)
|
|
135
215
|
|
|
136
216
|
if symbol_data.get("reference"):
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
217
|
+
# Check for preserved S-expression
|
|
218
|
+
preserved_ref = symbol_data.get("properties", {}).get("__sexp_Reference")
|
|
219
|
+
if preserved_ref:
|
|
220
|
+
# Use preserved format but update the value
|
|
221
|
+
ref_prop = list(preserved_ref)
|
|
222
|
+
if len(ref_prop) >= 3:
|
|
223
|
+
ref_prop[2] = symbol_data["reference"]
|
|
224
|
+
sexp.append(ref_prop)
|
|
225
|
+
else:
|
|
226
|
+
# No preserved format - create new (for newly added components)
|
|
227
|
+
ref_hide = is_power_symbol
|
|
228
|
+
ref_prop = self._create_property_with_positioning(
|
|
229
|
+
"Reference", symbol_data["reference"], pos, 0, "left", hide=ref_hide, rotation=rotation
|
|
230
|
+
)
|
|
231
|
+
sexp.append(ref_prop)
|
|
143
232
|
|
|
144
233
|
if symbol_data.get("value"):
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
)
|
|
234
|
+
# Check for preserved S-expression
|
|
235
|
+
preserved_val = symbol_data.get("properties", {}).get("__sexp_Value")
|
|
236
|
+
if preserved_val:
|
|
237
|
+
# Use preserved format but update the value
|
|
238
|
+
val_prop = list(preserved_val)
|
|
239
|
+
if len(val_prop) >= 3:
|
|
240
|
+
val_prop[2] = symbol_data["value"]
|
|
241
|
+
sexp.append(val_prop)
|
|
150
242
|
else:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
243
|
+
# No preserved format - create new (for newly added components)
|
|
244
|
+
if is_power_symbol:
|
|
245
|
+
val_prop = self._create_power_symbol_value_property(
|
|
246
|
+
symbol_data["value"], pos, lib_id, rotation
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
val_prop = self._create_property_with_positioning(
|
|
250
|
+
"Value", symbol_data["value"], pos, 1, "left", rotation=rotation
|
|
251
|
+
)
|
|
252
|
+
sexp.append(val_prop)
|
|
155
253
|
|
|
156
254
|
footprint = symbol_data.get("footprint")
|
|
157
255
|
if footprint is not None: # Include empty strings but not None
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
256
|
+
# Check for preserved S-expression
|
|
257
|
+
preserved_fp = symbol_data.get("properties", {}).get("__sexp_Footprint")
|
|
258
|
+
if preserved_fp:
|
|
259
|
+
# Use preserved format but update the value
|
|
260
|
+
fp_prop = list(preserved_fp)
|
|
261
|
+
if len(fp_prop) >= 3:
|
|
262
|
+
fp_prop[2] = footprint
|
|
263
|
+
sexp.append(fp_prop)
|
|
264
|
+
else:
|
|
265
|
+
# No preserved format - create new (for newly added components)
|
|
266
|
+
fp_prop = self._create_property_with_positioning(
|
|
267
|
+
"Footprint", footprint, pos, 2, "left", hide=True
|
|
268
|
+
)
|
|
269
|
+
sexp.append(fp_prop)
|
|
162
270
|
|
|
163
271
|
for prop_name, prop_value in symbol_data.get("properties", {}).items():
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
272
|
+
# Skip internal preservation keys
|
|
273
|
+
if prop_name.startswith("__sexp_"):
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
# Check if we have a preserved S-expression for this custom property
|
|
277
|
+
preserved_prop = symbol_data.get("properties", {}).get(f"__sexp_{prop_name}")
|
|
278
|
+
if preserved_prop:
|
|
279
|
+
# Use preserved format but update the value
|
|
280
|
+
prop = list(preserved_prop)
|
|
281
|
+
if len(prop) >= 3:
|
|
282
|
+
# Re-escape quotes when saving
|
|
283
|
+
escaped_value = str(prop_value).replace('"', '\\"')
|
|
284
|
+
prop[2] = escaped_value
|
|
285
|
+
sexp.append(prop)
|
|
286
|
+
else:
|
|
287
|
+
# No preserved format - create new (for newly added properties)
|
|
288
|
+
escaped_value = str(prop_value).replace('"', '\\"')
|
|
289
|
+
prop = self._create_property_with_positioning(
|
|
290
|
+
prop_name, escaped_value, pos, 3, "left", hide=True
|
|
291
|
+
)
|
|
292
|
+
sexp.append(prop)
|
|
169
293
|
|
|
170
294
|
# Add pin UUID assignments (required by KiCAD)
|
|
171
295
|
for pin in symbol_data.get("pins", []):
|
|
@@ -177,53 +301,83 @@ class SymbolParser(BaseElementParser):
|
|
|
177
301
|
# Add instances section (required by KiCAD)
|
|
178
302
|
from ...core.config import config
|
|
179
303
|
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
f"
|
|
194
|
-
|
|
304
|
+
# HIERARCHICAL FIX: Check if user explicitly set instances
|
|
305
|
+
# If so, preserve them exactly as-is (don't generate!)
|
|
306
|
+
user_instances = symbol_data.get("instances")
|
|
307
|
+
if user_instances:
|
|
308
|
+
logger.debug(f"🔍 HIERARCHICAL FIX: Component {symbol_data.get('reference')} has {len(user_instances)} user-set instance(s)")
|
|
309
|
+
# Build instances sexp from user data
|
|
310
|
+
instances_sexp = [sexpdata.Symbol("instances")]
|
|
311
|
+
for inst in user_instances:
|
|
312
|
+
project = inst.get('project', getattr(self, 'project_name', 'circuit'))
|
|
313
|
+
path = inst.get('path', '/')
|
|
314
|
+
reference = inst.get('reference', symbol_data.get('reference', 'U?'))
|
|
315
|
+
unit = inst.get('unit', 1)
|
|
316
|
+
|
|
317
|
+
logger.debug(f" Instance: project={project}, path={path}, ref={reference}, unit={unit}")
|
|
318
|
+
|
|
319
|
+
instances_sexp.append([
|
|
320
|
+
sexpdata.Symbol("project"),
|
|
321
|
+
project,
|
|
322
|
+
[
|
|
323
|
+
sexpdata.Symbol("path"),
|
|
324
|
+
path, # PRESERVE user-set hierarchical path!
|
|
325
|
+
[sexpdata.Symbol("reference"), reference],
|
|
326
|
+
[sexpdata.Symbol("unit"), unit],
|
|
327
|
+
],
|
|
328
|
+
])
|
|
329
|
+
sexp.append(instances_sexp)
|
|
195
330
|
else:
|
|
196
|
-
#
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
331
|
+
# No user-set instances - generate default (backward compatibility)
|
|
332
|
+
logger.debug(f"🔍 HIERARCHICAL FIX: Component {symbol_data.get('reference')} has NO user instances, generating default")
|
|
333
|
+
|
|
334
|
+
# Get project name from config or properties
|
|
335
|
+
project_name = symbol_data.get("properties", {}).get("project_name")
|
|
336
|
+
if not project_name:
|
|
337
|
+
project_name = getattr(self, "project_name", config.defaults.project_name)
|
|
338
|
+
|
|
339
|
+
# CRITICAL FIX: Use the FULL hierarchy_path from properties if available
|
|
340
|
+
# For hierarchical schematics, this contains the complete path: /root_uuid/sheet_symbol_uuid/...
|
|
341
|
+
# This ensures KiCad can properly annotate components in sub-sheets
|
|
342
|
+
hierarchy_path = symbol_data.get("properties", {}).get("hierarchy_path")
|
|
343
|
+
if hierarchy_path:
|
|
344
|
+
# Use the full hierarchical path (includes root + all sheet symbols)
|
|
345
|
+
instance_path = hierarchy_path
|
|
346
|
+
logger.debug(
|
|
347
|
+
f"🔧 Using FULL hierarchy_path: {instance_path} for component {symbol_data.get('reference', 'unknown')}"
|
|
348
|
+
)
|
|
349
|
+
else:
|
|
350
|
+
# Fallback: use root_uuid or schematic_uuid for flat designs
|
|
351
|
+
root_uuid = (
|
|
352
|
+
symbol_data.get("properties", {}).get("root_uuid")
|
|
353
|
+
or schematic_uuid
|
|
354
|
+
or str(uuid.uuid4())
|
|
355
|
+
)
|
|
356
|
+
instance_path = f"/{root_uuid}"
|
|
357
|
+
logger.debug(
|
|
358
|
+
f"🔧 Using root UUID path: {instance_path} for component {symbol_data.get('reference', 'unknown')}"
|
|
359
|
+
)
|
|
360
|
+
|
|
203
361
|
logger.debug(
|
|
204
|
-
f"🔧
|
|
362
|
+
f"🔧 Component properties keys: {list(symbol_data.get('properties', {}).keys())}"
|
|
205
363
|
)
|
|
364
|
+
logger.debug(f"🔧 Using project name: '{project_name}'")
|
|
206
365
|
|
|
207
|
-
|
|
208
|
-
f"🔧 Component properties keys: {list(symbol_data.get('properties', {}).keys())}"
|
|
209
|
-
)
|
|
210
|
-
logger.debug(f"🔧 Using project name: '{project_name}'")
|
|
211
|
-
|
|
212
|
-
sexp.append(
|
|
213
|
-
[
|
|
214
|
-
sexpdata.Symbol("instances"),
|
|
366
|
+
sexp.append(
|
|
215
367
|
[
|
|
216
|
-
sexpdata.Symbol("
|
|
217
|
-
project_name,
|
|
368
|
+
sexpdata.Symbol("instances"),
|
|
218
369
|
[
|
|
219
|
-
sexpdata.Symbol("
|
|
220
|
-
|
|
221
|
-
[
|
|
222
|
-
|
|
370
|
+
sexpdata.Symbol("project"),
|
|
371
|
+
project_name,
|
|
372
|
+
[
|
|
373
|
+
sexpdata.Symbol("path"),
|
|
374
|
+
instance_path,
|
|
375
|
+
[sexpdata.Symbol("reference"), symbol_data.get("reference", "U?")],
|
|
376
|
+
[sexpdata.Symbol("unit"), symbol_data.get("unit", 1)],
|
|
377
|
+
],
|
|
223
378
|
],
|
|
224
|
-
]
|
|
225
|
-
|
|
226
|
-
)
|
|
379
|
+
]
|
|
380
|
+
)
|
|
227
381
|
|
|
228
382
|
return sexp
|
|
229
383
|
|
|
@@ -236,13 +390,14 @@ class SymbolParser(BaseElementParser):
|
|
|
236
390
|
offset_index: int,
|
|
237
391
|
justify: str = "left",
|
|
238
392
|
hide: bool = False,
|
|
393
|
+
rotation: float = 0,
|
|
239
394
|
) -> List[Any]:
|
|
240
395
|
"""Create a property with proper positioning and effects like KiCAD."""
|
|
241
396
|
from ...core.config import config
|
|
242
397
|
|
|
243
398
|
# Calculate property position using configuration
|
|
244
|
-
prop_x, prop_y,
|
|
245
|
-
prop_name, (component_pos.x, component_pos.y), offset_index
|
|
399
|
+
prop_x, prop_y, text_rotation = config.get_property_position(
|
|
400
|
+
prop_name, (component_pos.x, component_pos.y), offset_index, rotation
|
|
246
401
|
)
|
|
247
402
|
|
|
248
403
|
# Build effects section based on hide status
|
|
@@ -266,7 +421,7 @@ class SymbolParser(BaseElementParser):
|
|
|
266
421
|
sexpdata.Symbol("at"),
|
|
267
422
|
round(prop_x, 4) if prop_x != int(prop_x) else int(prop_x),
|
|
268
423
|
round(prop_y, 4) if prop_y != int(prop_y) else int(prop_y),
|
|
269
|
-
|
|
424
|
+
text_rotation,
|
|
270
425
|
],
|
|
271
426
|
effects,
|
|
272
427
|
]
|
|
@@ -275,22 +430,39 @@ class SymbolParser(BaseElementParser):
|
|
|
275
430
|
|
|
276
431
|
|
|
277
432
|
def _create_power_symbol_value_property(
|
|
278
|
-
self, value: str, component_pos: Point, lib_id: str
|
|
433
|
+
self, value: str, component_pos: Point, lib_id: str, rotation: float = 0
|
|
279
434
|
) -> List[Any]:
|
|
280
|
-
"""Create Value property for power symbols with correct positioning.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
435
|
+
"""Create Value property for power symbols with correct positioning.
|
|
436
|
+
|
|
437
|
+
Matches circuit-synth power_symbol_positioning.py logic exactly.
|
|
438
|
+
"""
|
|
439
|
+
offset = 5.08 # KiCad standard offset
|
|
440
|
+
is_gnd_type = "GND" in lib_id.upper() or "VSS" in lib_id.upper()
|
|
441
|
+
|
|
442
|
+
# Rotation-aware positioning (matching circuit-synth logic)
|
|
443
|
+
if rotation == 0:
|
|
444
|
+
if is_gnd_type:
|
|
445
|
+
prop_x, prop_y = component_pos.x, component_pos.y + offset # GND points down, text below
|
|
446
|
+
else:
|
|
447
|
+
prop_x, prop_y = component_pos.x, component_pos.y - offset # VCC points up, text above
|
|
448
|
+
elif rotation == 90:
|
|
449
|
+
if is_gnd_type:
|
|
450
|
+
prop_x, prop_y = component_pos.x - offset, component_pos.y # GND left, text left
|
|
451
|
+
else:
|
|
452
|
+
prop_x, prop_y = component_pos.x + offset, component_pos.y # VCC right, text right
|
|
453
|
+
elif rotation == 180:
|
|
454
|
+
if is_gnd_type:
|
|
455
|
+
prop_x, prop_y = component_pos.x, component_pos.y - offset # GND inverted up, text above
|
|
456
|
+
else:
|
|
457
|
+
prop_x, prop_y = component_pos.x, component_pos.y + offset # VCC inverted down, text below
|
|
458
|
+
elif rotation == 270:
|
|
459
|
+
if is_gnd_type:
|
|
460
|
+
prop_x, prop_y = component_pos.x + offset, component_pos.y # GND right, text right
|
|
461
|
+
else:
|
|
462
|
+
prop_x, prop_y = component_pos.x - offset, component_pos.y # VCC left, text left
|
|
290
463
|
else:
|
|
291
|
-
#
|
|
292
|
-
prop_x = component_pos.x
|
|
293
|
-
prop_y = component_pos.y + 3.556
|
|
464
|
+
# Fallback for non-standard rotations
|
|
465
|
+
prop_x, prop_y = component_pos.x, component_pos.y - offset if not is_gnd_type else component_pos.y + offset
|
|
294
466
|
|
|
295
467
|
prop_sexp = [
|
|
296
468
|
sexpdata.Symbol("property"),
|