-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathAutoLayout.m
More file actions
1092 lines (986 loc) · 48.8 KB
/
AutoLayout.m
File metadata and controls
1092 lines (986 loc) · 48.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
function AutoLayout(selected_objects, varargin)
% AUTOLAYOUT Make a system more readable by automatically laying out
% the given Simulink objects (blocks and annotations only) with respect
% to each other. Other objects in the system are shifted to prevent
% overlapping with the laid out objects.
%
% Inputs:
% selected_objects List (vector or cell array) of Simulink objects
% (fullnames or handles) with the same parent
% system. Lines and ports are ignored, however,
% lines and ports associated with a given block
% are laid out along with the corresponding
% block.
% varargin Parameter-Value pairs as detailed below. Some of the
% options are also found in config.txt of the same
% directory as AutoLayout.m, if not specified via
% direct input, the default value of these parameters
% will be picked up from that file (these defaults are
% marked with "config.txt").
%
% Parameter-Value pairs:
% Parameters related to moving unselected objects in the system
% Parameter: 'LayoutStartBounds'
% Value: Vector given like a block's position parameter as
% [left top right bot] specifying the bounds of the positions of
% the given objects. Default: [] indicating that this should be
% determined by the object positions when this function is
% called.
% Parameter: 'ShiftAll'
% Value: 'on' - (Default) All objects in the same system as the selected
% objects may be shifted to prevent overlaps. I.e. unselected
% objects are just shifted out of the way.
% 'off' - Only objects given as input may be shifted.
%
% Parameters related to initial layout:
% Parameter: 'LayoutType' - Indicates the type of layout to perform
% to get the initial layout. This may affect other aspects of the
% layout that occur after as well since assumptions about the
% initial layout can sometimes be used to make other
% modifications.
% Value: 'GraphPlot' - Uses MATLAB's built-in GraphPlot objects to
% construct a graph where the blocks are nodes and signal
% lines are edges and then lays out blocks using the plot
% function with a layered layout.
% 'Graphviz' - Uses Graphviz (a 3rd-party tool) which must be
% installed to generate an initial layout in a similar
% fashion to GraphPlot (however Graphviz is generally
% able to provide a somewhat improved initial layout).
% 'DepthBased' - Assigns initial columns to place blocks in
% based on when a block is reached. Essentially, if block
% A connects to block B, then block B will have a depth
% equal to the depth of A + 1 and depth directly
% determines the column.
% From config.txt - (Default)
% Parameter: 'Columns' - Only available if LayoutType parameter is
% 'DepthBased'.
% Value: A containers.Map() variable mapping from block handles to
% the desired column number for the corresponding block. The
% minimum column is 1 and it will be the furthest left in the
% Simulink diagram. Blocks that are not in the map, but are in
% the input may be assigned a column arbitrarily. (Default) Use
% built-in functions to find reasonable columns automatically
% based on distance from other blocks.
% Parameter: 'ColumnWidthMode' - Column width refers to the space
% allocated for blocks in a given column (used for spacing
% between columns.
% Value: 'MaxBlock' - Each column is as wide as the widest block
% in the input set of blocks.
% 'MaxColBlock' - (Default) Each column is as wide as the
% widest block in that column.
% Parameter: 'ColumnAlignment' - Available if LayoutType parameter is
% one of the following: 'GraphPlot', 'Graphviz', 'DepthBased'
% (all current layout types Aug. 20, 2018).
% Value: 'left' - (Default) All blocks in a column will share a
% left position.
% 'right' - All blocks in a column will share a right
% position.
% 'center' - All blocks in a column will be centered around
% the same point on the horizontal axis.
% Parameter: 'HorizSpacing' - Refers to space between columns.
% Value: Any double. Default: 80.
%
% Parameters related to block width:
% Parameter: 'AdjustWidthParams'
% Value: Cell array of optional arguments to pass to adjustWidth.m,
% 'PerformOperation', 'off' is passed automatically. (Default)
% Empty cell array (pass no optional arguments except
% 'PerformOperation').
% Parameter: 'WidthMode' - Rule used for consistency of widths of
% blocks.
% Value: 'AsIs' - (Default) After initial adjustment of widths, no
% change is made.
% 'MaxBlock' - After initial adjustment of widths, each block
% in each column is made as wide as the widest block in
% the input set of blocks.
% 'MaxColBlock' - After initial adjustment of widths, each
% block in each column is made as wide as the widest
% block in that column.
% 'MaxOfType' - After initial adjustment of widths, each
% block is made as wide as the widest block of its type.
% Here, blocks have the same type if they share mask type
% and block type.
%
% Parameters related to block height:
% Parameter: 'AdjustHeightParams'
% Value: Cell array of optional arguments to pass to adjustHeight.m,
% 'PerformOperation', 'off' is passed automatically. (Default)
% Empty cell array (pass no optional arguments except
% 'PerformOperation').
% Parameter: 'SubHeightMethod' - Refers to the "Method" used to determine
% heights of SubSystem blocks (options are the same as in
% adjustHeightForConnectedBlocks).
% Value: Options are 'Sum', 'SumMax' (Default), 'MinMax', and 'Compact'.
% Parameter: 'VertSpacing' - Refers to space between blocks within a
% column (essentially this is used where alignment fails).
% Value: Any double. Default: 20.
% Parameter: 'AlignmentType'
% Value: 'Source' - (Default) Try to align a blocks with a source.
% 'Dest' - Try to align a blocks with a destination.
%
% Miscellaneous:
% Parameter: 'DesiredSumShape'
% Value: 'rectangular' - Changes the shapes of Sum blocks to be
% rectangular.
% 'round' - Changes the shapes of Sum blocks to be round.
% 'any' - (Default) Does not change the shapes of Sum blocks.
% Parameter: 'PortlessRule' - Determines where blocks with no ports should
% be placed in the layout with respect to the bounds of the rest of
% the selected objects after they have been laid out.
% Value: 'left' - Portless blocks placed left of the bounds.
% 'top' - Portless blocks placed above the bounds.
% 'right' - Portless blocks placed right of the bounds.
% 'bottom' - Portless blocks placed below the bounds.
% 'same_half_vertical' - Portless blocks placed above if they
% began in the top-half of the starting bounds, below
% otherwise.
% 'same_half_horizontal' - Portless blocks placed on the left if
% they began in the top-half of the starting bounds, on the
% right otherwise.
% From config.txt - (Default)
% Parameter: 'PortlessSortRule' - Determines the way blocks with no ports
% will be grouped with their placements defined by PortlessRule.
% Value: 'blocktype' - Portless blocks will be grouped by their BlockType
% parameter.
% 'masktype_blocktype' - Portless blocks will be grouped into
% unique MaskType-BlockType pairs.
% 'none' - No grouping is done.
% From config.txt - (Default) See the sort_portless config
% Parameter: 'InportRule' - Determines how to layout inport blocks.
% Value: 'left-align' - Inports are aligned on the far left of the layout
% bounds unless there are obstructions.
% 'none' - Inports are treated the same as other blocks in the
% layout.
% From config.txt - (Default)
% Parameter: 'OutportRule' - Determines how to layout outport blocks.
% Value: 'right-align' - Outports are aligned on the far right of the
% layout bounds unless there are obstructions.
% 'none' - Outports are treated the same as other blocks in the
% layout.
% From config.txt - (Default)
% Parameter: 'NoteRule' - Determines how to layout annotations.
% Value: 'on-right' - Selected annotations are moved to the right side of
% the system so they can be found easily.
% 'none' - Annotations are not moved at all.
% From config.txt - (Default)
% Parameter: 'ShowNames' - Determines whether or not to show block names.
% Value: 'no-change' - Block names will remain showing/hidden without
% change.
% 'all' - All blocks will show their name.
% 'none' - No blocks will show their name.
% From config.txt - (Default)
%
% Outputs:
% N/A
%
% Example:
% >> open_system('AutoLayoutDemo')
% *Select everything in AutoLayoutDemo (ctrl + A)*
% >> AutoLayout(gcbs)
% Result: Modifies the AutoLayoutDemo system with one that performs
% the same functionally, but is laid out to be more readable.
%% Input handling
% Handle parameter-value pairs
LayoutStartBounds = [];
ShiftAll = 'on';
LayoutType = default_layout_type();
Columns = containers.Map(); % indicates to find columns automatically
ColumnWidthMode = lower('MaxColBlock');
ColumnAlignment = 'left';
HorizSpacing = 80;
AdjustWidthParams = {};
WidthMode = lower('AsIs');
AdjustHeightParams = {};
SubHeightMethod = 'SumMax';
VertSpacing = 20;
AlignmentType = lower('Source');
DesiredSumShape = 'any';
PortlessRule = getAutoLayoutConfig('portless_rule', 'top');
PortlessSortRule = getAutoLayoutConfig('sort_portless', 'blocktype');
InportRule = getAutoLayoutConfig('inport_rule', 'left-align');
OutportRule = getAutoLayoutConfig('outport_rule', 'right-align');
NoteRule = getAutoLayoutConfig('note_rule', 'on-right');
ShowNames = getAutoLayoutConfig('show_names', 'no-change');
assert(mod(length(varargin),2) == 0, 'Even number of varargin arguments expected.')
for i = 1:2:length(varargin)
param = lower(varargin{i});
value = varargin{i+1};
if ischar(value) || (iscell(value) && all(cellfun(@(a) ischar(a), value)))
value = lower(value);
end
switch param
case lower('LayoutStartBounds')
assert(isa(value, 'double') && any(length(value) == [4 0]), ...
['Unexpected value for ' param ' parameter.'])
LayoutStartBounds = value;
case lower('ShiftAll')
assert(any(strcmpi(value, {'on','off'})), ...
['Unexpected value for ' param ' parameter.'])
ShiftAll = value;
case lower('LayoutType')
if strcmpi(value, 'Default')
tmp_value = default_layout_type();
else
tmp_value = value;
end
assert(any(strcmpi(tmp_value, {'GraphPlot', 'Graphviz', 'DepthBased'})), ...
['Unexpected value for ' param ' parameter.'])
LayoutType = tmp_value;
case lower('Columns')
assert(isa(value, 'containers.Map'), ...
['Unexpected value for ' param ' parameter.'])
if strcmp(value.KeyType, 'char')
Columns = fullname_map2handle_map(value);
else
Columns = value;
end
case lower('ColumnWidthMode')
assert(any(strcmpi(value,{'MaxBlock','MaxColBlock'})), ...
['Unexpected value for ' param ' parameter.'])
ColumnWidthMode = value;
case lower('ColumnAlignment')
assert(any(strcmpi(value,{'left','right','center'})), ...
['Unexpected value for ' param ' parameter.'])
ColumnAlignment = value;
case lower('HorizSpacing')
assert(isnumeric(value), ...
['Unexpected value for ' param ' parameter.'])
HorizSpacing = value;
case lower('AdjustWidthParams')
assert(iscell(value), ...
['Unexpected value for ' param ' parameter.'])
AdjustWidthParams = value;
case lower('WidthMode')
assert(any(strcmpi(value,{'AsIs','MaxBlock','MaxColBlock'})), ...
['Unexpected value for ' param ' parameter.'])
WidthMode = value;
case lower('AdjustHeightParams')
assert(iscell(value), ...
['Unexpected value for ' param ' parameter.'])
AdjustHeightParams = value;
case lower('SubHeightMethod')
assert(any(strcmpi(value, {'Sum', 'SumMax', 'MinMax', 'Compact'})), ...
['Unexpected value for ' param ' parameter.'])
SubHeightMethod = value;
case lower('VertSpacing')
assert(isnumeric(value), ...
['Unexpected value for ' param ' parameter.'])
VertSpacing = value;
case lower('AlignmentType')
assert(any(strcmpi(value,{'Source','Dest'})), ...
['Unexpected value for ' param ' parameter.'])
AlignmentType = value;
case lower('PortlessRule')
assert(any(strcmpi(value, ...
{'left', 'top', 'right', 'bottom', 'same_half_vertical', 'same_half_horizontal'})), ...
['Unexpected value for ' param ' parameter.'])
PortlessRule = value;
case lower('DesiredSumShape')
assert(any(strcmpi(value, {'rectangular', 'round', 'any'})), ...
['Unexpected value for ' param ' parameter.'])
DesiredSumShape = value;
case lower('PortlessSortRule')
assert(any(strcmpi(value, {'blocktype', 'masktype_blocktype', 'none'})), ...
['Unexpected value for ' param ' parameter.'])
PortlessSortRule = value;
case lower('InportRule')
assert(any(strcmpi(value, {'left-align', 'none'})), ...
['Unexpected value for ' param ' parameter.'])
InportRule = value;
case lower('OutportRule')
assert(any(strcmpi(value, {'right-align', 'none'})), ...
['Unexpected value for ' param ' parameter.'])
OutportRule = value;
case lower('NoteRule')
assert(any(strcmpi(value, {'on-right', 'none'})), ...
['Unexpected value for ' param ' parameter.'])
NoteRule = value;
case lower('ShowNames')
assert(any(strcmpi(value, {'no-change', 'all', 'none'})), ...
['Unexpected value for ' param ' parameter.'])
ShowNames = value;
otherwise
error(['Invalid parameter: ' param '.'])
end
end
% Check number of arguments
try
assert(nargin - length(varargin) == 1)
catch
error(' Expected 1 argument before optional parameters.');
end
% Make selected_objects a vector of handles
selected_objects = inputToNumeric(selected_objects);
% Remove lines and ports from selected_objects
[selected_blocks, ~, selected_annotations, ~] = separate_objects_by_type(selected_objects);
selected_objects = [selected_blocks, selected_annotations];
% Check first argument
% 1) Determine the system in which the layout is taking place
% 2) Check that all objects are in the system
% 3) Check that model is unlocked
% 4) If address has a LinkStatus, then check that it is 'none' or
% 'inactive'
if isempty(selected_objects)
disp('Nothing selected to lay out.')
return
else
% 1), 2)
system = getCommonParent(selected_objects);
% 3)
try
assert(strcmp(get_param(bdroot(system), 'Lock'), 'off'));
catch ME
if strcmp(ME.identifier, 'MATLAB:assert:failed') || ...
strcmp(ME.identifier, 'MATLAB:assertion:failed')
error('File is locked');
end
end
% 4)
try
assert(any(strcmp(get_param(system, 'LinkStatus'), {'none','inactive'})), 'LinkStatus must be ''none'' or ''inactive''.')
catch ME
if ~strcmp(ME.identifier,'Simulink:Commands:ParamUnknown')
rethrow(ME)
end
end
end
%% Identify the bounds of the layout prior to automatic layout
if isempty(LayoutStartBounds)
% Get current bounds of given objects
orig_bounds = bounds_of_sim_objects(selected_objects);
else
% Use given bounds
orig_bounds = LayoutStartBounds;
end
%% Rotate given blocks to a right orientation (for left-to-right dataflow)
setOrientations(selected_blocks);
%%
switch DesiredSumShape
case 'round'
change_sum_shape(selected_blocks, 'rectangular');
case 'rectangular'
change_sum_shape(selected_blocks, 'round');
case 'any'
% Skip, leave the sums alone
otherwise
error('Unexpected parameter value. Parameter: DesiredSumShape')
end
%%
showingNamesMap = get_showname_map(selected_blocks, ShowNames);
%% Determine which blocks should be laid out separate to the rest
% This refers primarily to laying out blocks with no ports as the
% layout approaches will generally use ports and connections through
% them to figure out where blocks belong.
%
% selected_objects - all objects to lay out
% objects - vector of object handles to lay out based on dataflow
% objectsToIsolate - vector of object handles to lay out separately
switch PortlessRule
case {'bottom','top','left','right','same_half_vertical','same_half_horizontal'}
% Remove portless blocks from the set of objects so they can be
% handled separately.
objectsToIsolate = getPortlessBlocks(selected_blocks);
objects = setdiff(selected_objects, objectsToIsolate);
case {'none'}
objectsToIsolate = [];
objects = selected_objects;
otherwise
error('Unexpected parameter value.')
end
%% Remove objects that aren't blocks or annotations
% Also get the blocks and annotations
% Separate objects into the different types
[blocks, ~, annotations, ~] = separate_objects_by_type(objects);
% Update objects to blocks and annotations
objects = [blocks, annotations];
blockLines = get_block_lines(blocks); % Lines connected to the given blocks
% Get just lines associated with the blocks
% TODO - involve the selected lines at least as an option
if ~isempty(blocks)
lines = get_block_lines(blocks);
else
lines = [];
end
%% Determine how to lay out blocks that should be laid out separately
% Get just blocks:
[blocksToIsolate, ~, ~, ~] = separate_objects_by_type(objectsToIsolate);
%
switch PortlessRule
case {'left', 'top', 'right', 'bottom'}
% Give all blocks a corresponding 'quadrant' value indicating
% where to place blocks (on the left, top, right, or bottom of
% other blocks). A quadrant is indicated by a point in the
% cartesian coordinate system.
sides = {'left', 'top', 'right', 'bottom'};
quads = [-1 0; 0 1; 1 0; 0 -1]; % Value for quadrants map corresponding with different PortlessRule values. 0 means doesn't matter. Values are points on a cartesian map.
quadrant = quads(strcmp(PortlessRule, sides), :);
quadrants_map = containers.Map('KeyType', 'double', 'ValueType', 'any');
for i = 1:length(blocksToIsolate)
quadrants_map(blocksToIsolate(i)) = quadrant;
end
case {'same_half_vertical', 'same_half_horizontal'}
center_of_blocks = position_center(bounds_of_sim_objects(blocksToIsolate));
quadrants_map = getWhichQuadrantBlocksAreIn(center_of_blocks, blocksToIsolate);
otherwise
error('Unexpected parameter value.')
end
%% Get a representation of the desired layout
% Only need to represent where things belong with respect to each
% other for now, not specific positions, sizing, or line routing
%
% layoutRepresentation - Cell array where each element represents a
% column in the final layout as a cell array of blocks. For
% LayoutType of GraphPlot and Graphviz the order of blocks within a
% column should be preserved in the final layout (for DepthBased the
% order within a column will not be considered when setting
% layoutRepresentation).
switch LayoutType
case lower({'GraphPlot','Graphviz'})
%%
% Perform initial layout using some graphing method
% Determine which approach to use:
% 1) MATLAB's GraphPlot objects; or
% 2) Graphviz (requires separate install)
switch LayoutType
case lower('GraphPlot')
GraphPlotLayout(blocks);
case lower('Graphviz')
GraphvizLayout(blocks);
otherwise
error(['Unexpected value, ' LayoutType ', to parameter, LayoutType.']);
end
layoutRepresentation = find_relative_layout(blocks);
case lower('DepthBased')
% Get a list of columns corresponding to the blocks list
if isempty(Columns)
% Use default function
cols = choose_impact_depths(blocks);
else
% Use given map
% For blocks not in the given map use the default function ...
% to get a column for them.
for i = 1:length(blocks)
tmp_blocks = [];
if ~Columns.isKey(blocks(i))
tmp_blocks(end+1) = blocks(i);
end
end
tmp_cols = getImpactDepths(tmp_blocks);
for i = 1:length(tmp_blocks)
Columns(tmp_blocks(i)) = tmp_cols(i);
end
cols = zeros(1, length(blocks));
for i = 1:length(blocks)
cols(i) = Columns(blocks(i));
end
end
assert(length(cols) == length(blocks))
% TODO: Add option when determining columns to place in/outports in the
% first/last column specfically
% Sort blocks into a cell array based on designated column.
% i.e. All column X blocks will be in a cell array in the Xth
% cell of blx_by_col
blx_by_col = cell(1,length(blocks));
for i = 1:length(blocks)
d = cols(i);
if isempty(blx_by_col{d})
blx_by_col{d} = cell(1,length(blocks));
end
blx_by_col{d}{i} = blocks(i);
end
blx_by_col(cellfun('isempty',blx_by_col)) = [];
for i = 1:length(blx_by_col)
blx_by_col{i}(cellfun('isempty',blx_by_col{i})) = [];
end
layoutRepresentation = blx_by_col;
otherwise
error('Unexpected parameter value.')
end
%% Set blocks to desired base widths
% Actual position horizontally doesn't matter yet
for i = 1:length(selected_blocks)
[~, pos] = adjustWidth(selected_blocks(i), 'PerformOperation', 'off', AdjustWidthParams{:});
set_param(selected_blocks(i), 'Position', pos);
end
%% Place blocks in their columns and adjust widths based on columns
layoutUsesColumns = any(strcmpi(LayoutType, {'GraphPlot', 'Graphviz', 'DepthBased'})); % Layout places blocks in columns.
if layoutUsesColumns
%% Set blocks to widths desired for consistency
% Adjust widths again to make them more consistent within a column
% (depending on an input parameter)
switch WidthMode
case lower('AsIs')
blockWidths = -1*ones(1,length(blocks)); % -1 to indicate no change
case lower('MaxBlock')
width = getMaxWidth(blocks); % Maximum width among all blocks
blockWidths = width*ones(1,length(blocks));
case lower('MaxColBlock')
blockWidths = -2*ones(1,length(blocks)); % No blocks should indicate a width of -2 when being set
count = 0;
for i = 1:length(layoutRepresentation)
width = getMaxWidth(layoutRepresentation{i}); % Maximum width in ith column
for j = 1:length(layoutRepresentation{i})
blockWidths(count+j) = width;
end
count = count + length(layoutRepresentation{i});
end
case lower('MaxOfType')
% Make blocks as wide as the widest block of its type
blockWidths = -2*ones(1,length(blocks)); % No blocks should indicate a width of -2 when being set
for i = 1:length(blocks)
width = getBlockWidth(blocks(i)); % initial width
for j = 1:length(blocks)
if cmp_type_of_block(blocks(i), blocks(j)) % blocks are same type
width_j = getBlockWidth(blocks(j));
if width_j > width % block is wider
width = width_j; % update maxwidth for the type
end
end
end
blockWidths(i) = width;
end
otherwise
error('Unexpected parameter.')
end
% set positions based on widths found above
count = 0;
for i = 1:length(layoutRepresentation)
for j = 1:length(layoutRepresentation{i})
b = layoutRepresentation{i}{j};
pos = get_param(b, 'Position');
if blockWidths(count+j) ~= -1
set_param(b, 'Position', pos + [0 0 pos(1)-pos(3)+blockWidths(count+j) 0]);
end
end
count = count + length(layoutRepresentation{i});
end
%% Get desired column widths in a vector
switch ColumnWidthMode
case lower('MaxBlock')
width = getMaxWidth(blocks); % Maximum width among all blocks
colWidths = width*ones(1,length(layoutRepresentation));
case lower('MaxColBlock')
colWidths = zeros(1,length(layoutRepresentation));
for i = 1:length(layoutRepresentation)
width = getMaxWidth(layoutRepresentation{i}); % Maximum width in ith column
colWidths(i) = width;
end
otherwise
error('Unexpected parameter value.')
end
%% Place blocks in their respective columns
% Height doesn't matter yet.
columnLeft = 100; % Left-most point in the current column. Arbitrarily 100 for first column.
for i = 1:length(layoutRepresentation)
% For each column:
columnWidth = colWidths(i); % Get width of current column
alignBlocksInColumn(layoutRepresentation{i}, ColumnAlignment, columnLeft, columnWidth)
% Advance column
columnLeft = columnLeft + columnWidth + HorizSpacing;
end
else
% Currently all layouts use columns so this shouldn't happen
% If a new type of layout is used this should be changed appropriately.
error('Unexpected result.')
end
%% Place names on bottom of blocks
setNamePlacements(blocks)
%% Set heights and vertical positions
switch LayoutType
case lower({'GraphPlot','Graphviz'})
%% Set blocks to desired heights
colOrder = 1:length(layoutRepresentation);
pType = 'Outport';
notPType = 'Inport';
set_blocks_to_desired_heights(layoutRepresentation, colOrder, SubHeightMethod, AdjustHeightParams, notPType);
%% Align and spread vertically
align_and_spread_vertically(layoutRepresentation, colOrder, pType, VertSpacing)
%%%Old approach
% Move blocks with single inport/outport so their port is in line with
% the source/destination port
%layoutRepresentation = vertAlign(layoutRepresentation);
% % layout = easyAlign(layout); %old method, still may be relevant since it attempts to cover more cases
%layout = layout2(address, layout, systemBlocks); %call layout2 after
case lower('DepthBased')
%% Set variables determined by AlignmentType parameter
switch AlignmentType
case lower('Source')
colOrder = 1:length(layoutRepresentation);
pType = 'Inport';
notPType = 'Outport';
case lower('Dest')
colOrder = length(layoutRepresentation):-1:1;
pType = 'Outport';
notPType = 'Inport';
otherwise
error('Unexpected parameter.')
end
%% Set blocks to desired heights
set_blocks_to_desired_heights(layoutRepresentation, colOrder, SubHeightMethod, AdjustHeightParams, notPType);
%% Align and spread vertically
align_and_spread_vertically(layoutRepresentation, colOrder, pType, VertSpacing);
otherwise
error('Unexpected parameter value.')
end
%% Handle Inports specially?
switch InportRule
case 'left-align'
% Inports go on the left of the selected_blocks
inports = find_in_blocks(blocks, 'BlockType', 'Inport');
layoutRepresentation = justifyBlocks(system, layoutRepresentation, inports, 1);
case 'none'
% Skip
otherwise
error('Unexpected parameter value.')
end
%% Handle Outports specially?
switch OutportRule
case 'right-align'
% Outports go on the left of the selected_blocks
outports = find_in_blocks(blocks, 'BlockType', 'Outport');
layoutRepresentation = justifyBlocks(system, layoutRepresentation, outports, 3);
case 'none'
% Skip
otherwise
error('Unexpected parameter value.')
end
%% Reposition portless blocks
% Place blocks that have no ports in a line along a side of the system
% determined by quadrants_map.
% Get bounds to place blocks around
bounds = bounds_of_sim_objects(blocks);
% Get axis to be used when deciding which side to place blocks on
switch PortlessRule
case {'left', 'right', 'same_half_horizontal'}
axis = 'x';
case {'top', 'bottom', 'same_half_vertical'}
axis = 'y';
otherwise
error('Unexpected parameter value.')
end
% Get map from sides to list of block handles
sides_map = quadrants_map2sides_map(quadrants_map, axis);
% Place blocks along a side of the system
blocksToSide(bounds, blocksToIsolate, sides_map, PortlessSortRule);
%% Do something with annotations
% Get bounds to place annotations around
bounds = bounds_of_sim_objects(setdiff(selected_objects, selected_annotations));
% Move all annotations to the right of the system, or leave them
switch NoteRule
case 'on-right'
placeAnnotationsRightOfBounds(bounds, selected_annotations)
case 'none'
% Do not layout annotations
otherwise
error('Unexpected parameter value.')
end
%% Show/hide block names
%(the initial layout may have inadvertently set it off)
%TODO make it so that the names don't need to be found earlier on at least -
%i.e. so this can be done whenever)
set_shownames(showingNamesMap)
%% Redraw lines
blockLines = autolayout_lines(blockLines);
%% Center objects on the original center
% I.e. Shift selected_objects so that the center of their bounds is in
% the same spot the center of the bounds was in to begin with.
% Get new bounds of objects
new_bounds = bounds_of_sim_objects(selected_objects);
% Get center of original bounds
orig_center = position_center(orig_bounds);
% Get center of new bounds
new_center = position_center(new_bounds);
% Get offset between new and original center
center_offset = orig_center - new_center;
% Shift objects by the offset
shift_sim_objects(selected_blocks, [], selected_annotations, center_offset);
new_bounds = bounds_of_sim_objects(selected_objects); % Update new bounds. Can't simply add the offset since shifting isn't always precise
%% Shift unselected objects to avoid overlap
switch ShiftAll
case lower('on')
% Push remaining blocks and annotations in the system away from
% the new bounds (if the bounds have expanded) or pull them
% toward the new bounds (otherwise)
% Get the objects that need to be shifted
system_blocks = find_blocks_in_system(system)';
non_layout_blocks = setdiff(system_blocks, selected_blocks);
system_annotations = find_annotations_in_system(system)';
non_layout_annotations = setdiff(system_annotations, selected_annotations);
% Figure out how to shift blocks and annotations
bound_shift = new_bounds - orig_bounds;
adjustObjectsAroundLayout(non_layout_blocks, orig_bounds, bound_shift, 'block');
adjustObjectsAroundLayout(non_layout_annotations, orig_bounds, bound_shift, 'annotation');
% TODO - depending on input parameters redraw/shift/etc lines
% affected by previous shifting
% non_layout_lines = get_block_lines(non_layout_blocks);
% adjustObjectsAroundLayout(non_layout_lines, orig_bounds, bound_shift, 'line');
% redraw_block_lines(blocks, 'autorouting', 'on')
% redraw_lines(getfullname(system), 'autorouting', 'on')
case lower('off')
% Shifting already done.
otherwise
error('Unexpected parameter value.')
end
%% Redraw lines
blockLines = autolayout_lines(blockLines);
%% Zoom on system
% If it ends up zoomed out that means there is something near the
% borders.
if exist('system', 'var') == 1
set_param(system, 'Zoomfactor', 'Fit to view');
end
end
function shift_sim_objects(blocks, lines, annotations, offset)
%
% Note: Shifting blocks in Simulink also shifts the connected lines
% (though not always in the desired manner).
%%%The commented out approach fails if shift amount is rounded in
%%%different ways for different blocks e.g. if instead of being moved
%%%by 2.5, the source block of a line is moved by 0 and the dest block
%%%of the line is moved by 5 -- maybe just round the offset right away
%%%(add parameter to allow this rounding probably)
% lineShift = {};
% for i = 1:length(blocks)
% ports = get_param(blocks(i), 'PortHandles');
% oports = ports.Outport;
% for j = 1:length(oports)
% lh = get_param(oports(j), 'Line');
% dstBlocks = get_param(lh, 'DstBlockHandle');
% if all(ismember(dstBlocks, blocks))
% % Sources and destinations of lh are in blocks - it will be
% % appropriate to shift all points in lh by the same amount
% % as the sources and destinations.
%
% % Record info related to this line to help shift it
% % appropriately later.
% points = get_param(lh, 'Points');
% srcBlockPos = get_param(blocks(i), 'Position');
% dstBlocksPos = get_param(dstBlocks, 'Position');
% lineShift{end+1} = struct('line', lh, 'points', points, ...
% 'srcBlock', blocks(i), 'srcBlockPos', srcBlockPos, ...
% 'dstBlocks', dstBlocks, 'dstBlocksPos', dstBlocksPos);
% end
% end
% end
shiftBlocks(blocks, [offset, offset]); % Takes 1x4 vector
% for i = 1:length(lineShift)
% % Shift lines with the blocks that shifted.
% realOffset = get_param(lineShift{i}.srcBlock, 'Position') - lineShift{i}.srcBlockPos;
%
% newPoints = lineShift{i}.points; % Initialize
% for j = 1:length(lineShift{i}.points)
% newPoints(j,:) = lineShift{i}.points(j,:) + realOffset(1:2);
% end
% set_param(lineShift{i}.line, 'Points', newPoints);
% end
shiftAnnotations(annotations, [offset, offset]); % Takes 1x4 vector
shiftLines(lines, offset); % Takes 1x2 vector
end
function adjustObjectsAroundLayout(objects, origBounds, boundShift, type)
% objects are all of the given type
%
% Move objects with the shift in bounds between original and new
% layout. The approach taken aims to keep objects in the same position
% relative to the original layout. This approach will not handle
% objects that were within the original bounds well, however, this is
% not considered a big problem because of the degree of difficulty in
% appropriately handling these cases even manually and further it's
% also a bizarre case that should generally be avoidable. If it turns
% out to need to be handled, a simple approach is to pick some
% direction to shift the objects that were within the original bounds
% and to do so as well as potentially increase the overall shift amount
% in that direction accordingly.
%
% Update: negative bound_shifts will be ignored. Better to leave free
% space than to disturb the other blocks.
switch type
case 'block'
getBounds = @blockBounds;
shiftObjects = @shiftBlocks;
case 'line'
getBounds = @lineBounds;
shiftObjects = @shiftLines;
case 'annotation'
getBounds = @annotationBounds;
shiftObjects = @shiftAnnotations;
otherwise
error('Unexpected object type.')
end
for i = 1:length(objects)
object = objects(i);
% Get bounds of the block
myBounds = getBounds(object);
myShift = [0 0 0 0]; % Desired shift for current object
idx = 1; % Left
if myBounds(idx) < origBounds(idx) && boundShift(idx) > 0
myShift = myShift + [boundShift(idx) 0 boundShift(idx) 0];
end
idx = 2; % Top
if myBounds(idx) < origBounds(idx) && boundShift(idx) > 0
myShift = myShift + [0 boundShift(idx) 0 boundShift(idx)];
end
idx = 3; % Right
if myBounds(idx) > origBounds(idx) && boundShift(idx) > 0
myShift = myShift + [boundShift(idx) 0 boundShift(idx) 0];
end
idx = 4; % Bottom
if myBounds(idx) > origBounds(idx) && boundShift(idx) > 0
myShift = myShift + [0 boundShift(idx) 0 boundShift(idx)];
end
shiftObjects({object}, myShift);
end
end
function blocks = find_blocks_in_system(system)
blocks = find_system(system, 'SearchDepth', 1, 'FindAll', 'on', 'Type', 'block', 'Parent', getfullname(system));
end
function annotations = find_annotations_in_system(system)
annotations = find_system(system, 'SearchDepth', 1, 'FindAll', 'on', 'Type', 'annotation');
end
function setHeights(layoutRepresentation, colOrder, subsMethod, AdjustHeightParams, connType, firstPass)
% TODO Current implementation expands blocks down regardless of
% input parameters - fix that - though it doesn't really matter
% since alignment will occur still.
%
portParamsIdx = find(strcmpi('PortParams', AdjustHeightParams));
if isempty(portParamsIdx)
% Add 'PortParams' with default value
AdjustHeightParams{end+1} = 'PortParams';
AdjustHeightParams{end+1} = {};
portParamsIdx = find(strcmpi('PortParams', AdjustHeightParams));
assert(~isempty(portParamsIdx))
end
portParamsVal = AdjustHeightParams{portParamsIdx(1)+1};
%
connTypeIdx = find(strcmpi('ConnectionType', portParamsVal));
if isempty(connTypeIdx)
% Add 'ConnectionType' with given value
portParamsVal{end+1} = 'ConnectionType';
portParamsVal{end+1} = connType;
end % else: Do nothing, if a user explicitly chose a ConnectionType.
% Set base method (will be updated during the loop below for certain
% blocks).
methodIdx = find(strcmpi('Method', portParamsVal));
if firstPass
firstPassMethod = 'Compact';
end
%subsMethod = 'SumMax';
if isempty(methodIdx)
callerMethod = 'Compact'; % Because Compact is default for the adjustHeight function.
% Add 'Method' parameter.
portParamsVal{end+1} = 'Method';
portParamsVal{end+1} = [];
else % The caller set a method.
callerMethod = portParamsVal{methodIdx(1)+1};
end
% Update methodIdx.
methodIdx = find(strcmpi('Method', portParamsVal));
%
for i = colOrder(length(colOrder):-1:1) % Reverse column order
for j = 1:length(layoutRepresentation{i})
b = layoutRepresentation{i}{j}; % Get current block
pos = get_param(b, 'Position');
% Set Method parameter.
% (Conditions here are based on what has seemed best in testing and
% could probably be improved.)
if firstPass
portParamsVal{methodIdx(1)+1} = firstPassMethod;
elseif strcmp(get_param(b, 'BlockType'), 'SubSystem')
portParamsVal{methodIdx(1)+1} = subsMethod;
else
portParamsVal{methodIdx(1)+1} = callerMethod;
end
AdjustHeightParams{portParamsIdx(1)+1} = portParamsVal;
[~, adj_position] = adjustHeight(b, 'PerformOperation', 'off', ...
AdjustHeightParams{:});
% TODO use the following parameter in the call above:
% 'ConnectedBlocks', connBlocks,
% connBlocks should be either the blocks that connect
% to the current block and are 1 column right or left
% depending on AlignmentType
% If going 1 column over would exit bounds or if there
% are no connBlocks then just get the compactHeight
desiredHeight = adj_position(4) - adj_position(2);
set_param(b, 'Position', pos + [0, 0, 0, -pos(4)+pos(2)+desiredHeight]);
end
end
end
function type = default_layout_type()
%
type = getAutoLayoutConfig('layout_type', 'default');
if strcmp(type, 'default')