Skip to content

Commit

Permalink
fixes bug in checking for legal cadnano export
Browse files Browse the repository at this point in the history
  • Loading branch information
dave-doty committed Oct 26, 2024
1 parent d03a81b commit f2e9db8
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 84 deletions.
4 changes: 2 additions & 2 deletions lib/src/middleware/all_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'dna_extensions_move_start.dart';
import 'forbid_create_circular_strand_no_crossovers_middleware.dart';
import 'helix_group_move_start.dart';
import 'adjust_grid_position.dart';
import 'export_cadnano_or_codenano_file.dart';
import 'export_cadnano_file.dart';
import 'assign_dna.dart';
import 'check_mirror_strands_legal.dart';
import 'edit_select_mode_change.dart';
Expand Down Expand Up @@ -48,7 +48,7 @@ final all_middleware = List<Middleware<AppState>>.unmodifiable([
export_svg_middleware,
save_file_middleware,
load_file_middleware,
export_cadnano_or_codenano_file_middleware,
export_cadnano_file_middleware,
example_design_selected_middleware,
throttle_middleware,
assign_dna_middleware,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ import '../state/app_state.dart';
import '../constants.dart' as constants;
import '../util.dart' as util;

export_cadnano_or_codenano_file_middleware(Store<AppState> store, dynamic action, NextDispatcher next) {
export_cadnano_file_middleware(Store<AppState> store, dynamic action, NextDispatcher next) {
next(action);

AppState state = store.state;
if (action is actions.ExportCadnanoFile) {
_save_file_cadnano(state, action.whitespace);
} else if (action is actions.ExportCodenanoFile) {
_save_file_codenano(state);
}
}

Expand All @@ -48,73 +46,13 @@ _save_file_cadnano(AppState state, bool whitespace) async {
}
}

_save_file_codenano(AppState state) async {
Design? design = state.maybe_design;
if (design == null) {
return;
}

var grids = design.groups.values.map((group) => group.grid).toSet();
if (!(grids.length == 1 && grids.first == Grid.none)) {
var msg = 'Grid must be set to none for all helix groups to export to codenano. '
'First convert all grids to none.';
window.alert(msg);
return;
}

Map design_json = state.design.to_json_serializable(suppress_indent: true);

// codenano parameters; taken from an example file
design_json['parameters'] = {
"z_step": 0.332,
"helix_radius": 1.0,
"bases_per_turn": 10.5,
"groove_angle": -2.2175948142986774,
"inter_helix_gap": 0.65,
};

// change version to version of codenano when I got the example file
design_json[constants.version_key] = '0.4.12';

List helices_json = design_json[constants.helices_key];
for (Map<String, dynamic> helix_json in helices_json) {
// add pitch, roll, and yaw defaults explicitly, and convert to radians
for (var angle_key in [constants.pitch_key, constants.roll_key, constants.yaw_key]) {
double? degrees = helix_json[angle_key];
helix_json[angle_key] = degrees == null ? 0.0 : util.to_radians(degrees);
}
// change "position" to "origin"
var pos = helix_json[constants.position_key];
helix_json.remove(constants.position_key);
helix_json['origin'] = pos;
}

List strands_json = design_json[constants.strands_key];
for (Map strand_json in strands_json) {
// change color to integer convention
if (strand_json.containsKey(constants.color_key)) {
var color = strand_json[constants.color_key];
var color_int = util.color_hex_to_decimal_int(color);
strand_json[constants.color_key] = color_int;
}
// change "forward" to "right"
for (NoIndent no_indent_json in strand_json[constants.substrands_key]) {
Map domain_json = no_indent_json.value;
if (!domain_json.containsKey(constants.forward_key)) {
window.alert('To export, strands cannot have any loopouts. '
'Please remove all loopouts before exporting.');
return;
}
bool forward = domain_json[constants.forward_key];
domain_json.remove(constants.forward_key);
domain_json['right'] = forward;
}
}

var encoder = SuppressableIndentEncoder(Replacer(), suppress: true);
var json_str = encoder.convert(design_json);
var default_filename = path.setExtension(state.ui_state.loaded_filename, '-codenano.json');
util.save_file(default_filename, json_str);
/// Converts the design to the cadnano v2 format.
/// Please see the spec [`misc/cadnano-format-specs/v2.txt`](https://github.com/UC-Davis-molecular-computing/scadnano-python-package/blob/main/misc/cadnano-format-specs/v2.txt)
/// for more info on that format.
String to_cadnano_v2_json(Design design, [String name = ""]) {
var encoder = SuppressableIndentEncoder(Replacer());
var content_serializable = to_cadnano_v2_serializable(design, name);
return encoder.convert(content_serializable);
}

/// Converts the design to the cadnano v2 format.
Expand Down Expand Up @@ -210,15 +148,6 @@ Map<String, dynamic> to_cadnano_v2_serializable(Design design, [String name = ""
return dct;
}

/// Converts the design to the cadnano v2 format.
/// Please see the spec [`misc/cadnano-format-specs/v2.txt`](https://github.com/UC-Davis-molecular-computing/scadnano-python-package/blob/main/misc/cadnano-format-specs/v2.txt)
/// for more info on that format.
String to_cadnano_v2_json(Design design, [String name = ""]) {
var encoder = SuppressableIndentEncoder(Replacer());
var content_serializable = to_cadnano_v2_serializable(design, name);
return encoder.convert(content_serializable);
}

int _get_multiple_of_x_sup_closest_to_y(int x, int y) {
return y % x == 0 ? y : y + (x - y % x);
}
Expand Down Expand Up @@ -419,9 +348,21 @@ void _cadnano_v2_place_crossover(Map<String, dynamic> helix_from_dct, Map<String
helix_from_dct[strand_type][end_from - 1]
.setRange(2, helix_from_dct[strand_type][end_from - 1].length, [helix_to, start_to]);
helix_to_dct[strand_type][end_to - 1].setRange(0, 2, [helix_from, start_from]);
if (helix_from_dct['row'] % 2 != helix_to_dct['row'] % 2) {
throw new IllegalCadnanoDesignError('''\
Paranemic crossovers are only allowed between helices that have the same parity of
row number, here helix num ${helix_from_dct['num']} and helix num ${helix_to_dct['num']}
have different parity of row number: respectively ${helix_from_dct['row']} and ${helix_to_dct['row']}''');
}
} else if (!forward_from && !forward_to) {
helix_from_dct[strand_type][start_from]
.setRange(2, helix_from_dct[strand_type][start_from].length, [helix_to, end_to - 1]);
helix_to_dct[strand_type][start_to].setRange(0, 2, [helix_from, end_from - 1]);
if (helix_from_dct['row'] % 2 != helix_to_dct['row'] % 2) {
throw new IllegalCadnanoDesignError('''\
Paranemic crossovers are only allowed between helices that have the same parity of
row number, here helix num ${helix_from_dct['num']} and helix num ${helix_to_dct['num']}
have different parity of row number: respectively ${helix_from_dct['row']} and ${helix_to_dct['row']}''');
}
}
}
2 changes: 1 addition & 1 deletion lib/src/middleware/oxdna_export.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import '../state/app_state.dart';
import '../actions/actions.dart' as actions;
import '../state/helix.dart';
import '../util.dart' as util;
import 'export_cadnano_or_codenano_file.dart' as export_cadnano;
import 'export_cadnano_file.dart' as export_cadnano;
import '../constants.dart' as constants;

oxdna_export_middleware(Store<AppState> store, dynamic action, NextDispatcher next) {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/middleware/oxview_update_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import '../actions/actions.dart' as actions;
import '../state/helix.dart';
import '../util.dart' as util;
import '../util.dart';
import 'export_cadnano_or_codenano_file.dart' as export_cadnano;
import 'export_cadnano_file.dart' as export_cadnano;
import '../constants.dart' as constants;
import 'oxdna_export.dart';
import '../app.dart';
Expand Down
18 changes: 17 additions & 1 deletion test/export_cadnano_v2_test.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:built_collection/built_collection.dart';
import 'package:scadnano/src/actions/actions.dart';
import 'package:scadnano/src/json_serializable.dart';
import 'package:scadnano/src/middleware/export_cadnano_or_codenano_file.dart';
import 'package:scadnano/src/middleware/export_cadnano_file.dart';
import 'package:scadnano/src/state/design.dart';
import 'package:scadnano/src/state/domain.dart';
import 'package:scadnano/src/state/grid.dart';
Expand Down Expand Up @@ -296,6 +296,22 @@ main() {
expect(output_design.helices.length, 4);
});

test('test_paranemic_crossover_other_direction', () async {
var helices = [
Helix(idx: 1, max_offset: 64, grid_position: GridPosition(19, 14), grid: Grid.square),
Helix(idx: 0, max_offset: 64, grid_position: GridPosition(19, 15), grid: Grid.square),
Helix(idx: 3, max_offset: 64, grid_position: GridPosition(19, 16), grid: Grid.square),
Helix(idx: 2, max_offset: 64, grid_position: GridPosition(19, 17), grid: Grid.square),
];
var design = Design(helices: helices);
design = design.draw_strand(3, 24).to(8).cross(1, 24).to(8).commit();
design = design.draw_strand(2, 50).to(24).cross(0, 50).to(24).commit();

String output_json = to_cadnano_v2_json(design);
Design output_design = Design.from_cadnano_v2_json_str(output_json);
expect(output_design.helices.length, 4);
});

// We do not handle Loopouts and design where the parity of the helix
// does not correspond to the direction.
test('test_parity_issue', () {
Expand Down

0 comments on commit f2e9db8

Please sign in to comment.