ea 0.6.99 → 0.6.100
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f60cf58bb521e911eb26fb30b628162e149a9521027fae0e77756ef1b8354dd
|
|
4
|
+
data.tar.gz: c0580ced4122e41650fbc5053fb0fb534a40c8cae84f758039dd8b2f575422ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6460bd84357b97c350c485526bcc62bc74309d924eacd9e7983347b101c22d1fcfc246dc7b76d443eb04cb360e2be890eaf1cffedf49103e9f2419b5205fe3a7
|
|
7
|
+
data.tar.gz: 2a8ce3556ffa2b5531e432002ab7a67439cd4f4ed171c1f84f141b264e9fa6913af3ffd8983dbb0be70c9282407fdb3695efb07728c0217c656688ed38f8d023
|
|
@@ -23,6 +23,56 @@ module Ea
|
|
|
23
23
|
set_stored_ray_origins(model, source, target, geom)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# A Mode 2 Generalization whose stored Path is a horizontal jog
|
|
27
|
+
# between vertically separated boxes that overlap in x is EA's
|
|
28
|
+
# auto-route: one vertical ray at the overlap midpoint plus SX.
|
|
29
|
+
# The stored jog is stale; the ray docks on both 1px outlines.
|
|
30
|
+
def mode2_overlap_ray(model, link_row, diagram_row, geom, context)
|
|
31
|
+
return model if context || !mode2_overlap_candidate?(model, link_row, geom)
|
|
32
|
+
|
|
33
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
34
|
+
column = overlap_ray_coordinate(source.x, source.width, target.x, target.width, geom[:sx])
|
|
35
|
+
return model unless column && vertical_separation?(source, target)
|
|
36
|
+
|
|
37
|
+
set_overlap_ray(model, column, source, target)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_overlap_ray(model, column, source, target)
|
|
41
|
+
model.waypoints = overlap_ray_ends(column, source, target).map do |x, y|
|
|
42
|
+
Ea::Model::Waypoint.new(position: Ea::Model::Point.new(x: x, y: y))
|
|
43
|
+
end
|
|
44
|
+
model.source_anchor, model.target_anchor = [source, target].map do |box|
|
|
45
|
+
Ea::Model::Point.new(x: column, y: box.y + box.height / 2)
|
|
46
|
+
end
|
|
47
|
+
model
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def mode2_overlap_candidate?(model, link_row, geom)
|
|
51
|
+
model.connector_type == "Generalization" && model.has_geometry_offsets &&
|
|
52
|
+
mode2_overlap_style?(model.style.to_h, geom) && model.waypoints.size == 4 &&
|
|
53
|
+
horizontal_jog?(intermediate_waypoints(link_row))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def mode2_overlap_style?(style, geom)
|
|
57
|
+
style[:mode] == "2" && style[:lwidth] == "2" && !style[:tree] && geom[:edge] == 1 && geom[:sx]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def horizontal_jog?(corners)
|
|
61
|
+
corners.size == 2 && corners[0][1] == corners[1][1]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def vertical_separation?(source, target)
|
|
65
|
+
source.y + source.height < target.y || target.y + target.height < source.y
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def overlap_ray_ends(column, source, target)
|
|
69
|
+
if target.y + target.height < source.y
|
|
70
|
+
[[column, source.y - 1], [column, target.y + target.height + 1]]
|
|
71
|
+
else
|
|
72
|
+
[[column, source.y + source.height + 1], [column, target.y - 1]]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
26
76
|
def empty_path_ray_gap?(source, target, geom)
|
|
27
77
|
return false unless source.x + source.width < target.x
|
|
28
78
|
|
|
@@ -55,6 +105,134 @@ module Ea
|
|
|
55
105
|
set_stored_ray_origins(model, source, target, geom)
|
|
56
106
|
end
|
|
57
107
|
|
|
108
|
+
# An empty-Path Mode 3 Association with explicit offsets on both
|
|
109
|
+
# ends is the straight line between the two stored ray origins.
|
|
110
|
+
# Origins outside their own box are not a stored ray.
|
|
111
|
+
def association_stored_ray(model, link_row, diagram_row, geom, context)
|
|
112
|
+
return model if context
|
|
113
|
+
return model unless association_ray_candidate?(model, link_row)
|
|
114
|
+
|
|
115
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
116
|
+
origins = association_ray_origins(source, target, geom)
|
|
117
|
+
return model unless origins
|
|
118
|
+
|
|
119
|
+
model.waypoints = [model.source_port, model.target_port].map do |point|
|
|
120
|
+
Ea::Model::Waypoint.new(position: point)
|
|
121
|
+
end
|
|
122
|
+
model.source_anchor, model.target_anchor = origins
|
|
123
|
+
model
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def association_ray_origins(source, target, geom)
|
|
127
|
+
origins = [stored_ray_origin(source, geom[:sx], geom[:sy]),
|
|
128
|
+
stored_ray_origin(target, geom[:ex], geom[:ey])]
|
|
129
|
+
inside = origins.zip([source, target]).all? { |o, b| inside_box?(o, b) }
|
|
130
|
+
origins if inside && sub_pixel_safe_origins?(origins, [source, target])
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def association_ray_candidate?(model, link_row)
|
|
134
|
+
model.connector_type == "Association" && link_row.path.to_s.empty? &&
|
|
135
|
+
model.has_geometry_offsets && model.source_port && model.target_port &&
|
|
136
|
+
association_ray_style?(model.style.to_h)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def association_ray_style?(style)
|
|
140
|
+
style[:mode] == "3" && !style[:tree] && %w[0 2].include?(style[:lwidth]) &&
|
|
141
|
+
%i[direct regenerated].none? { |key| style.key?(key) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Origins are whole pixels while EA's box centers may sit on a
|
|
145
|
+
# half pixel. An origin on its border then clips differently
|
|
146
|
+
# unless the line is axis-aligned, which no rounding changes.
|
|
147
|
+
def sub_pixel_safe_origins?(origins, boxes)
|
|
148
|
+
first, last = origins
|
|
149
|
+
return false if first == last
|
|
150
|
+
|
|
151
|
+
axis_aligned = first.x == last.x || first.y == last.y
|
|
152
|
+
axis_aligned || origins.zip(boxes).none? { |point, box| on_border?(point, box) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def on_border?(point, bounds)
|
|
156
|
+
[bounds.x, bounds.x + bounds.width].include?(point.x) ||
|
|
157
|
+
[bounds.y, bounds.y + bounds.height].include?(point.y)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def inside_box?(point, bounds)
|
|
161
|
+
point.x.between?(bounds.x, bounds.x + bounds.width) &&
|
|
162
|
+
point.y.between?(bounds.y, bounds.y + bounds.height)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# A Mode 2 Association with equal SX/EX (vertical EDGE 1/3) or
|
|
166
|
+
# equal SY/EY (horizontal EDGE 4) and no custom Path docks as a
|
|
167
|
+
# straight line through the middle of the boxes' overlap, moved
|
|
168
|
+
# by the shared stored offset. Stored SY counts upward.
|
|
169
|
+
def overlap_ray_origins(model, link_row, diagram_row, geom, context)
|
|
170
|
+
return model if context
|
|
171
|
+
return model unless overlap_ray_candidate?(model, link_row, geom)
|
|
172
|
+
|
|
173
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
174
|
+
origins = overlap_origins(source, target, geom)
|
|
175
|
+
return model unless origins
|
|
176
|
+
|
|
177
|
+
model.source_anchor, model.target_anchor = origins.map do |ox, oy|
|
|
178
|
+
Ea::Model::Point.new(x: ox, y: oy)
|
|
179
|
+
end
|
|
180
|
+
model.style[:overlap_ray] = true
|
|
181
|
+
model
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Both ray origins share the overlap coordinate on the axis the
|
|
185
|
+
# offsets move and sit at each box's centre on the other axis.
|
|
186
|
+
def overlap_origins(source, target, geom)
|
|
187
|
+
along, across, offset = overlap_axes(geom)
|
|
188
|
+
coordinate = overlap_ray_coordinate(*box_span(source, along), *box_span(target, along), offset)
|
|
189
|
+
return nil unless coordinate && disjoint_span?(*box_span(source, across), *box_span(target, across))
|
|
190
|
+
|
|
191
|
+
[source, target].map do |box|
|
|
192
|
+
start, size = box_span(box, across)
|
|
193
|
+
centre = start + size / 2.0
|
|
194
|
+
geom[:edge] == 4 ? [centre, coordinate] : [coordinate, centre]
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# [axis the offsets move, axis the boxes face, stored offset]
|
|
199
|
+
def overlap_axes(geom)
|
|
200
|
+
return [%i[y height], %i[x width], -geom[:sy]] if geom[:edge] == 4
|
|
201
|
+
|
|
202
|
+
[%i[x width], %i[y height], geom[:sx]]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def box_span(box, keys)
|
|
206
|
+
keys.map { |key| box.public_send(key) }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def overlap_ray_candidate?(model, link_row, geom)
|
|
210
|
+
model.connector_type == "Association" && model.style[:mode] == "2" &&
|
|
211
|
+
model.waypoints.size == 2 && link_row.path.to_s.empty? &&
|
|
212
|
+
model.has_geometry_offsets && uniform_overlap_offsets?(geom)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def uniform_overlap_offsets?(geom)
|
|
216
|
+
return false unless geom.values_at(:sx, :sy, :ex, :ey).all?
|
|
217
|
+
|
|
218
|
+
geom[:edge] == 4 ? geom[:sy] == geom[:ey] : [1, 3].include?(geom[:edge]) && geom[:sx] == geom[:ex]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Floor of the overlap midpoint plus the shared offset; nil
|
|
222
|
+
# when the boxes do not overlap on this axis.
|
|
223
|
+
def overlap_ray_coordinate(start_a, size_a, start_b, size_b, offset)
|
|
224
|
+
low = [start_a, start_b].max
|
|
225
|
+
high = [start_a + size_a, start_b + size_b].min
|
|
226
|
+
return nil if low > high
|
|
227
|
+
|
|
228
|
+
coordinate = ((low + high) / 2.0 + offset).floor
|
|
229
|
+
coordinate if coordinate.between?(low, high)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def disjoint_span?(start_a, size_a, start_b, size_b)
|
|
233
|
+
start_a + size_a < start_b || start_b + size_b < start_a
|
|
234
|
+
end
|
|
235
|
+
|
|
58
236
|
def stored_ray_boxes(link_row, diagram_row)
|
|
59
237
|
connector = database.find_connector(link_row.connectorid)
|
|
60
238
|
[source_placement_for(connector, diagram_row),
|
|
@@ -134,7 +312,7 @@ module Ea
|
|
|
134
312
|
|
|
135
313
|
case connector.connector_type
|
|
136
314
|
when "Aggregation"
|
|
137
|
-
return
|
|
315
|
+
return unless style[:lwidth] == "0" || !geometry_has_offsets?(link_row.geometry)
|
|
138
316
|
|
|
139
317
|
redock_child_below(points, bounds_from_rect(source_placement), bounds_from_rect(target_placement))
|
|
140
318
|
when "Association"
|
|
@@ -334,14 +512,13 @@ module Ea
|
|
|
334
512
|
# Keep a vertical tree trunk whose bends still fit both boxes
|
|
335
513
|
# and the open gap; the closest facing edges need not share its x.
|
|
336
514
|
def preserve_stored_tree?(points, link_row, connector, src, tgt)
|
|
337
|
-
return false unless
|
|
515
|
+
return false unless points.size == 4 && stored_tree_type?(points, link_row, connector)
|
|
338
516
|
|
|
339
517
|
style = DiagramStyleParser.parse(link_row.style)
|
|
340
518
|
return false unless style[:lwidth] == "2" || flush_width_zero_tree?(link_row)
|
|
341
|
-
return false unless style
|
|
342
|
-
(style[:mode] == "3" && style[:tree].nil?)
|
|
519
|
+
return false unless vertical_tree_style?(style)
|
|
343
520
|
|
|
344
|
-
tree_bends_fit?(points[1], points[-2], src, tgt)
|
|
521
|
+
tree_bends_fit?(points[1], points[-2], src, tgt, flush_width_zero_tree?(link_row))
|
|
345
522
|
end
|
|
346
523
|
|
|
347
524
|
# A width-2 Mode 3 horizontal tree keeps its stored vertical trunk
|
|
@@ -412,6 +589,38 @@ module Ea
|
|
|
412
589
|
model
|
|
413
590
|
end
|
|
414
591
|
|
|
592
|
+
# Same one-pixel collapse for a two-waypoint computed line when only
|
|
593
|
+
# the target carries an explicit port (the source end is the default
|
|
594
|
+
# edge midpoint): the left-hand end's y wins.
|
|
595
|
+
def collapse_partial_port_jog(model, link_row, diagram_row)
|
|
596
|
+
return model unless partial_port_jog_candidate?(model, link_row)
|
|
597
|
+
|
|
598
|
+
connection = database.find_connector(link_row.connectorid)
|
|
599
|
+
src, tgt = [connection.start_object_id, connection.end_object_id].map do |id|
|
|
600
|
+
placement = diagram_object_placement(diagram_row.diagram_id, id)
|
|
601
|
+
placement && bounds_from_rect(placement)
|
|
602
|
+
end
|
|
603
|
+
from, to = model.waypoints.map(&:position)
|
|
604
|
+
return model unless src && tgt && facing_jog_ports?(from, to, src, tgt)
|
|
605
|
+
|
|
606
|
+
level = [from, to].min_by(&:x).y
|
|
607
|
+
ends = [[from.x, level], [to.x, level]].map do |x, y|
|
|
608
|
+
Ea::Model::Point.new(x: x, y: y)
|
|
609
|
+
end
|
|
610
|
+
model.waypoints = ends.map { |point| Ea::Model::Waypoint.new(position: point) }
|
|
611
|
+
model.source_anchor = model.source_port = ends.first
|
|
612
|
+
model.target_anchor = model.target_port = ends.last
|
|
613
|
+
model
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def partial_port_jog_candidate?(model, link_row)
|
|
617
|
+
style = model.style.to_h
|
|
618
|
+
model.connector_type == "Aggregation" &&
|
|
619
|
+
link_row.path.to_s.empty? && style[:mode] == "3" && style[:lwidth] == "2" &&
|
|
620
|
+
!style.key?(:tree) && model.source_port.nil? && model.target_port &&
|
|
621
|
+
model.waypoints.size == 2
|
|
622
|
+
end
|
|
623
|
+
|
|
415
624
|
def computed_jog_candidate?(model, link_row, geom)
|
|
416
625
|
style = model.style.to_h
|
|
417
626
|
model.connector_type == "Generalization" && geom[:edge] == 2 &&
|
|
@@ -438,14 +647,32 @@ module Ea
|
|
|
438
647
|
style[:mode] == "3" && style[:tree] == "V" && style[:lwidth] == "0"
|
|
439
648
|
end
|
|
440
649
|
|
|
441
|
-
def
|
|
442
|
-
|
|
443
|
-
|
|
650
|
+
def vertical_tree_style?(style)
|
|
651
|
+
style[:tree] == "V" || (style[:mode] == "3" && style[:tree].nil?)
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def stored_tree_type?(points, link_row, connector)
|
|
655
|
+
connector.connector_type == "Aggregation" || flush_straight_association?(points, link_row, connector)
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# A flush Association keeps only a straight trunk (one bend stored twice).
|
|
659
|
+
def flush_straight_association?(points, link_row, connector)
|
|
660
|
+
connector.connector_type == "Association" && points[1] == points[-2] &&
|
|
661
|
+
flush_width_zero_tree?(link_row)
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# A flush trunk may also hang below its parent; a bend repeated at
|
|
665
|
+
# one x is a straight trunk with a single bend.
|
|
666
|
+
def tree_bends_fit?(first, last, source, target, flush)
|
|
667
|
+
first[1] == last[1] &&
|
|
668
|
+
within_vertical_gap?(first[1], source, target, flush) &&
|
|
444
669
|
within_horizontal_span?(first, source) && within_horizontal_span?(last, target)
|
|
445
670
|
end
|
|
446
671
|
|
|
447
|
-
def within_vertical_gap?(height, source, target)
|
|
448
|
-
height < source.y - 1 && height > target.y + target.height + 1
|
|
672
|
+
def within_vertical_gap?(height, source, target, flush)
|
|
673
|
+
return true if height < source.y - 1 && height > target.y + target.height + 1
|
|
674
|
+
|
|
675
|
+
flush && height > source.y + source.height + 1 && height < target.y - 1
|
|
449
676
|
end
|
|
450
677
|
|
|
451
678
|
def within_horizontal_span?(point, bounds)
|
|
@@ -421,8 +421,12 @@ module Ea
|
|
|
421
421
|
has_geometry_offsets: geometry_has_offsets?(link_row.geometry),
|
|
422
422
|
is_substitutable: substitutable_connector?(connector)
|
|
423
423
|
), link_row, diagram_row, geom)
|
|
424
|
+
model = collapse_partial_port_jog(model, link_row, diagram_row)
|
|
424
425
|
model = direct_empty_path_ray(model, link_row, diagram_row, geom, context)
|
|
425
|
-
|
|
426
|
+
model = mode2_overlap_ray(model, link_row, diagram_row, geom, context)
|
|
427
|
+
model = association_stored_ray(model, link_row, diagram_row, geom, context)
|
|
428
|
+
model = correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
|
|
429
|
+
overlap_ray_origins(model, link_row, diagram_row, geom, context)
|
|
426
430
|
end
|
|
427
431
|
|
|
428
432
|
# Ray origin for one connector end: the element's center
|
|
@@ -42,7 +42,7 @@ module Ea
|
|
|
42
42
|
anchors = [connector.source_anchor, connector.target_anchor]
|
|
43
43
|
bounds = [source_bounds, target_bounds]
|
|
44
44
|
original = clipped_pairs(bounds, anchors)
|
|
45
|
-
return original unless stroke_outline?(connector, bounds)
|
|
45
|
+
return original unless stroke_outline?(connector, bounds) || overlap_ray?(connector)
|
|
46
46
|
|
|
47
47
|
# EA docks these markers on the outer edge of the 2px box stroke.
|
|
48
48
|
outline = bounds.map do |box|
|
|
@@ -103,6 +103,14 @@ module Ea
|
|
|
103
103
|
end
|
|
104
104
|
private_class_method :stroke_outline?
|
|
105
105
|
|
|
106
|
+
# The QEA builder marks the Mode 2 Association overlap rays it
|
|
107
|
+
# produced; EA docks them on the outer stroke edge whatever the
|
|
108
|
+
# line width. Naturally aligned centres keep the legacy docking.
|
|
109
|
+
def overlap_ray?(connector)
|
|
110
|
+
connector.connector_type == "Association" && connector.style.to_h[:overlap_ray] == true
|
|
111
|
+
end
|
|
112
|
+
private_class_method :overlap_ray?
|
|
113
|
+
|
|
106
114
|
def outline_connector?(connector, bounds)
|
|
107
115
|
return true if %w[Generalization Aggregation].include?(connector.connector_type)
|
|
108
116
|
|
|
@@ -197,6 +205,7 @@ module Ea
|
|
|
197
205
|
bounds_map, divider_y_by_ref)
|
|
198
206
|
return nil unless bounds_map && divider_y_by_ref
|
|
199
207
|
return nil unless pairs.size == 2
|
|
208
|
+
return nil if explicit_association_ray?(pairs, connector)
|
|
200
209
|
|
|
201
210
|
rel = relationship_for(connector, model_index)
|
|
202
211
|
return nil unless rel
|
|
@@ -231,16 +240,28 @@ module Ea
|
|
|
231
240
|
end
|
|
232
241
|
private_class_method :explicit_horizontal_source_dock?
|
|
233
242
|
|
|
234
|
-
|
|
235
|
-
|
|
243
|
+
# A stored Association ray whose source port certifies the shared
|
|
244
|
+
# anchor row is one explicit horizontal line: EA seats neither end
|
|
245
|
+
# in a compartment row, unlike Generalization/Aggregation targets.
|
|
246
|
+
def explicit_association_ray?(pairs, connector)
|
|
247
|
+
return false unless explicit_horizontal_ray?(connector, %w[Association])
|
|
248
|
+
|
|
249
|
+
connector.source_anchor.y == connector.target_anchor.y &&
|
|
250
|
+
connector.source_port.y == connector.source_anchor.y &&
|
|
251
|
+
pairs.first[1] == pairs.last[1]
|
|
252
|
+
end
|
|
253
|
+
private_class_method :explicit_association_ray?
|
|
254
|
+
|
|
255
|
+
def explicit_horizontal_ray?(connector, types = %w[Generalization Aggregation])
|
|
256
|
+
explicit_horizontal_ray_style?(connector, types) && connector.has_geometry_offsets &&
|
|
236
257
|
connector.source_port && connector.target_port &&
|
|
237
258
|
connector.source_anchor && connector.target_anchor
|
|
238
259
|
end
|
|
239
260
|
private_class_method :explicit_horizontal_ray?
|
|
240
261
|
|
|
241
|
-
def explicit_horizontal_ray_style?(connector)
|
|
262
|
+
def explicit_horizontal_ray_style?(connector, types)
|
|
242
263
|
style = connector.style.to_h
|
|
243
|
-
|
|
264
|
+
types.include?(connector.connector_type) &&
|
|
244
265
|
style[:mode] == "3" && !style[:tree] && !style[:direct] && !style[:regenerated]
|
|
245
266
|
end
|
|
246
267
|
private_class_method :explicit_horizontal_ray_style?
|
data/lib/ea/version.rb
CHANGED