ea 0.6.89 → 0.6.91

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbd238ad74b281cd5cb44ae2df2b49edd56eec8ab0001f8cf1694259a974c385
4
- data.tar.gz: 978f6880bb2ffa3a9fd308047c62dd21e0869305ea832ac64b91de9f1c8303e9
3
+ metadata.gz: cf0f3e11888e4492386de976f97e4024e39d9dbc95083a5b184c0fb25d8cfead
4
+ data.tar.gz: 467687628f21bf50264c0210de75e2fad5013a14d7f2a1572d65dfb218429052
5
5
  SHA512:
6
- metadata.gz: 44a797e58abb32b6434cb83eec2d84270a6499dc3c8d986a6da1a68da1074982a9217655d20e835bd5fe8812c8780a7a9775a9495d970cf0eb849694b0d6d9d3
7
- data.tar.gz: 0c410271301a481b5085d7be62a2f45de58eb7fe46cad199e69e0f0f017f7648f4646305f2853ce248c5f6c62979f31c8d7087b7526945234315b8b6b6f63833
6
+ metadata.gz: 67a177d6e826985a3526c63bae7ed1f10e55b51ca97ec1507e5cb2f89f309b789f734c27cd41b678777e3ea72cb730a44485799972c3bbab916dedfd51f404c5
7
+ data.tar.gz: 55d5d873894f3e26fb786a71efaab326ecd4df2d16d167bf3dc7158e1775e139f9a9c2096cd79c5ae136315421c1d19156320d1c895837d691ecd7a97e772a2a
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Sources
5
+ module Qea
6
+ class DiagramBuilder
7
+ # Internal geometry for choosing the facing edges of stored routes.
8
+ module Routing
9
+ private
10
+
11
+ # Keep a vertical tree trunk whose bends still fit both boxes
12
+ # and the open gap; the closest facing edges need not share its x.
13
+ def preserve_stored_tree?(points, link_row, connector, src, tgt)
14
+ return false unless connector.connector_type == "Aggregation" && points.size == 4
15
+
16
+ style = DiagramStyleParser.parse(link_row.style)
17
+ return false unless style[:tree] == "V" && style[:lwidth] == "2"
18
+
19
+ tree_bends_fit?(points[1], points[-2], src, tgt)
20
+ end
21
+
22
+ def tree_bends_fit?(first, last, source, target)
23
+ first[0] != last[0] && first[1] == last[1] &&
24
+ within_vertical_gap?(first[1], source, target) &&
25
+ within_horizontal_span?(first, source) && within_horizontal_span?(last, target)
26
+ end
27
+
28
+ def within_vertical_gap?(height, source, target)
29
+ height < source.y - 1 && height > target.y + target.height + 1
30
+ end
31
+
32
+ def within_horizontal_span?(point, bounds)
33
+ point[0].between?(bounds.x, bounds.x + bounds.width)
34
+ end
35
+
36
+ # Returns a replacement 2-point route between the closest
37
+ # facing edges, or the original points when: the boxes overlap
38
+ # on both axes, the current route already docks those edges,
39
+ # or no aligned dock fits the edges' overlap range.
40
+ def facing_edge_route(points, src, tgt)
41
+ v_gap = v_dir = h_gap = h_dir = nil
42
+ if src.y >= tgt.y + tgt.height
43
+ v_gap = src.y - (tgt.y + tgt.height)
44
+ v_dir = [:t, :b]
45
+ elsif tgt.y >= src.y + src.height
46
+ v_gap = tgt.y - (src.y + src.height)
47
+ v_dir = [:b, :t]
48
+ end
49
+ if src.x >= tgt.x + tgt.width
50
+ h_gap = src.x - (tgt.x + tgt.width)
51
+ h_dir = [:l, :r]
52
+ elsif tgt.x >= src.x + src.width
53
+ h_gap = tgt.x - (tgt.x + tgt.width)
54
+ h_dir = [:r, :l]
55
+ end
56
+ return points if v_gap.nil? && h_gap.nil?
57
+
58
+ if !v_gap.nil? && (h_gap.nil? || v_gap <= h_gap)
59
+ src_edge, tgt_edge = v_dir
60
+ else
61
+ src_edge, tgt_edge = h_dir
62
+ end
63
+ return points if docks_edges?(points, src, src_edge, tgt, tgt_edge)
64
+
65
+ if %i[t b].include?(src_edge)
66
+ lo = [src.x, tgt.x].max
67
+ hi = [src.x + src.width, tgt.x + tgt.width].min
68
+ return points if lo > hi
69
+
70
+ x = points.first[0].clamp(lo, hi)
71
+ [[x, src_edge == :t ? src.y : src.y + src.height],
72
+ [x, tgt_edge == :t ? tgt.y : tgt.y + tgt.height]]
73
+ else
74
+ lo = [src.y, tgt.y].max
75
+ hi = [src.y + src.height, tgt.y + tgt.height].min
76
+ return points if lo > hi
77
+
78
+ y = points.first[1].clamp(lo, hi)
79
+ [[src_edge == :l ? src.x : src.x + src.width, y],
80
+ [tgt_edge == :l ? tgt.x : tgt.x + tgt.width, y]]
81
+ end
82
+ end
83
+
84
+ def docks_edges?(points, src, src_edge, tgt, tgt_edge)
85
+ on_edge?(points.first, src, src_edge) && on_edge?(points.last, tgt, tgt_edge)
86
+ end
87
+
88
+ def on_edge?(pt, b, edge, tol = 2)
89
+ x, y = pt
90
+ case edge
91
+ when :l then (x - b.x).abs <= tol && y.between?(b.y - tol, b.y + b.height + tol)
92
+ when :r then (x - (b.x + b.width)).abs <= tol && y.between?(b.y - tol, b.y + b.height + tol)
93
+ when :t then (y - b.y).abs <= tol && x.between?(b.x - tol, b.x + b.width + tol)
94
+ else (y - (b.y + b.height)).abs <= tol && x.between?(b.x - tol, b.x + b.width + tol)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -10,6 +10,10 @@ module Ea
10
10
  # DiagramConnectors (including pixel coordinates and parsed
11
11
  # styles).
12
12
  class DiagramBuilder
13
+ autoload :Routing, "ea/sources/qea/diagram_builder/routing"
14
+ include Routing
15
+ private_constant :Routing
16
+
13
17
  attr_reader :database
14
18
 
15
19
  # Generalizations whose EA CustomProperties carry
@@ -605,21 +609,15 @@ module Ea
605
609
  intermediate = intermediate_waypoints(link_row)
606
610
  if edge_out == 2 && source_port && target_port &&
607
611
  !valid_tree_path?(intermediate, source_placement)
608
- # EA recomputes tree routes at DRAW TIME from current
609
- # element positions when the stored Path no longer lands
610
- # on the source's facing edge (hand-verified: SY 8542's
611
- # stale Path vs EA-drawn route; CityObjectGroup's current
612
- # Path renders byte-exact and is kept). Computed route:
613
- # source port -> corner at (target dock x, source exit
614
- # y) -> target port.
612
+ # A stale tree Path (not on the source's facing edge) is
613
+ # recomputed: source port -> corner at (target dock x,
614
+ # source exit y) -> target port.
615
615
  points = [[source_point[0], source_point[1]],
616
616
  [target_point[0], source_point[1]],
617
617
  [target_point[0], target_point[1]]]
618
618
  elsif edge_out == 2 && intermediate.size == 1
619
- # Current tree Path (bend on the source's facing edge):
620
- # EA's draw-time route shape — source right edge at the
621
- # bend's y, corner at the bend, target facing edge at the
622
- # bend's x (byte-exact on CityObjectGroup).
619
+ # Current tree Path: source right edge at the bend's y,
620
+ # corner at the bend, target facing edge at the bend's x.
623
621
  src = bounds_from_rect(source_placement)
624
622
  tgt = bounds_from_rect(target_placement)
625
623
  bend = intermediate.first
@@ -630,21 +628,11 @@ module Ea
630
628
  [bend[0], target_y]]
631
629
  elsif intermediate.any?
632
630
  points = [source_point, *intermediate, target_point]
633
- # Generalizations with the parent ABOVE keep their stored
634
- # interior bends verbatim but re-dock the endpoints onto
635
- # the box edges AT THE ADJACENT BEND's x (hand-verified:
636
- # stored (542,975)(541,807)(543,807)(486,757) draws as
637
- # (541,975)(541,807)(543,807)(543,757) — only the two
638
- # endpoints move; the stale final x is replaced by the
639
- # last bend's trunk x).
631
+ # Parent-above Generalizations keep stored interior bends
632
+ # and re-dock only the endpoints, at the adjacent bend's x.
640
633
  if connector.connector_type == "Generalization"
641
- src_b = bounds_from_rect(source_placement)
642
- tgt_b = bounds_from_rect(target_placement)
643
- if tgt_b.y + tgt_b.height <= src_b.y
644
- points[0] = [points[1][0].clamp(src_b.x, src_b.x + src_b.width), src_b.y]
645
- points[-1] = [points[-2][0].clamp(tgt_b.x, tgt_b.x + tgt_b.width),
646
- tgt_b.y + tgt_b.height]
647
- end
634
+ redock_parent_above(points, link_row, source_placement,
635
+ target_placement)
648
636
  end
649
637
  elsif source_port || target_port
650
638
  # Exact ports are known — the SX/SY bend heuristics in
@@ -654,20 +642,24 @@ module Ea
654
642
  points = sx_sy_ex_ey_waypoints(source_point, target_point, geom)
655
643
  end
656
644
 
657
- # Associations/Aggregations between DISJOINT boxes: when the
658
- # stored route does not already dock the closest FACING edge
659
- # pair, EA redraws a straight aligned line between those
660
- # edges (corpus: edge pair = min facing gap, 85% accurate;
661
- # dock coordinate = the stored route's own coordinate
662
- # clamped into the edges' overlap; simulation on 417
663
- # identity-paired connectors: exact routes 41 -> 100).
664
- # Tree-style (EDGE=2) connectors are excluded — their
665
- # computed routes are already byte-exact.
645
+ # Between DISJOINT boxes, a stored route that does not dock the
646
+ # closest facing edge pair is redrawn straight between them.
647
+ # EDGE=2 routes are excluded; they are already exact.
666
648
  if %w[Association Aggregation].include?(connector.connector_type) &&
667
649
  edge_out != 2
668
- points = facing_edge_route(points,
669
- bounds_from_rect(source_placement),
670
- bounds_from_rect(target_placement))
650
+ src = bounds_from_rect(source_placement)
651
+ tgt = bounds_from_rect(target_placement)
652
+ unless preserve_stored_tree?(points, link_row, connector, src, tgt)
653
+ points = facing_edge_route(points, src, tgt)
654
+ end
655
+ # A width-2 Aggregation that keeps its stored bends is
656
+ # docked like a Generalization one.
657
+ if connector.connector_type == "Aggregation" &&
658
+ intermediate.any? && points.size >= 4 &&
659
+ generalization_outline_offset(link_row, points.size) == 1
660
+ redock_parent_above(points, link_row, source_placement,
661
+ target_placement, strict: true)
662
+ end
671
663
  end
672
664
 
673
665
  points.map do |x, y|
@@ -675,68 +667,6 @@ module Ea
675
667
  end
676
668
  end
677
669
 
678
- # Returns a replacement 2-point route between the closest
679
- # facing edges, or the original points when: the boxes overlap
680
- # on both axes, the current route already docks those edges,
681
- # or no aligned dock fits the edges' overlap range.
682
- def facing_edge_route(points, src, tgt)
683
- v_gap = v_dir = h_gap = h_dir = nil
684
- if src.y >= tgt.y + tgt.height
685
- v_gap = src.y - (tgt.y + tgt.height)
686
- v_dir = [:t, :b]
687
- elsif tgt.y >= src.y + src.height
688
- v_gap = tgt.y - (src.y + src.height)
689
- v_dir = [:b, :t]
690
- end
691
- if src.x >= tgt.x + tgt.width
692
- h_gap = src.x - (tgt.x + tgt.width)
693
- h_dir = [:l, :r]
694
- elsif tgt.x >= src.x + src.width
695
- h_gap = tgt.x - (tgt.x + tgt.width)
696
- h_dir = [:r, :l]
697
- end
698
- return points if v_gap.nil? && h_gap.nil?
699
-
700
- if !v_gap.nil? && (h_gap.nil? || v_gap <= h_gap)
701
- src_edge, tgt_edge = v_dir
702
- else
703
- src_edge, tgt_edge = h_dir
704
- end
705
- return points if docks_edges?(points, src, src_edge, tgt, tgt_edge)
706
-
707
- if %i[t b].include?(src_edge)
708
- lo = [src.x, tgt.x].max
709
- hi = [src.x + src.width, tgt.x + tgt.width].min
710
- return points if lo > hi
711
-
712
- x = points.first[0].clamp(lo, hi)
713
- [[x, src_edge == :t ? src.y : src.y + src.height],
714
- [x, tgt_edge == :t ? tgt.y : tgt.y + tgt.height]]
715
- else
716
- lo = [src.y, tgt.y].max
717
- hi = [src.y + src.height, tgt.y + tgt.height].min
718
- return points if lo > hi
719
-
720
- y = points.first[1].clamp(lo, hi)
721
- [[src_edge == :l ? src.x : src.x + src.width, y],
722
- [tgt_edge == :l ? tgt.x : tgt.x + tgt.width, y]]
723
- end
724
- end
725
-
726
- def docks_edges?(points, src, src_edge, tgt, tgt_edge)
727
- on_edge?(points.first, src, src_edge) && on_edge?(points.last, tgt, tgt_edge)
728
- end
729
-
730
- def on_edge?(pt, b, edge, tol = 2)
731
- x, y = pt
732
- case edge
733
- when :l then (x - b.x).abs <= tol && y.between?(b.y - tol, b.y + b.height + tol)
734
- when :r then (x - (b.x + b.width)).abs <= tol && y.between?(b.y - tol, b.y + b.height + tol)
735
- when :t then (y - b.y).abs <= tol && x.between?(b.x - tol, b.x + b.width + tol)
736
- else (y - (b.y + b.height)).abs <= tol && x.between?(b.x - tol, b.x + b.width + tol)
737
- end
738
- end
739
-
740
670
  # A tree Path is current when its bend height still falls
741
671
  # within the source element's y-range (the exit height EA
742
672
  # records); a bend outside the element is stale and EA
@@ -978,6 +908,35 @@ module Ea
978
908
 
979
909
  IdNormalizer.from_guid(conn.ea_guid)
980
910
  end
911
+
912
+ # Docks both ends of a bent route on the box edges at the
913
+ # adjacent bend's x when the target is above the source; interior
914
+ # bends stay. strict: skip an end whose bend is not beyond its dock.
915
+ def redock_parent_above(points, link_row, source_placement,
916
+ target_placement, strict: false)
917
+ src_b = bounds_from_rect(source_placement)
918
+ tgt_b = bounds_from_rect(target_placement)
919
+ return points unless tgt_b.y + tgt_b.height <= src_b.y
920
+
921
+ inset = generalization_outline_offset(link_row, points.size)
922
+ source_y = src_b.y
923
+ target_y = tgt_b.y + tgt_b.height
924
+ source_y -= inset if points[1][1] < source_y - inset
925
+ target_y += inset if points[-2][1] > target_y + inset
926
+ unless strict && points[1][1] >= source_y
927
+ points[0] = [points[1][0].clamp(src_b.x, src_b.x + src_b.width), source_y]
928
+ end
929
+ unless strict && points[-2][1] <= target_y
930
+ points[-1] = [points[-2][0].clamp(tgt_b.x, tgt_b.x + tgt_b.width), target_y]
931
+ end
932
+ points
933
+ end
934
+
935
+ def generalization_outline_offset(link_row, point_count)
936
+ return 0 if point_count < 4
937
+
938
+ DiagramStyleParser.parse(link_row.style)[:lwidth] == "2" ? 1 : 0
939
+ end
981
940
  end
982
941
  end
983
942
  end
@@ -36,16 +36,45 @@ module Ea
36
36
  return nil unless source_bounds && target_bounds
37
37
  return nil if source_ref == target_ref
38
38
 
39
- sa = connector.source_anchor
40
- ta = connector.target_anchor
41
- if sa && ta
42
- [anchor_exit(sa, source_bounds, ta),
43
- anchor_exit(ta, target_bounds, sa)]
39
+ anchors = [connector.source_anchor, connector.target_anchor]
40
+ bounds = [source_bounds, target_bounds]
41
+ original = clipped_pairs(bounds, anchors)
42
+ return original unless stroke_outline?(connector, bounds)
43
+
44
+ # EA docks these markers on the outer edge of the 2px box stroke.
45
+ outline = bounds.map do |box|
46
+ Ea::Model::Bounds.new(x: box.x - 1, y: box.y - 1,
47
+ width: box.width + 2, height: box.height + 2)
48
+ end
49
+ candidate = clipped_pairs(outline, anchors)
50
+ forward_route?(original, candidate) ? candidate : original
51
+ end
52
+
53
+ def stroke_outline?(connector, bounds)
54
+ connector.style.to_h[:lwidth] == "2" &&
55
+ %w[Generalization Aggregation].include?(connector.connector_type) &&
56
+ bounds.all? { |box| box.width.positive? && box.height.positive? }
57
+ end
58
+ private_class_method :stroke_outline?
59
+
60
+ def clipped_pairs(bounds, anchors)
61
+ source, target = bounds
62
+ source_anchor, target_anchor = anchors
63
+ if source_anchor && target_anchor
64
+ [anchor_exit(source_anchor, source, target_anchor),
65
+ anchor_exit(target_anchor, target, source_anchor)]
44
66
  else
45
- [edge_point(source_bounds, target_bounds),
46
- edge_point(target_bounds, source_bounds)]
67
+ [edge_point(source, target), edge_point(target, source)]
47
68
  end
48
69
  end
70
+ private_class_method :clipped_pairs
71
+
72
+ def forward_route?(original, candidate)
73
+ before = original.first.zip(original.last).map { |start, finish| finish - start }
74
+ after = candidate.first.zip(candidate.last).map { |start, finish| finish - start }
75
+ before.zip(after).sum { |old, current| old * current }.positive?
76
+ end
77
+ private_class_method :forward_route?
49
78
 
50
79
  # Exit point of the anchor-to-anchor segment through a box's
51
80
  # outline: smallest positive t whose crossing lies on the
@@ -101,21 +130,29 @@ module Ea
101
130
  divider_y_by_ref[target_ref]]
102
131
  return nil unless bounds.all? && dividers.all?
103
132
 
104
- changed = false
105
- adjusted = pairs.each_with_index.map do |(x, y), i|
106
- box = bounds[i]
107
- divider = dividers[i]
108
- at_edge = (x - box.x).abs <= 1 || (x - (box.x + box.width)).abs <= 1
109
- at_divider = (y - divider.to_f).abs <= 1
110
- if at_edge && at_divider
111
- changed = true
112
- [x, y + ROW_SLOT_PITCH]
113
- else
114
- [x, y]
115
- end
133
+ adjusted = row_slot_pairs(pairs, bounds, dividers)
134
+ if stroke_outline?(connector, bounds) &&
135
+ pairs == pairs_for(connector, diagram, model_index, bounds_map)
136
+ original = clipped_pairs(bounds, [connector.source_anchor, connector.target_anchor])
137
+ original_final = row_slot_pairs(original, bounds, dividers)
138
+ adjusted = original_final if original != pairs && !forward_route?(original_final, adjusted)
139
+ end
140
+ adjusted == pairs ? nil : adjusted
141
+ end
142
+
143
+ def row_slot_pairs(pairs, bounds, dividers)
144
+ pairs.each_with_index.map do |(x, y), index|
145
+ row_slot?(x, y, bounds[index], dividers[index]) ? [x, y + ROW_SLOT_PITCH] : [x, y]
116
146
  end
117
- changed ? adjusted : nil
118
147
  end
148
+ private_class_method :row_slot_pairs
149
+
150
+ def row_slot?(x_position, y_position, bounds, divider)
151
+ at_edge = (x_position - bounds.x).abs <= 1 ||
152
+ (x_position - (bounds.x + bounds.width)).abs <= 1
153
+ at_edge && (y_position - divider.to_f).abs <= 1
154
+ end
155
+ private_class_method :row_slot?
119
156
 
120
157
  def direct?(connector)
121
158
  style = connector.style || {}
@@ -75,7 +75,7 @@ module Ea
75
75
  stereo = stereo.nil? || stereo.empty? ? nil : "«#{stereo}»"
76
76
 
77
77
  build_texts(role:, mult:, standalone:,
78
- text_pos:, mult_pos:, stereo:)
78
+ text_pos:, mult_pos:, stereo:, text_box:)
79
79
  end
80
80
 
81
81
  private
@@ -110,12 +110,13 @@ module Ea
110
110
  end
111
111
 
112
112
  def build_texts(role:, mult:, standalone:, text_pos:, mult_pos:,
113
- stereo:)
113
+ stereo:, text_box:)
114
114
  if role && !role.empty? && text_pos
115
115
  x, y = text_pos
116
116
  texts = [text_at(x, y, role)]
117
117
  if stereo
118
- texts << text_at(x, y + property_y_offset, stereo)
118
+ stereo_x = x + stereotype_offset(role, stereo, text_box)
119
+ texts << text_at(stereo_x, y + property_y_offset, stereo)
119
120
  end
120
121
  if mult && !mult.empty? && mult_pos
121
122
  texts << text_at(*mult_pos, mult)
@@ -128,6 +129,16 @@ module Ea
128
129
  end
129
130
  end
130
131
 
132
+ def stereotype_offset(role, stereo, text_box)
133
+ return 0 unless text_box && text_box["alignment"] == 1
134
+
135
+ role_width = TextRenderer.estimate_width(role, font_size,
136
+ family: font_family).round
137
+ stereo_width = TextRenderer.estimate_width(stereo, font_size,
138
+ family: font_family).round
139
+ ((role_width - stereo_width) / 2.0).round
140
+ end
141
+
131
142
  # EA renders «Property» between role name and multiplicity
132
143
  # exactly on connector ends carrying
133
144
  # t_connector.SourceStereotype / DestStereotype = "Property"
@@ -32,7 +32,7 @@ module Ea
32
32
  content = midpoint_content(connector)
33
33
  return nil if content.nil?
34
34
 
35
- mx, my = midpoint(points)
35
+ mx, my = label_position(connector, points)
36
36
  # EA splits a "name: value" label at the colon-space into
37
37
  # two lines centered on the same slot (81F92FC7: "new
38
38
  # ownership:" / "LA_Right" at y 172/185, both centered on
@@ -95,6 +95,35 @@ module Ea
95
95
 
96
96
  LINE_PITCH = 13
97
97
 
98
+ def label_position(connector, points)
99
+ return midpoint(points) unless default_substitutable_seat?(connector) &&
100
+ upward_four_point_route?(points)
101
+
102
+ source, bend = points
103
+ [source[0] + 3, ((source[1] + bend[1]) / 2).floor - 1]
104
+ end
105
+
106
+ def default_substitutable_seat?(connector)
107
+ connector.is_a?(Ea::Model::DiagramConnector) && connector.is_substitutable &&
108
+ connector.connector_type == "Generalization" && font_size == 7 &&
109
+ stereotype_label(connector).nil? &&
110
+ default_constraint_box?(connector.label_boxes.to_h[:lmb])
111
+ end
112
+
113
+ def default_constraint_box?(box)
114
+ box && !box["hidden"] &&
115
+ box.values_at("ox", "oy", "rotation", "alignment") == [0, 0, 0, 0]
116
+ end
117
+
118
+ def upward_four_point_route?(points)
119
+ return false unless points.size == 4
120
+
121
+ source, first_bend, last_bend, target = points
122
+ source[0] == first_bend[0] && first_bend[1] == last_bend[1] &&
123
+ last_bend[0] == target[0] && source[1] > first_bend[1] &&
124
+ target[1] < last_bend[1]
125
+ end
126
+
98
127
  def midpoint(points)
99
128
  mid_idx = points.size / 2
100
129
  if points.size.odd?
data/lib/ea/version.rb CHANGED
@@ -4,5 +4,5 @@ module Ea
4
4
  # Gem release version. Extracted to its own file so the
5
5
  # `gem-release` tool's `gem bump` can find and update it via the
6
6
  # standard `lib/<gem>/version.rb` convention.
7
- VERSION = "0.6.89"
7
+ VERSION = "0.6.91"
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.89
4
+ version: 0.6.91
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-14 00:00:00.000000000 Z
11
+ date: 2026-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -815,6 +815,7 @@ files:
815
815
  - lib/ea/sources/qea/connector_relationship_map.rb
816
816
  - lib/ea/sources/qea/context_diagram_support.rb
817
817
  - lib/ea/sources/qea/diagram_builder.rb
818
+ - lib/ea/sources/qea/diagram_builder/routing.rb
818
819
  - lib/ea/sources/qea/diagram_style_parser.rb
819
820
  - lib/ea/sources/qea/id_normalizer.rb
820
821
  - lib/ea/sources/qea/instance_builder.rb