svgmapviewer-tools-floors 0.0.1

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.
Files changed (76) hide show
  1. package/LICENSE +15 -0
  2. package/inkscape/extensions/Attic/fix_guides.inx +17 -0
  3. package/inkscape/extensions/Attic/fix_guides.py +30 -0
  4. package/inkscape/extensions/Attic/fix_symbol_links.inx +17 -0
  5. package/inkscape/extensions/Attic/fix_symbol_links.py +71 -0
  6. package/inkscape/extensions/Attic/flatten_style.inx +17 -0
  7. package/inkscape/extensions/Attic/flatten_style.py +31 -0
  8. package/inkscape/extensions/Attic/load_markers.inx +17 -0
  9. package/inkscape/extensions/Attic/load_markers.py +117 -0
  10. package/inkscape/extensions/Attic/load_patterns.inx +17 -0
  11. package/inkscape/extensions/Attic/load_patterns.py +116 -0
  12. package/inkscape/extensions/Attic/load_symbols.inx +17 -0
  13. package/inkscape/extensions/Attic/load_symbols.py +164 -0
  14. package/inkscape/extensions/Attic/renumber_tree.inx +24 -0
  15. package/inkscape/extensions/Attic/renumber_tree.py +39 -0
  16. package/inkscape/extensions/Attic/repeat_path.inx +17 -0
  17. package/inkscape/extensions/Attic/repeat_path.py +58 -0
  18. package/inkscape/extensions/Attic/resolve_facility_links.inx +19 -0
  19. package/inkscape/extensions/Attic/resolve_facility_links.py +16 -0
  20. package/inkscape/extensions/Attic/sort_symbols.inx +17 -0
  21. package/inkscape/extensions/Attic/sort_symbols.py +48 -0
  22. package/inkscape/extensions/Attic/symbol_load.inx +17 -0
  23. package/inkscape/extensions/Attic/symbol_load.py +99 -0
  24. package/inkscape/extensions/Attic/tidy_tree.inx +22 -0
  25. package/inkscape/extensions/Attic/tidy_tree.py +178 -0
  26. package/inkscape/extensions/Attic/use_shop.inx +22 -0
  27. package/inkscape/extensions/Attic/use_shop.py +26 -0
  28. package/inkscape/extensions/README.md +34 -0
  29. package/inkscape/extensions/daijimaps/__init__.py +72 -0
  30. package/inkscape/extensions/daijimaps/address_tree.py +231 -0
  31. package/inkscape/extensions/daijimaps/common.py +49 -0
  32. package/inkscape/extensions/daijimaps/generate_addresses.py +41 -0
  33. package/inkscape/extensions/daijimaps/guards.py +47 -0
  34. package/inkscape/extensions/daijimaps/map_layer.py +47 -0
  35. package/inkscape/extensions/daijimaps/name.py +167 -0
  36. package/inkscape/extensions/daijimaps/renumber.py +38 -0
  37. package/inkscape/extensions/daijimaps/resolve_labels.py +65 -0
  38. package/inkscape/extensions/daijimaps/resolve_names.py +170 -0
  39. package/inkscape/extensions/daijimaps/save_addresses.py +274 -0
  40. package/inkscape/extensions/daijimaps/types.py +128 -0
  41. package/inkscape/extensions/daijimaps/visit_parents.py +37 -0
  42. package/inkscape/extensions/extract_labels.py +79 -0
  43. package/inkscape/extensions/fixup_floor_svg.py +28 -0
  44. package/inkscape/extensions/fixup_texts.inx +15 -0
  45. package/inkscape/extensions/fixup_texts.py +110 -0
  46. package/inkscape/extensions/fixup_tree.inx +15 -0
  47. package/inkscape/extensions/fixup_tree.py +17 -0
  48. package/inkscape/extensions/flatten_transform.inx +17 -0
  49. package/inkscape/extensions/flatten_transform.py +63 -0
  50. package/inkscape/extensions/import_labels.inx +20 -0
  51. package/inkscape/extensions/import_labels.py +52 -0
  52. package/inkscape/extensions/import_shops.inx +20 -0
  53. package/inkscape/extensions/import_shops.py +47 -0
  54. package/inkscape/extensions/install.sh +14 -0
  55. package/inkscape/extensions/load_shops.inx +20 -0
  56. package/inkscape/extensions/load_shops.py +86 -0
  57. package/inkscape/extensions/renumber_group.inx +15 -0
  58. package/inkscape/extensions/renumber_group.py +17 -0
  59. package/inkscape/extensions/resolve_labels.inx +20 -0
  60. package/inkscape/extensions/resolve_labels.py +142 -0
  61. package/inkscape/extensions/resolve_shops.inx +20 -0
  62. package/inkscape/extensions/resolve_shops.py +73 -0
  63. package/inkscape/extensions/unresolve_labels.inx +20 -0
  64. package/inkscape/extensions/unresolve_labels.py +53 -0
  65. package/inkscape/extensions/unresolve_shops.inx +20 -0
  66. package/inkscape/extensions/unresolve_shops.py +56 -0
  67. package/inkscape/templates/floors.svg +665 -0
  68. package/map-extract-floors.js +821 -0
  69. package/package.json +19 -0
  70. package/pyproject.toml +20 -0
  71. package/scripts/inkex-inkscape +14 -0
  72. package/scripts/inkex-python +14 -0
  73. package/scripts/inkex-setup +6 -0
  74. package/scripts/labels.sh +18 -0
  75. package/scripts/regen.py +77 -0
  76. package/scripts/regen.sh +24 -0
@@ -0,0 +1,72 @@
1
+ from .address_tree import AddressTree
2
+
3
+ from .generate_addresses import GenerateAddresses
4
+
5
+ from .guards import (
6
+ isBaseElement,
7
+ isCircle,
8
+ isEllipse,
9
+ isGroup,
10
+ isRectangle,
11
+ isTextElement,
12
+ isUse,
13
+ )
14
+
15
+ from .name import draw_label, draw_name, move_name, preferInt, read_name, redraw_name
16
+
17
+ from .renumber import renumber_group
18
+
19
+ from .resolve_names import ResolveNames
20
+
21
+ from .save_addresses import SaveAddresses
22
+
23
+ from .types import (
24
+ Address,
25
+ AddressNames,
26
+ AddressString,
27
+ Addresses,
28
+ Name,
29
+ NameAddresses,
30
+ NameString,
31
+ Names,
32
+ XY,
33
+ )
34
+
35
+
36
+ __all__ = [
37
+ # .address_tree
38
+ AddressTree,
39
+ # .generate_addresses
40
+ GenerateAddresses,
41
+ # .guards
42
+ isBaseElement,
43
+ isCircle,
44
+ isEllipse,
45
+ isGroup,
46
+ isRectangle,
47
+ isTextElement,
48
+ isUse,
49
+ # .name
50
+ draw_label,
51
+ draw_name,
52
+ move_name,
53
+ preferInt,
54
+ read_name,
55
+ redraw_name,
56
+ # .renumber
57
+ renumber_group,
58
+ # .resolve_names
59
+ ResolveNames,
60
+ # .save_addresses
61
+ SaveAddresses,
62
+ # .types
63
+ Address,
64
+ AddressNames,
65
+ AddressString,
66
+ Addresses,
67
+ Name,
68
+ NameAddresses,
69
+ NameString,
70
+ Names,
71
+ XY,
72
+ ]
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env python3
2
+ # coding=utf-8
3
+
4
+ from argparse import ArgumentParser
5
+ import inkex
6
+ import re
7
+ import os
8
+ from lxml import etree
9
+ from .visit_parents import _visit_parents, CONT, SKIP, Visit, Tree, Parents
10
+ from .types import AddressPos, JsonGlobalPaths, JsonLayerPaths, Links, PosAddress
11
+
12
+
13
+ class AddressTree(inkex.EffectExtension):
14
+ _layers: list[inkex.Group] = []
15
+
16
+ _addresses: AddressPos = {}
17
+ _all_addresses: AddressPos = {}
18
+ _points: PosAddress = {}
19
+ _all_points: PosAddress = {}
20
+ _links: Links = {}
21
+
22
+ # XXX address <g> id
23
+ # XXX (A)ddress
24
+ # e.g. `A1F-Shops-1-1`
25
+ _global_prefix = "A"
26
+
27
+ # e.g. `(Contents)`
28
+ _ignore_pattern = "^[(].*[)]$"
29
+
30
+ _layerPaths: JsonLayerPaths
31
+ _paths: JsonGlobalPaths
32
+
33
+ _find_layers_opts = {"skip_ignoring": True}
34
+
35
+ def _get_path(self, layer_name: str, prefix: str, suffix: str) -> str | None:
36
+ assert isinstance(layer_name, str)
37
+ ps = [
38
+ f"floors/{layer_name}/{prefix}.{suffix}",
39
+ f"floors/{prefix}/{layer_name}.{suffix}",
40
+ f"floors/{layer_name}-{prefix}.{suffix}",
41
+ f"floors/{prefix}-{layer_name}.{suffix}",
42
+ f"{layer_name}/{prefix}.{suffix}",
43
+ f"{prefix}/{layer_name}.{suffix}",
44
+ f"{layer_name}-{prefix}.{suffix}",
45
+ f"{prefix}-{layer_name}.{suffix}",
46
+ ]
47
+ svg_path: str = self.svg_path()
48
+ assert isinstance(svg_path, str)
49
+ for p in ps:
50
+ path: str = os.path.join(svg_path, p)
51
+ self.msg(f"_get_path: {path}")
52
+ if os.access(path, os.R_OK):
53
+ self.msg(f"_get_path: found: {path}")
54
+ return path
55
+ self.msg(f"_get_path: not found: {prefix}, {suffix}")
56
+ return None
57
+
58
+ def _ignoring(self, node):
59
+ return re.match(self._ignore_pattern, node.label) is not None
60
+
61
+ def _visitor_node_branch(self, node: Tree, parents: Parents) -> None:
62
+ pass
63
+
64
+ def _visitor_node_leaf(self, node: Tree, parents: Parents) -> None:
65
+ pass
66
+
67
+ def _visitor(self, node: Tree, parents: Parents) -> Visit:
68
+ if not isinstance(node, inkex.Group):
69
+ return SKIP
70
+ if node.label and self._ignoring(node):
71
+ return SKIP
72
+
73
+ # (T) Leaf (terminal) node?
74
+ # leaves MUST be <use> and define transform!
75
+ # XXX why `is not None` doesn't work?
76
+ leaf = isinstance(node, inkex.Use) and node.transform
77
+
78
+ if leaf:
79
+ self._visitor_node_leaf(node, parents)
80
+ return SKIP
81
+ else:
82
+ self._visitor_node_branch(node, parents)
83
+ return CONT
84
+
85
+ def _pre_collect_addresses(self, node) -> None:
86
+ pass
87
+
88
+ def _collect_addresses(self, node) -> None:
89
+ self.msg("=== _collect_addresses@AddressTree")
90
+ self._addresses = {}
91
+ self._points = {}
92
+
93
+ _visit_parents(node, self._visitor)
94
+
95
+ def _post_collect_addresses(self, node: inkex.Group) -> None:
96
+ pass
97
+
98
+ def _pre_process_addresses(self, node: inkex.Group) -> None:
99
+ pass
100
+
101
+ def _process_addresses(self, node: inkex.Group) -> None:
102
+ pass
103
+
104
+ def _post_process_addresses(self, node: inkex.Group) -> None:
105
+ pass
106
+
107
+ def _pre_layers(self) -> None:
108
+ pass
109
+
110
+ def _post_layers(self) -> None:
111
+ pass
112
+
113
+ def _find_group(self, layer, label) -> inkex.Group | None:
114
+ for child in list(layer):
115
+ if "label" in child:
116
+ self.msg(f"_find_group: {child.label}")
117
+ if not isinstance(child, inkex.Group):
118
+ # XXX msg
119
+ continue
120
+ if child.label is None or child.label != label:
121
+ # XXX msg
122
+ continue
123
+ self.msg(f"_find_group: found: {child.label}")
124
+ return child
125
+ return None
126
+
127
+ def _find_or_make_group(self, layer, label) -> inkex.Layer:
128
+ group = self._find_group(layer, label)
129
+ if group is None:
130
+ group = inkex.Layer()
131
+ group.label = label
132
+ layer.append(group)
133
+ return group
134
+
135
+ def _find_layers(self) -> None:
136
+ self.msg("=== _find_layers")
137
+ floor_pattern: str = self.options.floor
138
+
139
+ assert isinstance(self.document, etree._ElementTree)
140
+ assert isinstance(floor_pattern, str | None)
141
+ self._layers: list[inkex.Group] = [
142
+ node
143
+ for node in self.document.getroot()
144
+ if isinstance(node, inkex.Group)
145
+ if not (
146
+ node.label
147
+ and (self._find_layers_opts["skip_ignoring"] and self._ignoring(node))
148
+ )
149
+ if (
150
+ floor_pattern is None
151
+ or not isinstance(node.label, str)
152
+ or re.match(floor_pattern, node.label) is not None
153
+ )
154
+ ]
155
+ self.msg(f"=== _find_layers: {self._layers}")
156
+
157
+ def _find_assets(self) -> inkex.Group | None:
158
+ assert isinstance(self.document, etree._ElementTree), ""
159
+ res: list[inkex.Group] = [
160
+ node
161
+ for node in self.document.getroot()
162
+ if isinstance(node, inkex.Group)
163
+ if not (node.label and self._ignoring(node))
164
+ if isinstance(node.label, str)
165
+ and re.match("^(Assets)$", node.label) is not None
166
+ ]
167
+ if len(res) != 1:
168
+ return None
169
+ else:
170
+ return res[0]
171
+
172
+ def _set_paths(self, layer_name: str):
173
+ p = self.svg_path()
174
+ assert isinstance(p, str)
175
+
176
+ j = os.path.join
177
+ ln = layer_name
178
+
179
+ self._layerPaths = {
180
+ "addresses": j(p, f"addresses/{ln}.json"),
181
+ "unresolvedNames": j(p, f"unresolved_names/{ln}.json"),
182
+ "resolvedNames": j(p, f"resolved_names/{ln}.json"),
183
+ "tmpUnresolvedNames": j("/tmp", f"tmp_unresolved_names_{ln}.json"),
184
+ "tmpResolvedNames": j("/tmp", f"tmp_resolved_names_{ln}.json"),
185
+ "floorsAddresses": j(p, f"floors-addresses-{ln}.json"),
186
+ "floorsNames": j(p, f"floors-names-{ln}.json"),
187
+ }
188
+ self._paths = {
189
+ "bbox": j(p, "bbox.json"),
190
+ "facilities": j(p, "facilities.json"),
191
+ "floors": j(p, "floors.json"),
192
+ "origin": j(p, "origin.json"),
193
+ "viewbox": j(p, "viewbox.json"),
194
+ }
195
+
196
+ def _handle_layer(self, layer: inkex.Group, layer_name: str) -> None:
197
+ # XXX set .json paths
198
+ # input
199
+ # self._locs_json = os.path.join(self.svg_path(), "build", f"locs_{layer_name}.json")
200
+ # self._locs_json = os.path.join(self.svg_path(), f"locs.json")
201
+
202
+ self._set_paths(layer_name)
203
+
204
+ self._pre_collect_addresses(layer)
205
+ self._collect_addresses(layer)
206
+ self._post_collect_addresses(layer)
207
+ self._pre_process_addresses(layer)
208
+ self._process_addresses(layer)
209
+ self._post_process_addresses(layer)
210
+
211
+ def add_arguments(self, pars: ArgumentParser) -> None:
212
+ pars.add_argument("--tab", type=str, dest="tab")
213
+ pars.add_argument("--floor", type=str, default=".")
214
+ return super().add_arguments(pars)
215
+
216
+ def effect(self) -> None:
217
+ self.msg("==== AddressTree: start")
218
+ self._find_layers()
219
+ self._pre_layers()
220
+ for layer in self._layers:
221
+ layer_name = layer.label
222
+ if not isinstance(layer_name, str):
223
+ continue
224
+ self._handle_layer(layer, layer_name)
225
+ self._post_layers()
226
+ self.msg("==== AddressTree: end")
227
+
228
+
229
+ __all__ = [
230
+ AddressTree,
231
+ ]
@@ -0,0 +1,49 @@
1
+ import os
2
+
3
+ from .types import Address, AddressString, V
4
+
5
+
6
+ # XXX prefer 1 to 1.0
7
+ def unround(n: float) -> float:
8
+ f: float = round(n, ndigits=3)
9
+ i: float = round(f)
10
+ return f if f != i else i
11
+
12
+
13
+ def xy2v(x, y) -> V:
14
+ return {
15
+ "x": unround(x),
16
+ "y": unround(y),
17
+ #'w': r3w if r3w != rw else rw,
18
+ }
19
+
20
+
21
+ def a2astr(axy: Address) -> AddressString | None:
22
+ (astr, _xy) = axy
23
+ return astr
24
+
25
+
26
+ def a2v(axy: Address) -> V:
27
+ (_a, (x, y)) = axy
28
+ return xy2v(x, y)
29
+
30
+
31
+ def find_layer_file(dir: str, layer: str, prefix: str, suffix: str) -> str | None:
32
+ ps: list[str] = [
33
+ f"floors/{layer}/{prefix}.{suffix}",
34
+ f"floors/{prefix}/{layer}.{suffix}",
35
+ f"floors/{layer}-{prefix}.{suffix}",
36
+ f"floors/{prefix}-{layer}.{suffix}",
37
+ f"{layer}/{prefix}.{suffix}",
38
+ f"{prefix}/{layer}.{suffix}",
39
+ f"{layer}-{prefix}.{suffix}",
40
+ f"{prefix}-{layer}.{suffix}",
41
+ ]
42
+ for p in ps:
43
+ path: str = os.path.join(dir, p)
44
+ if os.access(path, mode=os.R_OK):
45
+ return path
46
+ return None
47
+
48
+
49
+ __all__ = [a2astr, a2v, xy2v]
@@ -0,0 +1,41 @@
1
+ import inkex
2
+
3
+ from .save_addresses import SaveAddresses
4
+
5
+
6
+ class GenerateAddresses(SaveAddresses):
7
+ _group_label = "(Addresses)"
8
+
9
+ def _cleanup_addresses(self, layer) -> None:
10
+ for child in list(layer):
11
+ if "label" in child and child.label == self._group_label:
12
+ child.delete()
13
+
14
+ def _generate_addresses_address(self, aparent, k, x, y, bb, href) -> None:
15
+ pass
16
+
17
+ def _generate_addresses(self, layer, layer_name: str) -> None:
18
+ self.msg("=== GenerateAddresses: _generate_addresses")
19
+ if len(self._addresses.items()) == 0:
20
+ self.msg("_generate_addresses: no items found!")
21
+ return
22
+
23
+ aparent = inkex.Group()
24
+ aparent.label = self._group_label
25
+ for k, ((x, y), bb, href) in self._addresses.items():
26
+ self._generate_addresses_address(aparent, k, x, y, bb, href)
27
+ layer.append(aparent)
28
+
29
+ def _pre_process_addresses(self, node: inkex.Group) -> None:
30
+ super()._pre_process_addresses(node)
31
+ self._cleanup_addresses(node)
32
+
33
+ def _process_addresses(self, node: inkex.Group) -> None:
34
+ self.msg("=== GenerateAddresses: _process_addresses")
35
+ super()._process_addresses(node)
36
+ name = node.label
37
+ if isinstance(name, str):
38
+ self._generate_addresses(node, name)
39
+
40
+
41
+ __all__ = [GenerateAddresses]
@@ -0,0 +1,47 @@
1
+ from typing import Any, TypeGuard
2
+
3
+ import inkex
4
+
5
+
6
+ def isBaseElement(x: Any) -> TypeGuard[inkex.BaseElement]:
7
+ return isinstance(x, inkex.BaseElement)
8
+
9
+
10
+ def isCircle(x: Any) -> TypeGuard[inkex.Circle]:
11
+ return isinstance(x, inkex.Circle)
12
+
13
+
14
+ def isEllipse(x: Any) -> TypeGuard[inkex.Ellipse]:
15
+ return isinstance(x, inkex.Ellipse)
16
+
17
+
18
+ def isGroup(x: Any) -> TypeGuard[inkex.Group]:
19
+ return isinstance(x, inkex.Group)
20
+
21
+
22
+ def isRectangle(x: Any) -> TypeGuard[inkex.Rectangle]:
23
+ return isinstance(x, inkex.Rectangle)
24
+
25
+
26
+ def isTextElement(x: Any) -> TypeGuard[inkex.TextElement]:
27
+ return isinstance(x, inkex.TextElement)
28
+
29
+
30
+ def isTspan(x: Any) -> TypeGuard[inkex.Tspan]:
31
+ return isinstance(x, inkex.Tspan)
32
+
33
+
34
+ def isUse(x: Any) -> TypeGuard[inkex.Use]:
35
+ return isinstance(x, inkex.Use)
36
+
37
+
38
+ __all__ = [
39
+ isBaseElement,
40
+ isCircle,
41
+ isEllipse,
42
+ isGroup,
43
+ isRectangle,
44
+ isTextElement,
45
+ isTspan,
46
+ isUse,
47
+ ]
@@ -0,0 +1,47 @@
1
+ import inkex
2
+
3
+
4
+ def fixup_tree_layer(group: inkex.Group) -> None:
5
+ layer_names = [
6
+ "(Names)",
7
+ "(Unresolved Names)",
8
+ "(Labels)",
9
+ "(Unresolved Labels)",
10
+ "Content",
11
+ "(Background)",
12
+ ]
13
+ for layer_name in layer_names:
14
+ find_layer_and_change_to_layer(group, layer_name)
15
+
16
+
17
+ def fixup_tree_layer_content(group: inkex.Group) -> None:
18
+ layer_names: list[str] = [
19
+ "(Labels)",
20
+ "(Unresolved Labels)",
21
+ "(Facilities)",
22
+ "(Shops)",
23
+ "Facilities",
24
+ "Shops",
25
+ "(Barriers)",
26
+ "(Spaces)",
27
+ "(Backgrounds)",
28
+ "(Background)",
29
+ ]
30
+ for layer_name in layer_names:
31
+ find_layer_and_change_to_layer(group, layer_name)
32
+
33
+
34
+ def find_layer_and_change_to_layer(group: inkex.Group, name: str) -> None:
35
+ for child in list(group):
36
+ if isinstance(child, inkex.Group) and child.label == name:
37
+ group_to_layer(child)
38
+
39
+
40
+ def group_to_layer(group: inkex.Group) -> inkex.Layer:
41
+ group_mode = group.get("inkscape:groupmode", None)
42
+ if group_mode != "layer":
43
+ group.set("inkscape:groupmode", "layer")
44
+ return group
45
+
46
+
47
+ __all__: list[str] = ["fixup_tree_layer", "fixup_tree_layer_content"]
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python3
2
+ # coding=utf-8
3
+
4
+ import inkex
5
+
6
+ from .types import AddressString, NameString, XY
7
+
8
+
9
+ def preferInt(n: float) -> float | int:
10
+ r: float = round(n)
11
+ return r if r == n else int(n)
12
+
13
+
14
+ def draw_label(text: str, size: float, x: float = 0, y: float = 0) -> inkex.TextElement:
15
+ t = inkex.TextElement()
16
+ t.label = text
17
+ t.update(
18
+ **{
19
+ "font-family": "'Noto Sans'",
20
+ # "-inkscape-font-specification": "'Noto Sans Ultra-Light'",
21
+ "font-size": f"{size}",
22
+ "font-weight": "200",
23
+ "line-height": "1.5",
24
+ "text-anchor": "middle",
25
+ "text-align": "center",
26
+ "writing-mode": "lr-tb",
27
+ "direction": "ltr",
28
+ "fill": "#000000",
29
+ "stroke": "none",
30
+ }
31
+ )
32
+ txts: list[str] = text.split()
33
+ for txt in txts:
34
+ ts = inkex.Tspan()
35
+ ts.text = txt
36
+ ts.update(
37
+ **{
38
+ "dy": 0,
39
+ "sodipodi:role": "line",
40
+ }
41
+ )
42
+ t.append(ts)
43
+ return t
44
+
45
+
46
+ def draw_name(text: str, x: float = 0, y: float = 0) -> inkex.TextElement:
47
+ t = inkex.TextElement()
48
+ t.label = text
49
+ t.update(
50
+ **{
51
+ "x": round(x),
52
+ "y": round(y),
53
+ "font-size": "2px",
54
+ "text-anchor": "middle",
55
+ }
56
+ )
57
+ ts = inkex.Tspan()
58
+ ts.text = text
59
+ t.append(ts)
60
+ return t
61
+
62
+
63
+ def redraw_name(
64
+ t: inkex.TextElement, text: str, x: float = 0, y: float = 0
65
+ ) -> inkex.TextElement:
66
+ t.label = text
67
+ t.update(
68
+ **{
69
+ "x": round(x),
70
+ "y": round(y),
71
+ "font-size": "2px",
72
+ "text-anchor": "middle",
73
+ }
74
+ )
75
+ first = None
76
+ for ts in t:
77
+ if first is None:
78
+ first = ts
79
+ else:
80
+ t.remove(ts)
81
+ if first is not None:
82
+ first.text = text
83
+ else:
84
+ ts = inkex.Tspan()
85
+ ts.text = text
86
+ t.append(ts)
87
+ return t
88
+
89
+
90
+ def remove_children_by_label(
91
+ group: inkex.Group, label: str
92
+ ) -> inkex.TextElement | None:
93
+ for child in group:
94
+ if not isinstance(child, inkex.TextElement):
95
+ # XXX msg
96
+ continue
97
+ if child.label == label:
98
+ group.remove(child)
99
+ return child
100
+ return None
101
+
102
+
103
+ def move_name(
104
+ old_group: inkex.Group,
105
+ new_group: inkex.Group,
106
+ old_name: str,
107
+ new_name: str,
108
+ x: float,
109
+ y: float,
110
+ ) -> inkex.Group | None:
111
+ remove_children_by_label(new_group, new_name)
112
+ child = remove_children_by_label(old_group, old_name)
113
+ t = (
114
+ draw_name(new_name, x, y)
115
+ if child is None or not isinstance(child, inkex.TextElement)
116
+ else redraw_name(child, new_name, x, y)
117
+ )
118
+ new_group.append(t)
119
+
120
+
121
+ type NameEntry = tuple[None | AddressString, NameString, XY]
122
+
123
+
124
+ def read_name(node: inkex.BaseElement) -> None | NameEntry:
125
+ if not isinstance(node, inkex.TextElement):
126
+ return None
127
+ assert isinstance(node.label, str)
128
+ name_and_address: list[str] = node.label.split(" @ ")
129
+ name: str = name_and_address[0]
130
+ if len(name_and_address) == 1:
131
+ address = None
132
+ else:
133
+ address: str = name_and_address[1]
134
+
135
+ # check "unmoved" node (x == 0 and y == 0 and transform is None)
136
+ (x, y) = (node.x, node.y)
137
+ tx = node.transform
138
+ p = (
139
+ inkex.Vector2d(0, 0)
140
+ if (x == 0 and y == 0 and tx is None)
141
+ else inkex.Vector2d(x, y)
142
+ if tx is None
143
+ else tx.apply_to_point(inkex.Vector2d(x, y))
144
+ )
145
+ return (address, name, (round(p.x), round(p.y)))
146
+
147
+
148
+ def move_label(
149
+ old_group: inkex.Group,
150
+ new_group: inkex.Group,
151
+ old_name: str,
152
+ new_name: str,
153
+ x: None | float = None,
154
+ y: None | float = None,
155
+ ) -> inkex.Group | None:
156
+ remove_children_by_label(new_group, new_name)
157
+ child = remove_children_by_label(old_group, old_name)
158
+ if child is None:
159
+ return
160
+ child.label = new_name
161
+ child.transform = None
162
+ child.set("x", None)
163
+ child.set("y", None)
164
+ new_group.append(child)
165
+
166
+
167
+ __all__ = [draw_label, draw_name, move_name, read_name, redraw_name, move_label]
@@ -0,0 +1,38 @@
1
+ import re
2
+
3
+ import inkex
4
+
5
+ import daijimaps
6
+
7
+
8
+ # XXX show log?
9
+ def renumber_group(node: inkex.Group):
10
+ ngroups = 0
11
+ nnongroups = 0
12
+ nnames = 0
13
+ m = re.compile("^[A-Z].*$")
14
+ for child in list(node):
15
+ if not daijimaps.isBaseElement(child):
16
+ continue
17
+ if isinstance(child, inkex.Group):
18
+ ngroups = ngroups + 1
19
+ else:
20
+ nnongroups = nnongroups + 1
21
+ label = child.label
22
+ if label is not None and m.match(label):
23
+ nnames = nnames + 1
24
+ # non-mixed children => renumber children
25
+ if (ngroups == 0 and nnames == 0) or (nnongroups == 0 and nnames == 0):
26
+ for idx, child in enumerate(list(node)):
27
+ if not daijimaps.isBaseElement(child):
28
+ continue
29
+ child.label = idx + 1
30
+ # all children are group => recurse into children
31
+ if nnongroups == 0:
32
+ for child in list(node):
33
+ if not daijimaps.isGroup(child):
34
+ continue
35
+ renumber_group(child)
36
+
37
+
38
+ __all__ = [renumber_group]