circuit-json-to-tscircuit 0.0.49 → 0.0.51
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.
- package/dist/{chunk-YHGRHULT.js → chunk-SGFWINLD.js} +82 -1
- package/dist/cli/main.js +2 -2
- package/dist/lib/index.js +1 -1
- package/lib/generate-footprint-tsx/convert-fabrication.ts +4 -1
- package/lib/get-component-using-template.ts +3 -0
- package/lib/get-pin-attributes.ts +91 -0
- package/package.json +1 -1
- package/tests/fabricationnotetext-rotation.test.ts +26 -0
- package/tests/fixtures/pin-attributes/ap2127k-2.8trg1.json +91 -0
- package/tests/fixtures/pin-attributes/f1c100s.json +1644 -0
- package/tests/test22-pin-attributes.test.tsx +198 -0
- package/tests/ti-evms/ti-drv8307evm-roundtrip.test.tsx +62 -62
- package/tests/ti-evms/ti-lm251772evm-pd-roundtrip.test.tsx +138 -138
- package/tests/ti-evms/ti-lm5155evm-fly-roundtrip.test.tsx +77 -77
- package/tests/ti-evms/ti-lmg342x-bb-evm-roundtrip.test.tsx +109 -109
|
@@ -170,7 +170,9 @@ var convertFabrication = (circuitJson) => {
|
|
|
170
170
|
if (fabText.layer === "bottom") {
|
|
171
171
|
attrs.push(`layer="bottom"`);
|
|
172
172
|
}
|
|
173
|
-
elementStrings.push(
|
|
173
|
+
elementStrings.push(
|
|
174
|
+
`<fabricationnotetext ${attrs.join(" ")}${formatPcbRotationAttr(fabText.ccw_rotation)} />`
|
|
175
|
+
);
|
|
174
176
|
}
|
|
175
177
|
for (const fabRect of fabricationNoteRects) {
|
|
176
178
|
const center = fabRect.center ?? { x: 0, y: 0 };
|
|
@@ -869,6 +871,83 @@ var generateSymbolTsx = (circuitJson) => {
|
|
|
869
871
|
`.trim();
|
|
870
872
|
};
|
|
871
873
|
|
|
874
|
+
// lib/get-pin-attributes.ts
|
|
875
|
+
var attributeNames = {
|
|
876
|
+
is_input: "isInput",
|
|
877
|
+
is_output: "isOutput",
|
|
878
|
+
is_bidirectional: "isBidirectional",
|
|
879
|
+
is_passive: "isPassive",
|
|
880
|
+
can_use_tri_state: "canUseTriState",
|
|
881
|
+
is_using_tri_state: "isUsingTriState",
|
|
882
|
+
can_use_open_collector: "canUseOpenCollector",
|
|
883
|
+
is_using_open_collector: "isUsingOpenCollector",
|
|
884
|
+
can_use_open_emitter: "canUseOpenEmitter",
|
|
885
|
+
is_using_open_emitter: "isUsingOpenEmitter",
|
|
886
|
+
is_gpio: "isGpio",
|
|
887
|
+
highlight_color: "highlightColor",
|
|
888
|
+
must_be_connected: "mustBeConnected",
|
|
889
|
+
provides_power: "providesPower",
|
|
890
|
+
requires_power: "requiresPower",
|
|
891
|
+
provides_ground: "providesGround",
|
|
892
|
+
requires_ground: "requiresGround",
|
|
893
|
+
provides_voltage: "providesVoltage",
|
|
894
|
+
requires_voltage: "requiresVoltage",
|
|
895
|
+
do_not_connect: "doNotConnect",
|
|
896
|
+
include_in_board_pinout: "includeInBoardPinout",
|
|
897
|
+
can_use_internal_pullup: "canUseInternalPullup",
|
|
898
|
+
is_using_internal_pullup: "isUsingInternalPullup",
|
|
899
|
+
needs_external_pullup: "needsExternalPullup",
|
|
900
|
+
can_use_internal_pulldown: "canUseInternalPulldown",
|
|
901
|
+
is_using_internal_pulldown: "isUsingInternalPulldown",
|
|
902
|
+
needs_external_pulldown: "needsExternalPulldown",
|
|
903
|
+
can_use_open_drain: "canUseOpenDrain",
|
|
904
|
+
is_using_open_drain: "isUsingOpenDrain",
|
|
905
|
+
can_use_push_pull: "canUsePushPull",
|
|
906
|
+
is_using_push_pull: "isUsingPushPull",
|
|
907
|
+
should_have_decoupling_capacitor: "shouldHaveDecouplingCapacitor",
|
|
908
|
+
recommended_decoupling_capacitor_capacitance: "recommendedDecouplingCapacitorCapacitance"
|
|
909
|
+
};
|
|
910
|
+
var capabilities = [
|
|
911
|
+
"i2c_sda",
|
|
912
|
+
"i2c_scl",
|
|
913
|
+
"spi_mosi",
|
|
914
|
+
"spi_miso",
|
|
915
|
+
"spi_sck",
|
|
916
|
+
"spi_cs",
|
|
917
|
+
"uart_tx",
|
|
918
|
+
"uart_rx"
|
|
919
|
+
];
|
|
920
|
+
var getPinAttributes = (circuitJson) => {
|
|
921
|
+
const components = circuitJson.filter((e) => e.type === "source_component");
|
|
922
|
+
if (components.length > 1) return void 0;
|
|
923
|
+
const componentId = components[0]?.source_component_id;
|
|
924
|
+
const pins = /* @__PURE__ */ new Map();
|
|
925
|
+
for (const port of circuitJson) {
|
|
926
|
+
if (port.type !== "source_port") continue;
|
|
927
|
+
if (componentId && port.source_component_id && port.source_component_id !== componentId)
|
|
928
|
+
continue;
|
|
929
|
+
const source = { ...port };
|
|
930
|
+
const attributes = {};
|
|
931
|
+
for (const [sourceName, propName] of Object.entries(attributeNames)) {
|
|
932
|
+
if (source[sourceName] !== void 0) {
|
|
933
|
+
attributes[propName] = source[sourceName];
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const supported = capabilities.filter(
|
|
937
|
+
(c) => source[`supports_${c}`] === true
|
|
938
|
+
);
|
|
939
|
+
const active = capabilities.filter(
|
|
940
|
+
(c) => source[`is_configured_for_${c}`] === true
|
|
941
|
+
);
|
|
942
|
+
if (supported.length) attributes.capabilities = supported;
|
|
943
|
+
if (active.length) attributes.activeCapabilities = active;
|
|
944
|
+
if (Object.keys(attributes).length === 0) continue;
|
|
945
|
+
const key = port.pin_number !== void 0 ? `pin${port.pin_number}` : port.name;
|
|
946
|
+
pins.set(key, { ...pins.get(key), ...attributes });
|
|
947
|
+
}
|
|
948
|
+
return pins.size ? Object.fromEntries(pins) : void 0;
|
|
949
|
+
};
|
|
950
|
+
|
|
872
951
|
// lib/get-component-using-template.ts
|
|
873
952
|
var getComponentUsingTemplate = ({
|
|
874
953
|
pinLabels,
|
|
@@ -880,6 +959,7 @@ var getComponentUsingTemplate = ({
|
|
|
880
959
|
}) => {
|
|
881
960
|
const footprintTsx = generateFootprintTsx(circuitJson);
|
|
882
961
|
const symbolTsx = generateSymbolTsx(circuitJson);
|
|
962
|
+
const pinAttributes = getPinAttributes(circuitJson);
|
|
883
963
|
return `
|
|
884
964
|
import { type ChipProps } from "tscircuit"
|
|
885
965
|
${pinLabels ? `const pinLabels = ${JSON.stringify(pinLabels, null, " ")} as const
|
|
@@ -888,6 +968,7 @@ ${pinLabels ? `const pinLabels = ${JSON.stringify(pinLabels, null, " ")} as con
|
|
|
888
968
|
${footprintTsx ? `footprint={${footprintTsx}}` : ""}
|
|
889
969
|
${symbolTsx ? `symbol={${symbolTsx}}` : ""}
|
|
890
970
|
${pinLabels ? "pinLabels={pinLabels}" : ""}
|
|
971
|
+
${pinAttributes ? `pinAttributes={${JSON.stringify(pinAttributes, null, " ")}}` : ""}
|
|
891
972
|
${objUrl ? `cadModel={{
|
|
892
973
|
objUrl: "${objUrl}",
|
|
893
974
|
rotationOffset: { x: 0, y: 0, z: 0 },
|
package/dist/cli/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
convertCircuitJsonToTscircuit
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-SGFWINLD.js";
|
|
5
5
|
|
|
6
6
|
// cli/main.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -16,7 +16,7 @@ var package_default = {
|
|
|
16
16
|
"./cli": "./dist/cli/main.js"
|
|
17
17
|
},
|
|
18
18
|
type: "module",
|
|
19
|
-
version: "0.0.
|
|
19
|
+
version: "0.0.50",
|
|
20
20
|
repository: {
|
|
21
21
|
type: "git",
|
|
22
22
|
url: "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
|
package/dist/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { su } from "@tscircuit/soup-util"
|
|
2
|
+
import { formatPcbRotationAttr } from "./footprint-tsx-attribute-formatters/format-pcb-rotation-attr"
|
|
2
3
|
import type { FootprintElementConverter } from "./converter-types"
|
|
3
4
|
import { escapeJsxText } from "./footprint-tsx-attribute-formatters/escape-jsx-text"
|
|
4
5
|
|
|
@@ -48,7 +49,9 @@ export const convertFabrication: FootprintElementConverter = (circuitJson) => {
|
|
|
48
49
|
attrs.push(`layer="bottom"`)
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
elementStrings.push(
|
|
52
|
+
elementStrings.push(
|
|
53
|
+
`<fabricationnotetext ${attrs.join(" ")}${formatPcbRotationAttr(fabText.ccw_rotation)} />`,
|
|
54
|
+
)
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
for (const fabRect of fabricationNoteRects) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AnyCircuitElement } from "circuit-json"
|
|
2
2
|
import { generateFootprintTsx } from "./generate-footprint-tsx"
|
|
3
3
|
import { generateSymbolTsx } from "./generate-symbol-tsx"
|
|
4
|
+
import { getPinAttributes } from "./get-pin-attributes"
|
|
4
5
|
|
|
5
6
|
export interface ComponentTemplateParams {
|
|
6
7
|
pinLabels?: Record<string, string[]> | Record<string, string> // ChipProps["pinLabels"]
|
|
@@ -21,6 +22,7 @@ export const getComponentUsingTemplate = ({
|
|
|
21
22
|
}: ComponentTemplateParams) => {
|
|
22
23
|
const footprintTsx = generateFootprintTsx(circuitJson)
|
|
23
24
|
const symbolTsx = generateSymbolTsx(circuitJson)
|
|
25
|
+
const pinAttributes = getPinAttributes(circuitJson)
|
|
24
26
|
return `
|
|
25
27
|
import { type ChipProps } from "tscircuit"
|
|
26
28
|
${pinLabels ? `const pinLabels = ${JSON.stringify(pinLabels, null, " ")} as const\n` : ""}export const ${componentName} = (props: ChipProps${pinLabels ? `<typeof pinLabels>` : ""}) => (
|
|
@@ -28,6 +30,7 @@ ${pinLabels ? `const pinLabels = ${JSON.stringify(pinLabels, null, " ")} as con
|
|
|
28
30
|
${footprintTsx ? `footprint={${footprintTsx}}` : ""}
|
|
29
31
|
${symbolTsx ? `symbol={${symbolTsx}}` : ""}
|
|
30
32
|
${pinLabels ? "pinLabels={pinLabels}" : ""}
|
|
33
|
+
${pinAttributes ? `pinAttributes={${JSON.stringify(pinAttributes, null, " ")}}` : ""}
|
|
31
34
|
${objUrl ? `cadModel={{\n objUrl: \"${objUrl}\",\n rotationOffset: { x: 0, y: 0, z: 0 },\n positionOffset: { x: 0, y: 0, z: 0 },\n }}` : ""}
|
|
32
35
|
${supplierPartNumbers ? `supplierPartNumbers={${JSON.stringify(supplierPartNumbers, null, " ")}}` : ""}
|
|
33
36
|
${manufacturerPartNumber ? `manufacturerPartNumber=\"${manufacturerPartNumber}\"` : ""}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
|
|
3
|
+
// Explicitly select electrical attributes; source-port IDs and other metadata
|
|
4
|
+
// must not become chip props. Newer Circuit JSON fields are read without parsing
|
|
5
|
+
// through an older schema, which would strip them from imported JSON.
|
|
6
|
+
const attributeNames = {
|
|
7
|
+
is_input: "isInput",
|
|
8
|
+
is_output: "isOutput",
|
|
9
|
+
is_bidirectional: "isBidirectional",
|
|
10
|
+
is_passive: "isPassive",
|
|
11
|
+
can_use_tri_state: "canUseTriState",
|
|
12
|
+
is_using_tri_state: "isUsingTriState",
|
|
13
|
+
can_use_open_collector: "canUseOpenCollector",
|
|
14
|
+
is_using_open_collector: "isUsingOpenCollector",
|
|
15
|
+
can_use_open_emitter: "canUseOpenEmitter",
|
|
16
|
+
is_using_open_emitter: "isUsingOpenEmitter",
|
|
17
|
+
is_gpio: "isGpio",
|
|
18
|
+
highlight_color: "highlightColor",
|
|
19
|
+
must_be_connected: "mustBeConnected",
|
|
20
|
+
provides_power: "providesPower",
|
|
21
|
+
requires_power: "requiresPower",
|
|
22
|
+
provides_ground: "providesGround",
|
|
23
|
+
requires_ground: "requiresGround",
|
|
24
|
+
provides_voltage: "providesVoltage",
|
|
25
|
+
requires_voltage: "requiresVoltage",
|
|
26
|
+
do_not_connect: "doNotConnect",
|
|
27
|
+
include_in_board_pinout: "includeInBoardPinout",
|
|
28
|
+
can_use_internal_pullup: "canUseInternalPullup",
|
|
29
|
+
is_using_internal_pullup: "isUsingInternalPullup",
|
|
30
|
+
needs_external_pullup: "needsExternalPullup",
|
|
31
|
+
can_use_internal_pulldown: "canUseInternalPulldown",
|
|
32
|
+
is_using_internal_pulldown: "isUsingInternalPulldown",
|
|
33
|
+
needs_external_pulldown: "needsExternalPulldown",
|
|
34
|
+
can_use_open_drain: "canUseOpenDrain",
|
|
35
|
+
is_using_open_drain: "isUsingOpenDrain",
|
|
36
|
+
can_use_push_pull: "canUsePushPull",
|
|
37
|
+
is_using_push_pull: "isUsingPushPull",
|
|
38
|
+
should_have_decoupling_capacitor: "shouldHaveDecouplingCapacitor",
|
|
39
|
+
recommended_decoupling_capacitor_capacitance:
|
|
40
|
+
"recommendedDecouplingCapacitorCapacitance",
|
|
41
|
+
} as const
|
|
42
|
+
|
|
43
|
+
const capabilities = [
|
|
44
|
+
"i2c_sda",
|
|
45
|
+
"i2c_scl",
|
|
46
|
+
"spi_mosi",
|
|
47
|
+
"spi_miso",
|
|
48
|
+
"spi_sck",
|
|
49
|
+
"spi_cs",
|
|
50
|
+
"uart_tx",
|
|
51
|
+
"uart_rx",
|
|
52
|
+
] as const
|
|
53
|
+
|
|
54
|
+
/** Electrical props for the single component represented by a chip template. */
|
|
55
|
+
export const getPinAttributes = (circuitJson: AnyCircuitElement[]) => {
|
|
56
|
+
const components = circuitJson.filter((e) => e.type === "source_component")
|
|
57
|
+
// Pin numbers are local to a component. Never combine multiple components'
|
|
58
|
+
// attributes into a single chip.
|
|
59
|
+
if (components.length > 1) return undefined
|
|
60
|
+
const componentId = components[0]?.source_component_id
|
|
61
|
+
const pins = new Map<string, Record<string, unknown>>()
|
|
62
|
+
for (const port of circuitJson) {
|
|
63
|
+
if (port.type !== "source_port") continue
|
|
64
|
+
if (
|
|
65
|
+
componentId &&
|
|
66
|
+
port.source_component_id &&
|
|
67
|
+
port.source_component_id !== componentId
|
|
68
|
+
)
|
|
69
|
+
continue
|
|
70
|
+
const source: Record<string, unknown> = { ...port }
|
|
71
|
+
const attributes: Record<string, unknown> = {}
|
|
72
|
+
for (const [sourceName, propName] of Object.entries(attributeNames)) {
|
|
73
|
+
if (source[sourceName] !== undefined) {
|
|
74
|
+
attributes[propName] = source[sourceName]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const supported = capabilities.filter(
|
|
78
|
+
(c) => source[`supports_${c}`] === true,
|
|
79
|
+
)
|
|
80
|
+
const active = capabilities.filter(
|
|
81
|
+
(c) => source[`is_configured_for_${c}`] === true,
|
|
82
|
+
)
|
|
83
|
+
if (supported.length) attributes.capabilities = supported
|
|
84
|
+
if (active.length) attributes.activeCapabilities = active
|
|
85
|
+
if (Object.keys(attributes).length === 0) continue
|
|
86
|
+
const key =
|
|
87
|
+
port.pin_number !== undefined ? `pin${port.pin_number}` : port.name
|
|
88
|
+
pins.set(key, { ...pins.get(key), ...attributes })
|
|
89
|
+
}
|
|
90
|
+
return pins.size ? Object.fromEntries(pins) : undefined
|
|
91
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { convertFabrication } from "../lib/generate-footprint-tsx/convert-fabrication"
|
|
3
|
+
|
|
4
|
+
// Repro for rotations lost by the TI EVM Circuit JSON -> TSX conversion.
|
|
5
|
+
test("preserves fabricationnotetext rotations on both layers", () => {
|
|
6
|
+
for (const layer of ["top", "bottom"]) {
|
|
7
|
+
for (const rotation of [0, 30, 90, 180, 270, -45, undefined]) {
|
|
8
|
+
const [tsx] = convertFabrication([
|
|
9
|
+
{
|
|
10
|
+
type: "pcb_fabrication_note_text",
|
|
11
|
+
layer,
|
|
12
|
+
ccw_rotation: rotation,
|
|
13
|
+
anchor_position: { x: 1, y: 2 },
|
|
14
|
+
text: "Assembly",
|
|
15
|
+
},
|
|
16
|
+
] as any)
|
|
17
|
+
if (rotation === undefined) expect(tsx).not.toContain("pcbRotation")
|
|
18
|
+
else expect(tsx).toContain(`pcbRotation="${rotation}deg"`)
|
|
19
|
+
expect(
|
|
20
|
+
new Bun.Transpiler({ loader: "tsx" }).transformSync(
|
|
21
|
+
`export default () => (${tsx})`,
|
|
22
|
+
),
|
|
23
|
+
).toBeTruthy()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"circuitJson": [
|
|
3
|
+
{
|
|
4
|
+
"type": "source_component",
|
|
5
|
+
"ftype": "simple_chip",
|
|
6
|
+
"source_component_id": "source_component_1",
|
|
7
|
+
"name": "U1",
|
|
8
|
+
"manufacturer_part_number": "AP2127K-28TRG1"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "source_port",
|
|
12
|
+
"source_port_id": "source_port_1",
|
|
13
|
+
"source_component_id": "source_component_1",
|
|
14
|
+
"name": "pin1",
|
|
15
|
+
"pin_number": 1,
|
|
16
|
+
"port_hints": ["VIN"],
|
|
17
|
+
"must_be_connected": true,
|
|
18
|
+
"requires_power": true,
|
|
19
|
+
"should_have_decoupling_capacitor": true,
|
|
20
|
+
"recommended_decoupling_capacitor_capacitance": "1uF"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"type": "source_port",
|
|
24
|
+
"source_port_id": "source_port_2",
|
|
25
|
+
"source_component_id": "source_component_1",
|
|
26
|
+
"name": "pin2",
|
|
27
|
+
"pin_number": 2,
|
|
28
|
+
"port_hints": ["GND"],
|
|
29
|
+
"must_be_connected": true,
|
|
30
|
+
"requires_ground": true
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "source_port",
|
|
34
|
+
"source_port_id": "source_port_3",
|
|
35
|
+
"source_component_id": "source_component_1",
|
|
36
|
+
"name": "pin3",
|
|
37
|
+
"pin_number": 3,
|
|
38
|
+
"port_hints": ["N_SHUTDOWN"],
|
|
39
|
+
"is_input": true,
|
|
40
|
+
"must_be_connected": true
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "source_port",
|
|
44
|
+
"source_port_id": "source_port_4",
|
|
45
|
+
"source_component_id": "source_component_1",
|
|
46
|
+
"name": "pin4",
|
|
47
|
+
"pin_number": 4,
|
|
48
|
+
"port_hints": ["NC"],
|
|
49
|
+
"do_not_connect": true
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"type": "source_port",
|
|
53
|
+
"source_port_id": "source_port_5",
|
|
54
|
+
"source_component_id": "source_component_1",
|
|
55
|
+
"name": "pin5",
|
|
56
|
+
"pin_number": 5,
|
|
57
|
+
"port_hints": ["VOUT"],
|
|
58
|
+
"is_output": true,
|
|
59
|
+
"provides_power": true,
|
|
60
|
+
"provides_voltage": 2.8,
|
|
61
|
+
"should_have_decoupling_capacitor": true,
|
|
62
|
+
"recommended_decoupling_capacitor_capacitance": "1uF"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"pinAttributes": {
|
|
66
|
+
"pin1": {
|
|
67
|
+
"requiresPower": true,
|
|
68
|
+
"mustBeConnected": true,
|
|
69
|
+
"shouldHaveDecouplingCapacitor": true,
|
|
70
|
+
"recommendedDecouplingCapacitorCapacitance": "1uF"
|
|
71
|
+
},
|
|
72
|
+
"pin2": {
|
|
73
|
+
"requiresGround": true,
|
|
74
|
+
"mustBeConnected": true
|
|
75
|
+
},
|
|
76
|
+
"pin3": {
|
|
77
|
+
"isInput": true,
|
|
78
|
+
"mustBeConnected": true
|
|
79
|
+
},
|
|
80
|
+
"pin4": {
|
|
81
|
+
"doNotConnect": true
|
|
82
|
+
},
|
|
83
|
+
"pin5": {
|
|
84
|
+
"isOutput": true,
|
|
85
|
+
"providesPower": true,
|
|
86
|
+
"providesVoltage": 2.8,
|
|
87
|
+
"shouldHaveDecouplingCapacitor": true,
|
|
88
|
+
"recommendedDecouplingCapacitorCapacitance": "1uF"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|