8000 i am ashamed for such a large commit · dragoncoder047/schemascii@dff1320 · GitHub
[go: up one dir, main page]

Skip to content

Commit dff1320

Browse files
i am ashamed for such a large commit
1 parent 793a07d commit dff1320

20 files changed

+283
-247
lines changed

options.md

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Data Section Options
33

44
<!-- This file was automatically generated by scripts/docs.py
5-
on Fri Sep 13 12:59:48 2024 -->
5+
on Fri Apr 11 14:37:03 2025 -->
66

77
## Scope `:WIRE-TAG`
88

@@ -73,7 +73,7 @@ Reference Designators: `B`, `BT`, `BAT`
7373
| color | str | Default color for everything | 'black' |
7474
| offset_scale | float | How far to offset the label from the center of the component. Relative to the global scale option. | 1 |
7575
| font | str | Text font for labels | 'monospace' |
76-
| value | str | Battery voltage | (required) |
76+
| value | str | Battery voltage | *required* |
7777
| capacity | str | Battery capacity in amp-hours | (no value) |
7878

7979
## Component `:CAPACITOR`
@@ -87,9 +87,37 @@ Reference Designators: `C`, `VC`, `CV`
8787
| color | str | Default color for everything | 'black' |
8888
| offset_scale | float | How far to offset the label from the center of the component. Relative to the global scale option. | 1 |
8989
| font | str | Text font for labels | 'monospace' |
90-
| value | str | Capacitance in farads | (required) |
90+
| value | str | Capacitance in farads | *required* |
9191
| voltage | str | Maximum voltage tolerance in volts | (no value) |
9292

93+
## Component `:DIODE`
94+
95+
Reference Designators: `D`, `CR`, `LED`, `IR`
96+
97+
| Option | Value | Description | Default |
98+
|:------:|:-----:|:------------|:-------:|
99+
| scale | float | Scale by which to enlarge the entire diagram by | 15 |
100+
| linewidth | float | Width of drawn lines | 2 |
101+
| color | str | Default color for everything | 'black' |
102+
| offset_scale | float | How far to offset the label from the center of the component. Relative to the global scale option. | 1 |
103+
| font | str | Text font for labels | 'monospace' |
104+
| voltage | str | Maximum reverse voltage rating | (no value) |
105+
| current | str | Maximum current rating | (no value) |
106+
107+
## Component `:LED`
108+
109+
Reference Designators: `D`, `CR`, `LED`, `IR`
110+
111+
| Option | Value | Description | Default |
112+
|:------:|:-----:|:------------|:-------:|
113+
| scale | float | Scale by which to enlarge the entire diagram by | 15 |
114+
| linewidth | float | Width of drawn lines | 2 |
115+
| color | str | Default color for everything | 'black' |
116+
| offset_scale | float | How far to offset the label from the center of the component. Relative to the global scale option. | 1 |
117+
| font | str | Text font for labels | 'monospace' |
118+
| voltage | str | Maximum reverse voltage rating | (no value) |
119+
| current | str | Maximum current rating | (no value) |
120+
93121
## Component `:INDUCTOR`
94122

95123
Reference Designators: `L`, `VL`, `LV`
@@ -101,7 +129,7 @@ Reference Designators: `L`, `VL`, `LV`
101129
| color | str | Default color for everything | 'black' |
102130
| offset_scale | float | How far to offset the label from the center of the component. Relative to the global scale option. | 1 |
103131
| font | str | Text font for labels | 'monospace' |
104-
| value | str | Inductance in henries | (required) |
132+
| value | str | Inductance in henries | *required* |
105133
| current | str | Maximum current rating in amps | (no value) |
106134

107135
## Component `:RESISTOR`
@@ -115,5 +143,5 @@ Reference Designators: `R`, `VR`, `RV`
115143
| color | str | Default color for everything | 'black' |
116144
| offset_scale | float | How far to offset the label from the center of the component. Relative to the global scale option. | 1 |
117145
| font | str | Text font for labels | 'monospace' |
118-
| value | str | Resistance in ohms | (required) |
119-
| power | str | Maximum power dissipation in watts (i.e. size of the resistor) | (no value) |
146+
| value | str | Resistance in ohms | *required* |
147+
| wattage | str | Maximum power dissipation in watts (i.e. size of the resistor) | (no value) |

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ classifiers = [
2121
"Topic :: Scientific/Engineering",
2222
]
2323
keywords = ["schematic", "electronics", "circuit", "diagram"]
24-
requires-python = ">=3.10"
24+
requires-python = ">=3.12"
2525

2626
[project.urls]
2727
Homepage = "https://github.com/dragoncoder047/schemascii"

schemascii/OLD_components_render.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
Cbox,
88
Terminal,
99
BOMData,
10-
XML,
1110
Side,
1211
arrow_points,
1312
polylinegon,
@@ -147,7 +146,7 @@ def integrated_circuit(
147146
sz = (box.p2 - box.p1) * scale
148147
mid = (box.p2 + box.p1) * scale / 2
149148
part_num, *pin_labels = map(str.strip, bom_data.data.split(","))
150-
out = XML.rect(
149+
out = xmltag("rect",
151150
x=box.p1.real * scale,
152151
y=box.p1.imag * scale,
153152
width=sz.real,
@@ -162,8 +161,8 @@ def integrated_circuit(
162161
term.pt + rect(1, SIDE_TO_ANGLE_MAP[term.side]))],
163162
**options)
164163
if "V" in label_style and part_num:
165-
out += XML.text(
166-
XML.tspan(part_num, class_="part-num"),
164+
out += xmltag("text",
165+
xmltag("tspan", part_num, class_="part-num"),
167166
x=mid.real,
168167
y=mid.imag,
169168
text__anchor="middle",
@@ -172,8 +171,8 @@ def integrated_circuit(
172171
)
173172
mid -= 1j * scale
174173
if "L" in label_style and not options["nolabels"]:
175-
out += XML.text(
176-
XML.tspan(f"{box.type}{box.id}", class_="cmp-id"),
174+
out += xmltag("text",
175+
xmltag("tspan", f"{box.type}{box.id}", class_="cmp-id"),
177176
x=mid.real,
178177
y=mid.imag,
179178
text__anchor="middle",
@@ -183,7 +182,7 @@ def integrated_circuit(
183182
s_terminals = sort_terminals_counterclockwise(terminals)
184183
for terminal, label in zip(s_terminals, pin_labels):
185184
sc_text_pt = terminal.pt * scale
186-
out += XML.text(
185+
out += xmltag("text",
187186
label,
188187
x=sc_text_pt.real,
189188
y=sc_text_pt.imag,
@@ -225,7 +224,7 @@ def jack(box: Cbox, terminals: list[Terminal], bom_data: BOMData, **options):
225224
if style == "circle":
226225
return (
227226
bunch_o_lines([(t1, t2)], **options)
228-
+ XML.circle(
227+
+ xmltag("circle",
229228
cx=sc_t2.real,
230229
cy=sc_t2.imag,
231230
r=scale / 4,
@@ -377,13 +376,13 @@ def switch(box: Cbox, terminals: list[Terminal], bom_data: BOMData, **options):
377376
mid = (t1 + t2) / 2
378377
angle = phase(t1 - t2)
379378
scale = options["scale"]
380-
out = (XML.circle(cx=(rect(-scale, angle) + mid * scale).real,
379+
out = (xmltag("circle", cx=(rect(-scale, angle) + mid * scale).real,
381380
cy=(rect(-scale, angle) + mid * scale).imag,
382381
r=scale / 4,
383382
stroke="transparent",
384383
fill=options["stroke"],
385384
class_="filled")
386-
+ XML.circle(cx=(rect(scale, angle) + mid * scale).real,
385+
+ xmltag("circle", cx=(rect(scale, angle) + mid * scale).real,
387386
cy=(rect(scale, angle) + mid * scale).imag,
388387
r=scale / 4,
389388
stroke="transparent",
@@ -448,7 +447,7 @@ def render_component(
448447
"Render the component into an SVG string."
449448
if box.type not in RENDERERS:
450449
raise UnsupportedComponentError(box.type)
451-
return XML.g(
450+
return xmltag("g",
452451
RENDERERS[box.type](box, terminals, bom_data, **options),
453452
class_=f"component {box.type}",
454453
)

schemascii/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,8 @@ def render(filename: str, text: str | None = None,
2828

2929

3030
if __name__ == "__main__":
31-
import schemascii.component as _comp
32-
print(_comp.Component.all_components)
31+
import schemascii.components.resistor as _r
32+
import schemascii.refdes as _rd
33+
import schemascii.utils as _u
34+
print(_r.Resistor(_rd.RefDes("R", 0, "", 0, 0), [[]], [
35+
_u.Terminal(0, "w", 0), _u.Terminal(0, "a", 0)]))

schemascii/__main__.py

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ class DataFudgeAction(argparse.Action):
1212
def __call__(self, parser, namespace, values: str, option_string=None):
1313
scope, sep, pair = values.partition(".")
1414
if not sep:
15-
parser.error("invalid -D format: missing .")
15+
parser.error("invalid -D argument: missing .")
1616
key, sep, val = pair.partition("=")
1717
if not sep:
18-
parser.error("invalid -D format: missing =")
18+
parser.error("invalid -D argument: missing =")
1919
items = getattr(namespace, self.dest) or _d.Data([])
2020
items |= _d.Data(
2121
[_d.Section(scope, {key: _d.parse_simple_value(val)})])
@@ -24,15 +24,24 @@ def __call__(self, parser, namespace, values: str, option_string=None):
2424

2525
def cli_main():
2626
ap = argparse.ArgumentParser(
27-
prog="schemascii", description="Render ASCII-art schematics into SVG.")
27+
prog="schemascii",
28+
description="Render ASCII-art schematics into SVG.")
2829
ap.add_argument(
29-
"-V", "--version", action="version",
30+
"-V",
31+
"--version",
32+
action="version",
3033
version="%(prog)s " + schemascii.__version__)
31-
ap.add_argument("in_file", help="File to process. (default: stdin)",
32-
default="-")
34+
ap.add_argument(
35+
"-i",
36+
"--in",
37+
type=argparse.FileType("r"),
38+
default=None,
39+
dest="in_file",
40+
help="File to process. (default: stdin)")
3341
ap.add_argument(
3442
"-o",
3543
"--out",
44+
type=argparse.FileType("w"),
3645
default=None,
3746
dest="out_file",
3847
help="Output SVG file. (default input file plus .svg, or stdout "
@@ -54,31 +63,33 @@ def cli_main():
5463
"so as to not conflict with your shell.\n\nThis option "
5564
"can be repeated as many times as necessary.")
5665
args = ap.parse_args()
57-
if args.out_file is None:
58-
args.out_file = args.in_file + ".svg"
59-
text = None
60-
if args.in_file == "-":
61-
text = sys.stdin.read()
62-
args.in_file = "<stdin>"
6366
try:
64-
with warnings.catch_warnings(record=True) as captured_warnings:
65-
result_svg = schemascii.render(args.in_file, text, args.fudge)
66-
except _errors.Error as err:
67-
print(type(err).__name__ + ":", err, file=sys.stderr)
68-
sys.exit(1)
69-
if captured_warnings:
70-
for warning in captured_warnings:
71-
print("Warning:", warning.message, file=sys.stderr)
72-
if args.warnings_are_errors:
73-
print("Error: warnings were treated as errors", file=sys.stderr)
74-
sys.exit(1)
75-
if args.out_file == "-":
76-
print(result_svg)
77-
else:
78-
with open(args.out_file, "w", encoding="utf-8") as out:
79-
out.write(result_svg)
80-
if args.verbose:
81-
print("Wrote SVG to", args.out_file)
67+
if args.in_file is None:
68+
args.in_file = sys.stdin
69+
if args.out_file is None:
70+
args.out_file = sys.stdout
71+
elif args.out_file is None:
72+
args.out_file = open(args.in_file.name + ".svg")
73+
try:
74+
with warnings.catch_warnings(record=True) as captured_warnings:
75+
result_svg = schemascii.render(args.in_file.name,
76+
args.in_file.read(), args.fudge)
77+
except _errors.Error as err:
78+
ap.error(str(err))
79+
80+
if captured_warnings:
81+
for warning in captured_warnings:
82+
print("Warning:", warning.message, file=sys.stderr)
83+
if args.warnings_are_errors:
84+
print("Error: warnings were treated as errors",
85+
file=sys.stderr)
86+
sys.exit(1)
87+
88+
args.out_file.write(result_svg)
89+
90+
finally:
91+
args.in_file.close()
92+
args.out_file.close()
8293

8394

8495
if __name__ == "__main__":

schemascii/annotation.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import schemascii.data_consumer as _dc
88
import schemascii.grid as _grid
9-
import schemascii.svg_utils as _svg
9+
import schemascii.svg as _svg
1010

1111
ANNOTATION_RE = re.compile(r"\[([^\]]+)\]")
1212

@@ -37,9 +37,9 @@ def find_all(cls, grid: _grid.Grid) -> list[Annotation]:
3737
return out
3838

3939
def render(self, scale, font, **options) -> str:
40-
return _svg.XML.text(
41-
html.escape(self.content),
42-
x=self.position.real * scale,
43-
y=self.position.imag * scale,
44-
style=f"font-family:{font}",
45-
alignment__baseline="middle")
40+
return _svg.xmltag("text",
41+
html.escape(self.content),
42+
x=self.position.real * scale,
43+
y=self.position.imag * scale,
44+
style=f"font-family:{font}",
45+
alignment__baseline="middle")

schemascii/component.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from __future__ import annotations
22

3+
import types
34
import typing
45
from collections import defaultdict
5-
from dataclasses import dataclass
6+
from dataclasses import dataclass, field
67

78
import schemascii.data_consumer as _dc
89
import schemascii.errors as _errors
@@ -32,6 +33,55 @@ class Component(_dc.DataConsumer, namespaces=(":component",)):
3233
blobs: list[list[complex]] # to support multiple parts.
3334
terminals: list[_utils.Terminal]
3435

36+
# Ellipsis can only appear at the end. this means like a wildcard meaning
37+
# that any other flag is suitable
38+
terminal_flag_opts: typing.ClassVar[
39+
dict[str, tuple[str | None] | t C2EE ypes.EllipsisType]] = (None,)
40+
41+
term_option: str = field(init=False)
42+
43+
def __post_init__(self):
44+
has_any = False
45+
# optimized check for number of terminals if they're all the same
46+
available_lengths = sorted(set(map(
47+
len, self.terminal_flag_opts.values())))
48+
for optlen in available_lengths:
49+
if len(self.terminals) == optlen:
50+
break
51+
else:
52+
raise _errors.TerminalsError(
53+
f"Wrong number of terminals on {self.rd.name}. "
54+
f"Got {len(self.terminals)} but "
55+
f"expected {" or ".join(available_lengths)}")
56+
for fo_name, fo_opt in self.terminal_flag_opts.items():
57+
if fo_opt is ...:
58+
has_any = True
59+
continue
60+
t_copy = self.terminals.copy()
61+
t_sorted: list[_utils.Terminal] = []
62+
match = True
63+
ellipsis = False
64+
for opt in fo_opt:
65+
if opt is ...:
66+
ellipsis = True
67+
break
68+
found = [t for t in t_copy if t.flag == opt]
69+
if not found:
70+
match = False
71+
break
72+
t_copy.remove(found[0])
73+
t_sorted.append(found[0])
74+
if not ellipsis and t_copy:
75+
match = False
76+
if not match:
77+
continue
78+
self.terminals = t_sorted + t_copy
79+
self.term_option = fo_name
80+
return
81+
if not has_any:
82+
raise _errors.TerminalsError(
83+
f"Illegal terminal flags around {self.rd.name}")
84+
3585
@property
3686
def namespaces(self) -> tuple[str, ...]:
3787
return self.rd.name, self.rd.short_name, self.rd.letter, ":component"

0 commit comments

Comments
 (0)
0